Базовая итерация массива в Ruby - PullRequest
7 голосов
/ 16 апреля 2010

Какой лучший способ пройти массив, перебирая другой массив? Например, если у меня есть два массива вроде следующего:

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

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

index = 0

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

Однако я чувствую, что использование индексной переменной - неправильный путь. Что было бы лучше?

Ответы [ 4 ]

24 голосов
/ 16 апреля 2010
dogs = names.zip(breeds).map { |name, breed| Dog.new(name, breed) }

Array#zip чередует целевой массив с элементами аргументов, поэтому

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

Вы можете использовать массивы различной длины (в этом случае целевой массив определяет длину результирующего массива с дополнительными записями, заполненными 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'] ]

Вы также можете объединить более двух массивов:

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

Array#map является отличным способом преобразования массива, поскольку он возвращает массив, где каждая запись является результатом запуска блока для соответствующей записи в целевом массиве.

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

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

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

Как и Мэтт Бриггс упомянул , #each_with_index - еще один хороший инструмент, о котором нужно знать. Он перебирает элементы массива, передавая блок каждому элементу по очереди.

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

При использовании итератора, подобного #each_with_index, вы можете использовать скобки, чтобы разбить элементы массива на составные части:

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'] ]
3 голосов
/ 17 апреля 2010

Это адаптировано из Flanagan и Matz, "Язык программирования Ruby", 5.3.5 "Внешние итераторы", пример 5-1, с. 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) }  

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

Выход:

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

что, я думаю, то, что мы хотели!

3 голосов
/ 16 апреля 2010

each_with_index приходит на ум, это лучший способ сделать это так, как вы это делаете. У rampion есть лучший общий ответ, хотя, эта ситуация для zip.

2 голосов
/ 18 мая 2010

Так же как и each_with_index (упомянутый Мэттом), есть each_index. Я иногда использую это, потому что это делает программу более симметричной, и поэтому неправильный код будет выглядеть неправильно .

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...