Home > Back-end >  map an array found in a hash to individual hash values
map an array found in a hash to individual hash values

Time:10-21

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"}]
  •  Tags:  
  • ruby
  • Related