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 anarray
- 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
What's the Point of Argv in Ruby
Why Doesn't Minitest::Spec Have a Wont_Raise Assertion
How to Save an Object to a File
How to Remove Permission Denied @ Rb_Sysopen - Gem Install Error
Select Arrays Between Date Ranges with Ruby
Looping Through an Array with Step
Ruby on Rails Rake Assets:Precompile Error
Is There an Expect Equivalent Gem for Ruby
Prevent Rails Test from Deleting Seed Data
Installing Jekyll on Ubuntu 14.04
Dynamically Set Local Variables in Ruby
Change the Binding of a Proc in Ruby
Using Implicit 'Subject' with 'Expect' in Rspec-2.11