[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

non-destructive merging of hashes in array

Giles Bowkett

3/13/2007 2:02:00 AM

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.

--
Giles Bowkett
http://www.gilesg...
http://gilesbowkett.bl...
http://giles.t...

13 Answers

Robert Dober

3/13/2007 2:39:00 AM

0

On 3/13/07, Giles Bowkett <gilesb@gmail.com> wrote:
<snip>
> 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
>
> --
> Giles Bowkett
> http://www.gilesg...
> http://gilesbowkett.bl...
> http://giles.t...
>
>


--
We have not succeeded in answering all of our questions.
In fact, in some ways, we are more confused than ever.
But we feel we are confused on a higher level and about more important things.
-Anonymous

Robert Dober

3/13/2007 2:43:00 AM

0

On 3/13/07, Mushfeq Khan <mushfeq.khan@gmail.com> 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
--
We have not succeeded in answering all of our questions.
In fact, in some ways, we are more confused than ever.
But we feel we are confused on a higher level and about more important things.
-Anonymous

Giles Bowkett

3/13/2007 3:09:00 AM

0

On 3/12/07, Mushfeq Khan <mushfeq.khan@gmail.com> 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.

--
Giles Bowkett
http://www.gilesg...
http://gilesbowkett.bl...
http://giles.t...

y2kbugxp90

3/13/2007 3:11:00 AM

0

On Mar 12, 8:39 pm, "Mushfeq Khan" <mushfeq.k...@gmail.com> 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 Reiser


Robert Dober

3/13/2007 7:36:00 AM

0

On 3/13/07, Harrison Reiser <y2kbugxp90@gmail.com> wrote:
> On Mar 12, 8:39 pm, "Mushfeq Khan" <mushfeq.k...@gmail.com> 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 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
--
We have not succeeded in answering all of our questions.
In fact, in some ways, we are more confused than ever.
But we feel we are confused on a higher level and about more important things.
-Anonymous

Trans

3/13/2007 12:03:00 PM

0



On Mar 12, 10:01 pm, "Giles Bowkett" <gil...@gmail.com> 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}
>
> 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[s]}}

oc.rabbits.sum

T.


Robert Dober

3/13/2007 12:40:00 PM

0

On 3/13/07, Trans <transfire@gmail.com> wrote:
>
>
> On Mar 12, 10:01 pm, "Giles Bowkett" <gil...@gmail.com> 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}
> >
> > 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[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

--
You see things; and you say Why?
But I dream things that never were; and I say Why not?
-- George Bernard Shaw

Trans

3/13/2007 10:36:00 PM

0

On Mar 13, 8:40 am, "Robert Dober" <robert.do...@gmail.com> wrote:
> On 3/13/07, Trans <transf...@gmail.com> wrote:
>
>
>
> > On Mar 12, 10:01 pm, "Giles Bowkett" <gil...@gmail.com> 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}
>
> > > 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[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.

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.

Robert Dober

3/14/2007 6:14:00 AM

0

On 3/13/07, Trans <transfire@gmail.com> wrote:
> On Mar 13, 8:40 am, "Robert Dober" <robert.do...@gmail.com> wrote:
> > On 3/13/07, Trans <transf...@gmail.com> wrote:
> >
> >
> >
> > > On Mar 12, 10:01 pm, "Giles Bowkett" <gil...@gmail.com> 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}
> >
> > > > 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[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.
>
> 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

--
You see things; and you say Why?
But I dream things that never were; and I say Why not?
-- George Bernard Shaw

Giles Bowkett

3/14/2007 8:49:00 PM

0

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.gilesg...
http://gilesbowkett.bl...
http://giles.t...


On 3/13/07, Trans <transfire@gmail.com> wrote:
> On Mar 13, 8:40 am, "Robert Dober" <robert.do...@gmail.com> wrote:
> > On 3/13/07, Trans <transf...@gmail.com> wrote:
> >
> >
> >
> > > On Mar 12, 10:01 pm, "Giles Bowkett" <gil...@gmail.com> 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}
> >
> > > > 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[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.
>
> 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.
>
>
>