0

I have:

apartment1 = {"base" => {"floor1" => {"apartment1" => {"rooms_number" => 4}}}}
apartment2 = {"base" => {"floor1" => {"apartment2" => {"rooms_number" => 6}}}}

that share {"base" => {"floor1" =>.

How would I merge the apartments under the shared part to get:

{"base" => {"floor1" => {
  "apartment1" => {"rooms_number" => 6},
  "apartment2" => {"rooms_number" => 6}
}}}

I merged apartment1 and apartment2 into apartments, and got this:

apartments = {}
apartments.merge!(apartment1)
# => {"base" => {"floor1" => {"apartment1" => {"rooms_number" => 4}}}} 
apartments.merge!(apartment2)
# => {"base" => {"floor1" => {"apartment2" => {"rooms_number" => 6}}}} 
sawa
  • 165,429
  • 45
  • 277
  • 381
simo
  • 23,342
  • 38
  • 121
  • 218

4 Answers4

1

If you're using Rails (or in any case if you're willing to use ActiveSupport), you can do this via deep_merge:

2.5.1 :001 > apartment1= {"base" => {"floor1" => {"apartment1" => {"rooms_number" => 4} } }}
 => {"base"=>{"floor1"=>{"apartment1"=>{"rooms_number"=>4}}}} 
2.5.1 :002 > apartment2= {"base" => {"floor1" => {"apartment2" => {"rooms_number" => 6} } }}
 => {"base"=>{"floor1"=>{"apartment2"=>{"rooms_number"=>6}}}} 
2.5.1 :003 > apartments = apartment1.deep_merge apartment2
 => {"base"=>{"floor1"=>{"apartment1"=>{"rooms_number"=>4}, "apartment2"=>{"rooms_number"=>6}}}} 

See https://apidock.com/rails/Hash/deep_merge

See also this similar question: Ruby: merge nested hash

Benissimo
  • 1,010
  • 9
  • 14
1

Handling manually:

apartments = apartment1
apartments['base']['floor1'].merge!(apartment2['base']['floor1'])

p apartments
#=> {"base"=>{"floor1"=>{"apartment1"=>{"rooms_number"=>4}, "apartment2"=>{"rooms_number"=>6}}}}

Or stealing (!) the algorithm for deep merge from Rails:

def deep_merge(first, second)
  first.merge(second) do |key, oldval, newval|
    oldval = oldval.to_hash if oldval.respond_to?(:to_hash)
    newval = newval.to_hash if newval.respond_to?(:to_hash)
    oldval.class.to_s == 'Hash' && newval.class.to_s == 'Hash' ? deep_merge(oldval, newval) : newval
  end
end

So you can use it in plain Ruby:

deep_merge(apartment1, apartment2) #=> {"base"=>{"floor1"=>{"apartment1"=>{"rooms_number"=>4}, "apartment2"=>{"rooms_number"=>6}}}}
iGian
  • 11,023
  • 3
  • 21
  • 36
1

The following recursive method should provide the desired results.

def combine_em(arr)
  (k1, k2), (v1, v2) = arr.map(&:flatten).transpose
  (k1==k2 && v1.is_a?(Hash)) ? { k1=>combine_em([v1, v2]) } :
    {}.merge(*arr) 
end

arr = [{"base"=>{"floor1"=>{"apt1"=>{"room"=>4}}}},
       {"base"=>{"floor1"=>{"apt2"=>{"room"=>6}}}}]
combine_em arr
  #=>  {"base"=>{"floor1"=>{"apt1"=>{"room"=>4},
  #                         "apt2"=>{"room"=>6}}}}

arr = [{"base"=>{"floor1"=>{"level1"=>{"apt1"=>{"room"=>4}}}}},
       {"base"=>{"floor1"=>{"level1"=>{"apt2"=>{"room"=>6}}}}}]
combine_em arr
  #=> {"base"=>{"floor1"=>{"level1"=>{"apt1"=>{"room"=>4},
  #                                   "apt2"=>{"room"=>6}}}}}

arr = [{"base"=>{"floor1"=>{"apt1"=>{"room"=>4}}}},
       {"base"=>{"floor2"=>{"apt1"=>{"room"=>6}}}}]
combine_em arr
  #=>  {"base"=>{"floor1"=>{"apt1"=>{"room"=>4}},
  #              "floor2"=>{"apt1"=>{"room"=>6}}}}

arr = [{"base"=>{"floor1"=>{"apt1"=>{"room1"=>4}}}},
       {"base"=>{"floor1"=>{"apt1"=>{"room2"=>6}}}}]
combine_em arr
  #=>  {"base"=>{"floor1"=>{"apt1"=>{"room1"=>4,
  #                                  "room2"=>6}}}}
arr = [{"base1"=>{"floor1"=>{"apt1"=>{"room"=>4}}}},
       {"base2"=>{"floor2"=>{"apt1"=>{"room"=>6}}}}]
combine_em arr
  #=> {"base1"=>{"floor1"=>{"apt1"=>{"room"=>4}}},
  #    "base2"=>{"floor2"=>{"apt1"=>{"room"=>6}}}}

arr = [{"base"=>{"floor1"=>{"apt1"=>{"room"=>4}}}},
       {"base"=>{"floor1"=>{"apt1"=>{"room"=>6}}}}]
combine_em arr
  #=>  {"base"=>{"floor1"=>{"apt1"=>{"room"=>6}}}}

The last example of arr (if it could occur), may not give the desired result. If so, it would be necessary to specify the desired return value in that case.

Hash#merge was changed in Ruby v2.6 to allow multiple arguments, which is why we can now write

arr = [{:a=>1}, {:b=>2}, {:c=>3}]
{}.merge(*arr)
  #=> {:a=>1, :b=>2, :c=>3}

To support earlier versions of Ruby, write

arr.reduce(&:merge)

which is shorthand for

arr.reduce { |h,g| h.merge(g) }

See Enumerable#reduce (aka inject).

To gain a full understand of how the recursion works it may be necessary to salt the method with puts statements.

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
0

You want to merge not at the root level, but at two levels deeper.

apartment1.merge(apartment2){|_, h1, h2| h1.merge(h2){|_, h1, h2| h1.merge(h2)}}
# =>
# {"base" => {"floor1" => {
#   "apartment1" => {"rooms_number" => 4},
#   "apartment2" => {"rooms_number" => 6}
# }}}
sawa
  • 165,429
  • 45
  • 277
  • 381