Ruby Convert Array to Nested Hash

Ruby convert array to nested hash

Try this:

array.reverse.inject(value) { |assigned_value, key| { key => assigned_value } }
#=> {"this"=>{"is"=>{"a"=>{"test"=>42}}}}

Convert array into nested hash for each element

First, a function to loop through the array and split those strings into an array which is much easier to work with.

def nest_array(array)
return array.each_with_object({}) do |string, hash|
values = string.split(/,/)

do_nesting(values, hash)
end
end

Then a recursive function to deal with each individual entry. ['a'] becomes { 'a' => nil }, ['a', 'b'] becomes { 'a' => 'b' } and ['a', 'b', 'c'] recurses to make a new hash from ['b', 'c'].

def do_nesting(values, hash)
if values.size <= 2
hash[values[0]] = values[1]
else
hash[values.shift] = do_nesting(values, {})
end

return hash
end

Use an Array to access nested Hash keys

You could make use of the newer method Hash#dig (ruby 2.3+) to access the nested hash, and then set the value on that:

# ideally this might be a method on Hash, so you wouldn't need to pass it in
def deep_set(hash, path, value)
*path, final_key = path
to_set = path.empty? ? hash : hash.dig(*path)

return unless to_set
to_set[final_key] = value
end

hash = {
"foo" => {
"bar" => { }
}
}

deep_set(hash, ["foo", "bar", "baz"], "xxxxxx")
deep_set(hash, ["baz"], "yyyyyy")
deep_set(hash, ["foo", "baz", "bar"], "zzzzzz")
puts hash
# => {"foo"=>{"bar"=>{"baz"=>"xxxxxx"}}, "baz"=>"yyyyyy"}

How could I convert an array of paths into nested array or hash dependant upon length

Code

def recurse(arr)
w_slash, wo_slash = arr.partition { |s| s.include?('/') }
a = w_slash.group_by { |s| s[/[^\/]+/] }.
map { |k,v| { k.to_sym=>recurse(v.map { |s| s[/(?<=\/).+/] }) } }
wo_slash.map(&:to_sym) + a
end

Examples

recurse array
#=> [:info,
# :services,
# {:about=>[:company, {:history=>[:part1, :part2]}]}]

arr = (array + ["a/b", "a/b/c", "a/b/c/d", "a/b/c/d/e"]).shuffle
#=> ["a/b/c", "services", "about/company", "about/history/part1", "info",
# "a/b", "about/history/part2", "a/b/c/d/e", "a/b/c/d"]
recurse arr
#=> [:services,
# :info,
# {:a=>[:b, {:b=>[:c, {:c=>[:d, {:d=>[:e]}]}]}]},
# {:about=>[:company, {:history=>[:part1, :part2]}]}]

Explanation

See Enumerable#partition and Enumerable#group_by.

The regular expression /[^\/]+/ reads, "match one or more characters that are not forward slashes". This could alternatively be written, s[0, s.index('/')].

The regular expression /(?<=\/).+/ reads, "match all characters following the first forward slash", (?<=\/) being a positive lookbehind. This could alternatively be written, s[s.index('/')+1..-1].

The initial steps are as follows.

w_slash, wo_slash = array.partition { |s| s.include?('/') }
#=> [["about/company", "about/history/part1", "about/history/part2"],
# ["info", "services"]]
w_slash
#=> ["about/company", "about/history/part1", "about/history/part2"]
wo_slash
#=> ["info", "services"]
h = w_slash.group_by { |s| s[/\A[^\/]+/] }
#=> {"about"=>["about/company", "about/history/part1", "about/history/part2"]}
a = h.map { |k,v| { k.to_sym=>recurse(v.map { |s| s[/(?<=\/).+/] }) } }
#=> [{:about=>[:company, {:history=>[:part1, :part2]}]}]
b = wo_slash.map(&:to_sym)
#=> [:info, :services]
b + a
#=> <as shown above>

In computing a, the first (and only) key-value pair of h is passed to the block and the two block variables are assigned values:

k,v = h.first
#=> ["about", ["about/company", "about/history/part1", "about/history/part2"]]
k #=> "about"
v #=> ["about/company", "about/history/part1", "about/history/part2"]

and the block calculations are preformed:

c = v.map { |s| s[/(?<=\/).+/] }
#=> ["company", "history/part1", "history/part2"]
{ k.to_sym=>recurse(c) }
#=> {:about=>[:company, {:history=>[:part1, :part2]}]}

and so on.

Ruby -- convert a nested hash to a multidimensional array

This is the way I would handle this:

h.values.each_with_object({}) do |h,obj|
obj.merge!(h) { |_k,v1,v2| ([v1] << v2).flatten }
end.values << h.keys
#=> [["abc", "cab"], ["123", "123456"], ["bob", "daisy"]]
  • First grab all the values (as Hashes)
  • loop through them with an accumulator ({})
  • merge! the values into the accumulator and on conflict append them to an array
  • return the values from the accumulator
  • then append the original keys

This is less explicit than @mudasobwa's answer and relies on the order of the first value to determine the output. e.g. if :tel came before :email the first 2 elements would have a reversed order

How to convert nested array to hash or JSON with values as array

You can first call group_by and then transform_values (Ruby 2.4+):

hash = array.group_by(&:first)
# => {"Colorado"=>[["Colorado", "Adams County"], ["Colorado", "Jefferson County"]], "California"=>[["California", "Amador"], ["California", "Tulare"]]}
hash.transform_values! { |value_list| value_list.map(&:last) }
# => {"Colorado"=>["Adams County", "Jefferson County"], "California"=>["Amador", "Tulare"]}

If your ruby doesn't have transform_values, you can just map!:

hash = array.group_by(&:first)
hash.each_value do |value_list|
value_list.map!(&:last)
end
# => {"Colorado"=>["Adams County", "Jefferson County"], "California"=>["Amador", "Tulare"]}

Converting array of hashes to array of nested hash in Ruby

All you need is to recursively build an inner hash:

data.
each_with_object(Hash.new { |h, k| h[k] = h.dup.clear }) do |h, acc|
(h[:category].split('.').
reduce(acc) do |inner, cat|
inner["label"] = cat
inner["options"] ||= {}
end || {}).
merge!("tag" => h[:tag], "description" => h[:description])
end
#⇒ {
# "label" => "Population",
# "options" => {
# "label" => "Behaviors",
# "options" => {
# "label" => "Commute",
# "options" => {
# "description" => "Work Outside the Home",
# "label" => "Vehicle",
# "options" => {
# "description" => "Bike to Work",
# "tag" => "mbike"
# },
# "tag" => "away"
# }
# }
# }
# }

Convert array to nested hash in ruby

First you could create an array like the following using the methods Array#map and Enumerator#with_index:

ary = ['a', 'b', 'c']
temporary = ary.map.with_index { |e, i| [e, { position: i }] }
# => [["a", {:position=>0}], ["b", {:position=>1}], ["c", {:position=>2}]]

Then you can convert the resulting array to hash using the Array#to_h method available since Ruby 2.1:

temporary.to_h
# => {"a"=>{:position=>0}, "b"=>{:position=>1}, "c"=>{:position=>2}}

For older versions of Ruby, the Hash.[] method will do:

Hash[temporary]
# => {"a"=>{:position=>0}, "b"=>{:position=>1}, "c"=>{:position=>2}}


Related Topics



Leave a reply



Submit