Raj Adroit
Raj Adroit

Reputation: 3878

Ruby hash merge by comparing a value

i have the following array of Hashes

[{:Day=>"May 2015", :Rent=>0, :Bond=>0, :RentInAdvance=>0},
 {:Day=>"May 2015", :Rent=>0, :Bond=>0, :RentInAdvance=>450},
 {:Day=>"May 2015", :Rent=>0, :Bond=>750, :RentInAdvance=>0},
 {:Day=>"Jun 2015", :Rent=>600, :Bond=>0, :RentInAdvance=>0},
 {:Day=>"Jul 2015", :Rent=>600, :Bond=>0, :RentInAdvance=>0},
 {:Day=>"Dec 2015", :Rent=>600, :Bond=>0, :RentInAdvance=>0}]

I need to merge those record using the Day key value, expected result is

[{:Day=>"May 2015", :Rent=>0, :Bond=>750, :RentInAdvance=>450},
 {:Day=>"Jun 2015", :Rent=>600, :Bond=>0, :RentInAdvance=>0},
 {:Day=>"Jul 2015", :Rent=>600, :Bond=>0, :RentInAdvance=>0},
 {:Day=>"Dec 2015", :Rent=>600, :Bond=>0, :RentInAdvance=>0}]

Thanks in advance..

Upvotes: 2

Views: 126

Answers (4)

Edgar Mkaka Joel
Edgar Mkaka Joel

Reputation: 96

i = 0
while i < arr.length-1
    j = i+1
    while j < arr.length-1
        #Compare dates of hash with another_hash
        if arr[i][:Day] == arr[j][:Day]
            #If matches, add another_hash values(numerical) to corresponding keys of hash
            arr[i].merge!(arr[j]) {|key, vi, vj| vi.is_a?(Fixnum) ? (vi + vj) : vi }
            #Delete another_hash after its values has been added to hash
            arr.delete_at(j)
            #Next, compare at same index since it now points to next hash after its previous value(another_hash) has been deleted
            j -= 1
        end
        j += 1
    end
    i += 1
end
arr

Upvotes: 0

ShallmentMo
ShallmentMo

Reputation: 449

origin = [{:Day=>"May 2015", :Rent=>0, :Bond=>0, :RentInAdvance=>0},
   {:Day=>"May 2015", :Rent=>0, :Bond=>0, :RentInAdvance=>450},
    {:Day=>"May 2015", :Rent=>0, :Bond=>750, :RentInAdvance=>0},
     {:Day=>"Jun 2015", :Rent=>600, :Bond=>0, :RentInAdvance=>0},
      {:Day=>"Jul 2015", :Rent=>600, :Bond=>0, :RentInAdvance=>0},
       {:Day=>"Dec 2015", :Rent=>600, :Bond=>0, :RentInAdvance=>0}]

result = origin.reduce({}) do |memo, obj|
  day = obj[:Day]
  memo[day] ||= Hash.new(0)
  memo[day][:Rent] = [memo[day][:Rent], obj[:Rent]].max
  memo[day][:Bond] = [memo[day][:Bond], obj[:Bond]].max
  memo[day][:RentInAdvance] = [memo[day][:RentInAdvance], obj[:RentInAdvance]].max
  memo
end.reduce([]) do |memo, obj|
  memo << {:Day => obj[0]}.merge(obj[1])
end

p result

I don't know how you will do with Rent, Bond and RentInAdvance, so I just find the maximum.

Upvotes: 0

B Seven
B Seven

Reputation: 45943

data = [{:Day=>"May 2015", :Rent=>0, :Bond=>0, :RentInAdvance=>0},
        {:Day=>"May 2015", :Rent=>0, :Bond=>0, :RentInAdvance=>450},
        {:Day=>"May 2015", :Rent=>0, :Bond=>750, :RentInAdvance=>0},
        {:Day=>"Jun 2015", :Rent=>600, :Bond=>0, :RentInAdvance=>0},
        {:Day=>"Jul 2015", :Rent=>600, :Bond=>0, :RentInAdvance=>0},
        {:Day=>"Dec 2015", :Rent=>600, :Bond=>0, :RentInAdvance=>0}]

