TransWikia.com

How to merge 2 arrays where value in one matches a value in another with different key in Ruby

Stack Overflow Asked by Mike2414 on February 18, 2021

I have an array that contains other arrays of items with prices but when one has a sale a new item is created How do I merge or pull value from one to the other to make 1 array so that the sale price replaces the non sale but contains the original price?

Example:

items=[{"id": 123, "price": 100, "sale": false},{"id":456,"price":25,"sale":false},{"id":678, "price":75, "sale":true, "parent_price_id":123}]

Transform into:

items=[{"id":456,"price":25,"sale":false},{"id":678, "price":75, "sale":true, "parent_price_id":123, "original_price": 100}]

2 Answers

It's not the prettiest solution, but here's one way you can do it. I added a minitest spec to check it against the values you provided and it gives the answer you're hoping for.

require "minitest/autorun"

def merge_prices(prices)
  # Create a hash that maps the ID to the values
  price_map =
    prices
    .map do |price|
      [price[:id], price]
    end
    .to_h
  # Create a result array which is initially duplicated from the original
  result = prices.dup
  result.each do |price|
    if price.key?(:parent_price)
      price[:original_price] = price_map[price[:parent_price]][:price]
      # Delete the original
      result.delete_if { |x| x[:id] == price[:parent_price] }
    end
  end
  result
end

describe "Merge prices" do
  it "should work" do
    input = [
      {"id":123, "price": 100, "sale": false},
      {"id":456,"price":25,"sale": false},
      {"id":678, "price":75, "sale": true, "parent_price":123}
    ].freeze
    expected_output = [
      {"id":456,"price":25,"sale": false},
      {"id":678, "price":75, "sale": true, "parent_price":123, "original_price": 100}
    ].freeze
    assert_equal(merge_prices(input), expected_output)
  end
end

Correct answer by Petro Podrezo on February 18, 2021

Let's being by defining items in an equivalent, but more familiar, way:

items = [
  [{:id=>123, :price=>100, :sale=>false}],
  [{:id=>456, :price=>25,  :sale=>false}],
  [{:id=>678, :price=>75,  :sale=>true, :parent_price=>123}]
] 

with the desired return value being:

[
  {:id=>456, :price=>25, :sale=>false},
  {:id=>678, :price=>75, :sale=>true, :parent_price=>123,
   :original_price=>100}
]

I assume that h[:sale] #=> false for every element of items (a hash) g for which g[:parent_price] = h[:id].

A convenient first step is to create the following hash.

h = items.map { |(h)| [h[:id], h] }.to_h
  #=> {123=>{:id=>123, :price=>100, :sale=>false},
  #    456=>{:id=>456, :price=>25, :sale=>false},
  #    678=>{:id=>678, :price=>75, :sale=>true, :parent_price=>123}}

Then:

  h.keys.each { |k| h[k][:original_price] =
    h.delete(h[k][:parent_price])[:price] if h[k][:sale] }
    #=> [123, 456, 678] (not used)

  h #=> {456=>{:id=>456, :price=>25, :sale=>false},
    #    678=>{:id=>678, :price=>75, :sale=>true, :parent_price=>123,
    #          :original_price=>100}} 

Notice that Hash#delete returns the value of the deleted key.

The last two steps are to extract the values from this hash and replace items with the resulting array of hashes:

items.replace(h.values)
    #=> [{:id=>456, :price=>25, :sale=>false},
    #    {:id=>678, :price=>75, :sale=>true, :parent_price=>123,
    #     :original_price=>100}] 

See Array#replace.

If desired we could combine these steps as follows.

items.replace(
  items.map { |(h)| [h[:id], h] }.to_h.tap do |h|
    h.keys.each { |k| h[k][:original_price] =
      h.delete(h[k][:parent_price])[:price] if h[k][:sale] }
    end.values)
  #=> [{:id=>456, :price=>25, :sale=>false},
  #    {:id=>678, :price=>75, :sale=>true, :parent_price=>123,
  #     :original_price=>100}] 

See Object#tap.

Answered by Cary Swoveland on February 18, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP