Flattening nested hash to a single hash with Ruby/Rails
You could do this:
def flatten_hash(hash)
hash.each_with_object({}) do |(k, v), h|
if v.is_a? Hash
flatten_hash(v).map do |h_k, h_v|
h["#{k}.#{h_k}".to_sym] = h_v
end
else
h[k] = v
end
end
end
flatten_hash(:foo => "bar",
:hello => {
:world => "Hello World",
:bro => "What's up dude?",
},
:a => {
:b => {
:c => "d"
}
})
# => {:foo=>"bar",
# => :"hello.world"=>"Hello World",
# => :"hello.bro"=>"What's up dude?",
# => :"a.b.c"=>"d"}
How to flatten nested hash in Ruby
REGIONS.values.reduce(&:merge)
How do I flatten a nested hash, recursively, into an array of arrays with a specific format?
As hinted at in the comments:
Looks pretty straightforward. Descend into hashes recursively, taking note of keys you visited in this branch. When you see an array, no need to recurse further. Append it to the list of keys and return
Tracking is easy, just pass the temp state down to recursive calls in arguments.
I meant something like this:
def tree_flatten(tree, path = [], &block)
case tree
when Array
block.call(path + tree)
else
tree.each do |key, sub_tree|
tree_flatten(sub_tree, path + [key], &block)
end
end
end
tree_flatten(tree_def) do |path|
p path
end
This code simply prints each flattened path as it gets one, but you can store it in an array too. Or even modify tree_flatten
to return you a ready array, instead of yielding elements one by one.
Flattening nested hash to an array
Recursive.
def flatten_nested_hash(categories)
categories.flat_map{|k, v| [k, *flatten_nested_hash(v)]}
end
Defining it on the Hash class.
class Hash
def flatten_nested; flat_map{|k, v| [k, *v.flatten_nested]} end
end
How to flatten a hash, making each key a unique value?
Interesting question!
Theory
Here's a recursive method to parse your data.
- It keeps track of which keys and indices it has found.
- It appends them in a
tmp
array. - Once a leaf object has been found, it gets written in a hash as value, with a joined
tmp
as key. - This small hash then gets recursively merged back to the main hash.
Code
def recursive_parsing(object, tmp = [])
case object
when Array
object.each.with_index(1).with_object({}) do |(element, i), result|
result.merge! recursive_parsing(element, tmp + [i])
end
when Hash
object.each_with_object({}) do |(key, value), result|
result.merge! recursive_parsing(value, tmp + [key])
end
else
{ tmp.join('_') => object }
end
end
As an example:
require 'pp'
pp recursive_parsing(data)
# {"Name"=>"Kim Kones",
# "License Number"=>"54321",
# "Details_Name"=>"Kones, Kim",
# "Details_Licenses_1_License Type"=>"PT",
# "Details_Licenses_1_License Number"=>"54321",
# "Details_Licenses_2_License Type"=>"Temp",
# "Details_Licenses_2_License Number"=>"T123",
# "Details_Licenses_3_License Type"=>"AP",
# "Details_Licenses_3_License Number"=>"A666",
# "Details_Licenses_3_Expiration Date"=>"12/31/2020"}
Debugging
Here's a modified version with old-school debugging. It might help you understand what's going on:
def recursive_parsing(object, tmp = [], indent="")
puts "#{indent}Parsing #{object.inspect}, with tmp=#{tmp.inspect}"
result = case object
when Array
puts "#{indent} It's an array! Let's parse every element:"
object.each_with_object({}).with_index(1) do |(element, result), i|
result.merge! recursive_parsing(element, tmp + [i], indent + " ")
end
when Hash
puts "#{indent} It's a hash! Let's parse every key,value pair:"
object.each_with_object({}) do |(key, value), result|
result.merge! recursive_parsing(value, tmp + [key], indent + " ")
end
else
puts "#{indent} It's a leaf! Let's return a hash"
{ tmp.join('_') => object }
end
puts "#{indent} Returning #{result.inspect}\n"
result
end
When called with recursive_parsing([{a: 'foo', b: 'bar'}, {c: 'baz'}])
, it displays:
Parsing [{:a=>"foo", :b=>"bar"}, {:c=>"baz"}], with tmp=[]
It's an array! Let's parse every element:
Parsing {:a=>"foo", :b=>"bar"}, with tmp=[1]
It's a hash! Let's parse every key,value pair:
Parsing "foo", with tmp=[1, :a]
It's a leaf! Let's return a hash
Returning {"1_a"=>"foo"}
Parsing "bar", with tmp=[1, :b]
It's a leaf! Let's return a hash
Returning {"1_b"=>"bar"}
Returning {"1_a"=>"foo", "1_b"=>"bar"}
Parsing {:c=>"baz"}, with tmp=[2]
It's a hash! Let's parse every key,value pair:
Parsing "baz", with tmp=[2, :c]
It's a leaf! Let's return a hash
Returning {"2_c"=>"baz"}
Returning {"2_c"=>"baz"}
Returning {"1_a"=>"foo", "1_b"=>"bar", "2_c"=>"baz"}
Flatten array of nested hashes
If you don't mind modifying the array in-place then you could say:
array.each { |h| h.merge!(h.delete('percentiles')) }
If you're not sure that all the hashes have 'percentiles'
keys then you could say:
# Explicitly check
array.each { |h| h.merge!(h.delete('percentiles')) if(h.has_key?('percentiles')) }
# Convert possible `nil`s to `{ }`
array.each { |h| h.merge!(h.delete('percentiles').to_h) }
# Filter before merging
array.select { |h| h.has_key?('percentiles') }.each { |h| h.merge!(h.delete('percentiles')) }
If you want to flatten all hash values then you can do things like this:
array.each do |h|
h.keys.each do |k|
if(h[k].is_a?(Hash))
h.merge!(h.delete(k))
end
end
end
If you don't want to modify the hashes inside the array then variations on:
flat = array.map(&:dup).each { |h| h.merge!(h.delete('percentiles')) }
flat = array.map do |e|
e.each_with_object({}) do |(k, v), h|
if(v.is_a?(Hash))
h.merge!(v)
else
h[k] = v
end
end
end
Flatten nested hashes in an array - Ruby
input.flat_map do |h|
# split the input into group and tests in the first place
h.partition { |k, v| k == "Group" }.reduce(&:product)
end.map do |(name, group), (_, data)|
# due to product above each element is a 2×2 array
# first item is the Group, the second is the TestN
{name => group}.merge(data.merge(data.delete("AdditionalInfo")))
end
Flattening nested hash to an array
Recursive.
def flatten_nested_hash(categories)
categories.flat_map{|k, v| [k, *flatten_nested_hash(v)]}
end
Defining it on the Hash class.
class Hash
def flatten_nested; flat_map{|k, v| [k, *v.flatten_nested]} end
end
Flatten a nested hash in Ruby on Rails
data = { ... }
h = data['Result'].first
h.merge!(h.delete('Links').first)
Converting a nested hash into a flat hash
Another way:
def flat_hash(h,f=[],g={})
return g.update({ f=>h }) unless h.is_a? Hash
h.each { |k,r| flat_hash(r,f+[k],g) }
g
end
h = { :a => { :b => { :c => 1,
:d => 2 },
:e => 3 },
:f => 4 }
flat_hash(h) #=> {[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}
Related Topics
Couldn't Require Openssl in Ruby
How to Alter the Timezone of a Datetime in Ruby
Rails - Whenever Gem - Dynamic Values
Problems Installing Ruby on Mountain Lion - Ruby 1.9.3 Wont' Compile
What's the Point of Unary Plus Operator in Ruby
Ubuntu Rails Install Fails on Zlib
How to Pass a Parameter for Gem Installation When I Run Bundle Install
How to Build a Rubygems Mirror Server
How to Url Encode a String in Ruby
How to Detect Certain Unicode Characters in a String in Ruby
How to Save an Object to a File
Project Euler 1:Find the Sum of All the Multiples of 3 or 5 Below 1000
"Stack Level Too Deep" Running Rake Db:Create:All
Call Controller from Rake Task
How to Get Sinatra to Auto-Reload the File After Each Change
How to Merge Array of Hashes to Get Hash of Arrays of Values