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

···

--
Posted via http://www.ruby-forum.com/.

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?

···

On Wed, Feb 24, 2010 at 10:40 PM, Glenn Ritz <glenn_ritz@yahoo.com> 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?

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 Nielsen

def iteration obj
if obj.is_a? Hash
obj.each { |new_obj| iteration(new_obj) }
else
# It's not a hash, do stuff with it!
end
end

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

Oops. I fixed line 3 above :slight_smile:

-Jonathan Nielsen

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

···

2010/2/24 Jonathan Nielsen <jonathan@jmnet.us>:

def iteration obj
if obj.is_a? Hash
obj.each { |new_obj| iteration(new_obj) }
else
# It's not a hash, do stuff with it!
end
end

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

Oops. I fixed line 3 above :slight_smile:

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

Xavier Noria 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 10:40 PM, Glenn Ritz <glenn_ritz@yahoo.com> > wrote:

--
Posted via http://www.ruby-forum.com/\.

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.

···

On Wed, Feb 24, 2010 at 11:17 PM, Glenn Ritz <glenn_ritz@yahoo.com> wrote:

Xavier Noria wrote:

On Wed, Feb 24, 2010 at 10:40 PM, Glenn Ritz <glenn_ritz@yahoo.com> >> 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.

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

···

2010/2/24 Xavier Noria <fxn@hashref.com>:

On Wed, Feb 24, 2010 at 11:17 PM, Glenn Ritz <glenn_ritz@yahoo.com> wrote:

Xavier Noria wrote:

On Wed, Feb 24, 2010 at 10:40 PM, Glenn Ritz <glenn_ritz@yahoo.com> >>> 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.

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' }}})

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

Yeah, much nicer.

···

On Thu, Feb 25, 2010 at 8:26 AM, Robert Klemme <shortcutter@googlemail.com> wrote:

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.

Robert Klemme wrote:

{'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/24 Xavier Noria <fxn@hashref.com>:

--
Posted via http://www.ruby-forum.com/\.

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

···

2010/2/28 Glenn Ritz <glenn_ritz@yahoo.com>:

Robert Klemme wrote:

2010/2/24 Xavier Noria <fxn@hashref.com>:

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

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