Ruby Hash Values is Array, need to convert to string - arrays

I have a hash of integers as keys and arrays of strings as values. I need to convert this to a new hash that inverts this relationship with each item from the array of strings in the original hash values becoming a key in the new hash and each original key becoming the associated value. For example:
original = {1 => ['a', 'b', 'c'], 2 => ['g', 'm', 'z']}
new_hash = {'a' => 1, 'b' => 1, 'c' => 1, 'g' => 2, 'm' => 2, 'z' => 2}
I'm struggling to extract the items from the original array values. It's easy enough to do
original.each { |k, v| new_hash[v] = k }
but this keeps the original array as the new key. I've tried doing something like
original.each { |k, v| new_hash[v.each { |i| i }] = k }
but this also returns the original array for some reason.

Another one, via Array#product:
original.flat_map { |k, v| v.product([k]) }.to_h
#=> {"a"=>1, "b"=>1, "c"=>1, "g"=>2, "m"=>2, "z"=>2}

original.flat_map { |k, vs| vs.map { |v| {v => k} } }.reduce(&:merge)

the below snippet will give what you want, but let me think on a more readable and elegant solution.
newhash = {}
original.each do |k,v|
v.each do |v2|
newhash[v2] = k
end
end
#=> {1=>["a", "b", "c"], 2=>["g", "m", "z"]}
newhash
#=> {"a"=>1, "b"=>1, "c"=>1, "g"=>2, "m"=>2, "z"=>2}

Your approach is close. You'll have to iterate each element in the values array when assigning the new key/value pair to the newHash
newHash = {}
original.each { |k, v| v.each {|i| newHash[i] = k}}

original.map { |number, ary| Hash[ary.map { |char| [char, number] }] }.reduce(&:merge)

Related

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) }

RUBY: Combining 2 different arrays with duplicate values into hash

I have 2 Arrays.
product_name = ["Pomegranate", "Raspberry", "Miracle fruit", "Raspberry"]
product_quantity = [2, 4, 5, 5]
I'd like to know how to initialize a hash such that it becomes
product_hash = {"Pomegranate"=>2, "Raspberry"=>9, "Miracle fruit"=>5}
Use each_with_object:
product_name.zip(product_quantity)
.each_with_object({}) {|(k, v), h| h[k] ? h[k] += v : h[k] = v }
#=> {"Pomegranate"=>2, "Raspberry"=>9, "Miracle fruit"=>5}
Or just use hash with default value:
product_name.zip(product_quantity)
.each_with_object(Hash.new(0)) {|(k, v), h| h[k] += v }
#=> {"Pomegranate"=>2, "Raspberry"=>9, "Miracle fruit"=>5}
I would start with something like this:
product_name.zip(product_quantity)
.group_by(&:first)
.map { |k, v| [k, v.map(&:last).inject(:+)] }
.to_h
#=> { "Pomegranate" => 2, "Raspberry" => 9, "Miracle fruit" => 5}
I suggest to lookup each method in the Ruby's docs for Array and Hash and to check in the console what each the intermediate step returns.
This is but a slight variation of #llya's solution #2.
product_name.each_index.with_object(Hash.new(0)) { |i,h|
h[product_name[i]] += h[product_quantity[i]] } .
Couldn't we just do:
product_name.zip(product_quantity).to_h
Seems to return the correct result for me?

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 } }

Can I iterate through an array of arrays and compare it to an array of integers

I have an array of arrays [[1,2,3],[4,5,6],[7,8,9]]. I also have an array of integers [3,4,5,6,8].
Is it possible for me to check if my integers match a complete array in the array of arrays?
So I have 4,5,6 in the int array, and it matches the middle array [4,5,6].
This should work
a = [[1,2,3],[4,5,6],[7,8,9]]
integers = [3,4,5,6,8]
a.any? { |sub_array| sub_array.all? { |item| integers.include? item } }
Try this:
array_1 = [[1,2,3],[4,5,6],[7,8,9]]
array_2 = [3,4,5,6,8]
array_1.any? { |e| (e - array_2).empty? }
# => true
array1 = [[1,2,3],[4,5,6],[7,8,9]]
array2 = [4,5,6]
result = array1.map{|inner_array| inner_array - array2}
# => [[1, 2, 3], [], [7, 8, 9]]
result.any?{|inner_array| inner_array.empty?}
# => true
Assuming you expect a true or false and order doesn't matter, the following works:
require 'set'
a1 = [[1,2,3],[4,5,6],[7,8,9]]
a2 = [3,4,5,6,8]
a1.any? { |item| item.to_set.subset? a2.to_set } #=> true
Assuming you want the index into a1 or nil
a1.index { |item| item.to_set.subset? a2.to_set }
Assuming you want the subset itself or nil
index = a1.index { |item| item.to_set.subset? a2.to_set }
index && a1[index]

