Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 486 Vote(s) - 3.48 Average
  • 1
  • 2
  • 3
  • 4
  • 5
What is the "right" way to iterate through an array in Ruby?

#1
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?
Reply

#2
Use each_with_index when you need both.

ary.each_with_index { |val, idx| # ...
Reply

#3
Trying to do the same thing consistently with arrays and hashes *might* just be a code smell, but, at the risk of my being branded as a codorous half-monkey-patcher, if you're looking for consistent behaviour, would this do the trick?:

class Hash
def each_pairwise
self.each { | x, y |
yield [x, y]
}
end
end

class Array
def each_pairwise
self.each_with_index { | x, y |
yield [y, x]
}
end
end

["a","b","c"].each_pairwise { |x,y|
puts "#{x} => #{y}"
}

{"a" => "Aardvark","b" => "Bogle","c" => "Catastrophe"}.each_pairwise { |x,y|
puts "#{x} => #{y}"
}
Reply

#4
The other answers are just fine, but I wanted to point out one other peripheral thing: Arrays are ordered, whereas Hashes are not in 1.8. (In Ruby 1.9, Hashes are ordered by insertion order of keys.) So it wouldn't make sense prior to 1.9 to iterate over a Hash in the same way/sequence as Arrays, which have always had a definite ordering. I don't know what the default order is for PHP associative arrays (apparently my google fu isn't strong enough to figure that out, either), but I don't know how you can consider regular PHP arrays and PHP associative arrays to be "the same" in this context, since the order for associative arrays seems undefined.

As such, the Ruby way seems more clear and intuitive to me. :)
Reply

#5
If you use the [enumerable][1] mixin (as Rails does) you can do something similar to the php snippet listed. Just use the each_slice method and flatten the hash.

require 'enumerator'

['a',1,'b',2].to_a.flatten.each_slice(2) {|x,y| puts "#{x} => #{y}" }

# is equivalent to...

{'a'=>1,'b'=>2}.to_a.flatten.each_slice(2) {|x,y| puts "#{x} => #{y}" }

Less monkey-patching required.

However, this does cause problems when you have a recursive array or a hash with array values. In ruby 1.9 this problem is solved with a parameter to the flatten method that specifies how deep to recurse.

# Ruby 1.8
[1,2,[1,2,3]].flatten
=> [1,2,1,2,3]

# Ruby 1.9
[1,2,[1,2,3]].flatten(0)
=> [1,2,[1,2,3]]

As for the question of whether this is a code smell, I'm not sure. Usually when I have to bend over backwards to iterate over something I step back and realize I'm attacking the problem wrong.

[1]:

[To see links please register here]

Reply

#6
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 depends on you (i.e. the programmer or the programming team) and the project.". The better Ruby programmer makes the better choice (of which syntactic power and/or which object oriented approach). The better Ruby programmer continues to look for more ways.


----------


Now, I want to ask another question, "What is the “right” way to iterate through a Range in Ruby backwards?"! (This question is how I came to this page.)

It is nice to do (for the forwards):

(1..10).each{|i| puts "i=#{i}" }

but I don't like to do (for the backwards):

(1..10).to_a.reverse.each{|i| puts "i=#{i}" }

Well, I don't actually mind doing that too much, but when I am teaching going backwards, I want to show my students a nice symmetry (i.e. with minimal difference, e.g. only adding a reverse, or a step -1, but without modifying anything else).
You can do (for symmetry):

(a=*1..10).each{|i| puts "i=#{i}" }

and

(a=*1..10).reverse.each{|i| puts "i=#{i}" }

which I don't like much, but you can't do

(*1..10).each{|i| puts "i=#{i}" }
(*1..10).reverse.each{|i| puts "i=#{i}" }
#
(1..10).step(1){|i| puts "i=#{i}" }
(1..10).step(-1){|i| puts "i=#{i}" }
#
(1..10).each{|i| puts "i=#{i}" }
(10..1).each{|i| puts "i=#{i}" } # I don't want this though. It's dangerous

You could ultimately do

class Range

def each_reverse(&block)
self.to_a.reverse.each(&block)
end

end

but I want to teach pure Ruby rather than object oriented approaches (just yet). I would like to iterate backwards:

- without creating an array (consider 0..1000000000)
- working for any Range (e.g. Strings, not just Integers)
- without using any extra object oriented power (i.e. no class modification)

I believe this is impossible without defining a `pred` method, which means modifying the Range class to use it. If you can do this please let me know, otherwise confirmation of impossibility would be appreciated though it would be disappointing. Perhaps Ruby 1.9 addresses this.

(Thanks for your time in reading this.)
Reply

#7
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 <code>each_slice</code> 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.
Reply

#8
The right way is the one you feel most comfortable with and which does what you want it to do. In programming there is rarely one 'correct' way to do things, more often there are multiple ways to choose.

If you are comfortable with certain way of doings things, do just it, unless it doesn't work - then it is time to find better way.
Reply

#9
In Ruby 2.1, each_with_index method is removed.
Instead you can use [each_index][1]

Example:

a = [ "a", "b", "c" ]
a.each_index {|x| print x, " -- " }

produces:

0 -- 1 -- 2 --

[1]:http://www.ruby-doc.org/core-2.1.3/Array.html#method-i-each_index
Reply

#10
Here are the four options listed in your question, arranged by freedom of control. You might want to use a different one depending on what you need.

1. Simply go through values:

array.each

2. Simply go through indices:

array.each_index

3. Go through indices + index variable:

for i in array

4. Control loop count + index variable:

array.length.times do | i |
Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through