Converting array of stringified key value pairs to hash in Ruby - arrays

I have some key-value pair strings in an array:
array = [ "Name = abc", "Id = 123", "Interest = Rock Climbing" ]
I need to convert it to a hash:
hash = { "Name" => "abc", "Id" => "123", "Interest" => "Rock Climbing" }
I must be doing something wrong because I'm getting weird mappings with my .shift.split resulting in {"Name=abc"=>"Id=123"}.

All you need to do is split each part of the array into a key and value (yielding an array of two-element arrays) and then pass the result to the handy Hash[] method:
arr = [ "Name = abc", "Id = 123", "Interest = Rock Climbing" ]
keys_values = arr.map {|item| item.split /\s*=\s*/ }
# => [ [ "Name", "abc" ],
# [ "Id", "123" ],
# [ "Interest", "Rock Climbing" ] ]
hsh = Hash[keys_values]
# => { "Name" => "abc",
# "Id" => "123",
# "Interest" => "Rock Climbing" }

You can do it this way (using Enumerable#each_with_object):
array.each_with_object({}) do |a, hash|
key,value = a.split(/\s*=\s*/) # splitting the array items into key and value
hash[key] = value # storing key => value pairs in the hash
end
# => {"Name"=>"abc", "Id"=>"123", "Interest"=>"Rock Climbing"}
If you find it little difficult to understand the each_with_object, you can do it in a naive way (Just accumulating the key and values in the result_hash):
result_hash = {}
array.each do |a|
key,value = a.split(/\s*=\s*/) # splitting the array items into key and value
result_hash[key] = value # storing key => value pairs in the result_hash
end
result_hash
# => {"Name"=>"abc", "Id"=>"123", "Interest"=>"Rock Climbing"}

Try this
array.map {|s| s.split('=')}.to_h
=> {"Name "=>" abc", "Id "=>" 123", "Interest "=>" Rock Climbing"}

array.each_with_object({}) { |s,h| h.update([s.split(/\s*=\s*/)].to_h) }
#=> {"Name"=>"abc", "Id"=>"123", "Interest"=>"Rock Climbing"}
For Ruby versions prior to 2.0 (when Array#to_h was introduced) replace [s.split(/\s*=\s*/)].h with Hash[[s.split(/\s*=\s*/)]].
The steps:
enum = array.each_with_object({})
#=> #<Enumerator: ["Name = abc", "Id = 123",
# "Interest = Rock Climbing"]:each_with_object({})>
We can see the elements of this enumerator by converting it to an array:
enum.to_a
#=> [["Name = abc", {}], ["Id = 123", {}], ["Interest = Rock Climbing", {}]]
The first element of enum is passed to the block, the block variables are assigned:
s,h = enum.next
#=> ["Name = abc", {}]
s #=> "Name = abc"
h #=> {}
and the block calculation is performed:
h.update([s.split(/\s*=\s*/)].to_h)
#=> h.update([["Name", "abc"]].to_h)
# {}.update({"Name"=>"abc"})
# {"Name"=>"abc"}
which is the updated value of h.
The next element of enum passed to the block is:
s,h = enum.next
#=> ["Id = 123", {"Name"=>"abc"}]
s #=> "Id = 123"
h #=> {"Name"=>"abc"}
h.update([s.split(/\s*=\s*/)].to_h)
#=> {"Name"=>"abc", "Id"=>"123"}
and so on.

Related

How to convert array like this ["John,Doe,11222019", "Mark,King,11232019", "Angle,Darma,11242019"] to Array of hash Ruby

How to convert array:
["John,Doe,11222019", "Mark,King,11232019", "Angle,Darma,11242019"]
to Array of hash like this using Ruby :
[
{ :name => "John Doe", :number => 11222019 },
{ :name => "Mark King", :number => 11232019 },
{ :name => "Angle Darma", :number => 11242019 },
]
Thank you very much!
You can do it simply as below,
array.map { |x| [:name, :number].zip(x.sub(',', ' ').split(',')).to_h }
# => [{:name=>"John Doe", :number=>11222019}, {:name=>"Mark King", :number=>11232019}, {:name=>"Angle Darma", :number=>11242019}]
Option using Ruby 2.6.1 Object#then:
ary = ["John,Doe,11222019", "Mark,King,11232019", "Angle,Darma,11242019"]
ary.map { |s| s.split(',').then{ |a| {name: a.first(2).join(' ') , number: a.last.to_i } } }
For Ruby 2.5.2 Object#yield_self:
ary.map { |s| s.split(',').yield_self{ |a| {name: a.first(2).join(' ') , number: a.last.to_i } } }
Both returning:
#=> [{:name=>"John Doe", :number=>11222019}, {:name=>"Mark King", :number=>11232019}, {:name=>"Angle Darma", :number=>11242019}]
arr = ["John,Doe,11222019", "Mark,King,11232019",
"Angle,Darma,11242019"]
arr.map do |s|
{name: s[/.+(?=,)/].tr(',',' '), number: s[/\d+/].to_i}
end
#=> [{:name=>"John Doe", :number=>11222019},
# {:name=>"Mark King", :number=>11232019},
# {:name=>"Angle Darma", :number=>11242019}]
The steps are as follows. Enumerable#map passes the first element of arr to the block and the block variable s is set equal to its value:
s = arr.first
#=> "John,Doe,11222019"
The block calculations are then performed:
a = s[/.+(?=,)/]
#=> "John,Doe"
This uses the method String#[] with the regular expression /.+(?=,)/. (?=,) is a positive lookahead that requires the match to be followed immediately by a comma. Because matches are by default greedy the lookahead matches the last comma in s.
b = a.tr(',',' ')
#=> "John Doe"
This uses the method String#tr. Alternatively, a.sub(',',' ') could be used.
c = s[/\d+/]
#=> "11222019"
d = c.to_i
#=> 11222019
The block then returns:
{ name: c, number: d }
#=> {:name=>"11222019", :number=>11222019}
which is the object to which s is mapped. The remaining two values of arr are passed to the block and similar calculations are performed.
a = ["John,Doe,11222019", "Mark,King,11232019", "Angle,Darma,11242019"]
Something like this
a.map do |f|
f = f.split(',')
{ name: "#{f[0]} #{f[1]}", number: f[2].to_i }
end
arr = ["John,Doe,11222019", "Mark,King,11232019", "Angle,Darma,11242019"]
arr.map do |item|
chunks = item.split(",")
{name: chunks[0...-1].join(" "), number: chunks[-1]}
end
Indexing by [0...-1] allows you to have variable number of items in the name part (middle name, or 2 piece last names) which is pretty common.

Ruby: How can I convert this array into this hash?

I have an array of arrays. Each item in the array contains three strings: a leg count, an animal and a sound.
a = [ ['4', 'dog', 'woof'] , ['4', 'cow', 'moo'], ['2', 'human', 'yo'] , ['2', 'yeti', 'wrarghh'] ]
I want to turn the array into this hash:
{
'2' => [ { 'human' => 'yo' }, { 'yeti' => 'wrarghh'} ],
'4' => [ { 'dog' => 'woof' }, { 'cow' => 'moo'} ]
}
I thought reduce would be the way to go but I'm not having much luck. My current stab looks like:
a.reduce({}) do |acc, item|
acc[item.first] = [] unless acc.key? item.first
acc[item.first] << { item[1] => item[2] }
end
But it gets an error:
NoMethodError: undefined method `key?' for [{"dog"=>"woof"}]:Array
What is the best way to achieve this?
a.each_with_object({}) { |(kout, kin, val), h| (h[kout] ||= []) << { kin => val } }
#=> {"4"=>[{"dog"=>"woof"}, {"cow"=>"moo"}], "2"=>[{"man"=>"yo"}, {"yeti"=>"wrarghh"}]}
We have
enum = a.each_with_object({})
#=> #<Enumerator: [["4", "dog", "woof"], ["4", "cow", "moo"], ["2", "man", "yo"],
# ["2", "yeti", "wrarghh"]]:each_with_object({})>
The first value is generated by this enumerator and passed to the block, and the block variables are assigned values:
(kout, kin, val), h = enum.next
#=> [["4", "dog", "woof"], {}]
which is decomposed as follows.
kout
#=> "4"
kin
#=> "dog"
val
#=> "woof"
h #=> {}
The block calculation is therefore
(h[kout] ||= []) << { kin => val }
#=> (h[kout] = h[kout] || []) << { "dog" => "wolf" }
#=> (h["4"] = h["4"] || []) << { "dog" => "wolf" }
#=> (h["4"] = nil ||= []) << { "dog" => "wolf" }
#=> (h["4"] = []) << { "dog" => "wolf" }
#=> [] << { "dog" => "wolf" }
#=> [{ "dog" => "wolf" }]
h["4"] || [] #=> [] since h has no key "4" and therefore h["4"] #=> nil.
The next value of enum is passed to the block and the calculations are repeated.
(kout, kin, val), h = enum.next
#=> [["4", "cow", "moo"], {"4"=>[{"dog"=>"woof"}]}]
kout
#=> "4"
kin
#=> "cow"
val
#=> "moo"
h #=> {"4"=>[{"dog"=>"woof"}]}
(h[kout] ||= []) << { kin => val }
#=> (h[kout] = h[kout] || []) << { "cow" => "moo" }
#=> (h["4"] = h["4"] || []) << { "cow" => "moo" }
#=> (h["4"] = [{"dog"=>"woof"}] ||= []) << { "cow" => "moo" }
#=> (h["4"] = [{"dog"=>"woof"}]) << { "cow" => "moo" }
#=> [{"dog"=>"woof"}] << { "cow" => "moo" }
#=> [{ "dog" => "wolf" }, { "cow" => "moo" }]
This time h["4"] || [] #=> [{ "dog" => "wolf" }] because h now has a key "4" with a truthy value ([{ "dog" => "wolf" }]).
The remaining calculations are similar.
You way works, but, for reduce, the return value (ie, the last line) of the block becomes the next value for (in this case) acc, so all you need to change is:
a.reduce({}) do |acc, item|
acc[item.first] = [] unless acc.key? item.first
acc[item.first] << { item[1] => item[2] }
acc # just add this line
end
Since the return value for Array#<< is the array itself, the second iteration gave acc as the array for the first element. There are, of course, lots of ways to do this, some arguably cleaner, but I find it's useful to know where I went wrong when something I think should work doesn't.

Merging hashes into arrays

I'm new in Ruby. I have two arrays of hashes
arr1 = [{"one"=> {"1"=> "a", "2" => "b"}, "two" => {"3" => "n", "5" => "h", "7" => "k"}]
arr2 = [{"one"=> {"8"=> "f", "11" => "r"}, "two" => {"7" => "o", "6" => "b", "14" => "b"}]
and I want to have one array like this:
arr3 = [{
"one"=> {"1"=> "a", "2" => "b", "8"=> "f", "11" => "r"},
"two" => {3" => 'n", "5" => "h", "7" => "k", 7" => 'o", "6" => "b", "14" => "b"}
]
so I want to merge hashes by keys and "add" their values. Can anyone help?
arr1 = [{"one"=>{"1"=>"a", "2"=>"b"}, "two"=>{"3"=>"n", "5"=>"h", "7"=>"k"}}]
arr2 = [{"one"=>{"8"=>"f", "11"=>"r"}, "two"=>{"7"=>"o", "6"=>"b", "14"=>"b"}}]
(arr1+arr2).each_with_object({}) { |g,h| h.update(g) { |_,o,n| o.merge(n) } }
# => {"one"=>{"1"=>"a", "2"=>"b", "8"=>"f", "11"=>"r"},
# "two"=>{"3"=>"n", "5"=>"h", "7"=>"o", "6"=>"b", "14"=>"b"}}
This uses the form of Hash#update (aka merge!) that uses a block ({ |_k,o,n| o.merge(n) }) to determine the value of the key _k when both hashes being merged have that key. (_ in _k tells the reader that that block variable is not used in the block calculation.) o and n are the values of that key in h and g respectively.
For each key k equal to "one" or "two", if the values (hashes) of arr1.first[k] and arr2.first[k] have a common key l, the merge operation will cause the value of l in arr1 will be overwritten by the value of l in arr2. If, for example, arr1.first["one"] #=> {"1"=>"a", "2"=>"b"} and arr2.first["one"] #=> {"8"=>"f", "2"=>"r"}, the merge will return {"1"=>"a", "2"=>"r", "8"=>"f"}
Even though arr1 and arr2 each contain a single element (a hash), the code above works fine when the arrays contain multiple hashes, and when there are more than two arrays. If the arrays always contain a single hash, the arrays serve no purpose and we might instead just reference the hashes:
h1 = {"one"=>{"1"=>"a", "2"=>"b"}, "two"=>{"3"=>"n", "5"=>"h", "7"=>"k"}}
h2 = {"one"=>{"8"=>"f", "11"=>"r"}, "two"=>{"7"=>"o", "6"=>"b", "14"=>"b"}}
and replace arr1+arr2 with [h1+h2].
Maybe not the most elegant but this works:
arr1 = [{"one"=>{"1"=>"a", "2"=>"b"}, "two"=>{"3"=>"n", "5"=>"h", "7"=>"k"}}]
arr2 = [{"one"=>{"8"=>"f", "11"=>"r"}, "two"=>{"7"=>"o", "6"=>"b", "14"=>"b"}}]
arr3 = []
arr1[0].each_key{|k| arr3<< {k => arr1[0][k].merge(arr2[0][k])}}
arr3
If you don't know how many hashes your original array will contain, simply replace arr1[0].each_key with arr1.each_index{|i| arr1[i].each_key and replace 0 with i in the merge.

How to merge two arrays of hashes

I have two arrays of hashes:
a = [
{
key: 1,
value: "foo"
},
{
key: 2,
value: "baz"
}
]
b = [
{
key: 1,
value: "bar"
},
{
key: 1000,
value: "something"
}
]
I want to merge them into one array of hashes, so essentially a + b except I want any duplicated key in b to overwrite those in a. In this case, both a and b contain a key 1 and I want the final result to have b's key value pair.
Here's the expected result:
expected = [
{
key: 1,
value: "bar"
},
{
key: 2,
value: "baz"
},
{
key: 1000,
value: "something"
}
]
I got it to work but I was wondering if there's a less wordy way of doing this:
hash_result = {}
a.each do |item|
hash_result[item[:key]] = item[:value]
end
b.each do |item|
hash_result[item[:key]] = item[:value]
end
result = []
hash_result.each do |k,v|
result << {:key => k, :value => v}
end
puts result
puts expected == result # prints true
uniq would work if you concatenate the arrays in reverse order:
(b + a).uniq { |h| h[:key] }
#=> [
# {:key=>1, :value=>"bar"},
# {:key=>1000, :value=>"something"},
# {:key=>2, :value=>"baz"}
# ]
It doesn't however preserve the order.
[a, b].map { |arr| arr.group_by { |e| e[:key] } }
.reduce(&:merge)
.flat_map(&:last)
Here we use hash[:key] as a key to build the new hash, then we merge them overriding everything with the last value and return values.
I would rebuild your data a bit, since there are redundant keys in hashes:
thin_b = b.map { |h| [h[:key], h[:value]] }.to_h
#=> {1=>"bar", 1000=>"something"}
thin_a = b.map { |h| [h[:key], h[:value]] }.to_h
#=> {1=>"bar", 1000=>"something"}
Then you can use just Hash#merge:
thin_a.merge(thin_b)
#=> {1=>"bar", 2=>"baz", 1000=>"something"}
But, if you want, you can get exactly result as mentioned in question:
result.map { |k, v| { key: k, value: v } }
#=> [{:key=>1, :value=>"bar"},
# {:key=>2, :value=>"baz"},
# {:key=>1000, :value=>"something"}]
using Enumerable#group_by and Enumerable#map
(b+a).group_by { |e| e[:key] }.values.map {|arr| arr.first}
If you need to merge two arrays of hashes that should be merged also and there is more than two keys, then next snippet should help:
[a, b].flatten
.compact
.group_by { |v| v[:key] }
.values
.map { |e| e.reduce(&:merge) }

Creating hash from array adding new keys

I have an array looking like this:
data =[[01, 777], [02, 888]]
Now I want to create a hash from it like below:
n_clip = [{"name"=>"01", "rep"=>"777"},{"name"=>"02", rep=>"888"}]
I tried to do this in that way:
n_clip = []
data.each do |a|
n_clip << Array[Hash[a.map {|| ["name", a.first]}], Hash[a.map {|| ["rep", a.last]}]]
end
but it doesn't work because I get:
n_clip = [[{"name"=>"01"},{"rep"="777"}], [{"name"=>"01"},{"rep"="777"}]]
and definitively it isn't what I expected.
data.map { |arr| { 'name' => arr[0], 'rep' => arr[1] } }
i would rather use symbols as hash keys
data.map { |arr| { name: arr[0], rep: arr[1] } }
If you wish to create an array of two hashes, each having the same two keys, the other answers are fine. The following handles the case where there are an arbitrary number of keys and data may contain an arbitrary number of elements.
def hashify(keys, arr_of_vals)
[keys].product(arr_of_vals).map { |ak,av| Hash[ak.zip(av)] }
end
keys = %w| name rep |
#=> ["name", "rep"]
arr_of_vals = [["01", "777"], ["02", "888"]]
hashify(keys, arr_of_vals)
#=> [{"name"=>"01", "rep"=>"777"}, {"name"=>"02", "rep"=>"888"}]
In your problem arr_of_vals must first be derived from [[1, 777], [02, 888]], but that is a secondary (rather mundane) problem that I will not address.
Another example:
keys = %w| name rep group |
#=> ["name", "rep", "group"]
arr_of_vals = [[1, 777, 51], [2, 888, 52], [1, 2, 53], [3, 4, 54]]
hashify(keys, arr_of_vals)
#=> [{"name"=>1, "rep"=>777, "group"=>51}, {"name"=>2, "rep"=>888, "group"=>52},
# {"name"=>1, "rep"=>2, "group"=>53}, {"name"=>3, "rep"=>4, "group"=>54}]
data.map { |name, rep| { 'name' => name.to_s, 'rep' => rep.to_s } }

Resources