How to insert a hash inside a hash ruby - ruby

I was wondering how I could insert a hash into another hash. For example, in:
{"abcd"=>{}, "hgfe"=>34567}
I want to put "hgfe" => 34567 into the "abcd" key.
output:
{"abcd"=>{"hgfe" => 34567}}
im wanting to convert this hash
"##### RUBY HASH ####
(1)
INPUT
{
'abcd.hgfe' => 34567,
'abcd.efgh.hijk' => 12345,
'abcd.efgh.ijkl' => 56789,
'wxyz.abcd' => 9876,
'wxyz.uvwx.abcd' => 23456,
}
(1)
OUTPUT
{
'abcd' => {
'efgh' => {
'hijk' => 12345,
'ijkl' => 56789
},
'hgfe' => 34567,
},
'wxyz' => {
'abcd' => 9876,
'uvwx' => {'abcd' => 23456}
}
}
"
my currrent code:
def method1(hash)
result = {}
array2 = []
hash.each_pair do|k, v|
array1 = k.split('.')
count = array1.length
hash2 = {}
array1.each_with_index do |str, index|
if (index + 1) == count
hash2[str] = v
else
hash2[str] = {}
end
end
puts hash2.inspect
puts "--------------"
end
result
end
hash_result = method1(h2c)

Do as below
hash = {"abcd"=>{}, "hgfe"=>34567}
hash['abcd']['hgfe'] = hash.delete('hgfe')
hash # => {"abcd"=>{"hgfe"=>34567}}
You can write something like below :
def delete_key_and_add_to_another_key(hash, update_key, del_key)
hash[update_key][del_key] = hash.delete(del_key)
hash
end
hash = {"abcd"=>{}, "hgfe"=>34567}
delete_key_and_add_to_another_key(hash, 'abcd', 'hgfe')

h = {"abcd"=>{}, "hgfe"=>34567}
f, l = h.partition { |_,v| v =={} }.flatten(1)
{ f.first=> { l.first => l.last } }
#=> {"abcd"=>{"hgfe"=>34567}}

Related

Array as key for a Hash. How to access values, how to format properly?

t = {
"Bachelor" => [1,2,3],
"Master" => [4,5,6]
}
r = t.invert
=> {[1, 2, 3]=>"Bachelor", [4, 5, 6]=>"Master"}
r[1]
=> nil
My question is, how do I access the value for key 1,2 etc in the example mentioned?
How to convert this to: {"1"=> "Bachelor","2"=> "Bachelor"..} etc?
res = t.each_with_object({}) do |(key, vals), res|
vals.each do |val|
res[val] = key
end
end
res[1]
=> "Bachelor"
res
=> {1=>"Bachelor", 2=>"Bachelor", 3=>"Bachelor", 4=>"Master", 5=>"Master", 6=>"Master"}
You could give your hash a default procedure which runs whenever the key can't be found:
t = {
"Bachelor" => [1,2,3],
"Master" => [4,5,6]
}
r = t.invert
r.default_proc = proc do |hash, key|
hash.find{|k,v| k.include?(key) }.last
end
#demo:
puts r[5] # => Master
Another useful mechanism which does not use a hash is a case when construct:
num=5
title = case num
when 1..3 then "Bachelor"
when 4..6 then "Master"
end
You could create a new hash by iterating through the array keys of t.invert:
h = t.invert.map do |k,v|
k.map {|kk| {kk => v} } }.flatten.inject(:merge)
=> {1=>"Bachelor", 2=>"Bachelor", 3=>"Bachelor", 4=>"Master", 5=>"Master", 6=>"Master"}
h[1]
=> "Bachelor"
res = {}
t.each { |key, values| values.inject(res) { |h, i| h[i] = key; h }}
res[1]
=> "Bachelor"
res
=> {1=>"Bachelor", 2=>"Bachelor", 3=>"Bachelor", 4=>"Master", 5=>"Master", 6=>"Master"}

How to find the largest value of a hash in an array of hashes

