RUBY: Combining 2 different arrays with duplicate values into hash - arrays

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?

Related

How to convert a ruby hash with an 'array' key to a nested hash in Ruby?

I'm trying to figure out how i can convert a complex hash like this:
{
["A", "B"]=>{"id"=>123,"name"=>"test"},
["A", "F"]=>{"id"=>236,"name"=>"another test"},
["C", "F"]=>{"id"=>238,"name"=>"anoother test"}
}
into an even more complex hash like
{
"A"=>{
"B"=>{"id"=>123,"name"=>"test"},
"F"=>{"id"=>236,"name"=>"another test"}
},
"C"=>{
"F"=>{"id"=>238,"name"=>"anoother test"}
}
}
Any help would be really welcome!
each_with_object could be the rescue:
hash.each_with_object(Hash.new {|h, k| h[k] = {}}) do |((first, last), v), memo|
memo[first].merge!(last => v)
end
#=> {"A"=>{"B"=>{"id"=>123, "name"=>"test"},
# "F"=>{"id"=>236, "name"=>"another test"}},
# "C"=>{"F"=>{"id"=>238, "name"=>"anoother test"}}}
You can also use Enumerable#group_by then Hash#transform_values by Enumerable#map to a new hash using Array#to_h:
h.group_by { |h,k| h.first }.transform_values { |v| v.map { |a, b| [a.last, b] }.to_h }

ruby: find item with most occurrences in array, if there is

With this code I can find most occurrences of items in an array:
letters.max_by { |i| letters.count(i) }
But this will return 2 for
a = [1, 2, 2, 3, 3]
although 3 has the same occurrence. How can I find out, if there really is an item with most occurrences? I would like to get false if there is no single champion.
This is pretty ugly and in need of refinement, but:
def champion(array)
grouped = array.group_by(&:itself).values.group_by(&:length)
best = grouped[grouped.keys.max]
if (best.length == 1)
best[0][0]
else
false
end
end
I'm not sure there's an easy single-shot solution for this, at least not one that's not O(n^2) or worse, which is unusual.
I guess you could do this if you don't care about performance:
def max_occurrences(arr)
arr.sort.max_by { |v| arr.count(v) } != arr.sort.reverse.max_by { |v| arr.count(v) } ? false : arr.max_by { |v| arr.count(v) }
end
I would do something like this:
def max_occurrences(arr)
counts = Hash.new { |h, k| h[k] = 0 }
grouped_by_count = Hash.new { |h, k| h[k] = [] }
arr.each { |el| counts[el] += 1 } # O(n)
counts.each { |el, count| grouped_by_count[count] << el } # O(n)
max = grouped_by_count.sort { |x, y| y[0] <=> x[0] }.first[1] # O(n log n)
max.length == 1 ? max[0] : false
end
It's no snazzy one-liner, but it's readable and runs in less than O(n log n).
a = [1, 2, 2, 3, 3]
occurrences = a.inject(Hash.new(0)){ |h, el| h[el] += 1; h } # => {1=>1, 2=>2, 3=>2}
max_occurences = occurrences.max_by{ |_, v| v } # => [2, 2]
max_occurences.count > 1 ? false : occurrences.key(max_occurences.first)

Recursively setting hash keys from an array of keys

I want a function that can take an array like [:a, :b, :c] and recursively set hash keys, creating what it needs as it goes.
hash = {}
hash_setter(hash, [:a, :b, :c], 'value')
hash #=> {:a => {:b => {:c => 'value' } } }
hash_setter(hash, [:a, :b, :h], 'value2')
hash #=> {:a => {:b => {:c => 'value', :h => 'value2' } } }
I'm aware that Ruby 2.3's dig can be used for getting in this way, though that doesnt quite get you to an answer. If there was a setter equivalent of dig that'd be what I'm looking for.
Code
def nested_hash(keys, v, h={})
return subhash(keys, v) if h.empty?
return h.merge(subhash(keys, v)) if keys.size == 1
keys[0..-2].reduce(h) { |g,k| g[k] }.update(keys[-1]=>v)
h
end
def subhash(keys, v)
*first_keys, last_key = keys
h = { last_key=>v }
return h if first_keys.empty?
first_keys.reverse_each.reduce(h) { |g,k| g = { k=>g } }
end
Examples
h = nested_hash([:a, :b, :c], 14) #=> {:a=>{:b=>{:c=>14}}}
i = nested_hash([:a, :b, :d], 25, h) #=> {:a=>{:b=>{:c=>14, :d=>25}}}
j = nested_hash([:a, :b, :d], 99, i) #=> {:a=>{:b=>{:c=>14, :d=>99}}}
k = nested_hash([:a, :e], 104, j) #=> {:a=>{:b=>{:c=>14, :d=>99}, :e=>104}}
nested_hash([:f], 222, k) #=> {:a=>{:b=>{:c=>14, :d=>99}, :e=>104}, :f=>222}
Observe that the value of :d is overridden in the calculation of j. Also note that:
subhash([:a, :b, :c], 12)
#=> {:a=>{:b=>{:c=>12}}}
This mutates the hash h. If that is not desired one could insert the line
f = Marshal.load(Marshal.dump(h))
after the line return subhash(keys, v) if h.empty? and change subsequent references to h to f. Methods from the Marshal module can be used to create a deep copy of a hash so the original hash is not be mutated.
Solved it with recursion:
def hash_setter(hash, key_arr, val)
key = key_arr.shift
hash[key] = {} unless hash[key].is_a?(Hash)
key_arr.length > 0 ? hash_setter(hash[key], key_arr, val) : hash[key] = val
end
def set_value_for_keypath(initial, keypath, value)
temp = initial
for key in keypath.first(keypath.count - 1)
temp = (temp[key] ||= {})
end
temp[keypath.last] = value
return initial
end
initial = {:a => {:b => {:c => 'value' } } }
set_value_for_keypath(initial, [:a, :b, :h], 'value2')
initial
Or if you prefer something more unreadable:
def set_value_for_keypath(initial, keypath, value)
keypath.first(keypath.count - 1).reduce(initial) { |hash, key| hash[key] ||= {} }[keypath.last] = value
end

Ruby Hash Values is Array, need to convert to string

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)

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