How to work with hash in ruby? - ruby

My hash example:
{"79_6"=>"0", "85_6"=>"1", "86_6"=>"1", "79_8638"=>"0", "80_8638"=>"0", "81_8638"=>"0", "82_8638"=>"1", "83_8638"=>"1", "84_8638"=>"0", "85_8638"=>"1", "86_8638"=>"0", "79_8639"=>"0", "80_8639"=>"0", "81_8639"=>"0", "82_8639"=>"0", "83_8639"=>"0", "84_8639"=>"0", "85_8639"=>"0", "86_8639"=>"0", "80_8640"=>"0", "81_8640"=>"1", "82_8640"=>"1", "83_8640"=>"1", "84_8640"=>"0", "85_8640"=>"0", "86_8640"=>"0"}
I need to get parameters for which the key is 1:
["85_6", "86_6", "82_8638", "83_8638", "85_8638", "81_8640", "82_8640", "83_8640"]
Next, i need to group:
{"6"=>"85, 86", "8638"=> "83, 82, 85", "8640" => "81, 82, 83"}

hash.select { |_, v| v == '1' }
.keys
.map { |e| e.split('_') }
.group_by(&:pop)
.map { |k, v| [k, v.join(', ')] }
.to_h
#⇒ {
# "6" => "85, 86",
# "8638" => "82, 83, 85",
# "8640" => "81, 82, 83"
# }

One more solution (with just 1 iteration):
h.each_with_object(Hash.new {|h, k| h[k] = ''}) do |(k, v), m|
f, s = k.split('_')
m[s] << (m[s].empty? ? f : ", #{f}") if v == '1'
end
#=> {"6"=>"85, 86", "8638"=>"82, 83, 85", "8640"=>"81, 82, 83"}

yes, i know this is a bucket of crap, but posting it here because of the wasted 15 mins -))
a = {"79_6"=>"0", "85_6"=>"1", "86_6"=>"1", "79_8638"=>"0", "80_8638"=>"0", "81_8638"=>"0", "82_8638"=>"1", "83_8638"=>"1", "84_8638"=>"0", "85_8638"=>"1", "86_8638"=>"0", "79_8639"=>"0", "80_8639"=>"0", "81_8639"=>"0", "82_8639"=>"0", "83_8639"=>"0", "84_8639"=>"0", "85_8639"=>"0", "86_8639"=>"0", "80_8640"=>"0", "81_8640"=>"1", "82_8640"=>"1", "83_8640"=>"1", "84_8640"=>"0", "85_8640"=>"0", "86_8640"=>"0"}
a.select {|k| a[k] == '1' }
.keys.map {|e| e.split('_')}
.map(&:reverse)
.group_by(&:first)
.map{|k,v| [k, v.flatten.join(",")] }
.gsub("#{k},", " ")] }.to_h

hash = {"79_6"=>"0", "85_6"=>"1", "86_6"=>"1", "79_8638"=>"0", "80_8638"=>"0", "81_8638"=>"0", "82_8638"=>"1", "83_8638"=>"1", "84_8638"=>"0", "85_8638"=>"1", "86_8638"=>"0", "79_8639"=>"0", "80_8639"=>"0", "81_8639"=>"0", "82_8639"=>"0", "83_8639"=>"0", "84_8639"=>"0", "85_8639"=>"0", "86_8639"=>"0", "80_8640"=>"0", "81_8640"=>"1", "82_8640"=>"1", "83_8640"=>"1", "84_8640"=>"0", "85_8640"=>"0", "86_8640"=>"0"}
new_hash = hash.keep_if {|k,v| v == "1"} # keep only value == "1"
.map{|a|a.first.gsub("_",",").split(",")} # clean data
.group_by(&:pop) # group data by the last value which become the key
new_hash.map{|k,v| [k, v.join(',')]}.to_h #transform array of values in string and array to hash
Hope it's help, don't be to harsh it's my first answer on StackOver ;)

