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

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|!这不是很疯狂吗?

623957 次浏览

这将遍历所有元素:

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

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

当需要时使用each_with_index。

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

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

  • each对于很多用法已经足够了,因为我不经常关心索引。
  • each_ with _index的作用类似于Hash#each -你得到值和索引。
  • each_index -只有索引。我不经常用这个。相当于"length.times"。
  • map是另一种迭代方法,当你想将一个数组转换为另一个数组时很有用。
  • select是当你想选择一个子集时使用的迭代器。
  • inject用于生成和或乘积,或收集单个结果。

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

试图始终如一地用数组和哈希可能做同样的事情只是一种代码气味,但是,冒着被打上可人的半猴子补丁的风险,如果你正在寻找一致的行为,这能做到吗?:

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

其他答案都很好,但我想指出另一个外围问题:数组是有序的,而哈希在1.8中不是。(在Ruby 1.9中,哈希值是按键的插入顺序排序的。)因此,在1.9之前,以与数组相同的方式/顺序遍历哈希是没有意义的,数组总是有明确的顺序。我不知道PHP关联数组的默认顺序是什么(显然我的谷歌fu也不够强,无法计算出这一点),但我不知道在这个上下文中如何可以认为常规PHP数组和PHP关联数组是“相同的”,因为关联数组的顺序似乎没有定义。

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

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

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

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

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

而且

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

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

我一直试图使用散列构建一个菜单(在野营Markaby中)。

每一项都有两个元素:菜单标签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名称)只是意味着增加切片值。就像一个散列,但是组由任意数量的项组成。完美的。

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

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

正确的方式是你觉得最舒服的方式,是你想让它做的方式。在编程中,很少有一种“正确”的方法来做事情,更多的是有多种方法可供选择。

如果你对某种做事方式感到舒服,那就去做,除非它不起作用——那么是时候寻找更好的方法了。

在Ruby 2.1中,each_with_index方法被移除。 相反,你可以使用each_index

例子:

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

生产:

0 -- 1 -- 2 --

以下是你问题中列出的四个选项,按控制自由程度排列。您可能想要使用不同的方法,这取决于您需要什么。

  1. 简单地通过值:

    array.each
    
  2. Simply go through indices:

    array.each_index
    
  3. Go through indices + index variable:

    for i in array
    
  4. Control loop count + index variable:

    array.length.times do | i |
    

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

一个尚未被提及的聪明方法是如何在标准库扩展的Ruby方面库中完成。从在这里:

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