Reputation: 9926
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
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
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
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
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