Non-destructive merging of hashes in array

Hi, I have an array of hashes. The keys in the hashes represent the same
things.

eg:

h1 = {:rabbits => 5}
h2 = {:rabbits => 10}

bunnies = [h1, h2]

I want to end up with this:

{:rabbits => 15}

It’s a trivial task, but what’s the quickest way to get there? I’m
certain it can be done on one line.

I know there’s a Hash#update, but it appears that it would just
replace the 10 with 5, or vice versa, rather than adding them up.

This works, but it seems clunky:

hashes.each do |h|
h.each do |k, v|
if new_hash[k]
new_hash[k] += v
else
new_hash[k] = v
end
end
end

It can be crammed all onto one line, too, but there must be a nicer way.

On 3/13/07, Giles B. [email protected] wrote:

It can be crammed all onto one line, too, but there must be a nicer way.

Maybe but I like my 5 lines already :wink:

r=Hash.new{|h,k| h[k]=0}
hashes.each{
|hash|
hash.each{ |k,v| r[k]+= v }
}

HTH
Robert

How’s this:

hashes.inject({}) {|result, hash| result.merge(hash) {|k, e, v|
(result[k]
|| 0) + v}}

or similarly:

hashes.inject(Hash.new {|h, k| 0}) {|result, hash| result.merge(hash)
{|k,
e, v| result[k] + v}}

Mushfeq.

On 3/13/07, Mushfeq K. [email protected] wrote:

How’s this:

hashes.inject({}) {|result, hash| result.merge(hash) {|k, e, v| (result[k]
|| 0) + v}}

or similarly:

hashes.inject(Hash.new {|h, k| 0}) {|result, hash| result.merge(hash) {|k,
e, v| result[k] + v}}

I bow to superior technique, the merge block form is particularly nice.

Mushfeq.

Robert

You can even do:

hashes.inject(Hash.new {|h, k| 0}) {|result, hash| result.merge(hash)
{|k,
e, v| e + v}}

Mushfeq.

On 3/12/07, Mushfeq K. [email protected] wrote:

You can even do:

hashes.inject(Hash.new {|h, k| 0}) {|result, hash| result.merge(hash) {|k,
e, v| e + v}}

That’s a very concise solution. It seems to be working, although I
need to verify that.

On 3/13/07, Harrison R. [email protected] wrote:

  • v}}

Harrison R.

and my humble contribution gives:

 hashes.inject{|memo, hash| memo.merge(hash) {|k,e,v| e+ v}}

which is - as Gilles guessed correctly - a concise oneliner, boy I love
Ruby.

Robert

On Mar 12, 8:39 pm, “Mushfeq K.” [email protected] wrote:

You can even do:

hashes.inject(Hash.new {|h, k| 0}) {|result, hash| result.merge(hash) {|k,
e, v| e + v}}

Oh, you beat me to it. I might add, though, that the parameters in
the initializer block can be safely removed:

hashes.inject(Hash.new {0}) {|memo, hash| memo.merge(hash) {|k,e,v| e

  • v}}

Harrison R.

On Mar 12, 10:01 pm, “Giles B.” [email protected] wrote:

hashes.each do |h|
h.each do |k, v|
if new_hash[k]
new_hash[k] += v
else
new_hash[k] = v
end
end
end

It can be crammed all onto one line, too, but there must be a nicer way.

(SORRY IF THIS GETS POSTED TWICE)

The inject/merge solutions are good, but they are one trick ponies.
How about something like:

OpenCollection[h1, h2].rabbits.sum

It shouldn’t be too hard to write:

require ‘ostruct’

class OpenCollection
class << self ; alias :[] :new ; end
def initialize(*hashes)
@opens = hashes.collect { |h| OpenStruct.new(h) }
end

 def method_missing(sym, *args)
   @opens.collect{ |o| o.send(sym) }
 end

end

Actually, I’d use Facets OpenObject instead OpenStruct myself, but
that’s just me. You’ll also need:

require ‘facets/core/enumerable/sum’

For fun, here’s a one line version (more or less):

require ‘facets/more/functor’

oc = Functor.new([h1,h2]){|s,m| m.map{|h| h[s]}}

oc.rabbits.sum

T.

On 3/13/07, Trans [email protected] wrote:

bunnies = [h1, h2]

end
It shouldn’t be too hard to write:
@opens.collect{ |o| o.send(sym) }
require ‘facets/more/functor’

oc = Functor.new([h1,h2]){|s,m| m.map{|h| h[s]}}

oc.rabbits.sum

T.

Tom Facet is a great thing and I do not fail to point to it regulary.
But sometimes I feel we have to flex our muscles in pure Ruby before
we shall use libraries, even excellent ones like Facets, just to
understand everything a little better.

This all does not mean that your post is not very valuable, I just
want to warn from the “Pull In A Library before Do Some Thinking”
approach.

i fear that this approach hurts the user as much as the library.

Cheers
Robert

On Mar 13, 8:40 am, “Robert D.” [email protected] wrote:

h2 = {:rabbits => 10}
I know there’s a Hash#update, but it appears that it would just
end
OpenCollection[h1, h2].rabbits.sum

For fun, here’s a one line version (more or less):
But sometimes I feel we have to flex our muscles in pure Ruby before
we shall use libraries, even excellent ones like Facets, just to
understand everything a little better.

