Welcome to Rithm’s series on problem-solving strategies. If you’re just joining us, you may want to start at the beginning. Here’s a list of the articles we’ve written:
- Understand the Problem
- Explore Concrete Examples
- Break It Down
- Solve a Simpler Problem
- Use Tools Strategically
- Look Back and Refactor
Once you have a clear understanding of the problem at hand and have convinced yourself of what the result should be in a few specific cases, it’s time to think about a more general approach. Very often looking at specific examples is enough to generate a roadmap for a solution. But if you find yourself still struggling to come up with a solution, here’s a strategy that can help.
Strategy #3: Break It Down
Any problem involving even a moderate amount of complexity will consist of a number of steps. And while it can be tempting to jump right in to a solution once you have an idea of how that solution should look, it never hurts to explicitly list out the steps that go in to your approach. This forces you to think about the code you’ll write before you write it, and helps you catch any lingering conceptual issues or misunderstandings before you dive in and have to worry about details (e.g. language syntax) as well.
An Example
Let’s return to our coding example from the previous strategy:
Write a function which takes in a string and returns counts of each character in the string.
Imagine you’ve asked questions about this problem, feel like you have a solid understanding of the task, and have the rough shape of a solution based on the following concrete examples:
charCount("aaaa"); /* { a: 4 } */ charCount("hello"); /* { h: 1, e: 1, l: 2, o: 1 } */ charCount("Your PIN number is 1234!") /* { 1: 1, 2: 1, 3: 1, 4: 1, b: 1, e: 1, i: 2, m: 1, n: 2, o: 1, p: 1, r: 2, s: 1, u: 2, y: 1 } */
It helps to begin with the very basic structure. By now, since you understand the problem, you should be able to write the name of the function, along with any arguments it takes:
function charCount(str) { }
You also know what the function should ultimately return: an object containing data on how many times different (case-insensitive) alphanumeric characters appear in the string. Let’s add a comment in the function describing this output:
function charCount(str) { // do something // return an object with keys that are lowercase alphanumeric characters in the string; values should be the counts for those characters }
These comments inside of the function body can provide a helpful structure when it comes to breaking down a problem. Whether you want to write some psuedocode or simply a list of steps you’ll need to work through, there’s tremendous value in thinking about the steps of a problem without worrying about the particular’s of a language’s syntax. This approach can help you identify parts of the problem that might be particularly challenging, and also forces you to think about each step of the solution, which can sometimes uncover gaps in your understanding.
In this case, it seems like there are a few steps involved in the process. In determining these steps, it’s always helpful to keep in mind the original inputs to the function, and the eventual output you’re trying to create.
- Since the output is a count of characters, we probably need to loop through the string and execute some code on a character-by-character basis.
- What’s the code we need to execute? Well, if we’re looking at a specific character, there’s a couple of different things that could happen:
a. The character is alphanumeric, and it already exists as a key in our object. In this case, we should increment the count for our key (in lowercase!).
b. The character is alphanumeric, but it doesn’t exist as a key in our object. In this case, we should add the key to our object (in lowercase!) and set its value to 1 (since we’ve found 1 instance of the character in the string).
c. The character isn’t alphanumeric (i.e. not a letter or a digit from 0 to 9). In this case, we shouldn’t do anything.
In the above, we’ve made reference to an object as well, so we’d better be sure we have an object to work with. Let’s put this structure inside of the function:
function charCount(str) { // 1. create an object to store our counts. // 2. loop through the string. For each character... // a. If the (lowercase) character is a key in our object, increment the count. // b. If the (lowercase) character is a letter or number not in the object, set as a key with a value of 1. // c. If the character is something else (space, punctuation, etc), don't do anything. // 3. return our object. }
Once you’ve broken a problem down into its component pieces, it becomes much more manageable. Rather than tackling the problem as a monolith, you can start tackling smaller pieces. In this case, maybe you forget the syntax for a for loop, but you know how to check whether a key is already in an object. Or vice versa. Whatever the case, breaking a problem down into smaller pieces gives you more pathways to start on a solution.
Of course, there are times when you may struggle with one of the components of the solution. You may even struggle with breaking a problem down in the first case. If this happens, it’s best to switch gears and move to another problem-solving strategy. We’ll offer up an alternative next time.