I'm getting data from an API and need to format it differently. I have a car_array
that consists of an array of hashes. However, sometimes there will be a sub-array as one of the hash values that contains more than 1 element. In this case, there should be a loop so that each element in the array gets mapped correctly as separate entries.
An example of data, note how prices
and options_package
are arrays with multiple elements.
[{
dealer_id: 1,
dealer_name: "dealership 1",
car_make: "jeep",
prices: ['30', '32', '35'],
options_package: ['A', 'B', 'C']
}, {
dealer_id: 2,
dealer_name: "dealership 2",
car_make: "ford",
prices: ['50', '55'],
options_package: ['X', 'Y']
}, {
dealer_id: 3,
dealer_name: "dealership 3",
car_make: "dodge",
prices: ['70'],
options_package: ['A']
}]
I would like to create multiple entries when there are multiple array elements
for example, the data above should be broken out and mapped as:
some_array = [
{ dealer_id: 1, dealer_name: "dealership 1", car_make: "jeep", price: '30', options_package: 'A' },
{ dealer_id: 1, dealer_name: "dealership 1", car_make: "jeep", price: '32', options_package: 'B' },
{ dealer_id: 1, dealer_name: "dealership 1", car_make: "jeep", price: '35', options_package: 'C' },
{ dealer_id: 2, dealer_name: "dealership 2", car_make: "ford", price: '50', options_package: 'X' },
{ dealer_id: 2, dealer_name: "dealership 2", car_make: "ford", price: '55', options_package: 'Y' },
{ dealer_id: 3, dealer_name: "dealership 3", car_make: "dodge", price: '70', options_package: 'A' }
]
Here's what I've got so far:
car_arr.each do |car|
if car['Prices'].length > 1
# if there are multiple prices/options loop through each one and create a new car
car.each do |key, value|
if key == 'Prices'
value.each do |price|
formatted_car_array << {
dealer_id: car['dealer_id'],
dealer_name: car['dealer_name'],
car_make: car['make'],
options_package: ???????,
price: price,
}
end
end
end
else
# there's only element for price and options_package
formatted_car_array << {
dealer_id: car['dealer_id'],
dealer_name: car['dealer_name'],
car_make: car['make'],
options_package: car['options_package'],
price: car['prices']
}
end
end
CodePudding user response:
Consider just one hash to start with, and how this problem can be solved for this simpler problem.
h = {
dealer_id: 1,
dealer_name: "dealership 1",
car_make: "jeep",
prices: ['30', '32', '35'],
options_package: ['A', 'B', 'C']
}
Let's get combinations of prices and options packages using #zip
.
h[:prices].zip(h[:options_package])
# => [["30", "A"], ["32", "B"], ["35", "C"]]
The length of 3
for this array corresponds with how many hashes we expect to get from it, so let's map over those, building a new hash each time.
h[:prices].zip(h[:options_package]).map do |price, pkg|
{
dealer_id: h[:dealer_id],
dealer_name: h[:dealer_name],
car_make: h[:car_make],
price: price,
options_package: pkg,
}
end
# => [{:price=>"30", :options_package=>"A", :dealership=>nil, :dealer_id=>1, :car_make=>"jeep"},
# {:price=>"32", :options_package=>"B", :dealership=>nil, :dealer_id=>1, :car_make=>"jeep"},
# {:price=>"35", :options_package=>"C", :dealership=>nil, :dealer_id=>1, :car_make=>"jeep"}]
Now you just need to #flat_map
this over your array.
car_arr.flat_map do |h|
h[:prices].zip(h[:options_package]).map do |price, pkg|
{
dealer_id: h[:dealer_id],
dealer_name: h[:dealer_name],
car_make: h[:car_make],
price: price,
options_package: pkg,
}
end
end
# => [{:price=>"30", :options_package=>"A", :dealership=>nil, :dealer_id=>1, :car_make=>"jeep"},
# {:price=>"32", :options_package=>"B", :dealership=>nil, :dealer_id=>1, :car_make=>"jeep"},
# {:price=>"35", :options_package=>"C", :dealership=>nil, :dealer_id=>1, :car_make=>"jeep"},
# {:price=>"50", :options_package=>"X", :dealership=>nil, :dealer_id=>2, :car_make=>"ford"},
# {:price=>"55", :options_package=>"Y", :dealership=>nil, :dealer_id=>2, :car_make=>"ford"},
# {:price=>"70", :options_package=>"A", :dealership=>nil, :dealer_id=>3, :car_make=>"dodge"}]
CodePudding user response:
If arr
is the array of hashes given in the question one may write the following.
arr.flat_map do |h|
h[:prices].zip(h[:options_package]).map do |p,o|
h.reject { |k,_| k == :prices }.merge(price: p, options_package: o)
end
end
#=> [{:dealer_id=>1, :dealer_name=>"dealership 1", :car_make=>"jeep",
# :options_package=>"A", :price=>"30"},
# {:dealer_id=>1, :dealer_name=>"dealership 1", :car_make=>"jeep",
# :options_package=>"B", :price=>"32"},
# {:dealer_id=>1, :dealer_name=>"dealership 1", :car_make=>"jeep",
# :options_package=>"C", :price=>"35"},
# {:dealer_id=>2, :dealer_name=>"dealership 2", :car_make=>"ford",
# :options_package=>"X", :price=>"50"},
# {:dealer_id=>2, :dealer_name=>"dealership 2", :car_make=>"ford",
# :options_package=>"Y", :price=>"55"},
# {:dealer_id=>3, :dealer_name=>"dealership 3", :car_make=>"dodge",
# :options_package=>"A", :price=>"70"}]
Note that this code need not be changed if key-value pairs are added to the hash or if the keys :dealer_id
and :dealer_name
are deleted or renamed.
CodePudding user response:
My answer is an extension upon the answer of Chris.
car_arr.flat_map do |h| h[:prices].zip(h[:options_package]).map do |price, pkg| { dealer_id: h[:dealer_id], dealer_name: h[:dealer_name], car_make: h[:car_make], price: price, options_package: pkg, } end end
This answer could be shortened if you're using the newest Ruby version.
Ruby 3.0 introduced Hash#except
, which lets you easily create a copy of a hash without the specified key(s). This allows us to reduce the answer to:
cars.flat_map do |car|
car[:prices].zip(car[:options_package]).map do |price, pkg|
car.except(:prices).merge(price: price, options_package: pkg)
end
end
car.except(:prices)
will create a new hash without the :prices
key/value-pair, we don't need to remove :options_package
, since merge
will overwrite the the old :options_package
value with a new value.
Ruby 3.1 introduced { x:, y: }
as syntax sugar for { x: x, y: y }
. This allows us to "reduce" the answer further to:
cars.flat_map do |car|
car[:prices].zip(car[:options_package]).map do |price, options_package|
car.except(:prices).merge(price:, options_package:)
end
end
CodePudding user response:
Input
input = [{
dealer_id: 1,
dealer_name: "dealership 1",
car_make: "jeep",
prices: ['30', '32', '35'],
options_package: ['A', 'B', 'C']
}, {
dealer_id: 2,
dealer_name: "dealership 2",
car_make: "ford",
prices: ['50', '55'],
options_package: ['X', 'Y']
}, {
dealer_id: 3,
dealer_name: "dealership 3",
car_make: "dodge",
prices: ['70'],
options_package: ['A']
}]
Code
result = input.map do |h|
prices = h[:prices]
options_package = h[:options_package]
h[:options_package].count.times.map do
h[:prices] = prices.shift
h[:options_package] = options_package.shift
h.dup
end
end
p result.flatten
Output
[{:dealer_id=>1, :dealer_name=>"dealership 1", :car_make=>"jeep", :prices=>"30", :options_package=>"A"},
{:dealer_id=>1, :dealer_name=>"dealership 1", :car_make=>"jeep", :prices=>"32", :options_package=>"B"},
{:dealer_id=>1, :dealer_name=>"dealership 1", :car_make=>"jeep", :prices=>"35", :options_package=>"C"},
{:dealer_id=>2, :dealer_name=>"dealership 2", :car_make=>"ford", :prices=>"50", :options_package=>"X"},
{:dealer_id=>2, :dealer_name=>"dealership 2", :car_make=>"ford", :prices=>"55", :options_package=>"Y"},
{:dealer_id=>3, :dealer_name=>"dealership 3", :car_make=>"dodge", :prices=>"70", :options_package=>"A"}]