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.