Related

Update a Hash Where the Values are Hashes

I have this hash where the keys are 0, 3, and 5 and the values are hashes.
{0=>{:occurrences=>1, :className=>"class"},
3=>{:occurrences=>3, :className=>"hello"},
5=>{:occurrences=>3, :className=>"nah"}}
How can I implement something like this:
h.map { |key|
if key[:occurrences] > 2
key[:occurrences] += 1
end
}
I know this syntax doesn't work. I want to increment the occurrence value when a condition is met and I am not sure how to access the key of a key but I would like the result to be:
{0=>{:occurrences=>1, :className=>"class"},
3=>{:occurrences=>4, :className=>"hello"},
5=>{:occurrences=>4, :className=>"nah"}}
To update the existing hash you can simply call each_value. It passes each value in your hash to the block and within the block you can update the value (based on a condition):
h = {
0=>{:occurrences=>1, :className=>"class"},
3=>{:occurrences=>3, :className=>"hello"},
5=>{:occurrences=>3, :className=>"nah"}
}
h.each_value { |v| v[:occurrences] += 1 if v[:occurrences] > 2 }
#=> {
# 0=>{:occurrences=>1, :className=>"class"},
# 3=>{:occurrences=>4, :className=>"hello"},
# 5=>{:occurrences=>4, :className=>"nah"}
# }
Just out of curiosity:
input.map do |k, v|
[k, v[:occurrences].to_i > 2 ? v.merge(occurrences: v[:occurrences] + 1) : v]
end.to_h
#⇒ {0=>{:occurrence=>1, :className=>"class"},
# 3=>{:occurrences=>4, :className=>"hello"},
# 5=>{:occurrences=>4, :className=>"nah"}}
h = { 0=>{ :occurrences=>1, :className=>"class" },
3=>{ :occurrences=>3, :className=>"hello" },
5=>{ :occurrences=>3, :className=>"nah" } }
f = h.dup
Non-destructive case
h.transform_values do |g|
g[:occurrences] > 2 ? g.merge(occurrences: g[:occurrences] + 1) : g
end
#=> { 0=>{ :occurrences=>1, :className=>"class" },
# 3=>{ :occurrences=>4, :className=>"hello" },
# 5=>{ :occurrences=>4, :className=>"nah" } }
h == f
#=> true
Destructive case
g = h.transform_values! do |g|
g[:occurrences] > 2 ? g.merge(occurrences: g[:occurrences] + 1) : g
end
#=> { 0=>{ :occurrences=>1, :className=>"class" },
# 3=>{ :occurrences=>4, :className=>"hello" },
# 5=>{ :occurrences=>4, :className=>"nah" } }
h == g
See Hash#transform_values and Hash#transform_values!, which made their debut in MRI v.2.4. Note that, in the destructive case, merge! is not needed.
For example I would want the entire hash returned but with updated
values: {0=>{:occurrence=>1, :className=>"class"},
3=>{:occurrences=>4, :className=>"hello"}, 5=>{:occurrences=>4,
:className=>"nah"}}. If the value of the occurrences key is greater
than two than I want to increment that value and still have the entire
hash.
Here you go:
h = {0=>{:occurrences=>1, :className=>"class"}, 3=>{:occurrences=>3, :className=>"hello"}, 5=>{:occurrences=>3, :className=>"nah"}}
new_h = h.map do |k, v|
if v[:occurrences] > 2
v[:occurrences] += 1
end
[k, v]
end.to_h

compare two hashes in ruby