In my array, I'm trying to retrieve the key with the largest value of "value_2", so in this case, "B":
myArray = [
"A" => {
"value_1" => 30,
"value_2" => 240
},
"B" => {
"value_1" => 40,
"value_2" => 250
},
"C" => {
"value_1" => 18,
"value_2" => 60
}
]
myArray.each do |array_hash|
array_hash.each do |key, value|
if value["value_2"] == array_hash.values.max
puts key
end
end
end
I get the error:
"comparison of Hash with Hash failed (ArgumentError)".
What am I missing?
Though equivalent, the array given in the question is generally written:
arr = [{ "A" => { "value_1" => 30, "value_2" => 240 } },
{ "B" => { "value_1" => 40, "value_2" => 250 } },
{ "C" => { "value_1" => 18, "value_2" => 60 } }]
We can find the desired key as follows:
arr.max_by { |h| h.values.first["value_2"] }.keys.first
#=> "B"
See Enumerable#max_by. The steps are:
g = arr.max_by { |h| h.values.first["value_2"] }
#=> {"B"=>{"value_1"=>40, "value_2"=>250}}
a = g.keys
#=> ["B"]
a.first
#=> "B"
In calculating g, for
h = arr[0]
#=> {"A"=>{"value_1"=>30, "value_2"=>240}}
the block calculation is
a = h.values
#=> [{"value_1"=>30, "value_2"=>240}]
b = a.first
#=> {"value_1"=>30, "value_2"=>240}
b["value_2"]
#=> 240
Suppose now arr is as follows:
arr << { "D" => { "value_1" => 23, "value_2" => 250 } }
#=> [{"A"=>{"value_1"=>30, "value_2"=>240}},
# {"B"=>{"value_1"=>40, "value_2"=>250}},
# {"C"=>{"value_1"=>18, "value_2"=>60}},
# {"D"=>{"value_1"=>23, "value_2"=>250}}]
and we wish to return an array of all keys for which the value of "value_2" is maximum (["B", "D"]). We can obtain that as follows.
max_val = arr.map { |h| h.values.first["value_2"] }.max
#=> 250
arr.select { |h| h.values.first["value_2"] == max_val }.flat_map(&:keys)
#=> ["B", "D"]
flat_map(&:keys) is shorthand for:
flat_map { |h| h.keys }
which returns the same array as:
map { |h| h.keys.first }
See Enumerable#flat_map.
Code
p myArray.pop.max_by{|k,v|v["value_2"]}.first
Output
"B"
I'd use:
my_array = [
"A" => {
"value_1" => 30,
"value_2" => 240
},
"B" => {
"value_1" => 40,
"value_2" => 250
},
"C" => {
"value_1" => 18,
"value_2" => 60
}
]
h = Hash[*my_array]
# => {"A"=>{"value_1"=>30, "value_2"=>240},
# "B"=>{"value_1"=>40, "value_2"=>250},
# "C"=>{"value_1"=>18, "value_2"=>60}}
k = h.max_by { |k, v| v['value_2'] }.first # => "B"
Hash[*my_array] takes the array of hashes and turns it into a single hash. Then max_by will iterate each key/value pair, returning an array containing the key value "B" and the sub-hash, making it easy to grab the key using first:
k = h.max_by { |k, v| v['value_2'] } # => ["B", {"value_1"=>40, "value_2"=>250}]
I guess the idea of your solution is looping through each hash element and compare the found minimum value with hash["value_2"].
But you are getting an error at
if value["value_2"] == array_hash.values.max
Because the array_hash.values is still a hash
{"A"=>{"value_1"=>30, "value_2"=>240}}.values.max
#=> {"value_1"=>30, "value_2"=>240}
It should be like this:
max = nil
max_key = ""
myArray.each do |array_hash|
array_hash.each do |key, value|
if max.nil? || value.values.max > max
max = value.values.max
max_key = key
end
end
end
# max_key #=> "B"
Another solution:
myArray.map{ |h| h.transform_values{ |v| v["value_2"] } }.max_by{ |k| k.values }.keys.first
You asked "What am I missing?".
I think you are missing a proper understanding of the data structures that you are using. I suggest that you try printing the data structures and take a careful look at the results.
The simplest way is p myArray which gives:
[{"A"=>{"value_1"=>30, "value_2"=>240}, "B"=>{"value_1"=>40, "value_2"=>250}, "C"=>{"value_1"=>18, "value_2"=>60}}]
You can get prettier results using pp:
require 'pp'
pp myArray
yields:
[{"A"=>{"value_1"=>30, "value_2"=>240},
"B"=>{"value_1"=>40, "value_2"=>250},
"C"=>{"value_1"=>18, "value_2"=>60}}]
This helps you to see that myArray has only one element, a Hash.
You could also look at the expression array_hash.values.max inside the loop:
myArray.each do |array_hash|
p array_hash.values
end
gives:
[{"value_1"=>30, "value_2"=>240}, {"value_1"=>40, "value_2"=>250}, {"value_1"=>18, "value_2"=>60}]
Not what you expected? :-)
Given this, what would you expect to be returned by array_hash.values.max in the above loop?
Use p and/or pp liberally in your ruby code to help understand what's going on.

