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

后端 未结 12 1718
盖世英雄少女心
盖世英雄少女心 2020-12-04 04:18

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 ite

相关标签:
12条回答
  • 2020-12-04 04:57

    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.

    0 讨论(0)
  • 2020-12-04 05:02

    Using the same method for iterating through both arrays and hashes makes sense, for example to process nested hash-and-array structures often resulting from parsers, from reading JSON files etc..

    One clever way that has not yet been mentioned is how it's done in the Ruby Facets library of standard library extensions. From here:

    class Array
    
      # Iterate over index and value. The intention of this
      # method is to provide polymorphism with Hash.
      #
      def each_pair #:yield:
        each_with_index {|e, i| yield(i,e) }
      end
    
    end
    

    There is already Hash#each_pair, an alias of Hash#each. So after this patch, we also have Array#each_pair and can use it interchangeably to iterate through both Hashes and Arrays. This fixes the OP's observed insanity that Array#each_with_index has the block arguments reversed compared to Hash#each. Example usage:

    my_array = ['Hello', 'World', '!']
    my_array.each_pair { |key, value| pp "#{key}, #{value}" }
    
    # result: 
    "0, Hello"
    "1, World"
    "2, !"
    
    my_hash = { '0' => 'Hello', '1' => 'World', '2' => '!' }
    my_hash.each_pair { |key, value| pp "#{key}, #{value}" }
    
    # result: 
    "0, Hello"
    "1, World"
    "2, !"
    
    0 讨论(0)
  • 2020-12-04 05:03

    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. :)

    0 讨论(0)
  • 2020-12-04 05:05

    If you use the enumerable 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.

    0 讨论(0)
  • 2020-12-04 05:05

    In Ruby 2.1, each_with_index method is removed. Instead you can use each_index

    Example:

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

    produces:

    0 -- 1 -- 2 --
    
    0 讨论(0)
  • 2020-12-04 05:08

    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.)

    0 讨论(0)
提交回复
热议问题