How to create a hash from a multidimensional array - ruby

I have these params :
x=["room_adults_1", "room_childs_1", "room_adults_2", "room_childs_2"]
And when I run this code:
y = x.map { |x| x.match(/^(room)_(adults|childs)_(\d+)/)}
z = y.map { |x| [x[1],[x[2],[x[3].reverse,""]]]}
I get this array:
=> [["room", ["adults", ["1", ""]]], ["room", ["childs", ["1", ""]]], ["room", ["adults", ["2", ""]]], ["room", ["childs", ["2", ""]]]]
I would like to transform this last result into a Hash. If I use z.to_h (collapse last element), I obtain {"room"=>["childs", ["2", ""]]}. But I would like a Hash like this:
{
"room":{
"adults":{[
{"1": ""},
{"2": ""}
]},
"child":{[
{"1": ""},
{"2": ""}
]}
}
}
How can I do?

x.map { |e| e.split '_' }
.group_by(&:shift) # room
.map { |k, v| [k, v.group_by(&:shift) # adults/children
.map { |k, v| [k, v.map { |e, *| {e => ""} } ] }
.to_h ] }
.to_h
#⇒ {"room"=>{"adults"=>[{"1"=>""}, {"2"=>""}],
# "childs"=>[{"1"=>""}, {"2"=>""}]}}

In this case I prefer each_with_object:
x = ["room_adults_1", "room_childs_1", "room_adults_2", "room_childs_2"]
export = x.each_with_object(Hash.new { |k, v| k[v] = Hash.new { |k, v| k[v] = [] } }) do |string, exp|
room, type, id = string.split("_")
exp[room][type] << {id => ""}
end
p export
# => {"room"=>{"adults"=>[{"1"=>""}, {"2"=>""}], "childs"=>[{"1"=>""}, {"2"=>""}]}}

I assume the desired result is
{:room=>{:adults=>[{:"1"=>""}, {:"2"=>""}], :childs=>[{:"1"=>""}, {:"2"=>""}]}}
as the construct given in the question is not a valid Ruby object, let alone a hash.
arr = ["room_adults_1", "room_childs_1", "room_adults_2", "room_childs_2"]
h = arr.map { |s| s.split('_')[1,2].map(&:to_sym) }.group_by(&:first)
#=> {:adults=>[[:adults, :"1"], [:adults, :"2"]],
# :childs=>[[:childs, :"1"], [:childs, :"2"]]}
{ room: h.merge(h) { |k,a,_| a.map { |_,b| { b=>"" } } } }
#=> {:room=>{:adults=>[{:"1"=>""}, {:"2"=>""}], :childs=>[{:"1"=>""}, {:"2"=>""}]}}
If the keys are to be strings, not symbols, remove .map(&:to_sym).

Related

Ruby: Swap out hash key with matching value from other hash

I have these hashes:
hash = {1 => "popcorn", 2 => "soda"}
other_hash = {1 => "dave", 2 => "linda", 3 => "bobby_third_wheel"}
I would like to replace the id reference with the name associated to the id in the second hash, if there is a record in other_hash that has nothing to match, it should just be dropped in the resulting hash. Like this:
the_one_hash_to_rule_them_all = {"dave" => "popcorn", "linda" => "soda"}
You can easly use this each_with_object method on "primary" hash with names.
other_hash.each_with_object({}) { |(id, name), h| h[name] = hash[id] if hash.key?(id) }
# => {"dave"=>"popcorn", "linda"=>"soda"}
hash.each_with_object({}){|(k,v), res| res[other_hash[k]] = v}
# => {"dave"=>"popcorn", "linda"=>"soda"}
First, an "array-comprehension" with the pattern enumerable.map { expr if condition }.compact and finally an Array#to_h.
h = other_hash.map { |k, v| [v, hash[k]] if hash.has_key?(k) }.compact.to_h
#=> {"dave"=>"popcorn", "linda"=>"soda"}
Also:
h = other_hash.select { |k, v| hash.has_key?(k) }.map { |k, v| [v, hash[k]] }.to_h
hash.map{|k, v| [other_hash[k], v]}.to_h
# => {"dave"=>"popcorn", "linda"=>"soda"}