Convert array into hash and add a counter value to the new hash

I have the following array of hashes:
[
{"BREAD" => {:price => 1.50, :discount => true }},
{"BREAD" => {:price => 1.50, :discount => true }},
{"MARMITE" => {:price => 1.60, :discount => false}}
]
And I would like to translate this array into a hash that includes the counts for each item:
Output:
{
"BREAD" => {:price => 1.50, :discount => true, :count => 2},
"MARMITE" => {:price => 1.60, :discount => false, :count => 1}
}
I have tried two approaches to translate the array into a hash.
new_cart = cart.inject(:merge)
hash = Hash[cart.collect { |item| [item, ""] } ]
Both work but then I am stumped at how to capture and pass the count value.
Expected output
{
"BREAD" => {:price => 1.50, :discount => true, :count => 2},
"MARMITE" => {:price => 1.60, :discount => false, :count => 1}
}
We are given the array:
arr = [
{"BREAD" => {:price => 1.50, :discount => true }},
{"BREAD" => {:price => 1.50, :discount => true }},
{"MARMITE" => {:price => 1.60, :discount => false}}
]
and make the assumption that each hash has a single key and if two hashes have the same (single) key, the value of that key is the same in both hashes.
The first step is create an empty hash to which will add key-value pairs:
h = {}
Now we loop through arr to build the hash h. I've added a puts statement to display intermediate values in the calculation.
arr.each do |g|
k, v = g.first
puts "k=#{k}, v=#{v}"
if h.key?(k)
h[k][:count] += 1
else
h[k] = v.merge({ :count => 1 })
end
end
displays:
k=BREAD, v={:price=>1.5, :discount=>true}
k=BREAD, v={:price=>1.5, :discount=>true}
k=MARMITE, v={:price=>1.6, :discount=>false}
and returns:
#=> [{"BREAD" =>{:price=>1.5, :discount=>true}},
# {"BREAD" =>{:price=>1.5, :discount=>true}},
# {"MARMITE"=>{:price=>1.6, :discount=>false}}]
each always returns its receiver (here arr), which is not what we want.
h #=> {"BREAD"=>{:price=>1.5, :discount=>true, :count=>2},
# "MARMITE"=>{:price=>1.6, :discount=>false, :count=>1}}
is the result we need. See Hash#key? (aka, has_key?), Hash#[], Hash#[]= and Hash#merge.
Now let's wrap this in a method.
def hashify(arr)
h = {}
arr.each do |g|
k, v = g.first
if h.key?(k)
h[k][:count] += 1
else
h[k] = v.merge({ :count=>1 })
end
end
h
end
hashify(arr)
#=> {"BREAD"=>{:price=>1.5, :discount=>true, :count=>2},
# "MARMITE"=>{:price=>1.6, :discount=>false, :count=>1}}
Rubyists would often use the method Enumerable#each_with_object to simplify.
def hashify(arr)
arr.each_with_object({}) do |g,h|
k, v = g.first
if h.key?(k)
h[k][:count] += 1
else
h[k] = v.merge({ :count => 1 })
end
end
end
Compare the two methods to identify their differences. See Enumerable#each_with_object.
When, as here, the keys are symbols, Ruby allows you to use the shorthand { count: 1 } for { :count=>1 }. Moreover, she permits you to write :count = 1 or count: 1 without the braces when the hash is an argument. For example,
{}.merge('cat'=>'meow', dog:'woof', :pig=>'oink')
#=> {"cat"=>"meow", :dog=>"woof", :pig=>"oink"}
It's probably more common to see the form count: 1 when keys are symbols and for the braces to be omitted when a hash is an argument.
Here's a further refinement you might see. First create
h = arr.group_by { |h| h.keys.first }
#=> {"BREAD" =>[{"BREAD"=>{:price=>1.5, :discount=>true}},
# {"BREAD"=>{:price=>1.5, :discount=>true}}],
# "MARMITE"=>[{"MARMITE"=>{:price=>1.6, :discount=>false}}]}
See Enumerable#group_by. Now convert the values (arrays) to their sizes:
counts = h.transform_values { |arr| arr.size }
#=> {"BREAD"=>2, "MARMITE"=>1}
which can be written in abbreviated form:
counts = h.transform_values(&:size)
#=> {"BREAD"=>2, "MARMITE"=>1}
See Hash#transform_values. We can now write:
uniq_arr = arr.uniq
#=> [{"BREAD"=>{:price=>1.5, :discount=>true}},
#= {"MARMITE"=>{:price=>1.6, :discount=>false}}]
uniq_arr.each_with_object({}) do |g,h|
puts "g=#{g}"
k,v = g.first
puts " k=#{k}, v=#{v}"
h[k] = v.merge(counts: counts[k])
puts " h=#{h}"
end
which displays:
g={"BREAD"=>{:price=>1.5, :discount=>true}}
k=BREAD, v={:price=>1.5, :discount=>true}
h={"BREAD"=>{:price=>1.5, :discount=>true, :counts=>2}}
g={"MARMITE"=>{:price=>1.6, :discount=>false}}
k=MARMITE, v={:price=>1.6, :discount=>false}
h={"BREAD"=>{:price=>1.5, :discount=>true, :counts=>2},
"MARMITE"=>{:price=>1.6, :discount=>false, :counts=>1}}
and returns:
#=> {"BREAD"=>{:price=>1.5, :discount=>true, :counts=>2},
# "MARMITE"=>{:price=>1.6, :discount=>false, :counts=>1}}
See Array#uniq.
This did the trick:
arr = [
{ bread: { price: 1.50, discount: true } },
{ bread: { price: 1.50, discount: true } },
{ marmite: { price: 1.60, discount: false } }
]
Get the count for each occurrence of hash, add as key value pair and store:
h = arr.uniq.each { |x| x[x.first.first][:count] = arr.count(x) }
Then convert hashes into arrays, flatten to a single array then construct a hash:
Hash[*h.collect(&:to_a).flatten]
#=> {:bread=>{:price=>1.50, :discount=>true, :count=>2}, :marmite=>{:price=>1.60, :discount=>false, :count=>1}}
Combined a couple of nice ideas from here:
https://raycodingdotnet.wordpress.com/2013/08/05/array-of-hashes-into-single-hash-in-ruby/
and here:
http://carol-nichols.com/2015/08/07/ruby-occurrence-couting/

