Choosing the Right Tool for the Job:
Upon arrival at coding bootcamp, there is an expectation that you already have at least a basic grasp on how to use some of the Enumerable iteration methods. Having studied the Ruby pre-work and practice problems diligently, I understood the mechanics but struggled with consistent implementation and accuracy.
Often, going into labs felt like stabbing in the dark, hoping to hit the magic words to arrive at the correct answer. If I type this, I get this? Ok, cool, but let me make sure I understand why. Is it .map or .reduce or .select or .filter? Don’t some of those mean the same thing? And which should I use and when? Our instructor reminded us from the beginning to write down the tools in our toolbox. With that in mind, now I don’t try to hammer something when a screwdriver will do the trick.
Before I get into specific problems, I want to address a commonly used but somewhat elusive method. The .reduce method is very tricky to understand and in doing my research for this blog, I realized I didn’t really get how this method operates.
How .reduce works:
The .reduce method takes two arguments in the block, the accumulator and the element making its current iteration. With each pass the two arguments are combined and that combination is used as the accumulator for the next pass until the item is reduced to one value.
I found more clarity learning that this method is known as foldLeft in other programming languages. You can envision it so the element on the right side of the block folds over onto the left with each iteration. For this reason, reduce is especially helpful handling sums and finding averages, as I will address in a bit.
It’s important to note that, like .map and .collect, .reduce and .inject can be used interchangeably. They are aliases for one another.
I’ll now be highlighting a couple of examples from a practice code challenge, explaining how I updated my answers for greater efficiency by using more precise tools. In going through this exercise and attempting to explain my code, I am reinforcing my understanding of these methods and doing myself a favor and hopefully helping others to get what I missed the first few — or many — times.
This object-oriented Ruby code challenge revolves around the relationships of four models, RestaurantOwner, Restaurant, Recipe, and MenuItem.
As the code challenge progressed, I felt confident in my methods establishing relationships among my classes, as well as in solving certain instance methods. I struggled more, though, as I got deeper into the aggregate and association methods. For example, ‘Recipe#cheapest_restaurant’ had me scratching my head for a while. The question asks that we return the Restaurant instance where this Recipe instance is the cheapest.
First, I had to update my seed data to make this more pleasantly testable, placing instances of the same recipe at different restaurants to show who has the best deal in town. Are some restaurants committing meal plagiarism? That’s not for us to decide.
First, I will show how I arrived at the correct answer using .reduce.
Here, I searched through the MenuItem class to find instances of a given recipe and its associated prices by calling on the .menu_items helper method I had already defined earlier in the problem set. I then compared the values of the recipe instances to return the cheapest instance. But that provided the lowest price, not the restaurant instance. For this reason, I set the .reduce method to a variable, which allowed me to method chain to return the information I needed.
So this was all a bit messy.
Many Ruby practitioners would have used a ternary operator with their .reduce method to clean up their code since we are checking whether a condition is true or false. But as a newcomer, I find keeping the explicit “if” conditional in place more intuitive. Understanding the logic is my primary concern during this phase of my programming journey.
I have streamlined the above code in a different way, through use of the .min_by enumerable.
Again, I have called on the helper method, but I have done so with greater precision. The question is, after all, asking for the cheapest (i.e. minimum) restaurant.
All of this is not meant to disparage .reduce. I have another example to demonstrate where it could be the preferable option.
A common deliverable for this sort of problem set is to return an average. In this case, ‘Recipe#average_price’ asks that we find the average price for all Recipe instances in the MenuItem class. To solve this problem, we iterate over the same group of arrays as our previous question.
In my first example below, I broke down this process into two steps. I used .map to create a new collection from the MenuItem arrays comprised of the Recipe instance prices. To this new array of values, I chain the .sum enumerable, and set it equal to a variable. Then, in the next line, I divide the sum by the number of menu.price instances in the array using .count (.length would have worked here, too). I include this particular example, as it requires several different enumerables and opportunities to open our toolbox. But more is certainly not better when writing efficient code.
Another way to go about solving this problem is to use .reduce.
First, I started the method at the value of zero (0), the value at which the count is started. On the right side of the block, I have the menu.price, which with each iteration, increases the accumulator on the left until it has cycled through the whole array and is reduced to the value we want, the sum. I could then divide to find the average.
The process of refactoring code is a useful tactic to ensure comprehension and verify your logic behind a solution. There will always be many ways to solve a problem, so understanding and using the many tools at your disposal will make your code cleaner and will allow you to work faster.