7

I am trying to define a function that it can print out any hash values in a tree format. The function will do something like this:

From

{"parent1"=>
    {"child1" => { "grandchild1" => 1,
                "grandchild2" => 2},
    "child2" => { "grandchild3" => 3,
                "grandchild4" => 4}}
}

To

   parent1:
        child1: 
              grandchild1:1
              grandchild2:2
        child2:
              grandchild3:3
              grandchild4:4

And this is my code so far:

def readprop(foo)
    level = ''
    if foo.is_a?(Hash)
        foo.each_key {|key| if foo[key].nil? == false
                puts level + key + ":"
                level += "   "
                readprop(foo[key])
            end 
        }
    else
        puts level + foo
        level = level[0,level.length - 2]
    end
end 

and it will give me a bad format like this:

parent1:
child1:
grandchild1:
1
   grandchild2:
2
   child2:
grandchild3:
3
   grandchild4:
4
lilixiaocc
  • 333
  • 1
  • 15

4 Answers4

5

You are almost there. One way to solve it is to make level a part of the recursive function parameters. x is the hash in the question.

Simple recursive version:

def print_hash(h,spaces=4,level=0)
  h.each do |key,val|
    format = "#{' '*spaces*level}#{key}: "
    if val.is_a? Hash
      puts format
      print_hash(val,spaces,level+1)
    else
      puts format + val.to_s
    end
  end
end

print_hash(x)

#parent1: 
#    child1: 
#        grandchild1: 1
#        grandchild2: 2
#    child2: 
#        grandchild3: 3
#        grandchild4: 4

In this case you could also convert it to YAML (as mentioned in a comment above)

require 'YAML'
puts x.to_yaml
#---
#parent1:
#  child1:
#    grandchild1: 1
#    grandchild2: 2
#  child2:
#    grandchild3: 3
#    grandchild4: 4
hirolau
  • 13,451
  • 8
  • 35
  • 47
  • thank you so much, this works perfect, and I also figure out the issue with my code, i have variable `level` initialized every time when it calls the function, Thank you again – lilixiaocc Aug 22 '16 at 19:29
2

I would use recursion, but there is another way that might be of interest to some. Below I've used a "pretty printer", awesome-print, to do part of the formatting (the indentation in particular), saving the result to a string, and then applied a couple of gsub's to the string to massage the results into the desired format.

Suppose your hash were as follows:

h = { "parent1"=>
        { "child1" => { "grandchild11" => 1,
                        "grandchild12" => { "great grandchild121" => 3 } },
          "child2" => { "grandchild21" => { "great grandchild211" =>
                                           { "great great grandchild2111" => 4 } },
                        "grandchild22" => 2 }
        }
    }

We could then do the following.

require 'awesome_print'

puts str = h.awesome_inspect(indent: -5, index: false, plain: true).
  gsub(/^\s*(?:{|},?)\s*\n|[\"{}]/, '').
  gsub(/\s*=>\s/, ':')

prints

 parent1:
      child1:
           grandchild11:1,
           grandchild12:
                great grandchild121:3
      child2:
           grandchild21:
                great grandchild211:
                     great great grandchild2111:4
           grandchild22:2 

The steps:

str = h.awesome_inspect(indent: -5, index: false, plain: true)

puts str prints

{
     "parent1" => {
          "child1" => {
               "grandchild11" => 1,
               "grandchild12" => {
                    "great grandchild121" => 3
               }
          },
          "child2" => {
               "grandchild21" => {
                    "great grandchild211" => {
                         "great great grandchild2111" => 4
                    }
               },
               "grandchild22" => 2
          }
     }
}

s1 = str.gsub(/^\s*(?:{|},?)\s*\n|[\"{}]/, '')

puts s1 prints

 parent1 => 
      child1 => 
           grandchild11 => 1,
           grandchild12 => 
                great grandchild121 => 3
      child2 => 
           grandchild21 => 
                great grandchild211 => 
                     great great grandchild2111 => 4
           grandchild22 => 2

s2 = s1.gsub(/\s*=>\s/, ':')

puts s2 prints the result above.

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
1

Not exactly what you require but I will submit this answer as I think you may find it useful:

require 'yaml'

hash = {"parent1"=> {"child1" => { "grandchild1" => 1,"grandchild2" => 2},
                     "child2" => { "grandchild3" => 3,"grandchild4" => 4}}}

puts hash.to_yaml

prints:

---
parent1:
  child1:
    grandchild1: 1
    grandchild2: 2
  child2:
    grandchild3: 3
    grandchild4: 4
Sagar Pandya
  • 9,323
  • 2
  • 24
  • 35
0

See Ruby Recursive Tree

Suppose we have

#$ mkdir -p foo/bar
#$ mkdir -p baz/boo/bee
#$ mkdir -p baz/goo

We can get

{
  "baz"=>{
    "boo"=>{
      "bee"=>{}},
    "goo"=>{}},
  "foo"=>{
    "bar"=>{}}}

We can traverse the tree as the following. So, here's a way to make a Hash based on directory tree on disk:

Dir.glob('**/*'). # get all files below current dir
  select{|f|
    File.directory?(f) # only directories we need
  }.map{|path|
    path.split '/' # split to parts
  }.inject({}){|acc, path| # start with empty hash
    path.inject(acc) do |acc2,dir| # for each path part, create a child of current node
      acc2[dir] ||= {} # and pass it as new current node
    end
    acc
  }

Thanks to Mladen Jablanović in the other answer for this concept.

Community
  • 1
  • 1
Muntasir Alam
  • 1,777
  • 2
  • 17
  • 26
  • Thank you @Munstasir Alam, but I just need to work on a given hash, do not need dealing with paths, kind like this [Javascript post](http://stackoverflow.com/questions/10455552/reading-an-objects-attributes-and-properties-using-recursion) – lilixiaocc Aug 22 '16 at 19:03