Alex Rivera | Logout

What is the "right" way to iterate through an array in Ruby?

Asked 2008-11-22T00:38:19.963
412

PHP, for all its warts, is pretty good on this count. There's no difference between an array and a hash (maybe I'm naive, but this seems obviously right to me), and to iterate through either you just do

foreach (array/hash as $key => $value)

In Ruby there are a bunch of ways to do this sort of thing:

array.length.times do |i|
end

array.each

array.each_index

for i in array

Hashes make more sense, since I just always use

hash.each do |key, value|

Why can't I do this for arrays? If I want to remember just one method, I guess I can use each_index (since it makes both the index and value available), but it's annoying to have to do array[index] instead of just value.


Oh right, I forgot about array.each_with_index. However, this one sucks because it goes |value, key| and hash.each goes |key, value|! Is this not insane?

Edit
Report

3 Answers

121

I think there is no one right way. There are a lot of different ways to iterate, and each has its own niche.

  • each is sufficient for many usages, since I don't often care about the indexes.
  • each_ with _index acts like Hash#each - you get the value and the index.
  • each_index - just the indexes. I don't use this one often. Equivalent to "length.times".
  • map is another way to iterate, useful when you want to transform one array into another.
  • select is the iterator to use when you want to choose a subset.
  • inject is useful for generating sums or products, or collecting a single result.

It may seem like a lot to remember, but don't worry, you can get by without knowing all of them. But as you start to learn and use the different methods, your code will become cleaner and clearer, and you'll be on your way to Ruby mastery.

answered 2008-11-22T00:58:50.530
64

I'm not saying that Array -> |value,index| and Hash -> |key,value| is not insane (see Horace Loeb's comment), but I am saying that there is a sane way to expect this arrangement.

When I am dealing with arrays, I am focused on the elements in the array (not the index because the index is transitory). The method is each with index, i.e. each+index, or |each,index|, or |value,index|. This is also consistent with the index being viewed as an optional argument, e.g. |value| is equivalent to |value,index=nil| which is consistent with |value,index|.

When I am dealing with hashes, I am often more focused on the keys than the values, and I am usually dealing with keys and values in that order, either key => value or hash[key] = value.

If you want duck-typing, then either explicitly use a defined method as Brent Longborough showed, or an implicit method as maxhawkins showed.

Ruby is all about accommodating the language to suit the programmer, not about the programmer accommodating to suit the language. This is why there are so many ways. There are so many ways to think about something. In Ruby, you choose the closest and the rest of the code usually falls out extremely neatly and concisely.

As for the original question, "What is the “right” way to iterate through an array in Ruby?", well, I think the core way (i.e. without powerful syntactic sugar or object oriented power) is to do:

for index in 0 ... array.size
  puts "array[#{index}] = #{array[index].inspect}"
end

But Ruby is all about powerful syntactic sugar and object oriented power, but anyway here is the equivalent for hashes, and the keys can be ordered or not:

for key in hash.keys.sort
  puts "hash[#{key.inspect}] = #{hash[key].inspect}"
end

So, my answer is, "The “right” way to iterate through an array in Ruby

answered 2009-03-27T23:25:21.510
5

I'd been trying to build a menu (in Camping and Markaby) using a hash.

Each item has 2 elements: a menu label and a URL, so a hash seemed right, but the '/' URL for 'Home' always appeared last (as you'd expect for a hash), so menu items appeared in the wrong order.

Using an array with each_slice does the job:

['Home', '/', 'Page two', 'two', 'Test', 'test'].each_slice(2) do|label,link|
   li {a label, :href => link}
end

Adding extra values for each menu item (e.g. like a CSS ID name) just means increasing the slice value. So, like a hash but with groups consisting of any number of items. Perfect.

So this is just to say thanks for inadvertently hinting at a solution!

Obvious, but worth stating: I suggest checking if the length of the array is divisible by the slice value.

answered 2009-06-15T10:41:44.437

Your Answer