2010-04-15 7 views
7

Was ist eine bessere Möglichkeit, ein Array während der Iteration durch ein anderes Array zu durchlaufen? Zum Beispiel, wenn ich zwei Arrays wie folgt aus:Basic-Array-Iteration in Ruby

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

Unter der Annahme, die Arrays entsprechen einander - das heißt, Rover ist ein Terrier, ist Fido ein Lhasa Apso, usw. - ich möchte erstellen ein Hundeklasse und ein neuer Hund-Objekt für jeden Artikel:

class Dog 
    attr_reader :name, :breed 

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

ich durch Namen und Rassen mit folgender laufen kann:

index = 0 

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

Allerdings habe ich das Gefühl, dass die Indexvariable ist die falsche Möglichkeit, darüber zu gehen. Was wäre ein besserer Weg?

Antwort

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

Array#zip verschachtelt die Ziel-Array mit Elementen der Argumente, so

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

Sie Arrays unterschiedlicher Längen verwenden (in diesem Fall das Ziel-Array über die Länge des resultierenden Arrays bestimmt, wobei der zusätzliche Einträge ausgefüllt mit 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'] ] 

Sie können auch zip mehr als zwei Arrays zusammen:

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

Array#map eine großartige Möglichkeit, um ein Array zu verwandeln, da es ein Array zurückgibt, wo jeder Eintrag ist das Ergebnis des Blocks läuft auf der entsprechende Eintrag im Zielarray.

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

Wenn Iteratoren über Arrays von Arrays verwenden, wenn Sie einen mehrere Parameterblock geben, werden die Array-Einträge automatisch in diese Parameter unterteilt werden:

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

Wie Matt Briggsmentioned ist #each_with_index ein weiteres gutes Werkzeug wissen von. Es durchläuft die Elemente eines Arrays und übergibt nacheinander jedem Element einen Block.

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

Wenn ein Iterator wie #each_with_index verwenden Sie Klammern verwenden können Array-Elemente in ihre Bestandteile aufzulösen:

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, liegt bei Ihnen besser –

+0

+1 'zip' http://ruby-doc.org/core/classes/Array.html#M002198 – OscarRyz

3

each_with_index Sprünge in dem Sinne, es ist ein besserer Weg, es so, wie Sie zu tun tun es. rampion hat eine bessere Gesamt-Antwort, diese Situation ist, wofür zip ist.

+0

+1: auf jeden Fall ein gutes Werkzeug zu wissen, eher als eine eigene Indexvariable zu verwalten. – rampion

3

Dies ist von Flanagan und Matz, "The Ruby Programmiersprache", 5.3.5 "Externe Iteratoren", Beispiel 5-1, p angepasst.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) } 

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

Ausgang:

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

was ich denke, ist das, was wir wollten!

+0

Dies ist auch eine sehr gute Lösung und hat den zusätzlichen Vorteil, die Objekte und ihre Attribute zurückzugeben. Vielen Dank! – michaelmichael

+0

Diese Schleife ließ meinen Kopf drehen, bis einige Forschungsergebnisse zeigten, wie es funktioniert. Pro Spitzhacken, "Schleife im Hintergrund rettet die StopIteration Ausnahme, die gut mit externen Iteratoren funktioniert." Ordentlich! –

2

Ebenso wie each_with_index (von Matt erwähnt), gibt es . Ich benutze das manchmal, weil es das Programm symmetrischer macht und daher falscher Code wird 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