I need to compare 2 hashes in ruby where one of the hash contains the numeric values in quoted string, which makes that a string. Consider the following 2 hashes:
hash1 = {"A"=>"0", "B"=>"1", "SVHTID"=>"VH", "D"=>"0", "E"=>"19930730", "F"=>"TEST - DEPOSIT", "G"=>"2.25000000"}
hash2 = {"a"=>"0", "b"=>1, "c"=>"VH", "d"=>0,"e"=>19930730, "f"=>"TEST - DEPOSIT", "g"=>2.25}
Now the code i have written so far is as follows:
hash2 = Hash[hash2.map {|key, value| [key.upcase, value] }]
hash1.each{|k,v| hash1[k] = hash1[k].to_i if hash1[k].match(/-?\d+(?:\.\d+)?/)}
hash1.keys.select { |key| hash1[key] != hash2[key] }.each { |key|
puts "expected #{key} => #{hash1[key]}, but found #{key} => #{hash2[key]}"
}
what is does is that it also converts the float value to integer and the original value is lost.
What i want is that when the above 2 hashes are compared the output should contain only G as mismatched type and following should be printed:
Expected: G=>2.25000000 but found G=>2.25
# normalization
h1, h2 = [hash1, hash2].map do |h|
h.map do |k, v|
[k.to_s.downcase, v.to_s.to_i.to_s == v.to_s ? v.to_i : v]
end.to_h
end
Now we are ready to compare:
h1.reject { |k, v| h2[k].nil? || h2[k] == v }
#⇒ { "g" => "2.25000000" }
This might be printed, formatted etc as you want.

How to sort an array of hashes into hashes with multiple values for a key?

I am working on Ruby on Rails project using rails4.
Scenario:
I have an array of hashes. An array contains hashes where keys are the same.
a = [{132=>[1000.0]}, {132=>[4000.0]}, {175=>[1000.0]}, {175=>[1000.0]}, {133=>[1200.0]}]
h = a.each {|key,value| key.each {|k| value}}
#{132=>[1000.0]}
#{132=>[4000.0]}
#{175=>[1000.0]}
#{175=>[1000.0]}
#{133=>[1200.0]}
Problem:
How to get rid off duplicate keys but with values added to unique keys like this:
{132=>[1000,4000]}
{175=>[1000,1000]}
{133=>[1200]}
Thank you.
That would do it:
a.inject({}) {|sum, hash| sum.merge(hash) {|_, old, new| old + new }}
This works for me:
p a.each_with_object(Hash.new([])) { |e, h| e.each { |k, v| h[k] += v } }
# => {132=>[1000.0, 4000.0], 175=>[1000.0, 1000.0], 133=>[1200.0]}
Another way:
a.each_with_object({}) do |g,h|
k, v = g.to_a.flatten
(h[k] ||= []) << v
end
#=> {132=>[1000.0, 4000.0], 175=>[1000.0, 1000.0], 133=>[1200.0]}
or
a.each_with_object(Hash.new { |h,k| h[k]=[] }) do |g,h|
k, v = g.to_a.flatten
h[k] << v
end
#=> {132=>[1000.0, 4000.0], 175=>[1000.0, 1000.0], 133=>[1200.0]}

Find highest value from hash that contains "nil"

