¿Cuál es la 'forma Ruby' para iterar sobre dos matrices a la vez?

127

Más una curiosidad sintáctica que un problema a resolver ...

Tengo dos matrices de igual longitud, y quiero iterar sobre ambas a la vez, por ejemplo, para emitir ambos valores en un cierto índice.

@budget = [ 100, 150, 25, 105 ]
@actual = [ 120, 100, 50, 100 ]

Sé que puedo usar each_indexe indexar en las matrices de esta manera:

@budget.each_index do |i|
  puts @budget[i]
  puts @actual[i]
end

¿Hay alguna manera de Ruby para hacer esto mejor? Algo como esto?

# Obviously doesn't achieve what I want it to - but is there something like this?
[@budget, @actual].each do |budget, actual|
  puts budget
  puts actual
end
nfm
fuente
1
son ambas las matrices de igual tamaño?
Anurag
1
Sí, ambos se sabe que son de la misma longitud
nfm

Respuestas:

276
>> @budget = [ 100, 150, 25, 105 ]
=> [100, 150, 25, 105]
>> @actual = [ 120, 100, 50, 100 ]
=> [120, 100, 50, 100]

>> @budget.zip @actual
=> [[100, 120], [150, 100], [25, 50], [105, 100]]

>> @budget.zip(@actual).each do |budget, actual|
?>   puts budget
>>   puts actual
>> end
100
120
150
100
25
50
105
100
=> [[100, 120], [150, 100], [25, 50], [105, 100]]
John La Rooy
fuente
12
'.each' puede desplegar elementos de matriz? Me pregunto cuánto más de Ruby no sé: /
Nikita Rybak
55
Si va a usar una expresión como esa, siempre es mejor usar paréntesis para las llamadas a métodos, por si acaso. @ budget.zip (@actual) .each
AboutRuby
1
@AboutRuby: ¡En este caso, es necesario! Fijo.
Marc-André Lafortune
2
Cuando veo líneas de rubí como esta, lanzo mis manos al aire y doy vueltas por la habitación como un campeón.
iGbanam
9
¿Esto escala? Si tengo 2 matrices de 10000 artículos, ¿esto requiere hacer una matriz de 20,000 artículos? Los documentos sugieren esto.
Tom Andersen
21

Use el Array.zipmétodo y páselo un bloque para iterar sobre los elementos correspondientes secuencialmente.

Anurag
fuente
20

Hay otra forma de iterar sobre dos matrices a la vez utilizando enumeradores:

2.1.2 :003 > enum = [1,2,4].each
 => #<Enumerator: [1, 2, 4]:each> 
2.1.2 :004 > enum2 = [5,6,7].each
 => #<Enumerator: [5, 6, 7]:each> 
2.1.2 :005 > loop do
2.1.2 :006 >     a1,a2=enum.next,enum2.next
2.1.2 :007?>   puts "array 1 #{a1} array 2 #{a2}"
2.1.2 :008?>   end
array 1 1 array 2 5
array 1 2 array 2 6
array 1 4 array 2 7

Los enumeradores son más potentes que los ejemplos utilizados anteriormente, ya que permiten series infinitas, iteraciones paralelas, entre otras técnicas.

Donato
fuente
1
¿Hay alguna manera de obtener el índice dentro de lo que se loopmuestra arriba?
Biju
16

Además de lo a.zip(b).each{|x,y| }que otros han dicho, también se puede decir [a,b].transpose.each{|x,y| }, lo que me parece un poco más simétrico. Sin embargo, probablemente no sea tan rápido, ya que está creando la [a,b]matriz adicional .

Paul A Jungwirth
fuente
11
+1 Una de las cosas buenas de esto transposees que genera una excepción si las dos matrices no tienen la misma longitud.
Andrew Grimm
14

Relacionado con la pregunta original, para iterar sobre matrices de longitud desigual donde desea que los valores circulen, puede usar

[1,2,3,4,5,6].zip([7,8,9].cycle)

y Ruby te dará

[[1, 7], [2, 8], [3, 9], [4, 7], [5, 8], [6, 9]]

Esto te salva de los nilvalores que obtendrás con solo usar zip

xavriley
fuente
6

Simplemente comprimir las dos matrices juntas funciona bien si se trata de matrices. Pero, ¿qué pasa si se trata de enumeradores interminables, como algo como esto:

enum1 = (1..5).cycle
enum2 = (10..12).cycle

enum1.zip(enum2)falla porque zipintenta evaluar todos los elementos y combinarlos. En cambio, haz esto:

enum1.lazy.zip(enum2)

Ese lazyte salva al hacer que el enumerador resultante sea de evaluación diferida.

Justin
fuente
2

¿Qué tal comprometerse y usar #each_with_index?

include Enumerable 

@budget = [ 100, 150, 25, 105 ]
@actual = [ 120, 100, 50, 100 ]

@budget.each_with_index { |val, i| puts val; puts @actual[i] }
Robert Schaaf
fuente