Reputation: 14740
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 key value pairs where the value is again another hash array, etc, etc. Also, I won't know how deeply nested each hash will be. To give an example:
{
:key1 => 'value1',
:key2 => 'value2',
:key3 => {
:key4 => 'value4',
:key5 => 'value5'
},
:key6 => {
:key7 => 'value7',
:key8 => {
:key9 => 'value9'
}
}
}
..And so on. What I want to do is save each key, value pair and the id of its parent. I figure this will probably be done recursively, I'm just unsure how because I'm unfamiliar with recursive functions. I know how to iterate through the data normally:
myHash.each {|key, value|
...Do something with the key and value ...
}
And so I'm guessing the recursive call will be something like this:
def save_pair (myHash)
myHash.each {|key, value|
if(value.class != Hash) ? Pair.create(key, value) : save_pair(value)
}
end
This is untested, and I'm still unsure how to incorporate saving the parent ids regardless.
Upvotes: 33
Views: 42724
Reputation: 105
I have faced this issue while trying to convert camel case hash keys to snake case. that's what I came up with:
def hash_to_underscore(hash)
res = {}
hash.each do |key, value|
res[to_underscore(key)] =
value.instance_of?(Hash) ? hash_to_underscore(value) : value
end
res
end
def to_underscore(key)
key.to_s.underscore.to_sym
end
Upvotes: 0
Reputation: 7708
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
Upvotes: 0
Reputation: 1653
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
Upvotes: 4
Reputation: 403
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.
Upvotes: 3
Reputation: 2951
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
Upvotes: 1
Reputation: 1678
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.
Upvotes: 15
Reputation: 11198
class Hash
def each_with_parent(parent=nil, &blk)
each do |k, v|
Hash === v ? v.each_with_parent(k, &blk) : blk.call([parent, k, v])
end
end
end
h = { :a => 1, :b => { :c => 3, :d => 4, :e => { :f => 5 } } }
h.each_with_parent { |i| p i }
# [nil, :a, 1]
# [:b, :c, 3]
# [:b, :d, 4]
# [:e, :f, 5]
Upvotes: 7
Reputation: 41222
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)
Upvotes: 25
Reputation: 2458
Have you tried something like that?
trios = []
def save_trio(hash, parent = nil)
hash.each do |key, value|
value.kind_of?(Hash) ? save_trio(value, key) : trios << {:key => key, :value => value, :parent => parent}
end
end
save_trio(myHash)
Upvotes: 0