Iterate the main array in Ruby

What is the best way to cross an array when repeating through another array? For example, if I have two arrays, for example:

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

Assuming the arrays match each other - that is, Rover - Terrier, Fido - Lhasa Apso, etc. - I would like to create a dog class and a new object for each subject:

 class Dog attr_reader :name, :breed def initialize(name, breed) @name = name @breed = breed end end 

I can iterate over names and breeds with the following:

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

However, I get the feeling that using an index variable is the wrong way to solve it. What would be better?

+7
arrays ruby
source share
4 answers
 dogs = names.zip(breeds).map { |name, breed| Dog.new(name, breed) } 

Array#zip interleaves the target array with argument elements, so

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

You can use arrays of different lengths (in this case, the target array determines the length of the resulting array, and additional entries are filled using 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'] ] 

You can also archive more than two arrays:

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

Array#map is a great way to transform an array, since it returns an array where each record is the result of starting a block in the corresponding record in the target array.

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

When using iterators over arrays of arrays, if you give several blocks of parameters, the array records will be automatically divided into these parameters:

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

As Matt Briggs mentioned , #each_with_index is another good tool to be aware of. It iterates through the elements of the array, passing each element in turn.

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

When using an iterator such as #each_with_index , you can use parentheses to split the elements of the array into its component parts:

 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'] ] 
+24
source share

each_with_index is striking, this is the best way to do it the way you do it. rampion has a better general answer, although this situation is for zip.

+3
source share

This is adapted from Flanagan and Matz, โ€œRuby Programming Language,โ€ 5.3.5 โ€œExternal Iterators,โ€ Example 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) } 

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

Output:

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

which I think is what we wanted!

+3
source share

Like each_with_index (mentioned by Matt), there is each_index . I sometimes use this because it makes the program more symmetrical, and therefore the wrong code looks 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 
+2
source share

All Articles