Fun with ruby `each_with_object` AKA it's Ruby there has to be a better way
November 20, 2018•261 words
I was solving ruby exercism "word count", and I stumbled into a common pattern:
def word_count
word_counter = {}
words.each do |word|
word_count = words.count word
word_counter[word] = word_count
end
word_counter
end
That could be summarized as:
- Have an array that you have to do something about it
- Create a new hash
- Do something with enumerator
- Return the new hash
Obviously the code is clear in it's intent, but it's too verbose and it's bugs me the to end the method with the variable (if you don't state it, the word_couunt
method would return the last iteration).
So, let's see what the Enumerable module has to offer:
each_with_object(obj) { |(*args), memo_obj| ... } → obj
each_with_object(obj) → an_enumerator
Iterates the given block for each element with an arbitrary object given, and returns the initially given object.
If no block is given, returns an enumerator.
The thing I loved is the last part of the first paragraph: 'returns the initially given object', so it's the perfect tool to start using a new hash without having to (explicitly) create it, and since no block is being passed, it returns the enumerator itself:
def word_count
words.each_with_object({}) do |word, counter|
counter[word] = words.count word
end
end
Another example from the pangram exercise, also from exercism:
class Pangram
def self.pangram? sentence
('a'..'z')
.each_with_object(sentence.downcase!)
.all? { |char| sentence.include? char }
end
end
Normally I would solve it, something like:
class Pangram
def self.pangram? sentence
sentence.downcase!
('a'..'z').all? { |char| sentence.include? char }
end
end