Qual é a 'maneira Ruby' de iterar duas matrizes ao mesmo tempo

127

Mais uma curiosidade de sintaxe do que um problema para resolver ...

Eu tenho duas matrizes de comprimento igual e quero iterar sobre as duas ao mesmo tempo - por exemplo, para gerar os dois valores em um determinado índice.

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

Eu sei que posso usar each_indexe indexar as matrizes da seguinte maneira:

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

Existe uma maneira Ruby de fazer isso melhor? Algo assim ?

# 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
fonte
1
são as matrizes de tamanhos iguais?
Anurag
1
Sim - ambos conhecidos por
terem

Respostas:

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
fonte
12
'.each' pode desdobrar elementos da matriz? Pergunto-me como muito mais do Ruby Eu não sei: /
Nikita Rybak
5
Se você usar uma expressão como essa, é sempre melhor usar parênteses para chamadas de método, apenas por precaução. @ budget.zip (@actual) .each
AboutRuby
1
@AboutRuby: Neste caso, é necessário! Fixo.
Marc-André Lafortune
2
quando vejo linhas de rubi assim, jogo minhas mãos no ar e ando pela sala como um campeão!
IGbanam
9
Isso escala? Se eu tiver 2 10000 matrizes de itens, será necessário criar uma matriz de 20.000 itens? Os documentos sugerem isso.
Tom Andersen
21

Use o Array.zipmétodo e passe um bloco para iterar sobre os elementos correspondentes sequencialmente.

Anurag
fonte
20

Há outra maneira de iterar mais de duas matrizes ao mesmo tempo usando 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

Os enumeradores são mais poderosos que os exemplos usados ​​acima, porque permitem séries infinitas, iteração paralela, entre outras técnicas.

Donato
fonte
1
Existe uma maneira de obter o índice dentro do loopmostrado acima?
Biju
16

Além do a.zip(b).each{|x,y| }que outros disseram, você também pode dizer [a,b].transpose.each{|x,y| }, o que me parece um pouco mais simétrico. Provavelmente não é tão rápido, pois você está criando uma [a,b]matriz extra .

Paul A Jungwirth
fonte
11
+1 Uma das coisas legais transposeé que isso gera uma exceção se as duas matrizes não tiverem o mesmo comprimento.
Andrew Grimm
14

Relacionado à pergunta original, para iterar sobre matrizes de comprimento desigual, nas quais você deseja que os valores circulem, você pode usar

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

e Ruby vai te dar

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

Isso evita os nilvalores que você obterá usando apenas zip

xavriley
fonte
6

Simplesmente compactar as duas matrizes funciona bem se você estiver lidando com matrizes. Mas e se você estiver lidando com enumeradores intermináveis, como algo como estes:

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

enum1.zip(enum2)falha porque ziptenta avaliar todos os elementos e combiná-los. Em vez disso, faça o seguinte:

enum1.lazy.zip(enum2)

Aquele lazyeconomiza, fazendo com que o enumerador resultante avalie preguiçosamente.

Justin
fonte
2

Que tal comprometer e 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
fonte