Ruby: Converting a Nested Ruby Hash to an Un-Nested One

Ruby: Converting a nested Ruby hash to an un-nested one

Here's a first cut at a complete solution. I'm sure you can write it more elegantly, but this seems fairly clear. If you save this in a Ruby file and run it, you'll get the output I show below.

class Hash
def unnest
new_hash = {}
each do |key,val|
if val.is_a?(Hash)
new_hash.merge!(val.prefix_keys("#{key}-"))
else
new_hash[key] = val
end
end
new_hash
end

def prefix_keys(prefix)
Hash[map{|key,val| [prefix + key, val]}].unnest
end
end

p ({"a" => 2, "f" => 5}).unnest
p ({"a" => {"b" => 3}, "f" => 5}).unnest
p ({"a" => {"b" => {"c" => 4}, "f" => 5}}).unnest

Output:

{"a"=>2, "f"=>5}
{"a-b"=>3, "f"=>5}
{"a-b-c"=>4, "a-f"=>5}

Merge nested hash without overwritting in Ruby

For Rails there is the deep_merge function for ActiveSupport that does exactly what you ask for.

You can implement the same for yourself as follows:

class ::Hash
def deep_merge(second)
merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
self.merge(second, &merger)
end
end

Now,

h1 = {"a"=>"text"}
h2 = {"b"=>{"x"=>"hola"}}
h3 = {"b"=>{"y"=>"pto"}}
h1.deep_merge(h2).deep_merge(h3)
# => {"a"=>"text", "b"=>{"x"=>"hola", "y"=>"pto"}}

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.

How to iterate over a nested Ruby hash to add a new key/value pair based on existing key/value data?

In addition to @MurifoX answer, you can use #transform_values

ages.transform_values do |value|
if value["age"] >= 65
value["age_group"] = "senior"
elsif value["age"] > 17 && value["age"] < 65
value["age_group"] = "adult"
else
value["age_group"] = "kid"
end
end

How to set nested hash in ruby dynamically?

Hash#[]= is a single method. You cannot do Hash#[] all the way to the last key and do = to it. Rather, leave out the last key and do Hash#[]= individually on it.

*key, last = key.split(".")
key.inject(h, :fetch)[last] = "b"

rails nested hash with default values

The doc for Hash::new explains the three ways of initializing a hash and, in your case, you are using an object in the Hash constructor:

If obj is specified, this single object will be used for all default values.

If you want that each missing key creates it's own object, create the hash with a block, like this:

h = Hash.new { |h,k| h[k] = { count: 0, rating: 0 } }

Then:

2.6.3 :012 > h
=> {}
2.6.3 :013 > h['a'][:count] = 5
=> 5
2.6.3 :015 > h
=> {"a"=>{:count=>5, :rating=>0}}

(Ruby) How to convert a nested hash into a csv file

I would do something like this:

require 'csv'

CSV.open('the_file.csv', 'w') do |csv|
hash.each do |id, attributes|
csv << [id, *attributes.values_at(:price, :brand)]
end
end

How to avoid NoMethodError for missing elements in nested hashes, without repeated nil checks?

Ruby 2.3.0 introduced a method called dig on both Hash and Array.

name = params.dig(:company, :owner, :name)

It returns nil if the key is missing at any level.

If you are using a version of Ruby older than 2.3, you can install a gem such as ruby_dig or hash_dig_and_collect, or implement the functionality yourself:

module RubyDig
def dig(key, *rest)
if value = (self[key] rescue nil)
if rest.empty?
value
elsif value.respond_to?(:dig)
value.dig(*rest)
end
end
end
end

if RUBY_VERSION < '2.3'
Array.send(:include, RubyDig)
Hash.send(:include, RubyDig)
end


Related Topics



Leave a reply



Submit