2010-04-15 50 views
7

在迭代另一个数组的同时遍历数组的更好方法是什么?例如,如果我有两个阵列类似如下:Ruby中的基本数组迭代

names = [ "Rover", "Fido", "Lassie", "Calypso"] 
breeds = [ "Terrier", "Lhasa Apso", "Collie", "Bulldog"] 

假设阵列与一个对应另一个 - 也就是说,流动站是梗,菲多是拉萨犬等 - 我想创建狗类,以及一个新的狗对象为每个项目:

class Dog 
    attr_reader :name, :breed 

    def initialize(name, breed) 
    @name = name 
    @breed = breed 
    end 
end 

我可以通过名称和品种重复下列要求:

index = 0 

names.each do |name| 
    Dog.new("#{name}", "#{breeds[index]}") 
    index = index.next 
end 

但是,我得到的感觉,使用索引变量错的 方式去了解它。什么会更好?

回答

24
dogs = names.zip(breeds).map { |name, breed| Dog.new(name, breed) } 

Array#zip交织与的参数元素的目标阵列,所以

irb> [1, 2, 3].zip(['a', 'b', 'c']) 
#=> [ [1, 'a'], [2, 'b'], [3, 'c'] ] 

可以使用不同长度的阵列(在该情况下,目标阵列决定了所得阵列的长度,与额外的条目填入nil)。

irb> [1, 2, 3, 4, 5].zip(['a', 'b', 'c']) 
#=> [ [1, 'a'], [2, 'b'], [3, 'c'], [4, nil], [5, nil] ] 
irb> [1, 2, 3].zip(['a', 'b', 'c', 'd', 'e']) 
#=> [ [1, 'a'], [2, 'b'], [3, 'c'] ] 

您也可以压缩多于两个阵列一起:

irb> [1,2,3].zip(['a', 'b', 'c'], [:alpha, :beta, :gamma]) 
#=> [ [1, 'a', :alpha], [2, 'b', :beta], [3, 'c', :gamma] ] 

Array#map是改造的阵列的好方法,因为它返回一个阵列,其中每个条目是上运行的块的结果目标数组中的相应条目。

irb> [1,2,3].map { |n| 10 - n } 
#=> [ 9, 8, 7 ] 

当使用迭代过数组的数组,如果得到多个参数块,该阵列的条目将被自动分成那些参数:

irb> [ [1, 'a'], [2, 'b'], [3, 'c'] ].each { |array| p array } 
[ 1, 'a' ] 
[ 2, 'b' ] 
[ 3, 'c' ] 
#=> nil 
irb> [ [1, 'a'], [2, 'b'], [3, 'c'] ].each do |num, char| 
...> puts "number: #{num}, character: #{char}" 
...> end 
number 1, character: a 
number 2, character: b 
number 3, character: c 
#=> [ [1, 'a'], [2, 'b'], [3, 'c'] ] 

Matt Briggsmentioned#each_with_index是另一个很好的工具了解。它遍历数组的元素,依次传递每个元素的块。

irb> ['a', 'b', 'c'].each_with_index do |char, index| 
...> puts "character #{char} at index #{index}" 
...> end 
character a at index 0 
character b at index 1 
character c at index 2 
#=> [ 'a', 'b', 'c' ] 

当使用像#each_with_index迭代器,你可以用括号把数组元素分解成它们的组成部分:

irb> [ [1, 'a'], [2, 'b'], [3, 'c'] ].each_with_index do |(num, char), index| 
...> puts "number: #{num}, character: #{char} at index #{index}" 
...> end 
number 1, character: a at index 0 
number 2, character: b at index 1 
number 3, character: c at index 2 
#=> [ [1, 'a'], [2, 'b'], [3, 'c'] ] 
+0

+1,你是好 – 2010-04-15 20:45:15

+0

+1'zip' http://ruby-doc.org/core/classes/Array.html#M002198 – OscarRyz 2010-04-15 20:52:48

3

each_with_index飞跃在脑海中,这是一个更好的方式来做到这一点,你的方式正在做。虽然rampion有更好的整体回答,但这种情况是zip的作用。

+0

+1:绝对是一个很好的工具来了解,而比维护你自己的索引变量。 – rampion 2010-04-15 20:46:42

3

这是改编自Flanagan和Matz的“Ruby编程语言”,5.3.5“外部迭代器”,例5-1,p。139:

++++++++++++++++++++++++++++++++++++++++++

require 'enumerator' # needed for Ruby 1.8 

names = ["Rover", "Fido", "Lassie", "Calypso"] 
breeds = ["Terrier", "Lhasa Apso", "Collie", "Bulldog"] 

class Dog 
    attr_reader :name, :breed 

    def initialize(name, breed) 
     @name = name 
     @breed = breed 
    end 
end 

def bundle(*enumerables) 
    enumerators = enumerables.map {|e| e.to_enum} 
    loop {yield enumerators.map {|e| e.next} } 
end 

bundle(names, breeds) {|x| p Dog.new(*x) } 

+++++++++++++++++++++++++++++++++++++++++++

输出:

#<Dog:0x10014b648 @name="Rover", @breed="Terrier"> 
#<Dog:0x10014b0d0 @name="Fido", @breed="Lhasa Apso"> 
#<Dog:0x10014ab80 @name="Lassie", @breed="Collie"> 
#<Dog:0x10014a770 @name="Calypso", @breed="Bulldog"> 

我认为这是我们想要的!

+0

这也是一个非常好的解决方案,并具有返回对象及其属性的额外好处。谢谢! – michaelmichael 2010-04-17 16:24:32

+0

这个循环让我头脑转动,直到一些研究揭示它是如何工作的。每镐“,循环默默地拯救StopIteration异常,这与外部迭代器很好地结合。”整齐! – 2010-04-18 07:43:16

2

以及each_with_index(马特提到),有each_index。我有时会使用它,因为它会使程序更加对称,因此错误代码将会是look wrong

names.each_index do |i| 
    name, breed = dogs[i], breeds[i] #Can also use dogs.fetch(i) if you want to fail fast 
    Dog.new(name, breed) 
end