2010-04-15 8 views
7

¿Cuál es una mejor manera de atravesar una matriz mientras se itera a través de otra? Por ejemplo, si tengo dos matrices como la siguiente:Iteraciones de matriz básica en Ruby

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

Suponiendo que las matrices se corresponden entre sí - es decir, Rover es un Terrier, Fido es un Lhasa Apso, etc - Me gustaría crear una clase de perro, y un nuevo objeto perro para cada elemento:

class Dog 
    attr_reader :name, :breed 

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

puedo iterar a través de nombres y razas con lo siguiente:

index = 0 

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

Sin embargo, tengo la sensación de que el uso de la variable de índice es lo malo manera de hacerlo. ¿Cuál sería una mejor manera?

Respuesta

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

Array#zip intercala la matriz de destino con elementos de los argumentos, por lo

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

Puede utilizar matrices de diferentes longitudes (en cuyo caso la matriz de destino determina la longitud de la matriz resultante, con el entradas adicionales completadas con 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'] ] 

También puede comprimir más de dos matrices conjuntamente:

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

Array#map es una gran manera de transformar una matriz, ya que devuelve una matriz en donde cada entrada es el resultado de ejecutar el bloque de la entrada correspondiente en la matriz de destino.

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

Al usar iteradores sobre matrices de matrices, si se le da un bloque de parámetros múltiples, las entradas de matriz se romperá automáticamente en esos parámetros:

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

Como Matt Briggsmentioned, #each_with_index es otra buena herramienta saber sobre. Se repite a través de los elementos de una matriz, pasando un bloque cada elemento a su vez.

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

Al utilizar un iterador como #each_with_index se pueden utilizar paréntesis para romper elementos de la matriz en sus partes constituyentes:

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, el suyo es mejor –

+0

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

3

each_with_index saltos a la mente, es una mejor manera de hacerlo de la manera que lo están haciendo rampion tiene una mejor respuesta general, sin embargo, esta situación es para lo que zip es.

+0

1: definitivamente una buena herramienta para conocer, en lugar que mantener tu propia variable de índice. – rampion

3

Esto es una adaptación de Flanagan y Matz, "The Ruby Programming Language", 5.3.5 "Iteradores externos", Ejemplo 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) } 

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

salida:

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

que creo que es lo que queríamos!

+0

esta es una solución muy buena también, y tiene la ventaja añadida de devolver los objetos y sus atributos. ¡Gracias! – michaelmichael

+0

Ese bucle hizo que mi cabeza girara hasta que algunas investigaciones revelaron cómo funciona. Por Pico, "el ciclo rescata silenciosamente la excepción StopIteration, que funciona bien con iteradores externos". ¡Ordenado! –

2

Así como each_with_index (mencionado por Matt), hay each_index. A veces uso esto porque hace que el programa sea más simétrico y, por lo tanto, el código incorrecto será 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