Fun with ruby `each_with_object` AKA it's Ruby there has to be a better way

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:

  1. Have an array that you have to do something about it
  2. Create a new hash
  3. Do something with enumerator
  4. 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

You'll only receive email when they publish something new.

More from Pablo Olmos de Aguilera Corradini
All posts