Enumeration refers to traversing over objects.
In Ruby, we have Enumerable classes like Array, Hash and Range which get their
enumeration features by including the Enumerable module.
This Enumerable module provides various methods
#include?, #count, #map, #select and #uniq which we frequently use,
and of course #tally about which this blog is.
#tally counts the number of occurrences of an element in a collection and
returns a hash containing the count for all elements.
To know more about Enumerable#tally that got introduced in Ruby 2.7, check out our previous
blog.
Example
Consider the example of a stationery store. The shopkeeper sells his/her goods on a per-unit basis.
This type of problem is a perfect candidate to leverage our #tally method to
calculate the total quantity of items sold in a week.
Consider the customers add items one by one to place an order as follows:
order_one = %i{ pen pencil eraser sharpener pen pen }
order_two = %i{ sharpener eraser eraser eraser eraser sharpener sharpener sharpener sharpener }Before
Calculating the quantity of each item
tally_one = order_one.tally
# => { pen: 3, pencil: 1, eraser: 1, sharpener: 1 }
tally_two = order_two.tally
# => { sharpener: 5, eraser: 4 }weekly_tally = (order_one + order_two).tally # order_one, order_two, ..., order_seven
# => { pen: 3, pencil: 1, eraser: 5, sharpener: 6 }As we can see in the above example, the quantity of erasers and sharpeners has increased.
We have concatenated order_one and order_two arrays and then called #tally to get the combined
count of the items ordered.
After
To calculate the total at runtime, Ruby 3.1 Enumerable#tally now accepts an optional hash to count occurrences.
In this case, we can store the running tally of the number of items and pass it to the #tally method.
order_one = %i{ pen pencil eraser sharpener pen pen }
order_two = %i{ sharpener eraser eraser eraser eraser sharpener sharpener sharpener sharpener }
weekly_tally = {}
weekly_tally = order_one.tally(weekly_tally)
# => {pen: 3, pencil: 1, eraser: 1, sharpener: 1}
weekly_tally = order_two.tally(weekly_tally)
# => {pen: 3, pencil: 1, eraser: 5, sharpener: 6}
# And so onAs we can see in the above example in Ruby 3.1, we have passed the weekly_tally hash
as an argument to the #tally method that stores the count of items.
The count of erasers and sharpeners in order_two got added to the weekly_tally hash,
returning the combined quantity for each item.
Note
- A hash with a default value can be passed to the
#tallymethod. The keys which are present in the array will ignore the default hash value and keys not present will return the default value instead ofnil.
order = %i{ pen pencil eraser sharpener pen pen }
tally = order.tally(Hash.new 5)
# => { pen: 3, pencil: 1, eraser: 1, sharpener: 1 }
tally[:ruler]
# => 5- Similarly,
Proccan be passed to the#tallymethod. If the key is present in the arrayProcwill be ignored. For keys not present in the array, theProcgets executed.
order = %i{ pen pencil eraser sharpener pen pen }
tally = order.tally(Hash.new { puts "Proc called" })
# => { pen: 3, pencil: 1, eraser: 1, sharpener: 1 }
tally[:ruler]
Proc called
# => nil- Keys that are not present in the array are merged with the resulting hash.
For eg.,
first_nameandlast_namethat are not present in theorderarray got merged as it is.
order = %i{ pen pencil eraser sharpener pen pen }
tally = order.tally({ first_name: "Sam", last_name: "Example" }) # Allowed
# => { first_name: "Sam", last_name: "Example", pen: 3, pencil: 1, eraser: 1, sharpener: 1 }- Keys that are present in the array should only take integer value else it will raise a
TypeError.
order = %i{ pen pencil eraser sharpener pen pen }
tally = order.tally({ pencil: "colored" }) # Not allowed
# => TypeError: wrong argument type String (expected Integer)