I’m currently studying for RB139, but some recent study sessions got me thinking about the very first interview assessment I had to study for.
This was how I broke things down:
1. Read problem out loud.
2. Input/Output:
3. Examples/Rules:
4. Steps:
5. Helper Methods: (as needed)
Let’s say we were given this problem (an 8th kyu kata from Codewars — you’ll want to work your way up to 5th and 6th kyu before you take the test):
Our football team finished the championship. The result of each match look like "x:y". Results of all matches are recorded in the collection.For example: ["3:1", "2:2", "0:1", ...]Write a function that takes such collection and counts the points of our team in the championship. Rules for counting points for each match:
if x > y: 3 points
if x < y: 0 point
if x = y: 1 pointNotes:there are 10 matches in the championship
0 <= x <= 4
0 <= y <= 4
Input/Output:
Thinking about the input and output was really important to me, much more than trying to restate the problem in my own words. Restating the problem in my own words felt like I was adding more language, when what my brain needed was less: strip the problem down into its very basic components. What’s more basic than inputs and outputs?
What every problem is essentially going to ask you to do is to build a method.
I like to think of methods as magic boxes. You put something in, and it spits something else out. Half the battle of building one of these little magic boxes is actually knowing what is going in, and what is going to come out.
So step one essentially looks like this:
At this point I don’t care what the box looks like on the inside (which is why it’s blacked out). To begin building it, I just want to know what goes in and what goes out.
Practically, what sort of information do I include?
- Each input or output on its own line (it’s cleaner)
- The data structure it will be in (if it’s a collection, also define what it’s a collection of)
- What it represents within the context of the problem
input: an array of strings, each representing a score for a game
(if I had a second input it would go here)
output: an integer, representing the total points the team scored
Done. Already, I can breathe a little sigh of relief that I’m beginning to understand what is being asked of me.
Examples/Rules
At this point, I may have some idea of what I need to do, or I may have no clue at all. If I’m at the former stage, it means I have a bunch of assumptions that I now need to test, and correct if necessary (it’s almost always necessary). If I’m at the latter stage, then walking through each of the examples is going to be what helps me formulate a better idea of some of the processes that are going to be happening inside my magic box.
I usually start out writing down all the explicit rules: things that are obvious or mentioned outright. I also write down anything that comes to mind, if it’s an assumption I write a question mark (?) so I know to double check. Duplicating information found in other sections is fine, what matters is you jot it down, so you free up your brain. Anything I want to “keep in mind”, I dump it here. This section is my mind now.
examples/rules:
- the output is an integer
- there's only 10 strings in each array (?)
- each string looks like this "x:y"
- to calculate points we need to compare x and y
- if x is greater: +3
- if y is greater: +0
- if x == y: +1
When I’ve exhausted my explicit rules, I turn to the examples. The trick I’ve found here is to take your time. Slow down. Force yourself to slow down. You’ve probably already found that this is very difficult — it feels like you’re wasting time! But actually walking through each example, will help you solve it right the first time around, which ultimately makes you faster.
Make sure each example has the output that you expect. If it doesn’t, good! Excellent! That’s a problem you’ve avoided. Ask yourself why it’s not what you expected, correct your assumption. Every time something surprises you, write it down. If you ask the TA a question, and they give you an answer, write that answer down too. You’re collecting information right now. Sometimes a question might form in your mind, and you want to create your own example, and your expected output, and ask for verification:
me: so if I have an array that is ["3:2", "4:2"], then the output would be 6?TA: correct.
If you’re walking through the examples slowly enough, by now you’ve both collected enough information to get started, and have developed some vague idea of how to go forward. If not, I like to incorporate a little bonus step called sandbox
.
Sandbox (bonus section!)
In illustration, the beginning stages of every image is 100 crappy thumbnails (the actual number varies). None of these are going to be the finished piece. The important part is you play with some ideas, and get a feel for what might be effective before you commit valuable time to any one idea.
In problem-solving, this is where I do things like go into irb
to verify a code snippet, to see if it may be a viable way of solving a sub-problem, or map out the data structure transformation that needs to occur.
For example, for this problem, perhaps I know that I’m going to need to extract the numbers out from each of the the given strings in order to compare them. Maybe it’s been a while since I had to do that. So I might hop over to irb
and test:
"3:2"[0]
=> "3"
"3:2"[-1]
=> "2"
Excellent! This is a viable way for me to extract the numbers. This will do.
If I’m still fuzzy on the steps I need to take to get from input to output, I try to think about the data structure’s journey from input to output.
["3:2", "4:2"] -> ??? -> 6
["3:2", "4:2"] -> "3:2" -> ??? -> 6
["3:2", "4:2"] -> "3:2" -> x = 3, y = 2 -> ??? -> 6
["3:2", "4:2"] -> "3:2" -> x = 3, y = 2 -> (compare x & y) -> 6
If you take this journey, and put it into words, you often find you’ve got a solid starting place for the next step.
["3:2", "4:2"] -> ??? -> 6
["3:2", "4:2"] -> "3:2" -> ??? -> 6
# I need to work with each string object
["3:2", "4:2"] -> "3:2" -> x = 3, y = 2 -> ??? -> 6
# I need to extract the numbers from each string object and assign them to variables. NOTE-TO-SELF: make sure to change into integers
["3:2", "4:2"] -> "3:2" -> x = 3, y = 2 -> (compare x & y) -> 6
# I need to use the given rules to compare x and y to get the points for that game
Steps
At this point, I just need to write down, in plain english, what I need to do. Personally, calling this section ‘steps’ instead of ‘algorithm’ helps remind me of the simplicity of the language I need to use.
steps:
- iterate through each string
- for each string, extract the numbers, and assign to variables
- compare the numbers, and determine the points for each game
- add the points to a total
Usually, I keep my steps high-level, usually only having 3–5 steps. When I go beyond 3–5 I know that I’m either going into implementation detail too soon, or I’m trying to solve multiple problems at the same time.
For example, you’ll notice at step 3, I say “determine the points for each game”, which is still quite abstract. I’m not thinking about how I’m going to do that (case statement? if-else statements?). I just know that I need to do it to get to the next step.
Once I have the highest level steps outlined, now I can go in and flesh out the vague parts, like in step 3. Usually, vague parts that have sub-steps of their own are good candidates for helper methods.
I could simplify the steps above even more:
steps:
1. go through each game, one by one
2. determine the points for each game
3. add all the points up
Helper Methods
Each magic box works best when it does one thing. That makes it easier to build it, and easier to figure out what’s happening when things go wrong.
Any time something in my steps feels vague, I like to pretend I need to build a special part for my magic box. The same way a car has an engine, a brakes system, and AC system, my magic box is going to need specialized components that do specific tasks. And it just so happens that this special part is also a little magic box (because a helper method is also a method!).
Our main method is currently looking something like this image below. We can see through it now, it’s not all black, and we’ve got all the main steps filled in from start to finish. Now we just need to build that special part (step 2 of our simplified algorithm)!
I start building my helper methods the same way I start building the main one: by thinking about inputs and outputs.
helper methods:
- determine_points()
- input: a string, representing the game ("x:y")
- output: an integer, representing the points for that game
You see where I’m going with this, don’t you? Because I’ve played around in the sandbox
a little bit before, I have a pretty good idea of how I’m going to implement this. Let’s write it down.
helper methods:
- determine_points()
- input: a string, representing the game ("x:y")
- output: an integer, representing the points for that game
- steps:
- extract "x" and "y"
- compare
- return appropriate score
At this point, I have everything I need to get started. You might feel you need more, and you might feel like you had everything you need a few steps ago. It varies wildly from person to person and problem to problem.
You might also feel like you have everything you need now, but as you start coding realize you need to flesh something out a bit more. When that happens, it’s important to stop. Go back to your algorithm, slow down, and think it through: what are you missing? Is it another “special component?” What are the inputs and outputs to that tiny magic box? Stopping and returning to your algorithm is what keeps you from hacking and slashing.
Implementation
After all that hard work, implementation should be easy! Still, sometimes, especially under pressure, I can lose my train of thought.
In art, particularly figure drawing, you want to sketch out the large shapes, like the ellipse of the head, before you even think about drawing details like noses and eyes. If the placement of the large shapes are wrong, you know the rest of the drawing is doomed. That’s essentially what we did in our planning process, but you can apply the same idea during implementation with placeholders:
def points(array) # my main methodenddef determine_points(string) # my helper methodend
I define all my methods, leaving them empty on the inside. It’s like a todo list. Often, I then implement the broad strokes of my main method.
def points(array)
total = 0
array.each{ |game| total += determine_points(game) }
total
end# notice I call determine_points (my helper method), before I've even implemented it
But sometimes, I find it easier to build the magic box for each helper method first. That way I can solve the small problems, and test that each ‘box’ works before I fit them into the larger box. I do this especially when the main method feels daunting and I need some easy wins to calm my nerves.
If the problem is difficult and it feels like I need something extra to remind me what I’m doing, I like to paste in the steps as comments in the method definition, and then resolve each step line-by-line, just to make sure I’m not missing anything:
def determine_points(string)
# extract "x" and "y"
# compare
# return appropriate score
end
Then:
def determine_points(string)
# extract "x" and "y"
x = string[0].to_i
y = string[-1].to_i
# compare
# return appropriate score
end
Finally:
def determine_points(string)
x = string[0].to_i
y = string[-1].to_i
if x > y
3
elsif x < y
0
else
1
end
end
It’s not pretty, but at this point, we’re looking for functional, not clever (you can always refactor later, once you’ve made something that works).
I would then go ahead and write my own test cases for my helper method:
determine_points("3:2") == 3 # => true
determine_points("1:2") == 0 # => true
determine_points("1:1") == 1 # => true
I want to make sure they all pass so I know my helper method is solid.
The whole program would look like this:
def points(array)
total = 0
array.each{ |game| total += determine_points(game) }
total
enddef determine_points(string)
x = string[0].to_i
y = string[-1].to_i
if x > y
3
elsif x < y
0
else
1
end
end
Other notes:
- Jot things down as they come into your head, even if it seems out of order. (For example, if you’re in the input/output section, but there’s a thought screaming at you that belongs in the rules section, just write it down under that section and then return to input/output, don’t fight it for the sake of order). If you don’t know where to put them yet, make a
sandbox
ornotes
section and drop it there. You have very limited space in your working memory. The sooner you externalize a lingering thought, the more brainpower you have to actually work on the problem. - If you’re getting stuck, no matter which stage you’re at (planning or implementation), try to formulate a question out of that feeling of being stuck. Write it down as a comment if you have to. Articulate the problem you’re having. “Hmm, I’m having trouble thinking of a way to get from this string, to isolating the integers I need. I tried doing ____, but that doesn’t seem to be working, because of ___. What are some other ways I can try?” This goes back to externalizing your thoughts. By letting them out, you aren’t just thinking better about them, you’re also creating space in your mind for the answer. If I’m in the implementing stage and I need a moment to decide how I’m going to translate a step into code, I might leave a placeholder comment like
# isolate numbers from string
, while I ponder for a couple of seconds, or return to my algorithm if needed. - Non-technical tip: The most helpful thing anybody ever said to me about how to approach the interview was to treat it like you were taking the interviewer on a journey with you, through the adventure that is solving the problem. That’s all. They are not your enemy. In fact, they want this to be a great adventure too! It’s going to have ups and downs as you get stuck and unstuck — that’s fine. Just remember to take the interviewer along with you as you move along the process.
In summary, some things I do that are helpful for me:
- Thinking of methods explicitly in terms of input and outputs
- Abstracting away anything that seemed even vaguely sub-process heavy into its own magic box (helper method)
- Going through examples slowly and thoroughly
- Having a
sandbox
section if I need it - Mapping out the journey of the data structures as they get manipulated or processed from input to output, to help me think through the steps I need to implement
- Making place holders for the methods I know I need to create before I fill in the implementation details
- Writing comments within my method definitions to keep me on track
- Verbalizing my thoughts when I feel stuck, and using self-directed questions to guide myself towards a possible answer
Formulating your own
Ultimately, the goal of this post was not really to show you the best way to solve problems, far from it. It’s really more to give you ideas and ways of thinking that you can adopt for yourself if it makes sense to.
I came to this process organically. When I went through the easy-medium problems, I didn’t have a set process. PEDAC didn’t make sense to me at first. It seemed like overkill. Then I spent some time on Codewars, doing something like 80 katas. Some of the problems are not always worded as nicely as they are at Launch School. I needed a way to standardize the problems for myself, and eventually this template came to be what it was. The exposure to a lot of different problems and other people’s (admirably succinct!) solutions, also helped me immensely.
In Retrospect, and Final Words
I think my design background helped a lot with my problem solving ability. PEDAC to me, felt similar to trying to create a design brief after meeting with a client. The problem you’re given is akin to the first meeting with the client. They’re going to tell you a lot of things, and half of it may not even be relevant to what they want you to do, and the other half of what they want you to do may not even be stated explicitly at all. They’re going to have implicit expectations, and your job is going to be teasing that out of them through questions or through examples of work they show you.
Your PEDAC template is going to be whatever format you find is most effective for taking any sort of information and organizing it according to how you think. It doesn’t have to look like the actual P-E-D-A-C.
“But,” you may say, “What if I’m missing something?”
If you code with others, especially with folks who are a bit ahead of you in the program, you’ll get a lot of feedback about your PEDAC, and this is going to help you refine it further and make it more effective. At some point my template was:
input:
output:
rules:
steps:
But a study buddy of mine told me I wasn’t looking at the examples thoroughly enough, and it was messing me up. So I began paying more attention, and rules
became examples/rules
.
Likewise, as you study more with others, you’ll find some things they do that just make sense to you, and you’ll pick those things up, just as I hope you were able to pick up a couple of things from here.
Good luck :-)