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。然而,这个很糟糕,因为它是|值,键|和哈希。每个键|,值|!这不是很疯狂吗?


当前回答

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.

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

其他回答

我认为没有唯一正确的方法。有许多不同的迭代方法,每种方法都有自己的细分市场。

对于许多用法,每个都足够了,因为我不经常关心索引。 each_ with _index的作用类似于Hash#each——你得到值和索引。 Each_index—只有索引。我不经常用这个。相当于"length.times"。 Map是迭代的另一种方式,当你想将一个数组转换为另一个数组时很有用。 Select是要选择子集时使用的迭代器。 Inject在生成总和或乘积或收集单个结果时很有用。

看起来要记住的东西太多了,但别担心,你不知道所有的单词也能应付过去。但是当您开始学习和使用不同的方法时,您的代码将变得越来越清晰,您将走上精通Ruby的道路。

当需要时使用each_with_index。

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

使用相同的方法迭代数组和散列是有意义的,例如处理嵌套的散列和数组结构,通常由解析器产生,读取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

已经有哈希#each_pair,哈希#each的别名。所以在这个补丁之后,我们也有了数组#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, !"

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

至于这是否是代码气味的问题,我不确定。通常当我不得不竭尽全力迭代某些内容时,我就会后退一步,意识到我在错误地处理问题。

我不是说数组-> |值,索引|和哈希-> |键,值|不是疯狂的(见Horace Loeb的评论),但我是说有一种理智的方式来期望这种安排。

当我处理数组时,我关注的是数组中的元素(而不是索引,因为索引是临时的)。方法是每个带索引,即每个+索引,或|每个,索引|,或|值,索引|。这也与索引被视为可选参数相一致,例如|值|相当于|值,index=nil|与|值一致,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}" }

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

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

其实,我并不介意这么做,但当我在逆向教学时,我想向我的学生展示一个漂亮的对称(即差别最小,例如只添加反向,或步长-1,但不修改其他任何东西)。 你可以这样(为了对称):

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

and

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

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