379

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]而不是仅仅value.


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

4

12 回答 12

606

这将遍历所有元素:

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

# Output:

1
2
3
4
5
6

这将遍历所有元素,为您提供值和索引:

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

# Output:

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

从你的问题中我不太确定你在找哪一个。

于 2008-11-22T00:40:50.550 回答
101

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

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

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

于 2008-11-22T00:58:50.530 回答
61

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

当我处理数组时,我专注于数组中的元素(不是索引,因为索引是暂时的)。方法是each 带索引,即each+index,或|each,index|,或|value,index|。这也与被视为可选参数的索引一致,例如 |value| 等价于 |value,index=nil| 这与 |value,index| 一致。

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

如果您想要鸭式打字,那么要么显式使用 Brent Longborough 所示的定义方法,要么使用 maxhawkins 所示的隐式方法。

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

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

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 解决了这个问题。

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

于 2009-03-27T23:25:21.510 回答
20

两者都需要时使用 each_with_index。

ary.each_with_index { |val, idx| # ...
于 2008-11-22T00:44:06.767 回答
12

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

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

于 2008-11-23T16:19:33.430 回答
11

以下是您的问题中列出的四个选项,按控制自由排列。您可能希望根据需要使用不同的。

  1. 只需通过值:

    array.each
    
  2. 只需通过索引:

    array.each_index
    
  3. 遍历索引 + 索引变量:

    for i in array
    
  4. 控制循环计数 + 索引变量:

    array.length.times do | i |
    
于 2015-08-21T17:07:08.447 回答
9

尝试用数组和哈希一致地做同样的事情可能只是一种代码味道,但是,如果你正在寻找一致的行为,那么我可能会被贴上色彩斑斓的半猴子修补程序的烙印,这是否可以解决问题?:

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}"
}
于 2008-11-22T13:57:01.770 回答
5

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

每个项目有 2 个元素:一个菜单标签和一个URL,所以哈希看起来是正确的,但是 'Home' 的 '/' URL 总是出现在最后(正如你对哈希的期望一样),所以菜单项出现在错误的命令。

使用数组来each_slice完成这项工作:

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

为每个菜单项添加额外的值(例如 CSS ID名称)只是意味着增加切片值。因此,就像散列一样,但包含由任意数量的项目组成的组。完美的。

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

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

于 2009-06-15T10:41:44.437 回答
4

如果您使用可枚举的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]]

至于这是否是代码异味的问题,我不确定。通常,当我不得不向后弯腰迭代某些东西时,我会后退一步,并意识到我在解决问题上是错误的。

于 2009-01-13T05:16:25.440 回答
3

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

例子:

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

产生:

0 -- 1 -- 2 --
于 2014-09-29T12:11:09.117 回答
2

正确的方式是您觉得最舒服的方式,并且按照您的意愿行事。在编程中,很少有一种“正确”的方式来做事,更多的是有多种方式可供选择。

如果您对某些做事方式感到满意,那就去做吧,除非它不起作用——那么是时候寻找更好的方式了。

于 2014-01-09T16:52:14.603 回答
2

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

尚未提及的一种巧妙方法是它是如何在标准库扩展的Ruby Facets库中完成的。这里

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_indexHash#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, !"
于 2020-04-25T17:38:18.437 回答