YAML obj merging

G

gga

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?
 
D

Dave Burt

gga said:
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?

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
 
G

gga

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

Florian Gross

gga said:
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.

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

Shashank Date

Hi Florian,

Florian Gross wrote:

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
 

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,773
Messages
2,569,594
Members
45,120
Latest member
ShelaWalli
Top