SyncMaster
SyncMaster

Reputation: 9926

How to loop over two arrays and create a map in Ruby

I am new to Ruby and trying out few examples on this link.

Say, I have two arrays

Array1: ["square", "circle", "triagle"]

Array2: ["red", "blue"]

I want to create a map such as,

[{:shape=>"square", :color=>"red"}]
[{:shape=>"square", :color=>"blue"}]
[{:shape=>"circle", :color=>"red"}]
[{:shape=>"circle", :color=>"blue"}]
[{:shape=>"triangle", :color=>"red"}]
[{:shape=>"triangle", :color=>"blue"}]

Here is the code I have tried.

def processArray6(array1, array2)
    newMap = [array1].map do |entry|
    {
        shape: entry,
        color: "abc" # Should write another for-loop here to loop over array2
    }
    end
end

array1 = ["square", "circle", "triangle"]
array2 = ["red", "blue,"]

p processArray6(array1, array2)

Output for the above code:

[{:shape=>["square", "circle", "triangle"], :color=>"abc"}]

I am not very certain on how to loop over the array2.

I am from Java background and still trying to understand how an entire map gets returned from a function and how to process each element of an array and create a map.

Upvotes: 1

Views: 1412

Answers (4)

Cary Swoveland
Cary Swoveland

Reputation: 110675

Array#product is commonly used here, but it returns an array (from which the hash is constructed), which could consume an excessive amount of memory if the two arrays whose product is being computed are large. Here's a way of obtaining the hashes without using product (or creating a temporary array by other means).

keys = [:shape, :color]
arr1 = ["square", "circle", "triangle"]
arr2 = ["red", "blue"]

arr1.each_with_object([]) do |shape, arr|
  arr2.each { |color| arr << keys.zip([shape, color]).to_h }
end
  #=> [{:shape=>"square", :color=>"red"},
  #    {:shape=>"square", :color=>"blue"},
  #    {:shape=>"circle", :color=>"red"},
  #    ...
  #    {:shape=>"triangle", :color=>"blue"}]              

It's curious that there is no Array method that produces an enumerator that generates the elements in the array returned by Array#product. It's easy enough to make one. (See Enumerator::new.)

class Array
  def product_each(arr)
    Enumerator.new do |y|
      each { |e| arr.each { |f| y << [e,f] } }
    end
  end
end

Then

enum = arr1.product_each(arr2)
loop { p enum.next }

displays:

["square", "red"]
["square", "blue"]
["circle", "red"]
["circle", "blue"]
["triangle", "red"]
["triangle", "blue"]

enabling us to compute:

arr1.product_each(arr2).map { |pair| keys.zip(pair).to_h }
  #=> [{:shape=>"square", :color=>"red"},
  #    {:shape=>"square", :color=>"blue"},
  #    {:shape=>"circle", :color=>"red"},
  #    ...
  #    {:shape=>"triangle", :color=>"blue"}]              

without creating a temporary array.

Upvotes: 2

tadman
tadman

Reputation: 211560

You want to compute the "product" of the two lists, which is actually really easy as Ruby has the product function specifically for this:

KEYS = %i[ shape color ]

def all_shape_colors(shapes, colors)
  shapes.product(colors).map do |pair|
    KEYS.zip(pair).to_h
  end
end

zip is used here to key the two entries in each pair.

In practice it looks like this:

shapes = %w[ square circle triagle ]

colors = %w[ red blue ]

all_shape_colors(shapes, colors)
# => [{:shape=>"square", :color=>"red"}, {:shape=>"square", :color=>"blue"}, {:shape=>"circle", :color=>"red"}, {:shape=>"circle", :color=>"blue"}, {:shape=>"triagle", :color=>"red"}, {:shape=>"triagle", :color=>"blue"}]

Note the use of %w[ x y ] instead of [ "x", "y" ] as a way of expressing the same array with more minimal syntax. %i[ ... ] is the same but returns an array of symbols instead of strings.

You can actually go one further here and make a very generic method that does this combination for you on an arbitrary number of things:

def hash_product(**things)
  keys = things.keys

  things.values.inject do |s, options|
    # Compute product and flatten, necessary for chains > length 2
    s.product(options).map(&:flatten)
  end.map do |set|
    keys.zip(set).to_h
  end
end

Where you now use it like this:

hash_product(shape: shapes, color: colors)

That means there's no requirement for a fixed set of keys. Anything will work:

hash_product(hair: %w[ red blue green ], hat: %w[ white black yellow ], shoes: %w[ orange brown tan])
# => [{:hair=>"red", :hat=>"white", :shoes=>"orange"}, {:hair=>"red", :hat=>"white", :shoes=>"brown"}, ... ]

Upvotes: 2

Sebasti&#225;n Palma
Sebasti&#225;n Palma

Reputation: 33420

If what you need is an array of hashes, where every hash has the keys shape and color, then you can use product between array1 and array2 and then just map the result of that:

array1.product(array2).map { |shape, color| { shape: shape, color: color } }
# [{:shape=>"square", :color=>"red"}, {:shape=>"square", :color=>"blue"}, {:shape=>"circle", :color=>"red"}, {:shape=>"circle", :color=>"blue"}, {:shape=>"triagle", :color=>"red"}, {:shape=>"triagle", :color=>"blue"}]

Upvotes: 5

James
James

Reputation: 21

The following will return an array of hashes

def processArray6(array1, array2)

array3 = []

array2.each{|color| array1.each{|shape| array3 << {:shape => shape, :color => color}}}

return array3

end


>> processArray6(array1, array2)
=> [{:color=>"red", :shape=>"square"}, {:color=>"red", :shape=>"circle"}, {:color=>"red", :shape=>"triagle"}, {:color=>"blue", :shape=>"square"}, {:color=>"blue", :shape=>"circle"}, {:color=>"blue", :shape=>"triagle"}]

Upvotes: 2

Related Questions