Iterate over a deeply nested level of hashes in Ruby

后端 未结 8 1730
眼角桃花
眼角桃花 2020-12-05 00:13

So I have a hash, and for each level of the hash, I want to store its key and value. The problem is, a value can be another hash array. Furthermore, that hash can contain ke

相关标签:
8条回答
  • 2020-12-05 00:31

    If you want to recursively edit the hash, you could do something like this:

    # Iterates over a Hash recursively
    def each_recursive(parent, &block)
      parent.each do |path, value|
        if value.kind_of? Hash
          each_recursive parent, &block
        elsif value.is_a? Array
          # @TODo something different for Array?
        else
          yield(parent, path, container_or_field)
        end
      end
    end
    

    And you can do something like:

    hash = {...}
    each_recursive(hash) do |parent, path, value|
      parent[path] = value.uppercase
    end
    
    0 讨论(0)
  • 2020-12-05 00:34

    I recomend to use #deep_locate of hashie gem https://www.rubydoc.info/github/intridea/hashie/Hashie/Extensions/DeepLocate#deep_locate-instance_method

    little bit hacky always return false not to seeking

    hash.extend(Hashie::Extensions::DeepLocate)
    hash.deep_locate -> (key, value, object) do 
      # what you want to do here!
      # key: hash key
      # value: hash value
      # object: hash_object
      false # prevent to stop seeking
    end
    
    0 讨论(0)
  • 2020-12-05 00:37

    If I understand the goal, then you should be able to pass in the parent to your save method. For the top level, it will be nil. The following shows the idea where puts is used as a place holder for the "save".

    def save_pair(parent, myHash)
      myHash.each {|key, value|
        value.is_a?(Hash) ? save_pair(key, value) :
                puts("parent=#{parent.nil? ? 'none':parent}, (#{key}, #{value})")
      }
    end
    

    Here is an example call to it:

    hash = Hash.new
    hash["key1"] = "value1"
    hash["key2"] = "value2"
    hash["key3"] = Hash.new
    hash["key3"]["key4"] = "value4"
    hash["key3"]["key5"] = "value5"
    hash["key6"] = Hash.new
    hash["key6"]["key7"] = "value7"
    hash["key6"]["key8"] = Hash.new
    hash["key6"]["key8"]["key9"] = "value9"
    
    save_pair(nil, hash)
    
    0 讨论(0)
  • 2020-12-05 00:38

    This should do for JSON well. Minor enhancements to Mark's code where it converts everything to uppercase in a given hash:

    def capitalize_hash(myHash)
        myHash.each {|key, value|
            puts "isHash: #{value.is_a?(Hash)}: " + value.to_s
            value.is_a?(Hash) ? capitalize_hash(value) : ( value.is_a?(Array) ? (myHash[key] = capitalize_array(value)) : (myHash[key] = value.try(:upcase)))
        }
    end
    
    def capitalize_array(myArray)
        myArray.each {|value|
            puts "isHash: #{value.is_a?(Hash)}: " + value.to_s
            value.is_a?(Array) ? capitalize_array(value) : ( value.is_a?(Hash) ? capitalize_hash(value) : value.try(:upcase))
        }
    end
    
    0 讨论(0)
  • 2020-12-05 00:39

    Here is recursive (read improved) version of Hash::each(Hash::each_pair) with block and enumerator support:

    module HashRecursive
        refine Hash do
            def each(recursive=false, &block)
                if recursive
                    Enumerator.new do |yielder|
                        self.map do |key, value|
                            value.each(recursive=true).map{ |key_next, value_next| yielder << [[key, key_next].flatten, value_next] } if value.is_a?(Hash)
                            yielder << [[key], value]
                        end
                    end.entries.each(&block)
                else
                    super(&block)
                end
            end
            alias_method(:each_pair, :each)
        end
    end
    
    using HashRecursive
    

    Here are usage examples of Hash::each with and without recursive flag:

    hash = {
        :a => {
            :b => {
                :c => 1,
                :d => [2, 3, 4]
            },
            :e => 5
        },
        :f => 6
    }
    
    p hash.each, hash.each {}, hash.each.size
    # #<Enumerator: {:a=>{:b=>{:c=>1, :d=>[2, 3, 4]}, :e=>5}, :f=>6}:each>
    # {:a=>{:b=>{:c=>1, :d=>[2, 3, 4]}, :e=>5}, :f=>6}
    # 2
    
    p hash.each(true), hash.each(true) {}, hash.each(true).size
    # #<Enumerator: [[[:a, :b, :c], 1], [[:a, :b, :d], [2, 3, 4]], [[:a, :b], {:c=>1, :d=>[2, 3, 4]}], [[:a, :e], 5], [[:a], {:b=>{:c=>1, :d=>[2, 3, 4]}, :e=>5}], [[:f], 6]]:each>
    # [[[:a, :b, :c], 1], [[:a, :b, :d], [2, 3, 4]], [[:a, :b], {:c=>1, :d=>[2, 3, 4]}], [[:a, :e], 5], [[:a], {:b=>{:c=>1, :d=>[2, 3, 4]}, :e=>5}], [[:f], 6]]
    # 6
    
    hash.each do |key, value|
        puts "#{key} => #{value}"
    end
    # a => {:b=>{:c=>1, :d=>[2, 3, 4]}, :e=>5}
    # f => 6
    
    hash.each(true) do |key, value|
        puts "#{key} => #{value}"
    end
    # [:a, :b, :c] => 1
    # [:a, :b, :d] => [2, 3, 4]
    # [:a, :b] => {:c=>1, :d=>[2, 3, 4]}
    # [:a, :e] => 5
    # [:a] => {:b=>{:c=>1, :d=>[2, 3, 4]}, :e=>5}
    # [:f] => 6
    
    hash.each_pair(recursive=true) do |key, value|
        puts "#{key} => #{value}" unless value.is_a?(Hash)
    end
    # [:a, :b, :c] => 1
    # [:a, :b, :d] => [2, 3, 4]
    # [:a, :e] => 5
    # [:f] => 6
    

    Here is example from the question itself:

    hash = {
        :key1   =>  'value1',
        :key2   =>  'value2',
        :key3   =>  {
            :key4   =>  'value4',
            :key5   =>  'value5'
        },
        :key6   =>  {
            :key7   =>  'value7',
            :key8   =>  {
                :key9   =>  'value9'
            }
        }
    }
    
    hash.each_pair(recursive=true) do |key, value|
        puts "#{key} => #{value}" unless value.is_a?(Hash)
    end
    # [:key1] => value1
    # [:key2] => value2
    # [:key3, :key4] => value4
    # [:key3, :key5] => value5
    # [:key6, :key7] => value7
    # [:key6, :key8, :key9] => value9
    

    Also take a look at my recursive version of Hash::merge(Hash::merge!) here.

    0 讨论(0)
  • 2020-12-05 00:43

    I know this is a late reply, but I just implemented a non-recursive solution to your problem and thought it is worth sharing.

    class Hash
      def deep_traverse(&block)
        stack = self.map{ |k,v| [ [k], v ] }
        while not stack.empty?
          key, value = stack.pop
          yield(key, value)
          if value.is_a? Hash
            value.each{ |k,v| stack.push [ key.dup << k, v ] }
          end
        end
      end
    end
    

    Then, coming back to your original problem, you can do:

    h = {
      :key1 => 'value1',
      :key2 => 'value2',
      :key3 => {
         :key4 => 'value4',
         :key5 => 'value5'
      },
      :key6 => {
        :key7 => 'value7',
        :key8 => {
          :key9 => 'value9'
        }
      }
    }
    h.deep_traverse{ |path,value| p [ path, value ] }
    # => [[:key6], {:key7=>"value7", :key8=>{:key9=>"value9"}}]
    #    [[:key6, :key8], {:key9=>"value9"}]
    #    [[:key6, :key8, :key9], "value9"]
    #    [[:key6, :key7], "value7"]
    #    [[:key3], {:key4=>"value4", :key5=>"value5"}]
    #    [[:key3, :key5], "value5"]
    #    [[:key3, :key4], "value4"]
    #    [[:key2], "value2"]
    #    [[:key1], "value1"]
    

    There is also a gist version.

    0 讨论(0)
提交回复
热议问题