Add values of an array inside a hash

I created a hash that has an array as a value.
{
"0":[0,14,0,14],
"1":[0,14],
"2":[0,11,0,12],
"3":[0,11,0,12],
"4":[0,10,0,13,0,11],
"5":[0,10,0,14,0,0,0,11,12,0],
"6":[0,0,12],
"7":[],
"8":[0,14,0,12],
"9":[0,14,0,0,11,14],
"10":[0,11,0,12],
"11":[0,13,0,14]
}
I want the sum of all values in each array. I expect to have such output as:
{
"0":[24],
"1":[14],
"2":[23],
"3":[23],
"4":[34],
"5":[47],
"6":[12],
"7":[],
"8":[26],
"9":[39],
"10":[23],
"11":[27]
}
I do not know how to proceed from here. Any pointers are thankful.
I would do something like this:
hash = { "0" => [0,14,0,14], "1" => [0,14], "7" => [] }
hash.each { |k, v| hash[k] = Array(v.reduce(:+)) }
# => { "0" => [28], "1" => [14], "7" => [] }
For hash object you as this one.
hash = {"0"=>[0,14,0,14],"1"=>[0,14],"2"=>[0,11,0,12],"3"=>[0,11,0,12],"4"=>[0,10,0,13,0,11],"5"=>[0,10,0,14,0,0,0,11,12,0],"6"=>[0,0,12],"7"=>[],"8"=>[0,14,0,12],"9"=>[0,14,0,0,11,14],"10"=>[0,11,0,12],"11"=>[0,13,0,14]}
You could change value of each k => v pair
hash.each_pair do |k, v|
hash[k] = [v.reduce(0, :+)]
end
will resolve in
hash = {"0"=>[28], "1"=>[14], "2"=>[23], "3"=>[23], "4"=>[34], "5"=>[47], "6"=>[12], "7"=>[0], "8"=>[26], "9"=>[39], "10"=>[23], "11"=>[27]}
If your string is just like you mentioned you can parse it using JSON, or jump that step if you have already an Hash.
You can check the documentation of inject here
require 'json'
json_string = '
{
"0":[0,14,0,14],
"1":[0,14],
"2":[0,11,0,12],
"3":[0,11,0,12],
"4":[0,10,0,13,0,11],
"5":[0,10,0,14,0,0,0,11,12,0],
"6":[0,0,12],
"7":[],
"8":[0,14,0,12],
"9":[0,14,0,0,11,14],
"10":[0,11,0,12],
"11":[0,13,0,14]
}
'
hash = JSON.parse json_string
result = Hash.new
hash.each do |key, value|
result[key] = value.inject(:+)
end
puts result.inspect
and the result:
{"0"=>28, "1"=>14, "2"=>23, "3"=>23, "4"=>34, "5"=>47, "6"=>12, "7"=>nil, "8"=>26, "9"=>39, "10"=>23, "11"=>27}
Classic example for map/reduce. You need to iterate on the hash keys and values (map {|k,v|}), and for each value count the sum using reduce(0) {|acc, x| acc+x}
h = {1 => [1,2,3], 2 => [3,4,5], 7 => []} #example
a = h.map {|k,v| [k ,v.reduce(0) {|acc, x| acc+x}] }
=> [[1, 6], [2, 12], [7, 0]]
Hash[a]
=> {1=>6, 2=>12, 7=>0}

Convert multidimensional array to array of hashes

