在Ruby中迭代数组的“正确”方法是什么?

时间:2008-11-22 00:38:20

标签: ruby arrays loops

对于它的所有瑕疵,PHP在这方面非常好。数组和哈希之间没有区别(也许我很天真,但这对我来说显然是正确的),并且可以通过迭代来完成

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

在Ruby中有很多方法可以做这种事情:

array.length.times do |i|
end

array.each

array.each_index

for i in array

哈希更有意义,因为我总是使用

hash.each do |key, value|

为什么我不能为数组做这个?如果我只想记住一种方法,我想我可以使用each_index(因为它使索引和值都可用),但是必须执行array[index]而不仅仅是{{1} }。


哦,对,我忘了value。但是,这个很糟糕,因为array.each_with_index|value, key|转为hash.each!这不是疯了吗?

12 个答案:

答案 0 :(得分:519)

这将迭代所有元素:

array = [1, 2, 3, 4, 5, 6]
array.each { |x| puts x }

打印:

1
2
3
4
5
6

这将迭代所有给出值和索引的元素:

array = ["A", "B", "C"]
array.each_with_index {|val, index| puts "#{val} => #{index}" }

打印:

A => 0
B => 1
C => 2

我不确定你的问题是哪一个。

答案 1 :(得分:75)

我认为没有一种正确的方式。有许多不同的迭代方法,每个方法都有自己的利基。

  • each足以满足许多用法,因为我不经常关心索引。
  • each_ with _index就像Hash#each - 你得到的值和索引。
  • each_index - 只是索引。我不经常使用这个。相当于“length.times”。
  • map是另一种迭代方式,当你想将一个数组转换成另一个数组时非常有用。
  • select是您想要选择子集时使用的迭代器。
  • inject对于生成金额或产品或收集单个结果非常有用。

要记住它似乎很多,但不要担心,你可以在不知道所有这些的情况下过去。但是当你开始学习和使用不同的方法时,你的代码将变得更清晰,更清晰,你将会在Ruby的掌握之路上。

答案 2 :(得分:56)

我不是说Array - > |value,index|Hash - > |key,value|并非疯狂(见Horace Loeb的评论),但我说有一种理智的方式可以期待这种安排。

当我处理数组时,我专注于数组中的元素(而不是索引,因为索引是暂时的)。该方法各自具有索引,即每个+索引,或者|每个索引|或|value,index|。这也与被视为可选参数的索引一致,例如|值|相当于| value,index = nil |这与| value,index |。

一致

当我处理哈希时,我经常更关注键而不是值,而且我通常按顺序处理键和值,key => valuehash[key] = value

如果你想要鸭子打字,那么要么明确地使用Brent Longborough所展示的定义方法,要么使用maxhawkins显示的隐式方法。

Ruby是关于适应程序员的语言,而不是适合语言的程序员。这就是为什么有这么多方法。有很多方法可以考虑某些事情。在Ruby中,您选择最接近的代码,其余的代码通常会非常简洁明了。

至于最初的问题,“在Ruby中迭代数组的”正确“方法是什么?”,好吧,我认为核心方式(即没有强大的语法糖或面向对象的权力)是:< / p>

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

但Ruby完全是关于强大的语法糖和面向对象的力量,但无论如何这里是哈希的等价物,键可以是否有序:

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

所以,我的回答是,“在Ruby中迭代数组的”正确“方法取决于你(即程序员或编程团队)和项目。”更好的Ruby程序员可以做出更好的选择(语法能力和/或面向对象的方法)。更好的Ruby程序员继续寻找更多方法。


现在,我想问另一个问题,“在Ruby中迭代一个Range的”正确“方法是什么?”! (这个问题是我如何来到这个页面。)

很高兴(对于前锋):

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

但我不喜欢做(向后):

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

嗯,我实际上并不介意这样做太多,但是当我教导倒退时,我想向学生展示一个很好的对称性(即差异很小,例如只添加一个反向,或者步骤-1) ,但不修改任何其他内容)。 你可以做(​​对称):

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

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

我不太喜欢,但你做不到