Structuring ruby multidimensional hash by first hash

I have the hash below:
mm = {
0 => {
0 => 'p1',
1 => 'p2',
2 => 'p3'
},
1 => {
0 => 'idfp1',
1 => 'idfp2',
2 => 'idfp3'
},
2 => {
0 => 'idfp12',
1 => 'idfp22',
2 => 'idfp32'
}
}
And i'm trying to sort it by the hash with a key of 0. In the first hash (0), there are k-v pairs of number to identifier.
In every subsequent hash (1 and 2), 0 points to the 0 from the first hash, 1 points to the 1 from the first hash, etc.
In each hash after 0 (1 and 2), there are IDs (id for person 1) that belong to p1 (person 1).
I've tried to sort this by creating a new hash with only the first hash in the one above to no avail. This is my attempt. The keys are correct but it's pointing to nil, when it should be pointing to the hash with each person's id.
ids = {}
org = {}
mm[0].each do |id, name|
ids[id] = name
end
mm.drop(1).each do |one|
one.each do |key, id|
org[ids[key]] = id
end
end
How can I achieve this in Ruby?
Edit:
In case the explanation doesn't suffice, here is the desired result:
org = {
'p1' => {
0 => 'idfp1',
1 => 'idfp12'
},
'p2' => {
0 => 'idfp2',
1 => 'idfp22'
},
'p3' => {
0 => 'idfp3',
1 => 'idfp32'
}
}
Two ways:
#1
Code
mm[0].invert.each_with_object({}) { |(k,i),h|
h[k] = (1...mm.size).each_with_object ({}) { |j,g| g[j] = mm[j][i] } }
#=> {"p1"=>{1=>"idfp1", 2=>"idfp12"},
# "p2"=>{1=>"idfp2", 2=>"idfp22"},
# "p3"=>{1=>"idfp3", 2=>"idfp32"}}
Explanation
a = mm[0]
#=> {0=>"p1", 1=>"p2", 2=>"p3"}
b = a.invert
#=> {"p1"=>0, "p2"=>1, "p3"=>2}
b.each_with_object({}) { |(k,i),h|
h[k] = (1...mm.size).each_with_object ({}) { |j,g| g[j] = mm[j][i] } }
#=> {"p1"=>{1=>"idfp1", 2=>"idfp12"},
# "p2"=>{1=>"idfp2", 2=>"idfp22"},
# "p3"=>{1=>"idfp3", 2=>"idfp32"}}
#2
Code
mm.values
.map(&:values)
.transpose
.each_with_object({}) { |a,h| h[a.shift] = Hash[[*(0...a.size)].zip(a) ] }
#=> {"p1"=>{0=>"idfp1", 1=>"idfp12"},
# "p2"=>{0=>"idfp2", 1=>"idfp22"},
# "p3"=>{0=>"idfp3", 1=>"idfp32"}}
Explanation
a = mm.values
#=> [{0=>"p1", 1=>"p2", 2=>"p3" },
# {0=>"idfp1", 1=>"idfp2", 2=>"idfp3" },
# {0=>"idfp12", 1=>"idfp22", 2=>"idfp32"}]
b = a.map(&:values
#=> [[ "p1", "p2", "p3" ],
# [ "idfp1", "idfp2", "idfp3" ],
# [ "idfp12", "idfp22", "idfp32"]]
c = b.transpose
#=> [["p1", "idfp1", "idfp12"],
# ["p2", "idfp2", "idfp22"],
# ["p3", "idfp3", "idfp32"]]
c.each_with_object({}) { |a,h| h[a.shift] = Hash[[*(0...a.size)].zip(a) ] }
#=> {"p1"=>{0=>"idfp1", 1=>"idfp12"},
# "p2"=>{0=>"idfp2", 1=>"idfp22"},
# "p3"=>{0=>"idfp3", 1=>"idfp32"}}

How to change format of nested hashes

I'm looking for a solution how to write the format function which will take a string or nested hash as an argument and return the flatten version of it with the path as a key.
arg = "foo"
format(arg) # => { "hash[keys]" => "foo" }
arg = {:a => "foo", :b => { :c => "bar", :d => "baz" }}
format(arg) # => { "hash[keys][a]" => "foo", "hash[keys][b][c]" => "bar", "hash[keys][b][d]" => "baz" }
def hash_flatten h
h.inject({}) do |a,(k,v)|
if v.is_a?(Hash)
hash_flatten(v).each do |sk, sv|
a[[k]+sk] = sv
end
else
k = k ? [k] : []
a[k] = v
end
a
end
end
def format h
if h.is_a?(Hash)
a = hash_flatten(h).map do |k,v|
key = k.map{|e| "[#{e}]"}.join
"\"event[actor]#{key}\" => \"#{v}\""
end.join(', ')
else
format({nil => h})
end
end
arg = "sth"
puts format(arg)
# => "event[actor]" => "sth"
arg = {:a => "sth", :b => { :c => "sth else", :d => "trololo" }}
puts format(arg)
# => "event[actor][a]" => "sth", "event[actor][b][c]" => "sth else", "event[actor][b][d]" => "trololo"

Resources