I have this data:
input = [ [ 'abc', '1.1' ], [ 'abc', '1.2' ], [ 'xyz', '3.14' ] ]
I would like output like the following:
[ { 'abc' => [ '1.1', '1.2' ] }, { 'xyz' => '3.14' } ]
Is it possible to achieve this in one chained expression?
I'd do it like this:
input = [['abc', '1.1'], ['abc','1.2'], ['xyz', '3.14']]
output = input.each_with_object(Hash.new{ |h, k| h[k] = [] }) { |(k, v), h| h[k] << v }
output # => {"abc"=>["1.1", "1.2"], "xyz"=>["3.14"]}
An alternate, which isn't as straightforward is:
input.group_by{ |k,v| k }.map{ |k, v| [k, v.map(&:last)] }.to_h # => {"abc"=>["1.1", "1.2"], "xyz"=>["3.14"]}
Your output structure
output: [{'abc' => ['1.1', '1.2']}, {'xyz' => '3.14'}]
is a very poor way to use a hash. Instead, you should have one hash, with multiple elements, since you're combining the like-key's values into one key.
If you REALLY need the output that way, as some seem to think, then simply append a map to the returned value:
input.each_with_object(Hash.new{ |h, k| h[k] = [] }) { |(k, v), h| h[k] << v }.map{ |k, v| {k => v} }
# => [{"abc"=>["1.1", "1.2"]}, {"xyz"=>["3.14"]}]
Simplest way I could think of:
input.group_by {|x| x[0]}.each_pair.map { |k, v| {k => (a=v.map(&:last)).count > 1 ? a : a[0]} }
The simplest way to do it is like this:
def convert_marray_to_hash(input)
hash = Hash.new { |hash, key| hash[key] = [] }
output = []
input.each { |array| hash[array[0]] << array[1] }
hash.keys.each { |key| output << { key => hash[key] } }
output
end
There are many ways to do it, but that way presents itself nicely and is readable.
This works pretty well:
input = [ [ 'abc', '1.1' ], [ 'abc','1.2' ], [ 'xyz', '3.14' ] ]
input.each_with_object({}) do |(key, val), hsh|
hsh[key] = val and next unless hsh.key?(key)
hsh[key] = [ *hsh[key], val ]
end
.map {|key, val| { key => val } }
# => [ { "abc" => [ "1.1", "1.2" ] },
# { "xyz" => "3.14" }
# ]
If you omit the final map you end up with a hash, which seems like a more natural result, but this matches the output specified, at least.

Getting a hash from map instead of an array

a = ["ab", "dc", "vv", "dd"]
a.each_with_index.map { |v, i| { ("prefix_" + i.to_s).to_sym => v }}
=> [{:prefix_0=>"ab"}, {:prefix_1=>"dc"}, {:prefix_2=>"vv"}, {:prefix_3=>"dd"}]
I want to get a hash as a return value. How can I do this?
This is one way :
a = ["ab", "dc", "vv", "dd"]
Hash[a.map.with_index { |v, i| ["prefix_#{i}".to_sym, v] }]
# => {:prefix_0=>"ab", :prefix_1=>"dc", :prefix_2=>"vv", :prefix_3=>"dd"}
In Ruby 2.1 >=, use Array#to_h as below :-
a.map.with_index { |v, i| ["prefix_#{i}".to_sym, v] }.to_h
You can chain enumerators together, so another option is something like this:
a.each_with_index.each_with_object({}) { |(v, i), h| h["prefix_#{i}"] = v }

How to map a Ruby hash?

Is there a better way to map a Ruby hash? I want to iterate over each pair and collect the values. Perhaps using tap?
hash = { a:1, b:2 }
output = hash.to_a.map do |one_pair|
k = one_pair.first
v = one_pair.last
"#{ k }=#{ v*2 }"
end
>> [
[0] "a=2",
[1] "b=4"
]
Ruby's hash includes the Enumerable module which includes the map function.
hash = {a:1, b:2}
hash.map { |k, v| "#{k}=#{v * 2}" }
Enumerable#map | RubyDocs
Err, yes, with map, invoked directly on the hash:
{ a:1, b:2 }.map { |k,v| "#{k}=#{v*2}" } # => [ 'a=2', 'b=4' ]
hash = { a:1, b:2 }
hash.map{|k,v| [k,v*2]* '='}
# => ["a=2", "b=4"]

Resources