Here's a hash that keeps track of how much of each fruit I have
fruits = {"apples" => 10, "pears" => 15, "bananas" => 15, "grapes" => 12}
And I want to know which fruit I have the most of.
If there are tie-breakers then just return them all.
# easy
max_quantity = fruits.values.max
max_fruits = fruits.select { |k, v| v == max_quantity }.keys
# fast
max_quantity = -1.0/0.0
max_fruits = []
fruits.each do |k, v|
if v > max_quantity
max_quantity = v
max_fruits = []
end
max_fruits.push k if v == max_quantity
end
Since exceptional cases are Bad(tm), both of these always return an array.
max_value = fruits.values.max
keys = fruits.select{|k, v| v == max_value}.keys
Related
I have a hash:
a = {"Q1"=>1, "Q2"=>2, "Q5"=>3, "Q8"=>3}
I want to retrieve a set of keys from it such that the sum of their values equals a certain number, for example 5. In such case, the output should be:
Q2 Q5
Please help me on how to get this.
def find_combo(h, tot)
arr = h.to_a
(1..arr.size).find do |n|
enum = arr.combination(n).find { |e| e.map(&:last).sum == tot }
return enum.map(&:first) unless enum.nil?
end
end
h = {"Q1"=>1, "Q2"=>2, "Q5"=>3, "Q8"=>3}
find_combo(h, 5) #=> ["Q2", "Q5"]
find_combo(h, 2) #=> ["Q2"]
find_combo(h, 6) #=> ["Q5", "Q8"]
find_combo(h, 4) #=> ["Q1", "Q5"]
find_combo(h, 8) #=> ["Q2", "Q5", "Q8"]
find_combo(h, 9) #=> ["Q1", "Q2", "Q5", "Q8"]
find_combo(h, 10) #=> nil
Just out of curiosity:
hash = {"Q1"=>1, "Q2"=>2, "Q5"=>3, "Q8"=>3}
arr = hash.to_a
1.upto(hash.size).
lazy.
find do |i|
res = arr.combination(i).find do |h|
h.map(&:last).sum == 5
end
break res if res
end.tap { |result| break result.to_h if result }
#⇒ {"Q2" => 2, "Q5" => 3}
I have a hash like this:
t={"4nM"=>"Triangle", "I40"=>"Triangle", "123"=>"Square"}
And I want to turn it into a hash like:
{"Triangle" => ["4nM", "I40"], "Square" => ["123"]}
What is the best way to do this?
I start with group_by but then the code gets to be a bit convoluted....
This is what I did:
t.group_by { |k, v| v }.map { |type, group| {type => group.flatten.reject { |x| x == type } } }
h = { "4nM"=>"Triangle", "I40"=>"Triangle", "123"=>"Square" }
h.each_with_object({}) { |(k,v),h| (h[v] ||= []) << k }
#=> {"Triangle"=>["4nM", "I40"], "Square"=>["123"]}
The expression
(h[v] ||= []) << k
expands to
(h[v] = h[v] || []) << k
If h has a key v, h[k] will be truthy, so the expression above reduces to
(h[v] = h[v]) << k
and then
h[v] << k
If h does not have a key v, h[k] #=> nil, so the expression above reduces to
(h[v] = []) << k
resulting in
h[v] #=> [k]
Alternatively, we could write
h.each_with_object(Hash.new { |h,k| h[k] = [] }) { |(k,v),h| h[v] << k }
#=> {"Triangle"=>["4nM", "I40"], "Square"=>["123"]}
See Hash::new for an explanation of the use of a block for returning the default values of keys that are not present in the hash.
This is the shortest I could write :
t.group_by(&:last).map{|k,v|[k,v.map(&:first)]}.to_h
Still 4 characters longer than #Cary Swoveland's answer.
Note that in Rails, Hash#transform_values makes it a bit easier :
t.group_by{|_,v| v }.transform_values{|v| v.map(&:first) }
You can cut it down a little bit by doing this
t.group_by {|k,v| v}.map{|k,v| {k => v.map(&:first)}}
but your original implementation was already pretty concise.
t={"4nM"=>"Triangle", "I40"=>"Triangle", "123"=>"Square"}
h = Hash.new{[]}
t.each{|k,v| h[v] <<= k}
How to merge array of hash based on the same keys in ruby?
example :
a = [{:a=>1},{:a=>10},{:b=>8},{:c=>7},{:c=>2}]
How to get result like this?
a = [{:a=>[1, 10]},{:b=>8},{:c=>[7, 2]}]
Try
a.flat_map(&:entries)
.group_by(&:first)
.map{|k,v| Hash[k, v.map(&:last)]}
Another alternative:
a = [{:a=>1},{:a=>10},{:b=>8},{:c=>7},{:c=>2}]
p a.each_with_object({}) { |h, o| h.each { |k,v| (o[k] ||= []) << v } }
# => {:a=>[1, 10], :b=>[8], :c=>[7, 2]}
It also works when the Hashes have multiple key/value combinations, e.g:
b = [{:a=>1, :b=>5, :x=>10},{:a=>10, :y=>2},{:b=>8},{:c=>7},{:c=>2}]
p b.each_with_object({}) { |h, o| h.each { |k,v| (o[k] ||= []) << v } }
# => {:a=>[1, 10], :b=>[5, 8], :x=>[10], :y=>[2], :c=>[7, 2]}
Minor addition to answer by Arie Shaw to match required answer:
a.flat_map(&:entries)
.group_by(&:first)
.map{|k,v| Hash[k, v.size.eql?(1) ? v.last.last : v.map(&:last) ]}
#=> [{:a=>[1, 10]}, {:b=>8}, {:c=>[7, 2]}]
I'd do :
a = [{:a=>1},{:a=>10},{:b=>8},{:c=>7},{:c=>2}]
merged_hash = a.each_with_object({}) do |item,hsh|
k,v = item.shift
hsh[k] = hsh.has_key?(k) ? [ *Array( v ), hsh[k] ] : v
end
merged_hash.map { |k,v| { k => v } }
# => [{:a=>[10, 1]}, {:b=>8}, {:c=>[2, 7]}]
update
A better taste :
a = [{:a=>1},{:a=>10},{:b=>8},{:c=>7},{:c=>2}]
merged_hash = a.each_with_object({}) do |item,hsh|
k,v = item.shift
(hsh[k] ||= []) << v
end
merged_hash.map { |k,v| { k => v } }
# => [{:a=>[10, 1]}, {:b=>8}, {:c=>[2, 7]}]
I have an array:
values = [["branding", "color", "blue"],
["cust_info", "customer_code", "some_customer"],
["branding", "text", "custom text"]]
I am having trouble tranforming it to hash as follow:
{
"branding" => {"color"=>"blue", "text"=>"custom text"},
"cust_info" => {"customer_code"=>"some customer"}
}
You can use default hash values to create something more legible than inject:
h = Hash.new {|hsh, key| hsh[key] = {}}
values.each {|a, b, c| h[a][b] = c}
Obviously, you should replace the h and a, b, c variables with your domain terms.
Bonus: If you find yourself needing to go N levels deep, check out autovivification:
fun = Hash.new { |h,k| h[k] = Hash.new(&h.default_proc) }
fun[:a][:b][:c][:d] = :e
# fun == {:a=>{:b=>{:c=>{:d=>:e}}}}
Or an overly-clever one-liner using each_with_object:
silly = values.each_with_object(Hash.new {|hsh, key| hsh[key] = {}}) {|(a, b, c), h| h[a][b] = c}
Here is an example using Enumerable#inject:
values = [["branding", "color", "blue"],
["cust_info", "customer_code", "some_customer"],
["branding", "text", "custom text"]]
# r is the value we are are "injecting" and v represents each
# value in turn from the enumerable; here we create
# a new hash which will be the result hash (res == r)
res = values.inject({}) do |r, v|
group, key, value = v # array decomposition
r[group] ||= {} # make sure group exists
r[group][key] = value # set key/value in group
r # return value for next iteration (same hash)
end
There are several different ways to write this; I think the above is relatively simple. See extracting from 2 dimensional array and creating a hash with array values for using a Hash (i.e. grouper) with "auto vivification".
Less elegant but easier to understand:
hash = {}
values.each do |value|
if hash[value[0]]
hash[value[0]][value[1]] = value[2]
else
hash[value[0]] = {value[1] => value[2]}
end
end
values.inject({}) { |m, (k1, k2, v)| m[k1] = { k2 => v }.merge m[k1] || {}; m }
I have a hash like
h = {1 => {"inner" => 45}, 2 => {"inner" => 46}, "inner" => 47}
How do I delete every pair that contains the key "inner"?
You can see that some of the "inner" pairs appear directly in h while others appear in pairs in h
Note that I only want to delete the "inner" pairs, so if I call my mass delete method on the above hash, I should get
h = {1 => {}, 2 => {}}
Since these pairs don't have a key == "inner"
Really, this is what reject! is for:
def f! x
x.reject!{|k,v| 'inner' == k} if x.is_a? Hash
x.each{|k,v| f! x[k]}
end
def f x
x.inject({}) do |m, (k, v)|
v = f v if v.is_a? Hash # note, arbitrarily recursive
m[k] = v unless k == 'inner'
m
end
end
p f h
Update: slightly improved...
def f x
x.is_a?(Hash) ? x.inject({}) do |m, (k, v)|
m[k] = f v unless k == 'inner'
m
end : x
end
def except_nested(x,key)
case x
when Hash then x = x.inject({}) {|m, (k, v)| m[k] = except_nested(v,key) unless k == key ; m }
when Array then x.map! {|e| except_nested(e,key)}
end
x
end
Here is what I came up with:
class Hash
def deep_reject_key!(key)
keys.each {|k| delete(k) if k == key || self[k] == self[key] }
values.each {|v| v.deep_reject_key!(key) if v.is_a? Hash }
self
end
end
Works for a Hash or a HashWithIndifferentAccess
> x = {'1' => 'cat', '2' => { '1' => 'dog', '2' => 'elephant' }}
=> {"1"=>"cat", "2"=>{"1"=>"dog", "2"=>"elephant"}}
> y = x.with_indifferent_access
=> {"1"=>"cat", "2"=>{"1"=>"dog", "2"=>"elephant"}}
> x.deep_reject_key!(:"1")
=> {"1"=>"cat", "2"=>{"1"=>"dog", "2"=>"elephant"}}
> x.deep_reject_key!("1")
=> {"2"=>{"2"=>"elephant"}}
> y.deep_reject_key!(:"1")
=> {"2"=>{"2"=>"elephant"}}
Similar answer but it is a whitelist type approach. For ruby 1.9+
# recursive remove keys
def deep_simplify_record(hash, keep)
hash.keep_if do |key, value|
if keep.include?(key)
deep_simplify_record(value, keep) if value.is_a?(Hash)
true
end
end
end
hash = {:a => 1, :b => 2, :c => {:a => 1, :b => 2, :c => {:a => 1, :b => 2, :c => 4}} }
deep_simplify_record(hash, [:b, :c])
# => {:b=>2, :c=>{:b=>2, :c=>{:b=>2, :c=>4}}}
Also here are some other methods which I like to use for hashes.
https://gist.github.com/earlonrails/2048705