days = data.map{|d| d[ :Day ]}.uniq

def sum_by_day_for data, key
  data.map{| d| d[ key ]}.inject( :+ )
end

sum = days.map do |day|
  same_days = data.select{|d| d[ :Day ] == day}

  { Day:day, 
    Rent:sum_by_day_for( same_days, :Rent ), 
    Bond:sum_by_day_for( same_days, :Bond ),
    RentInAdvance:sum_by_day_for( same_days, :RentInAdvance )}
end

puts sum

Upvotes: 0

tessi
tessi

Reputation: 13574

TLDR:

data.group_by do |d|
  d[:Day]
end.values.map do |days|
  days.inject do |a,b|
    {
      Day: a[:Day],
      Rent: a[:Rent] + b[:Rent],
      Bond: a[:Bond] + b[:Bond],
      RentInAdvance: a[:RentInAdvance] + b[:RentInAdvance]
    }
  end
end

Explanation:

Let's say we have your data in the data variable:

data = [{:Day=>"May 2015", :Rent=>0, :Bond=>0, :RentInAdvance=>0},
        {:Day=>"May 2015", :Rent=>0, :Bond=>0, :RentInAdvance=>450},
        {:Day=>"May 2015", :Rent=>0, :Bond=>750, :RentInAdvance=>0},
        {:Day=>"Jun 2015", :Rent=>600, :Bond=>0, :RentInAdvance=>0},
        {:Day=>"Jul 2015", :Rent=>600, :Bond=>0, :RentInAdvance=>0},
        {:Day=>"Dec 2015", :Rent=>600, :Bond=>0, :RentInAdvance=>0}]

Then we can group (group_by) the array by the :Day value, to get

grouped_data = data.group_by do |d|
  d[:Day]
end

# {"May 2015"=>
#   [{:Day=>"May 2015", :Rent=>0, :Bond=>0, :RentInAdvance=>0},
#    {:Day=>"May 2015", :Rent=>0, :Bond=>0, :RentInAdvance=>450},
#    {:Day=>"May 2015", :Rent=>0, :Bond=>750, :RentInAdvance=>0}],
#  "Jun 2015"=>[{:Day=>"Jun 2015", :Rent=>600, :Bond=>0, :RentInAdvance=>0}],
#  "Jul 2015"=>[{:Day=>"Jul 2015", :Rent=>600, :Bond=>0, :RentInAdvance=>0}],
#  "Dec 2015"=>[{:Day=>"Dec 2015", :Rent=>600, :Bond=>0, :RentInAdvance=>0}]}

The key of the resulting hash is the date, and the value is an array of data-hashes. We are only interested in the values and map the array of data-hashes to a single merged data-hash with:

merged_data = grouped_data.values.map do |days|
  days.inject do |a,b|
    {
      Day: a[:Day],
      Rent: a[:Rent] + b[:Rent],
      Bond: a[:Bond] + b[:Bond],
      RentInAdvance: a[:RentInAdvance] + b[:RentInAdvance]
    }
  end
end

The inject merges two data-hashes (a and b) into a new hash. Which gives us the final result:

puts merged_data
# {:Day=>"May 2015", :Rent=>0, :Bond=>750, :RentInAdvance=>450}
# {:Day=>"Jun 2015", :Rent=>600, :Bond=>0, :RentInAdvance=>0}
# {:Day=>"Jul 2015", :Rent=>600, :Bond=>0, :RentInAdvance=>0}
# {:Day=>"Dec 2015", :Rent=>600, :Bond=>0, :RentInAdvance=>0}

PS: It might help to read the ruby documentation on the methods, if you're not familiar with them. I've added a link to each method used.

Upvotes: 1

Related Questions