Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

YAML obj merging

0 views
Skip to first unread message

gga

unread,
Mar 10, 2005, 12:42:15 AM3/10/05
to
I would like to have an object (think 2 hashes one inside the other)
that would get created from reading in two YAML descriptions and
merging them together.

I'm looking that given something like:
---
A:
v: valueA
---
B:
v: valueB
x: value
=====
C = A + B should result in:
C:
v: valueA # B's v: valueB discarded, overridden by valueA
x: value # B's x: value not discarded

Before I write my own stuff to do that sort of operation on a hash, I
was wondering if there were already some libs written to do something
similar or, put in another way, what would be the best ruby way of
doing so?

Dave Burt

unread,
Mar 10, 2005, 2:31:07 AM3/10/05
to
"gga" <GGarr...@aol.com> asked:

Hi,

I may not completely understand what you're trying to do, but it seems to me
that it's just basic stuff that's built in to Hash; in particular, the
update method.

irb(main):001:0> require 'yaml'
=> true
irb(main):002:0> input = YAML.load_stream <<END
irb(main):003:0" ---
irb(main):004:0" A:
irb(main):005:0" v: valueA
irb(main):006:0" ---
irb(main):007:0" B:
irb(main):008:0" v: valueB
irb(main):009:0" x: value
irb(main):010:0" END
=> #<YAML::Stream:0x28f1de8 @options={}, @documents=[{"A"=>{"v"=>"valueA"}},
{"B
"=>{"v"=>"valueB", "x"=>"value"}}]>
irb(main):011:0> a = input.documents[0]['A']
=> {"v"=>"valueA"}
irb(main):012:0> b = input.documents[1]['B']
=> {"v"=>"valueB", "x"=>"value"}
irb(main):013:0> c = b; c.update(a)
=> {"v"=>"valueA", "x"=>"value"}
irb(main):014:0> puts YAML.dump({'C' => c})
---
C:
v: valueA
x: value
=> nil

If you want, you can even add + to Hash:

irb(main):018:0> class Hash
irb(main):019:1> def +(other)
irb(main):020:2> other.update(self)
irb(main):021:2> end
irb(main):022:1> end
=> nil
irb(main):023:0> a + b
=> {"v"=>"valueA", "x"=>"value"}

Cheers,
Dave


gga

unread,
Mar 11, 2005, 1:50:12 AM3/11/05
to
That's kind of what I want but not quite.
The problem is that I want hashes that contain other hashes to also
follow the same rule.

That is:
irb> a = { 'A' => { 'A1' => 'a' } }
irb> b = { 'A' => { 'B1' => 'b' } }
irb> b.merge(a)
{"A"=>{"A1"=>"a"}}

When what I really want is this to be done recursively, so that:
irb> a = { 'A' => { 'A1' => 'a' } }
irb> b = { 'A' => { 'B1' => 'b' } }
irb> b.mix(a) # ficticious method
{"A"=>{"A1"=>"a", "B1" => 'b' }}

I guess I probably need to add my own method to do this.

Trans

unread,
Mar 11, 2005, 4:47:37 PM3/11/05
to
Ruby Facects has hash/weave. It does what you wish and a little more.

http://rubyforge.org/projects/calibre/

T.

Florian Gross

unread,
Mar 13, 2005, 6:07:06 AM3/13/05
to
gga wrote:

I'm coming a bit late to this discussion (I've only spotted it after
I've read through this weeks' Ruby Weekly News), but here's my version:

class Hash
# Similar to Hash#merge, but this version works recursively so that
# nested Hashs will be merged as well. You can specify a collision
# block which will be invoked for conflicting items. (It takes the
# same argument list as the collision block supplied to Hash#merge:
# |hash, item1, item2|)
#
# Usage example:
# a = { "A" => { "A1" => "a" } }
# b = { "A" => { "B1" => "b" } }
# b.mix(a) # => {"A" => {"A1" => "a", "B1" => "b" }}
def mix(other, &collision_block)
merge_block = lambda do |hash, item1, item2|
if item1.is_a?(Hash) and item2.is_a?(Hash) then
item1.merge(item2, &merge_block)
elsif collision_block then
collision_block.call(hash, item1, item2)
else
item2
end
end

self.merge(other, &merge_block)
end
end

So basically the block that Hash#merge can take is insanely useful. I've
also used for just detecting Hash collisions in the past as well.

Shashank Date

unread,
Mar 13, 2005, 9:24:06 AM3/13/05
to
Hi Florian,

Florian Gross wrote:

<snip>

> class Hash
> # Similar to Hash#merge, but this version works recursively so that
> # nested Hashs will be merged as well. You can specify a collision
> # block which will be invoked for conflicting items. (It takes the
> # same argument list as the collision block supplied to Hash#merge:

> |hash, item1, item2|)

I am not able to understand how the collision block thing will work
here. Can you please give an example? I tried this, but it did not work
like I expected:

b.mix(a){|key,old,nu| old} #=> {"A"=>{"B1"=>"b", "A1"=>"a"}}

> #
> # Usage example:
> # a = { "A" => { "A1" => "a" } }
> # b = { "A" => { "B1" => "b" } }
> # b.mix(a) # => {"A" => {"A1" => "a", "B1" => "b" }}
> def mix(other, &collision_block)
> merge_block = lambda do |hash, item1, item2|
> if item1.is_a?(Hash) and item2.is_a?(Hash) then
> item1.merge(item2, &merge_block)
> elsif collision_block then

When does this elsif get executed?

> collision_block.call(hash, item1, item2)
> else
> item2
> end
> end
>
> self.merge(other, &merge_block)
> end
> end
>
> So basically the block that Hash#merge can take is insanely useful. I've
> also used for just detecting Hash collisions in the past as well.

I did not know that Hash#merge could take a block. Thanks for pointing
it out.

-- shanko

0 new messages