0

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 

4 Answers 4

2

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"}] 
Sign up to request clarification or add additional context in comments.

Comments

2

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.

Comments

2

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 

1 Comment

It does make the answer a bit longer, since 2 * len(options_package) = 30 and len(options_package) + 2 * len(pkg) = 21, but it is more expressive and probably improves readability. I've updated the answer.
1

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"}] 

2 Comments

Bear in mind this will mutate the original hash, which is not necessarily wrong but does need to be considered. Also hash may be a slightly misleading name for an array of hashes.
hash name might be misleading. Yes. Thanks for letting me know.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.