2

I have an array with hashes in it. If they have the same key I just want to add its value.

@receivers << result

@receivers
=> [{:email=>"[email protected]", :amount=>10.00}]
result
=> {:email=>"[email protected]", :amount=>7.00}

I want the result of above to look like this

[{:email=>"[email protected]", :amount=>17.00}]

Does anyone know how to do this?

Here is the the entire method

  def receivers
    @receivers = []
    orders.each do |order|
      product_email = order.product.user.paypal_email
      outfit_email  = order.outfit_user.paypal_email
      if order.user_owns_outfit?
        result = { email: product_email, amount: amount(order.total_price) }
      else
        result = { email: product_email, amount: amount(order.total_price, 0.9),
                   email: outfit_email,  amount: amount(order.total_price, 0.1) }
      end
      @receivers << result
    end
  end
2
  • 1
    With "same key" you mean same :email value? Commented May 3, 2016 at 11:57
  • @Stefan, that's a good question.. Commented May 3, 2016 at 11:59

5 Answers 5

4

Using Enumerable#group_by

@receivers.group_by {|h| h[:email]}.map do |k, v|
  {email: k, amount: v.inject(0){|s,h| s + h[:amount] } }
end
# => [{:email=>"[email protected]", :amount=>17.0}]

Using Enumerable#each_with_object

@receivers.each_with_object(Hash.new(0)) {|h, nh| nh[h[:email]]+= h[:amount] }.map do |k, v|
 {email: k, amount: v}
end
Sign up to request clarification or add additional context in comments.

Comments

1
# Output: [{ "[email protected]" => 29.0 }, { "[email protected]" => 39.0 }]
def receivers
  return @receivers if @receivers
  # Produces: { "[email protected]" => 29.0, "[email protected]" => 39.0 }
  partial_result = orders.reduce Hash.new(0.00) do |result, order|
    product_email = order.product.user.paypal_email
    outfit_email  = order.outfit_user.paypal_email

    if order.user_owns_outfit?
      result[product_email] += amount(order.total_price)
    else
      result[product_email] += amount(order.total_price, .9)
      result[outfit_email]  += amount(order.total_price, .1)
    end

    result
  end

  @receivers = partial_result.reduce [] do |result, (email, amount)|
    result << { email => amount }
  end
end

Comments

0

I would just write the code this way:

def add(destination, source)
  if destination.nil?
    return nil
  end
  if source.class == Hash
    source = [source]
  end
  for item in source
    target = destination.find {|d| d[:email] == item[:email]}
    if target.nil?
      destination << item
    else
      target[:amount] += item[:amount]
    end
  end
  destination
end

usage:

@receivers = []
add(@receivers, {:email=>"[email protected]", :amount=>10.00})
=> [{:email=>"[email protected]", :amount=>10.0}]
add(@receivers, @receivers)
=> [{:email=>"[email protected]", :amount=>20.0}]

3 Comments

Explicit type checking and for loops are not very idiomatic.
well, it's not in the loop, but the idea is that you can offer different ways of calling the method
It's purely stylistic, but consider return nil if destination.nil?; source = [source] if source_class == Hash. (or if Hash === source). Rather than possibly converting source to an array, you could write for item in [*source]. (That's the first time I've ever written for... :-)).
0
a = [
  {:email=>"[email protected]", :amount=>10.0},
  {:email=>"[email protected]", :amount=>7.0}
]

a.group_by { |v| v.delete :email } # group by emails
 .map { |k, v| [k, v.inject(0) { |memo, a| memo + a[:amount] } ] } # sum amounts
 .map { |e| %i|email amount|.zip e } # zip to keys
 .map &:to_h # convert nested arrays to hashes

Comments

0

From what I understand, you could get away with just .inject:

a = [{:email=>"[email protected]", :amount=>10.00}]
b = {:email=>"[email protected]", :amount=>7.00}
c = {email: '[email protected]', amount: 10}

[a, b, c].flatten.inject({}) do |a, e|
  a[e[:email]] ||= 0
  a[e[:email]] += e[:amount]
  a
end

=> {
  "[email protected]" => 17.0,
  "[email protected]" => 10
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.