Iterate over hash of nested hashes

G

Glenn Ritz

Hi,

I'd like to be able to take a hash whose values are either hashes or
some other object. If the values are hashes, I'd like to iterate over
them and keep this up until the values aren't hashes. And I won't know
how many level there will be until the program is running. Any
suggestions?

Thanks,

Glenn
 
J

Jonathan Nielsen

def iteration obj
=C2=A0if obj.is_a? Hash
=C2=A0 =C2=A0obj.each { |new_obj| iteration(new_obj) }
=C2=A0else
=C2=A0 =C2=A0 # It's not a hash, do stuff with it!
=C2=A0end
end

my_hash.each { |obj| iteration(obj) }

Oops. I fixed line 3 above :)

-Jonathan Nielsen
 
X

Xavier Noria

I'd like to be able to take a hash whose values are either hashes or
some other object. If the values are hashes, I'd like to iterate over
them and keep this up until the values aren't hashes. =C2=A0And I won't k= now
how many level there will be until the program is running. =C2=A0Any
suggestions?

The iterator should yield... what? key, value pairs when it gets to a
leave? Only values? All pairs no matter the type of the value?
 
J

Jonathan Nielsen

Hi,
I'd like to be able to take a hash whose values are either hashes or
some other object. If the values are hashes, I'd like to iterate over
them and keep this up until the values aren't hashes. =C2=A0And I won't k= now
how many level there will be until the program is running. =C2=A0Any
suggestions?

def iteration obj
if obj.is_a? Hash
iteration obj
else
# It's not a hash, do stuff with it!
end
end

my_hash.each { |obj| iteration(obj) }



^^ Probably not the best way, but it works...

-Jonathan Nielsen
 
R

Robert Klemme

2010/2/24 Jonathan Nielsen said:
Oops. =A0I fixed line 3 above :)

You could also do

def iter(h,&b)
h.each do |k,v|
case v
when Hash
iter(v,&b)
else
b[v]
end
end
end

iter my_hash do |obj|
p obj
end

Although the disadvantage is that doing it this way the structure
information cannot be evaluated in the block.

Kind regards

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
G

Glenn Ritz

Xavier said:
The iterator should yield... what? key, value pairs when it gets to a
leave? Only values? All pairs no matter the type of the value?

If the Hash looks like this:

{'en' => {'A' => 1, 'B' => {'C' => 3, 'D' => 'four' }}

I'd like to be able to create a new hash that looks like this (it's for
a gem that I am trying to write):

