Exploring Enumerable in Ruby

One of talks I was able to attend at last week’s RubyFuza conference was by Andrew Timberlake titled ‘Ruby’s Hidden Gems’. It basically revolved around examples of how we often implement something in Ruby that is already supported in the API. I’ve blogged about this before – how I found myself writing C# code, but doing it in Ruby.

A few of the example revolved around the Enumerable module, so I’m going to take a look at some of the built-in methods within this module. We also discussed how it’s good practice to try and implement these methods yourself, so I’m going to try that.

Enumerable#any?

Let’s say you have an array of numbers and you want to test if any of them are higher than 10. You might write the following:

arr = *1..15

higher_than_10 = false
arr.each do |elem|
  higher_than_10 = elem > 10
  break if higher_than_10
end

Which is rather ugly and unnecessary. You could also try several of the other Enumerable methods to get to the same result:

higher_than_10 = arr.select { |elem| elem > 10 }.length > 0
higher_than_10 = arr.count { |elem| elem > 10 } > 0

Which seems better, until you realize that you should be using the Enumerable#any? method:

higher_than_10 = arr.any? { |elem| elem > 10 }

Implementing the any? method is pretty straightforward.

def any?(list)
  list.each do |elem|
    return true if yield(elem)
  end
  false
end

Enumerable#all?

As the name implies, this method applies a block to the collection and returns true if the block evaluates to true for all values.

all_higher_than_10 = arr.all? { |elem| elem > 10 }

What might the Enumerable#all? method look like if we tried to implement it?

def all?(list)
  list.each do |elem|
    return false unless yield(elem)
  end
  true
end

Pretty neat.

Enumerable#take_while

I can’t really imagine a scenario where this method would be useful, but I guess it’s good to know that it exists.

first_few = arr.take_while { |elem| elem < 25 }

What might the Enumerable#take_while method look like if we had to implement it ourselves?

def take_while(list)
  res = []
  list.each do |elem|
    return res unless yield(elem)
    res << elem
  end
end

I feel there should be a nicer way of implementing this, but I can’t really see it.

Enumerable#zip

Once in a while you might find yourself in a situation where you’re working with multiple arrays of the same length containing corresponding values. For example, you might have two arrays – one with a list of names and one with a list of surnames. This usually leads to some pretty messy code when we try to iterate over this.

names = %w{ Bob Steve Dave Peter }
surnames = %w{ Miller Scott Parker Smith }
for i in 0...names.length do
  puts "#{names[i]} #{surnames[i]}"
end
# Bob Miller
# Steve Scott
# Dave Parker
# Peter Smith

It’s not all that often that we need to use a for loop in Ruby – we can get rid of it by using the Enumerable#each_with_index method.

names.each_with_index do |name, i|
  puts "#{name} #{surnames[i]}"
end
# Bob Miller
# Steve Scott
# Dave Parker
# Peter Smith

This is still pretty messy – luckily Ruby has the Enumerable#zip method. This converts each element to an array and then merges each array with corresponding elements from the arguments. (You can actually pass in multiple argument arrays, which makes it a bit trickier)

names = %w{ Bob Steve Dave Peter }
surnames = %w{ Miller Scott Parker Smith }

p names.zip(surnames)
# [["Bob", "Miller"], ["Steve", "Scott"], ["Dave", "Parker"], ["Peter", "Smith"]]

Ruby will substitute nil if there are no corresponding elements in the arguments. Can we try and implement Enumerable#zip ourselves?

def zip(list, *args)
  res = []
  list.each_with_index do |elem, i| 
    elem_arr = elem.to_a
    args.each do |arg_arr|
      elem_arr << arg_arr[i]
    end
    res << elem_arr
  end
  res
end

I’m not too crazy about the double loop – if you have a better implementation, I would love to see it! Happy coding.