Find highest value from hash that contains "nil" - ruby

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

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.

splitting an array into a hash

Im looking to split an array of strings and creating a hash out of it.
I have an algorithm that splits a string into an array by commas this:1, is:1, a:1, string:1
def split_answer_to_hash(str)
words = str.split(',')
answer = {}
words.each do |w|
a = w.split(':')
h = Hash[ *a.collect { |v| [ v, a[1] ] } ]
answer = h
end
answer
end
What I need to do now is to make the left side of the colon the key to the hash and the right side of the colon the value of the hash. example: {"this" =>1, "is"=>1, "a"=>1, "string"=>1 }
*a.collect is iterating through the array and making the value another key. How can I go about this with out that happening?
The easiest way is:
string = 'this:1, is:1, a:1, string:1'
hash = Hash[*string.split(/:|,/)]
#=> {"this"=>"1", " is"=>"1", " a"=>"1", " string"=>"1"}
Having just one answer to this question just won't do:
str = "this:1, is:1, a:1, string:1"
Hash[str.scan(/(?:([^:]+):(\d+)(?:,\s)?)/)]
.tap { |h| h.keys.each { |k| h[k] = h[k].to_i } }
#=> {"this"=>1, "is"=>1, "a"=>1, "string"=>1}
Object#tap is used merely to convert the values from strings to integers. If you'd prefer:
h = Hash[str.scan(/(?:([^:]+):(\d+)(?:,\s)?)/)]
h.keys.each { |k| h[k] = h[k].to_i }
h
#=> {"this"=>1, "is"=>1, "a"=>1, "string"=>1}
For Ruby 2.1, you can replace Hash[arr] with arr.to_h.

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

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