Iterate over hash of nested hashes

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

On Wed, Feb 24, 2010 at 10:40 PM, Glenn R. [email protected]
wrote:

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?

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?

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?

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

Oops. I fixed line 3 above :slight_smile:

-Jonathan N.

2010/2/24 Jonathan N. [email protected]:

Oops. I fixed line 3 above :slight_smile:

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

Xavier N. wrote:

On Wed, Feb 24, 2010 at 10:40 PM, Glenn R. [email protected]
wrote:

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?

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.

On Wed, Feb 24, 2010 at 11:17 PM, Glenn R. [email protected]
wrote:

The iterator should yield… what? key, value pairs when it gets to a

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 = {}
h.each do |k, v|
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’d use Active Support’s #returning if available.

2010/2/24 Xavier N. [email protected]:

{‘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

On Thu, Feb 25, 2010 at 8:26 AM, Robert K.
[email protected] wrote:

 end
end

while you need to do a more complicated code change.

Yeah, much nicer.

Robert K. wrote:

2010/2/24 Xavier N. [email protected]:

{‘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.

2010/2/28 Glenn R. [email protected]:

 end
 when Hash
while you need to do a more complicated code change.
additive.
puts h0.classify(h1).classify(h2).inspect
  case o
end

But this is probably wrong in a few ways. Â Any 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