(*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

你最终可以做到

class Range

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

end

但我想教纯Ruby而不是面向对象的方法(仅此而已)。我想倒退:

  • 不创建数组(考虑0..1000000000)
  • 适用于任何范围(例如字符串,而不仅仅是整数)
  • 不使用任何额外的面向对象的功率(即没有类修改)

我认为如果不定义pred方法,这是不可能的,这意味着修改Range类以使用它。如果你能做到这一点,请告诉我,否则将无法确认不可能性,虽然这将是令人失望的。也许Ruby 1.9解决了这个问题。

(感谢您抽出时间阅读本文。)

答案 3 :(得分:17)

如果需要,请使用each_with_index。

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

答案 4 :(得分:12)

其他答案很好,但我想指出另一个外围的东西:数组是有序的,而哈希不是1.8。 (在Ruby 1.9中,Hashes按键的顺序排序。)因此,在1.9之前以与Arrays相同的方式/序列迭代Hash是没有意义的,Arrays总是有一个明确的排序。我不知道PHP关联数组的默认顺序是什么(显然我的google fu还不够强大,但也不知道),但我不知道如何考虑常规PHP数组和PHP关联数组在这种情况下是“相同的”,因为关联数组的顺序似乎未定义。

因此,Ruby方式对我来说似乎更加清晰和直观。 :)

答案 5 :(得分:7)

尝试与数组和哈希一致地做同样的事情可能只是代码气味,但是,冒着我被烙为一个有序的半猴子修补程序的风险,如果你是寻找一致的行为,这会成功吗?:

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}"
}

答案 6 :(得分:5)

我一直在尝试使用哈希构建一个菜单(在 Camping Markaby 中)。

每个项目都有2个元素:菜单标签网址,因此哈希值似乎正确,但“首页”的'/'网址始终显示在最后(正如你所期望的那样,所以菜单项的顺序错误。

使用each_slice的数组完成工作:

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

为每个菜单项添加额外值(例如,像CSS ID 名称)只是意味着增加切片值。所以,就像一个哈希,但包含任意数量的项目组。完美。

所以这只是感谢你无意中暗示了一个解决方案!

明显,但值得说明:我建议检查数组的长度是否可以被切片值整除。

答案 7 :(得分:5)

以下是您的问题中列出的四个选项,由控制权自由排列。根据您的需要,您可能想要使用另一个。

  1. 只需浏览价值观:

    array.each_index
    
  2. 只需浏览指数:

    for i in array
    
  3. 浏览指数+索引变量:

    array.length.times do | i |
    
  4. 控制循环计数+索引变量:

    if(count==1 && $(this).attr("src")=="W.png"){
        $(this).attr("src","X.png");
            count++;
    }
    
    else if(count==2 && $(this).attr("src")=="W.png") {
            $(this).attr("src", "O.png");
            count--;
    }
    

答案 8 :(得分:3)

如果您使用enumerable mixin(如Rails那样),您可以执行类似于列出的php代码段的操作。只需使用each_slice方法并展平哈希。

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}" }

需要更少的猴子修补。

但是,当您具有递归数组或具有数组值的哈希时,这确实会导致问题。在ruby 1.9中,这个问题通过flatten方法的参数来解决,该参数指定了递归的深度。

# 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]]

关于这是否是代码味道的问题,我不确定。通常当我不得不向后弯腰迭代某些东西时,我退后一步意识到我正在解决这个问题。

答案 9 :(得分:2)

正确的方式是您感觉最舒服的方式,以及您希望它做的事情。在编程中,很少有一种“正确”的做法,更常见的是有多种方法可供选择。

如果你对某些行为方式感到满意,那就去做吧,除非它不起作用 - 那么现在是时候寻找更好的方法了。

答案 10 :(得分:2)

在Ruby 2.1中,删除了each_with_index方法。 相反,您可以使用each_index

示例:

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

产生

0 -- 1 -- 2 --

答案 11 :(得分:0)

使用相同的方法遍历数组和散列是有意义的,例如,处理通常由解析器,读取JSON文件等产生的嵌套哈希和数组结构。

一种尚未被提及的聪明方法是如何在标准库扩展的Ruby Facets库中完成它。来自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

已经有Hash#each_pair,别名为Hash#each。因此,在此补丁程序之后,我们还有Array#each_pair,可以互换使用它来遍历哈希和数组。这解决了OP观察到的与Array#each_with_index相比将Hash#each的块参数取反的精神错乱。用法示例:

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, !"