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 atype => $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 listsslip
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
Ruby Array Access 2 Consecutive(Chained) Elements At a Time
Why Doesn't Ruby Support Method Overloading
Using Sinatra For Larger Projects Via Multiple Files
Why Does String Interpolation Work in Ruby When There Are No Curly Braces
Altering the Primary Key in Rails to Be a String
What's the Best Way to Model Recurring Events in a Calendar Application
How to Get a Query String from a Url in Rails
Rails: How to Change the Title of a Page
Rails 4: List of Available Datatypes
No Such File to Load - Rubygems (Loaderror)
How to Avoid Tripping Over Utf-8 Bom When Reading Files
How to Use Ruby/Rails Variables Inside Sass