Что такое Ruby-способ перебора сразу двух массивов

127

Скорее синтаксическое любопытство, чем проблема, которую нужно решить ...

У меня есть два массива одинаковой длины, и я хочу перебрать их сразу - например, чтобы вывести оба их значения по определенному индексу.

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

Я знаю, что могу использовать each_indexи индексировать массивы следующим образом:

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

Есть ли способ сделать это лучше с помощью Ruby ? Что-то вроде этого?

# 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
МСЧ
источник
1
оба массива имеют одинаковый размер?
Anurag
1
Ага - оба, как известно, одинаковой длины
nfm

Ответы:

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]]
Джон Ла Рой
источник
12
'.each' может разворачивать элементы массива? Интересно, сколько еще Руби я не знаю: /
Никита Рыбак
5
Если вы собираетесь использовать подобное выражение, всегда лучше использовать круглые скобки для вызовов методов, на всякий случай. @ budget.zip (@actual) .each
AboutRuby
1
@AboutRuby: В этом случае это необходимо! Исправлена.
Марк-Андре Лафортюн,
2
когда я вижу такие рубиновые линии, я поднимаю руки вверх и хожу по комнате, как чемпион!
iGbanam
9
Это масштабируется? Если у меня есть 2 массива по 10000 элементов, требуется ли для этого создание массива из 20 000 элементов? Документы предлагают это.
Том Андерсен
21

Используйте Array.zipметод и передайте ему блок для последовательного перебора соответствующих элементов.

Анураг
источник
20

Есть еще один способ перебрать сразу два массива с помощью счетчиков:

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

Счетчики более эффективны, чем примеры, использованные выше, потому что они позволяют использовать бесконечные серии, параллельные итерации и другие методы.

Донато
источник
1
Есть ли способ получить индекс внутри loopпоказанного выше?
Biju
16

В дополнение к тому, a.zip(b).each{|x,y| }что говорили другие, вы также можете сказать [a,b].transpose.each{|x,y| }, что мне кажется немного более симметричным. Вероятно, не так быстро, поскольку вы создаете дополнительный [a,b]массив.

Пол А Юнгвирт
источник
11
+1 Одна из приятных вещей transposeзаключается в том, что возникает исключение, если два массива не одинаковой длины.
Эндрю Гримм
14

Связанный с исходным вопросом, для перебора массивов неравной длины, где вы хотите, чтобы значения циклически менялись, вы можете использовать

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

и Руби даст тебе

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

Это избавляет вас от nilзначений, которые вы получите от простого использования zip.

xavriley
источник
6

Простое соединение двух массивов вместе хорошо работает, если вы имеете дело с массивами. Но что, если вы имеете дело с бесконечными счетчиками, например такими:

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

enum1.zip(enum2)терпит неудачу, потому что zipпытается оценить все элементы и объединить их. Вместо этого сделайте следующее:

enum1.lazy.zip(enum2)

Это lazyспасает вас, делая результирующий перечислитель ленивой оценкой.

Джастин
источник
2

Как насчет компрометации и использования #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] }
Роберт Шааф
источник