This all does not mean that your post is not very valuable, I just
want to warn from the “Pull In A Library before Do Some Thinking”
approach.

i fear that this approach hurts the user as much as the library.

I understand what youre saying --and I waited on posting this until
others gave solutions. Though in this particular case I think there’s
some pretty good meat here, ie. the OpenCollection class I literally
just made up on the spot. Of course that still leaves Enumerable#sum,
but that’s rather straight forward: a.inject(0){|s,n| s+=n;s}.

The Functor was just a little playful plug. If you’ve ever seen the
functor code you know it’s a generalization of what the OpenCollection
class is doing. I actually would like to see Functor included in
Ruby’s standard lib. But I haven’t been able to convince Matz of it’s
usefulness. So I try to publicly use it when ever I get the chance.

HTH,
T.

On 3/13/07, Trans [email protected] wrote:

h1 = {:rabbits => 5}

  new_hash[k] = v
 end

Tom Facet is a great thing and I do not fail to point to it regulary.
I understand what youre saying --and I waited on posting this until

HTH,
T.

OMG was I too rude, maybe? Probably just to stupid to really
understand your mail :frowning:
The good thing is though that I understand now what you wanted to tell
us.
And I am one of the greatest fans of magic dot.
Thx and sorry.
Robert

The functor seems pretty cool. The sad thing is, I still have the most
primitive implementation possible in my actual code. The reason is, I
don’t want to pop it in without fully understanding it.

The memo solution looks cleanest, that’s really just a gut feeling
though. Let me just make sure I get it. Here it is:

hashes.inject{|memo, hash| memo.merge(hash) {|k,e,v| e+ v}}

Now partly it turns out my problem is slightly more complicated. It’s
not an array of hashes; it’s an array of objects which can return
hashes. So it basically looks like this:

def enter_output
@items = get_the_items
@happy_output_hash = {}

non-destructively merge all hashes within @items into one hash

@items.each do |item|
item.hash_within.each do |key, value|
if @happy_output_hash[key]
@happy_output_hash[key] += value
else
@happy_output_hash[key] = value
end
end
end
end

(Code altered to enhance obviousness.)

Would the correct translation of the memo solution to accomodate this
be something like this?

items.inject{|happy_output_hash, item|
happy_output_hash.merge(item.hash_within) {|k,e,v| e + v}}

Also, what does the Functor solution actually do? That sounds Lispy,
which appeals to me, but I want to be sure the code is maintainable by
lesser mortals, such as, for example, me.


Giles B.
http://www.gilesgoatboy.org

http://giles.tumblr.com/

On Mar 14, 4:49 pm, “Giles B.” [email protected] wrote:

not an array of hashes; it’s an array of objects which can return
else

items.inject{|happy_output_hash, item|
happy_output_hash.merge(item.hash_within) {|k,e,v| e + v}}

Should work. Or you could split it into two lines if it’s easier to
read:

hashes = items.collect{ |item| item.hash_within }

Also, what does the Functor solution actually do? That sounds Lispy,
which appeals to me, but I want to be sure the code is maintainable by
lesser mortals, such as, for example, me.

(Depending on what your doing exactly) the inject/merge is probably
just fine.

Functor is pretty straight forward. The basic definition is:

class Functor
def initialize(*objs, &block)
@objs = objs
@block = block
end
def method_missing(sym,args)
@block.call(@sym,
(@objs + args))
end
end

T.

Giles B. wrote:

Hi, I have an array of hashes. The keys in the hashes represent the same
things.

eg:

h1 = {:rabbits => 5}
h2 = {:rabbits => 10}

bunnies = [h1, h2]

I want to end up with this:

{:rabbits => 15}

res = Hash.new(0) # to give the new keys a sensible default
bunnies.each {|arr| arr.each { | key, value | res[key] += value }}

P.S.: I thought about
res = {}
bunnies.each {|arr| arr.each { | key, value | (res[key]||=0) += value }}

but (res[key]||=0) is not a lvalue. It was even considered a syntax
error with the helpfull error message of:

syntax error, unexpected tOP_ASGN, expecting ‘}’

On 3/14/07, Giles B. [email protected] wrote:

Would the correct translation of the memo solution to accomodate this
be something like this?

items.inject{|happy_output_hash, item|
happy_output_hash.merge(item.hash_within) {|k,e,v| e + v}}

There is a subtle difference between

hashes.inject(Hash.new {0}) {|memo, hash| memo.merge(hash) {|k,e,v| e

  • v}}
    hashes.inject{|memo, hash| memo.merge(hash) {|k,e,v| e + v}}

(and by extension, your suggestion above, which is a correct
adaptation of the latter).

The optional parameter to inject provides a “seed” value. If no seed
value is
provided, the first element of the enumerable is used as the seed and
the block
evaluated over the tail only. In most cases this won’t matter, and using
an
argumentless inject is hunky-dory. But there are two gotchas:

  1. If the array is empty, providing no seed means that +nil+ will be
    used as
    the seed value. Probably not what you want as you’ll get a NoMethodError
    for
    nil#merge.

  2. If not all the hashes in the array have the same keys, you’ll
    probably get
    some NoMethodErrors for nil#+. That’s the reason the seed hash in the
    former
    version provides an explicit default value.

Jacob F.