{'en' => {'A' => Fixnum, 'B' => {'C' => Fixnum, 'D' => String }}

So I think it should yield the key value pairs when it gets to a leaf,
but it also seems like I will need more to be able to create the above
hash.
 
X

Xavier Noria

If the Hash looks like this:

{'en' =3D> {'A' =3D> 1, 'B' =3D> {'C' =3D> 3, 'D' =3D> 'four' }}

I'd like to be able to create a new hash that looks like this (it's for
a gem that I am trying to write):

{'en' =3D> {'A' =3D> Fixnum, 'B' =3D> {'C' =3D> Fixnum, 'D' =3D> String }= }

So I think it should yield the key value pairs when it gets to a leaf,
but it also seems like I will need more to be able to create the above
hash.

For example

def classify_values(h)
newh =3D {}
h.each do |k, v|
newh[k] =3D v.is_a?(Hash) ? classify_values(v) : v.class
end
newh
end

p classify_values({'en' =3D> {'A' =3D> 1, 'B' =3D> {'C' =3D> 3, 'D' =3D> 'f=
our' }}})

I'd use Active Support's #returning if available.
 
R

Robert Klemme

2010/2/24 Xavier Noria said:
If the Hash looks like this:

{'en' =3D> {'A' =3D> 1, 'B' =3D> {'C' =3D> 3, 'D' =3D> 'four' }}

I'd like to be able to create a new hash that looks like this (it's for
a gem that I am trying to write):

{'en' =3D> {'A' =3D> Fixnum, 'B' =3D> {'C' =3D> Fixnum, 'D' =3D> String = }}

So I think it should yield the key value pairs when it gets to a leaf,
but it also seems like I will need more to be able to create the above
hash.

For example

def classify_values(h)
=A0newh =3D {}
=A0h.each do |k, v|
=A0 =A0newh[k] =3D v.is_a?(Hash) ? classify_values(v) : v.class
=A0end
=A0newh
end

p classify_values({'en' =3D> {'A' =3D> 1, 'B' =3D> {'C' =3D> 3, 'D' =3D> =
'four' }}})

I would do it a tad differently:

def classify(o)
case o
when Hash
h =3D {}
o.each {|k,v| h[k] =3D classify(v)}
h
else
o.class
end
end

Advantage is that you can stuff anything in the method while your
variant requires the argument to be a Hash. The difference may seem
subtle but if you add more collection types for special treatment,
you'll will notice a difference in effort to implement it. I can
simply do

def classify(o)
case o
when Hash
h =3D {}
o.each {|k,v| h[k] =3D classify(v)}
h
when Array
o.map {|v| classify(v)}
else
o.class
end
end

while you need to do a more complicated code change.

Kind regards

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
X

Xavier Noria

def classify(o)
=C2=A0case o
=C2=A0when Hash
=C2=A0 =C2=A0h =3D {}
=C2=A0 =C2=A0o.each {|k,v| h[k] =3D classify(v)}
=C2=A0 =C2=A0h
=C2=A0when Array
=C2=A0 =C2=A0o.map {|v| classify(v)}
=C2=A0else
=C2=A0 =C2=A0o.class
=C2=A0end
end

while you need to do a more complicated code change.

Yeah, much nicer.
 
G

Glenn Ritz

Robert said:
2010/2/24 Xavier Noria said:
{'en' => {'A' => Fixnum, 'B' => {'C' => Fixnum, 'D' => String }}
� �newh[k] = v.is_a?(Hash) ? classify_values(v) : v.class
�end
�newh
end

p classify_values({'en' => {'A' => 1, 'B' => {'C' => 3, 'D' => 'four' }}})

I would do it a tad differently:

def classify(o)
case o
when Hash
h = {}
o.each {|k,v| h[k] = classify(v)}
h
else
o.class
end
end

Advantage is that you can stuff anything in the method while your
variant requires the argument to be a Hash. The difference may seem
subtle but if you add more collection types for special treatment,
you'll will notice a difference in effort to implement it. I can
simply do

def classify(o)
case o
when Hash
h = {}
o.each {|k,v| h[k] = classify(v)}
h
when Array
o.map {|v| classify(v)}
else
o.class
end
end

while you need to do a more complicated code change.

Kind regards

robert

Thanks, this is very helpful.

I am trying to adapt this a bit, but with no luck so far.

What I'd like to do is to start with a hash and have the above code be
additive.

In other words, if I have the following code:

h0 = {}
h1 = {'en' => {'A' => 5, 'B' => { 'C' => 'xxx', 'D' => { 'E' => 4 }}}}
h2 = {'en' => {'F' => 'yyy'}}


I'd like to be able to call the classify method like this:

puts h0.classify(h1).classify(h2).inspect

And get the following result:

{'en' => {'A' => Fixnum, 'B' => { 'C' => String, 'D' => { 'E' => Fixnum
}}, 'F' => String }}

So I figured it would be something like this:

class Hash
def classify(o)
case o
when Hash
h = self
o.each {|k,v| h[k] = classify(v)}
h
when Array
o.map {|v| classify(v)}
else
o.class
end
end
end

But this is probably wrong in a few ways. Any suggestions would be
appreciated.

Thanks.
 
R

Robert Klemme

2010/2/28 Glenn Ritz said:
Robert said:
2010/2/24 Xavier Noria said:
{'en' =3D> {'A' =3D> Fixnum, 'B' =3D> {'C' =3D> Fixnum, 'D' =3D> Strin= g }}
=EF=BF=BD =EF=BF=BDnewh[k] =3D v.is_a?(Hash) ? classify_values(v) : v.c= lass
=EF=BF=BDend
=EF=BF=BDnewh
end

p classify_values({'en' =3D> {'A' =3D> 1, 'B' =3D> {'C' =3D> 3, 'D' =3D=
'four' }}})

I would do it a tad differently:

def classify(o)
=C2=A0 case o
=C2=A0 when Hash
=C2=A0 =C2=A0 h =3D {}
=C2=A0 =C2=A0 o.each {|k,v| h[k] =3D classify(v)}
=C2=A0 =C2=A0 h
=C2=A0 else
=C2=A0 =C2=A0 o.class
=C2=A0 end
end

Advantage is that you can stuff anything in the method while your
variant requires the argument to be a Hash. =C2=A0The difference may see= m
subtle but if you add more collection types for special treatment,
you'll will notice a difference in effort to implement it. =C2=A0I can
simply do

def classify(o)
=C2=A0 case o
=C2=A0 when Hash
=C2=A0 =C2=A0 h =3D {}
=C2=A0 =C2=A0 o.each {|k,v| h[k] =3D classify(v)}
=C2=A0 =C2=A0 h
=C2=A0 when Array
=C2=A0 =C2=A0 o.map {|v| classify(v)}
=C2=A0 else
=C2=A0 =C2=A0 o.class
=C2=A0 end
end

while you need to do a more complicated code change.

Kind regards

robert

Thanks, this is very helpful.

I am trying to adapt this a bit, but with no luck so far.

What I'd like to do is to start with a hash and have the above code be
additive.

In other words, if I have the following code:

h0 =3D {}
h1 =3D {'en' =3D> {'A' =3D> 5, 'B' =3D> { 'C' =3D> 'xxx', 'D' =3D> { 'E' = =3D> 4 }}}}
h2 =3D {'en' =3D> {'F' =3D> 'yyy'}}


I'd like to be able to call the classify method like this:

puts h0.classify(h1).classify(h2).inspect

And get the following result:

{'en' =3D> {'A' =3D> Fixnum, 'B' =3D> { 'C' =3D> String, 'D' =3D> { 'E' = =3D> Fixnum
}}, 'F' =3D> String }}

So I figured it would be something like this:

class Hash
=C2=A0def classify(o)
=C2=A0 =C2=A0case o
=C2=A0 =C2=A0when Hash
=C2=A0 =C2=A0 =C2=A0h =3D self
=C2=A0 =C2=A0 =C2=A0o.each {|k,v| h[k] =3D classify(v)}
=C2=A0 =C2=A0 =C2=A0h
=C2=A0 =C2=A0when Array
=C2=A0 =C2=A0 =C2=A0o.map {|v| classify(v)}
=C2=A0 =C2=A0else
=C2=A0 =C2=A0 =C2=A0o.class
=C2=A0 =C2=A0end
=C2=A0end
end

But this is probably wrong in a few ways. =C2=A0Any suggestions would be
appreciated.

I would separate this in two steps:

1. merge Hashes intelligently (i.e. with a bit more logic than
Hash#merge default behavior).

2. classify.

So this would probably be something like

class Hash
def merge_deep!(hs)
merge! hs do |key,old_val,new_val|
case old_val
when Hash
old_val.merge_deep! new_val
else
new_val
end
end
end
end

see
http://www.ruby-doc.org/ruby-1.9/classes/Hash.html#M000406

Kind regards

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 

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,995
Messages
2,570,226
Members
46,815
Latest member
treekmostly22

Latest Threads

Top