I have a hash which looks like this
#hash = {
0=>[{"name"=>"guest", "value"=>7.9}],
1=>[nil], 2=>[nil], 3=>[nil], 4=>[nil], 5=>[nil], 6=>[nil], 7=>[nil], 8=>[nil],
9=>[nil], 10=>[nil], 11=>[nil], 12=>[nil], 13=>[nil], 14=>[nil], 15=>[nil],
16=>[nil], 17=>[nil], 18=>[nil],
19=>[{"name"=>"test", "value"=>2.5}],
20=>[{"name"=>"roam", "value"=>2.5}],
21=>[{"name"=>"test2", "value"=>1.58}],
22=>[{"name"=>"dff", "value"=>1.9}],
23=>[{"name"=>"dddd", "value"=>3.16}]
}
I want the highest value from this hash in a variable. The output should be
#h = 7.9 \\only float value which should be highest among all
so I am doing like this
#hash.each do |k, v|
if !v.nil?
#h= [v.flatten.sort{ |v1, v2| v2['value'] <=> v1['value'] }.first['value']]
end
end
but sometimes it works, and most of the times it doesn't.
#hash.values.flatten.compact.map { |h| h["value"] }.max
=> 7.9
Which equates to:
Get the values of the hash as an array
Flatten all the elements in the values array
Compact to remove all nil entries
Map the remaining entries to the ["value"] element in the hash
Return the maximum of all those value
It makes a lot of assumptions about the format of your #hash though.
I prefer #Shadwell's solution, but here's another way:
hash.select { |_,v| v.first }
.max_by { |_,v| v.first["value"] }
.last
.first["value"]
#=> 7.9
The steps (with all but one n=>[nil] element removed for readabiity):
hash = { 0=>[{"name"=>"guest", "value"=>7.9}],
1=>[nil],
19=>[{"name"=>"test", "value"=>2.5}],
20=>[{"name"=>"roam", "value"=>2.5}],
21=>[{"name"=>"test2", "value"=>1.58}],
22=>[{"name"=>"dff", "value"=>1.9}],
23=>[{"name"=>"dddd", "value"=>3.16}]}
h = hash.select { |_,v| v.first }
#=> { 0=>[{"name"=>"guest", "value"=>7.9}],
# 19=>[{"name"=>"test", "value"=>2.5}],
# 20=>[{"name"=>"roam", "value"=>2.5}],
# 21=>[{"name"=>"test2", "value"=>1.58}],
# 22=>[{"name"=>"dff", "value"=>1.9}],
# 23=>[{"name"=>"dddd", "value"=>3.16}]}
a = h.max_by { |_,v| v.first["value"] }
#=> [0, [{"name"=>"guest", "value"=>7.9}]]
b = a.last
#=> [{"name"=>"guest", "value"=>7.9}]
b.first["value"]
#=> 7.9

Dump YAML-like key names of Hash

What's a convenient way to get a list of all Hash keys (with nesting) separated by dots?
Given I have a hash:
{ level1: { level21: { level31: 'val1',
level32: 'val2' },
level22: 'val3' }
}
Desired output (array of strings) which represents all key paths in a hash:
level1.level21.level31
level1.level21.level32
level1.level22
My current solution:
class HashKeysDumper
def self.dump(hash)
hash.map do |k, v|
if v.is_a? Hash
keys = dump(v)
keys.map { |k1| [k, k1].join('.') }
else
k.to_s
end
end.flatten
end
end
It also available as gist (with specs).
Well, it depends on what you mean by cleaner, but here's a smaller version that…
Will work on subclasses Hashes or Hash-alikes
Extends Hash, making it look cleaner in your code.
class Hash
def keydump
map{|k,v|v.keydump.map{|a|"#{k}.#{a}"} rescue k.to_s}.flatten
end
end
results:
{ level1: { level21: { level31: 'val1',
level32: 'val2' },
level22: 'val3' }
}.keydump
=> ["level1.level21.level31", "level1.level21.level32", "level1.level22"]
Here is my vision of this:
h = { 'level1' => { 'level2' => { 'level31' => 'val1', 'level32' => 'val2' } } }
class Hash
def nested_keys
self.inject([]) { |f, (k,v)| f += [k, v.is_a?(Hash) ? v.nested_keys : []] }.flatten
end
end
keys = h.nested_keys
p keys
#=> ["level1", "level2", "level31", "level32"]
k1, k2 = keys.shift, keys.shift
puts [k1, k2, keys.shift].join('.')
#=> level1.level2.level31
puts [k1, k2, keys.shift].join('.')
#=> level1.level2.level32
Here is a Working Demo
I just committed some code to RubyTree that adds from_hash() which would allow you to do this:
require 'rubytree'
Tree::TreeNode.from_hash(hash).each_leaf.map{|n| "#{n.name}.#{n.parentage.map(&:name).reverse.join('.')}" }
=> ["level1.level21.level31", "level1.level21.level32", "level1.level22"]
Aside from the gem require, it's a one-liner :)

Resources