Reducing Your Code Odor With Reduce
Tips for Boiling Down Your Code
Meet Mikey:
Mikey sells lemonade Monday through Friday. At the end of each day Mikey comes home, counts his quarters and stores the daily earnings in an array. Each index in the array represents the total sales for a given day (index 0 representing Monday, index 4 representing Friday). Let’s take a look at Mikey’s week 1 earnings:
week_1 = [20.75, 22.5, 34, 22, 16.25]
Mikey decides he wants to keep track of his weekly sales in addition to the daily sales. Mikey is new to programming, but he’s figured out a clever way to have his computer do the counting for him. He will use the function below to easily sum the amounts from each day:
def sum_nums(arr)
sum = 0
arr.each do |num|
sum += num
end sum
end
Mikey will pass theweek_1
array to his method #sum_nums
and store the result in a variable called one_week_total
:
one_week_total = sum_nums(week_1)
# => 115.5
Congrats to Mikey for figuring out a way to sum his weekly earnings, but what Mikey doesn’t know is that there is already a Ruby method defined to do the same thing. Mikey, meet #reduce
:
Here is a brief definition of #reduce
, borrowed from ruby-doc.org:
Combines all elements of enum by applying a binary operation, specified by a block or a symbol that names a method or operator.reduce { |memo, obj| block } → obj
Let’s see how the code would look had he used the method #reduce
to calculate his week one earnings:
one_week_total = week_1.reduce { |sum, num| sum += num }
# => 115.5
Even though passing week_1
to #sum_nums
or #reduce
will produce the same result, Mikey is a Rubyist, and Rubyist’s like to make their code easy to read AND easy to write. Mikey realizes that he can reduce the size of his Ruby file by a couple of lines if he omits his #sum_nums
method.
Invoking Reduce With a Start Value
Mikey crushed it at the stand during his second week of operating. Now Mikey is curious to see the sum of what he has earned over the past 2 weeks. Let’s take a look at his week 2 daily earnings:
week_2 = [35.25, 30.25, 25, 28.75, 37]
Mikey is going to store his two-week total in a variable. Let’s see the code Mikey implements in order to accomplish this:
two_week_total =
week_2.reduce { |sum, num| sum += num } + one_week_total
# => 271.75
Mikey runs his code and discovers that while his program is working just fine, the readability of the code has slightly diminished. Having to add his one_week_total
variable to the end of the block is something Mikey would prefer not to see, especially not in a file that he authored himself. #reduce
is Mikey’s new favorite method and he was hoping to continue using it to sum his earnings. Mikey fears that the quality of his code will suffer if he continues to use #reduce
. Poor Mikey.
Luckily for us, Mikey is resilient and reminds himself that he has been running a lemonade stand for two whole weeks. If he can manage that, then surely he can find a way to continue using the #reduce
method while maintaining the readability of his code. After many seconds of sleuthing on Google, Mikey discovers that #reduce
can be invoked with a start value. This makes Mikey happy.
Let’s reimplement the code written above and pass one_week_total
as a start value to Mikey’s summation:
two_week_total =
week_2.reduce(one_week_total) { |sum, num| sum += num }
# => 271.75
By passing an argument to #reduce
, the summing of the collection will start at the value passed rather than starting at 0. Including this start value enables Mikey to conveniently find the total sum of his reduced value from week 2 by adding it to his one_week_total
— all without having to sacrifice the readability of his code. He can even use it to sum this month’s earnings with next month’s!
monthly_total = week_4.reduce(three_week_total) {|sum,num| sum += num}
Go Mikey!