non-destructive merging of hashes in array

G

Giles Bowkett

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.
 
R

Robert Dober

On 3/13/07 said:
It can be crammed all onto one line, too, but there must be a nicer way.

Maybe but I like my 5 lines already ;)

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

HTH
Robert
 
R

Robert Dober

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.
Robert
 
G

Giles Bowkett

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.
 
H

Harrison Reiser

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 Reiser
 
R

Robert Dober

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 Reiser
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
 
T

Trans

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.

(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}}

oc.rabbits.sum

T.
 
R

Robert Dober

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.

(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}}

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
 
T

Trans

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.
(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}}
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.


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.
 
R

Robert Dober

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

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.
(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}}
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.


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.

OMG was I too rude, maybe? Probably just to stupid to really
understand your mail :(
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
 
G

Giles Bowkett

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 Bowkett
http://www.gilesgoatboy.org
http://gilesbowkett.blogspot.com
http://giles.tumblr.com/


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

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.
(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}}
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.


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.
 
T

Trans

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}}

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.
 
J

Jenda Krynicky

Giles said:
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 '}'
 
J

Jacob Fugal

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 Fugal
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,537
Members
45,023
Latest member
websitedesig25

Latest Threads

Top