Never been to DZone Snippets before?

Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

Extracting all keys from a multi-dimensional hash (See related posts)

Extract all complete key sequences from a multi-dimensional hash (with the last key not pointing to another hash; cf. h[1][2][3] vs h[1][2][3][4] below).


class Hash

   def extract_keys

      keys = []

      each_pair do |k1, v1|

         if v1.is_a?(Hash)

            v1.each_pair { |k2, v2|
               if !v2.is_a?(Hash) then keys << [k1, k2]; next end
            v2.each_pair { |k3, v3|
               if !v3.is_a?(Hash) then keys << [k1, k2, k3]; next end
            v3.each_pair { |k4, v4|
               if !v4.is_a?(Hash) then keys << [k1, k2, k3, k4]; next end
            v4.each_pair { |k5, v5|
               if !v5.is_a?(Hash) then keys << [k1, k2, k3, k4, k5]; next end
            v5.each_pair { |k6, v6|
               if !v6.is_a?(Hash) then keys << [k1, k2, k3, k4, k5, k6]; next end
               # add more v[n].each_pair ... loops to process more hash dimensions
            } } } } }      # "}" * 5

         else
            keys << [k1]
         end

      end
      
      keys

   end


   def all_values
      extract_keys.map do |subar|
         key = ""
         subar.size.times { |i| key << "[subar[#{i}]]" }
         hash_str = "self" << key << " rescue nil"   # example: "self[subar[0]][subar[1]][subar[2]][subar[3]] rescue nil"
         hash_value = eval(hash_str) 
      end
   end


#-------------------------


   # Find every path and it's value in a Hash, http://snippets.dzone.com/posts/show/3565
   # Author: Florian Aßmann

   def each_path
      raise ArgumentError unless block_given?
      self.class.each_path(self) { |path, object| yield(path, object) }
   end

   protected
   #def self.each_path(object, path = '', &block)
   def self.each_path(object, path = [], &block)   # alternative
      if object.is_a?(Hash)
         object.each do |key, value|
            #self.each_path(value, "#{ path }#{ key }/", &block)
            self.each_path(value, [path , key].flatten, &block)   # alternative
         end
      else 
         yield(path, object)
      end
   end 

end


h = {"a"=>"b", "c"=>"d", 1=>{2=>{"e"=>"f", 3=>{4=>"value"}}}} 

puts h[1][2].class          # Hash
puts h[1][2]["e"].class     # String

extracted_keys = h.extract_keys
puts extracted_keys.inspect         # [["a"], [1, 2, "e"], [1, 2, 3, 4], ["c"]]

puts h[1][2].has_key?("e")                 # true
puts extracted_keys.include?([1, 2, "e"])  # true


h = {700=>{4=>"value"}, "a"=>"b", 3=>{4=>"value"}, "c"=>"d", 1=>{2=>{"e"=>"f", 3=>{4=>"value"}, 300=>{4=>"value"}}}} 
p h
p h.extract_keys    #=> [["a"], [1, 2, "e"], [1, 2, 300, 4], [1, 2, 3, 4], ["c"], [700, 4], [3, 4]]
p h.all_values      #=> ["b", "f", "value", "value", "d", "value", "value"]


#-----------------


paths = []
complex_hash = Hash[
  :a => { :aa => '1', :ab => '2' },
  :b => { :ba => '3', :bb => '4' }
]
complex_hash.each_path { |path, value| paths << [ path, value ] }

p paths    # => [[[:b, :ba], "3"], [[:b, :bb], "4"], [[:a, :ab], "2"], [[:a, :aa], "1"]]
puts


h = {"a"=>"b", "c"=>"d", 1=>{2=>{"e"=>"f", 3=>{4=>"value"}}}} 
h = {"a"=>"b", "l" => lambda { |x| x+1 }, 1=>{2=>{"e"=>"f", 3=>{4=>"value"}}}} 
h = {700=>{4=>"value"}, "a"=>"b", nil => "NILVALUE", 3=>{4=>"value"}, "c"=>"d", 1=>{2=>{"e"=>"f", 3=>{4=>"value"}, 300=>{4=>"value"}}}} 

p h
p h.extract_keys

keys = []
h.each_path { |path, value| keys << path }
p keys   # complete key sequences (with last key not pointing to another hash)

paths = []
h.each_path { |path, value| paths << [ path, value ] }
p paths   # complete key sequences plus values

vals = []
h.each_path { |path, value| vals << value }
p vals   # all values of complete key sequences

p h.all_values   # same


Comments on this post

peter posts on Apr 12, 2006 at 17:37
This definitely doesn't do what I think you want it to do. If you correct the spelling mistake, the output is meaningless:

[[[1, 2, 3]], [1, 2, "e"], [1, 2, 3, 4], [[1, 2, 3]]]

..

Do you want to do this instead?

h = {"a"=>"b", "c"=>"d", 1=>{2=>{"e"=>"f", 3=>{4=>"value"}}}}

class Hash
        def extract_keys
                keys = []
                self.each { |key, value|
                        if value.class == Hash
                                keys << value.extract_keys        
                        else
                                keys << key
                        end
                }
                keys.flatten
        end
end

puts h.extract_keys.inspect
ntk posts on Apr 13, 2006 at 14:34
Thanks for pointing out the typo!

My goal was to extract the complete key sequences so that it becomes possible to handle nested values or
check if there is a given key sequence in a multi-dimensional hash (cf. "No keys, nor other hash methods on
multidimensional hash" via http://www.rubyweeklynews.org/20050828 ).
peter posts on Apr 13, 2006 at 19:23
Oh okay, check this out then. It does what your one does but nests to any depth:

class Hash
        def extract_keys(keys = [], temp = [])
                temp = temp.dup
                temp2 = temp.dup
                self.each { |key, value|
                        if value.class != Hash
                               temp << key
                                keys << temp
                                #puts temp.inspect
                                temp = []
                      end
                }
                temp = temp2
                self.each { |key, value|
                        if value.class == Hash
                                temp << key
                                keys = value.extract_keys(keys, temp)
                        end
                }

                keys
        end
end                                                                                                                
ntk posts on Apr 14, 2006 at 11:08
Great going, Peter! Thanks! Definitely deserves its own post URL!
peter posts on Apr 14, 2006 at 15:58
That's okay, I like a challenge from time to time, and the repeated 'if' statements were calling out for recursion :)

You need to create an account or log in to post comments to this site.


Click here to browse all 4861 code snippets

Related Posts