Array to Hash with Ruby

I am trying to convert this array:
["dog", 5 , "big house"]
to hash:
{"dog" => 3 , 5 => 25, "big house" => 9}
The value will be the number of characters of the string (key).
If it's an integer (key), then the value will be to the power of 2.
This is what I got so far, but it only converts the string (key):
h = {}
arr.each do |x,y|
y = x.length
h[x] = y
end
▶ arr = ["dog", 5, "big house"]
#⇒ [ "dog", 5, "big house" ]
▶ arr.zip(arr.map do |e|
▷ case e
▷ when String then e.length
▷ when Integer then e ** 2
▷ else raise 'Neither string nor numeric given'
▷ end
▷ end).to_h
#⇒ { "dog" => 3, 5 => 25, "big house" => 9 }
You could use the Enumberable#each_with_object method like so:
array = ["dog",5,"big house"]
array.each_with_object({}) {|x,hash| x.is_a?(String) ? hash[x] = x.length : hash[x] = x**2}
# => {"dog"=>3,5=>25,"big house"=>9}
The each_with_object method is very similar to the inject method, so it'll iterate through the array, then once completed it'll return the newly given object. The difference between each_with_object and inject is that each_with_object is immutable, so you can't do something like:
(1..5).each_with_object(0) {|num,sum| sum += num}
It'll only return a zero.
You can use the Hash[...] constructor to convert an array of [key, value] pairs to a Hash. So here's another option:
arr = ["dog", 5, "big house"]
result = Hash[ arr.map { |e| [e, e.to_i == e ? e**2 : e.length] } ]
# => {"dog"=>3, 5=>25, "big house"=>9}
Since Ruby 2, you can use Array#to_h to do the same thing:
result = arr.map { |e| [e, e.to_i == e ? e**2 : e.length] }.to_h
# => {"dog"=>3, 5=>25, "big house"=>9}
If you have the following array:
arr = ["dog", 5, "big house"]
First you can create a method to convert elements into your desired format based on the element's class:
def convert_to_hash_val(element)
case element
when Fixnum
element**2
else
element.size
end
end
Then create an array of [element, converted_element] pairs and use the Hash#[] function to convert this to key, value pairs:
Hash[ arr.map {|element| [element, convert_to_hash_val(element)]} ]
# => { "dog" => 3, 5 => 25, "big house" => 9 }
You can use inject method.
is_a? method can judge the element is String or Integer.
["dog", 2 , "big house"].inject({}) do |sum, e|
if e.is_a? Integer
sum.merge({e => e*e})
elsif e.is_a? String
sum.merge({e => e.length})
end
end
=> {"dog"=>3, 2=>4, "big house"=>9}
as #Myst said, h[:key] = value has better performance than merge, so you also can do like this:
["dog", 2 , "big house"].inject({}) do |sum, e|
if e.is_a? Integer
sum[e] = e*e
elsif e.is_a? String
sum[e] = e.length
end
sum
end
arr= ["dog", 5, "big house"]
two_dimensional_array = arr.zip(arr.map do |e|
case e
when String then e.length
when Integer then e ** 2
else raise 'Neither string nor numeric given'
end
end)
my_hash = Hash[*two_dimensional_array.flatten]
result_hash = {}
arr = ["dog", 2, "big house"]
arr.each do |el|
if el.is_a?(String)
result_hash[el] = el.length
elsif el.is_a?(Fixnum)
result_hash[el] = el ** 2
end
end
Remember that the each block called on an array only throws one argument into the block, which is the currently iterated element.

Resources