Converting a Nested Hash into a Flat Hash

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}

How would I parse a flat Ruby hash in to a nested hash?

First you can split up the hash into multiple hashes according to their prefix number (feel free to run this to see the return val)

groups = input.
group_by { |k,v| k.match(/builder_rule_(\d+)/)[1] }.
transform_values(&:to_h)

at this point, creating the inner objects is easier if you just use some hard-coding to build the key-valls:

result = groups.each_with_object({}) do |(prefix, hash), memo|
memo[prefix] = {
"filter" => hash["builder_rule_#{prefix}_filter"],
"operator" => hash["builder_rule_#{prefix}_operator"],
"values" => hash.select do |key, val|
key =~ /builder_rule_#{prefix}_value/
end.sort_by { |key, val| key }.map { |(key, val)| val }
}
end

It might possibly be confusing what .sort_by { |key, val| key }.map { |(key, val)| val } means. I can spell it out:

  • hash.select { |key, val| key =~ /builder_rule_#{prefix}_value/ } gets the key-vals which are going to be used for the "values" array. It returns a hash.
  • .sort_by { |key, val| key } turns the hash into an array of [key, val] tuples , sorted by the key. This is so that the values appear in the correct order.
  • .map { |(key, val)| val } turns the nested array into a single-level array, discarding the keys

You could also use sort_by(&:first).map(&:second), though you need active support to use Array#second

Converting a hash into a nested hash

Here's an iterative solution, a recursive one is left as an exercise to the reader:

def convert(h={})
ret = {}
h.each do |k,v|
node = ret
k[0..-2].each {|x| node[x]||={}; node=node[x]}
node[k[-1]] = v
end
ret
end

convert(your_hash) # => {:f=>4, :a=>{:b=>{:c=>1, :d=>2}, :e=>3}}

How to trasform all values in a nested hash?

Interesting to learn of the deep_merge approach taken in the answer by "The F". Here is another approach which requires adding a few helper methods.

First, the helper methods:

From the top answer here (converting-a-nested-hash-into-a-flat-hash):

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

From a Github repo called ruby-bury (this functionality was proposed to Ruby core, but rejected)

class Hash
def bury *args
if args.count < 2
raise ArgumentError.new("2 or more arguments required")
elsif args.count == 2
self[args[0]] = args[1]
else
arg = args.shift
self[arg] = {} unless self[arg]
self[arg].bury(*args) unless args.empty?
end
self
end
end

And then a method tying it together:

def change_all_values(hash, &blk)    
# the next line makes the method "pure functional"
# but can be removed otherwise.
hash = Marshal.load(Marshal.dump(hash))

flat_hash(hash).each { |k,v| hash.bury(*(k + [blk.call(v)])) }
hash
end

A usage example:

irb(main):063:0> a = {a: 1, b: { c: 1 } }
=> {:a=>1, :b=>{:c=>1}}
irb(main):064:0> b = change_all_values(a) { |val| val + 1 }
=> {:a=>2, :b=>{:c=>2}}
irb(main):066:0> a
=> {:a=>1, :b=>{:c=>1}}

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 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"}

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

Deep transforming Hash into the flat Array of paths

It is pretty standard Tree traversal problem. You could use DFS via recursion:

# for Array.wrap; It's needed in pure ruby script, not in Rails
require 'active_support/all'

def deep_flatten(tree, path, result)
tree.each do |key, value|
Array.wrap(value).each do |e|
if e.is_a? Hash
deep_flatten(e, path + [key], result)
else
result << path + [key, e]
end
end
end
end

tree = {
friend: [:id, :name],
meta: {
board: [:id, :name],
column: [:id, :name, users: [:id, :name]]
},
trello: [:id, :name]
}

result = []
deep_flatten(tree, [], result)
result.each do |line|
puts line.inspect
end

It outputs:

[:friend, :id]
[:friend, :name]
[:meta, :board, :id]
[:meta, :board, :name]
[:meta, :column, :id]
[:meta, :column, :name]
[:meta, :column, :users, :id]
[:meta, :column, :users, :name]
[:trello, :id]
[:trello, :name]

Array.wrap

How to functionally convert a nested hash to a list of records?



my @money = %money.map:
-> ( :key($type), :value(%records) ) {
slip

:$type xx *
Z
( 'subtype' X=> %records.keys )
Z
( 'value' X=> %records.values )
}

You could do .kv.map: -> $type, %records {…}


  • -> ( :key($type), :value(%records) ) {…} destructures a Pair object
  • :$type creates a type => $type Pair
  • :$type xx * repeats :$type infinitely (Z stops when any of it's inputs stops)
  • ('subtype' X=> %records.keys) creates a list of Pairs

    (Note that .keys and .values are in the same order if you don't modify the Hash between the calls)
  • Z zips two lists
  • slip causes the elements of the sequence to slip into the outer sequence

    (flat would flatten too much)

If you wanted them to be sorted

my @money = %money.sort.map: # 'coins' sorts before 'notes'
-> ( :key($type), :value(%records) ) {

# sort by the numeric part of the key
my @sorted = %records.sort( +*.key.match(/^\d+/) );

slip

:$type xx *
Z
( 'subtype' X=> @sorted».key )
Z
( 'value' X=> @sorted».value )
}

You could do .sort».kv.map: -> ($type, %records) {…}



Related Topics



Leave a reply



Submit