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


341

尽管有很多缺点,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|!这不是疯了吗?


1
我猜array#each_with_index的用途|value, key|,因为该方法顾名思义订单,而用于为了hash#each模仿hash[key] = value语法?
Benjineer 2014年

3
如果你是刚开始使用Ruby中循环,然后检查了使用选择,拒绝,收集,注入和检测
马修卡里尔

Answers:


560

这将遍历所有元素:

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
离题,我无法在数组中的ruby 1.9中找到each_with_index
CantGetANick 2012年

19
@CantGetANick:Array类包含具有此方法的Enumerable模块。
xuinkrbin。

87

我认为没有正确的方法。有很多不同的迭代方法,每种方法都有其自身的优势。

  • each 足够用于许多用途,因为我通常不关心索引。
  • each_ with _index 就像Hash#each一样-获得值和索引。
  • each_index-只是索引。我不经常使用这个。等效于“ length.times”。
  • map 是另一种迭代方法,当您要将一个数组转换为另一个数组时很有用。
  • select 是要选择子集时要使用的迭代器。
  • inject 可用于生成总和或乘积,或收集单个结果。

似乎要记住很多东西,但是请不要担心,您可能会不了解所有这些情况而度过。但是,随着您开始学习和使用不同的方法,您的代码将变得更加清晰明了,并且您将掌握Ruby。


好答案!我想提一下#reject之类的反义词和#collect之类的别名。
艾米莉·崔Ch

60

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

当我处理数组时,我专注于数组中的元素(而不是索引,因为索引是暂时的)。该方法是每个带有索引的,即每个+索引,或| 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解决了这个问题。

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


5
1.upto(10) do |i| puts i end10.downto(1) do puts i end给出所需的对称性。希望能有所帮助。不知道它是否适用于字符串等。
苏伦(Suren)

(1..10).to_a.sort {| x,y | y <=> x} .each {| i | 放“ i =#{i}”}-反向速度较慢。
Davidslv

你可以[*1..10].each{|i| puts "i=#{i}" }
Hauleth

19

两者同时使用时,请使用each_with_index。

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

12

其他答案也很好,但是我想指出其他一些外围问题:数组是有序的,而哈希值不在1.8中。(在Ruby 1.9中,哈希值是按键的插入顺序排序的。)因此,在1.9之前,以与始终具有确定顺序的Arrays相同的方式/顺序遍历Hash毫无意义。我不知道PHP关联数组的默认顺序是什么(显然我的google fu也不够强大,无法弄清楚这一点),但是我不知道如何考虑常规的PHP数组和PHP关联数组。在这种情况下应“相同”,因为关联数组的顺序似乎不确定。

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


1
哈希和数组是一回事!数组将整数映射到对象,哈希将对象映射到对象。数组只是哈希的特殊情况,不是吗?
汤姆·雷曼

1
就像我说的,数组是有序集合。映射(一般意义上)是无序的。如果将键集限制为整数(例如,使用数组),则键集必须具有顺序。在通用映射(哈希/关联数组)中,键可能没有顺序。
Pistos

@Horace:哈希和数组不一样。如果一个是另一个的特殊情况,则它们不能相同。但更糟糕的是,数组不是一种特殊的哈希,这只是查看它们的一种抽象方式。正如上面的Brent所指出的,可互换使用哈希和数组可能表示代码有异味。
Zane 2014年

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

7

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

  1. 只需查看值:

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

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

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

    array.length.times do | i |

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名称)仅意味着增加分片值。因此,就像散列一样,但具有由任意数量的项目组成的组。完善。

因此,这只是感谢您无意中暗示了解决方案!

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


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

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


2

正确的方法是您感到最自在的方法,并且可以按照自己的意愿去做。在编程中,很少有一种“正确”的做事方式,更多情况下是有多种选择方式。

如果您对某些特定的处理方式感到满意,请按部就班,除非它不起作用-那么该是时候找到更好的方法了。


2

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

例:

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

产生:

0 -- 1 -- 2 --

4
这不是真的。如接受答案的注释中所述,之所以each_with_index没有出现在文档中是因为它是由Enumerable模块提供的。
ihaztehcodez

0

使用相同的方法遍历数组和哈希是有意义的,例如处理嵌套的哈希和数组结构,这些结构通常是由解析器,读取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_index该疯狂使block参数与相比是相反的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, !"
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.