Creating Hash of Hash from an Array of Array - ruby

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 }

Related

ruby select inner hash from nested hash

I need to filter a nested hash to return items for a particular combination of attributes. If the attribute is present it returns that hash, if the attribute is not present it returns the default. If the attribute is set to 'none' it returns nothing. Consider the following hash:
{
"size"=>{
"default"=>{
"jeans"=>"boyfriend"
},
"blue"=>"none"
},
"style"=>{
"default"=>{
"shoes"=>"boots"
},
"blue"=>{
"jeans"=>"jeggings"
}
}
}
if the color is 'black', then
{
"size"=>{
"jeans"=>"boyfriend"
},
"style"=>{
"shoes"=>"boots"
}
}
or if the color is 'blue', then
{
"size"=>{
},
"style"=>{
"jeans"=>"jeggings"
}
}
What is best way to do this? I have tried various combinations of select and delete but either end up with an array or a hash with the color key included.
Letting h be the hash given in the question, the following method will return the desired hash if my understanding of the question is correct.
def doit(h, color)
h.each_with_object({}) do |(k,f),g|
c,v = f.find { |kk,_| kk != "default" }
if c == color
g[k] = v.is_a?(Hash) ? v : {}
else
g[k] = f["default"]
end
end
end
doit(h, 'black')
#=> {"size"=>{"jeans"=>"boyfriend"}, "style"=>{"shoes"=>"boots"}}
doit(h, 'blue')
#=> {"size"=>{}, "style"=>{"jeans"=>"jeggings"}}
The steps for the second example are as follows.
color = 'blue'
enum = h.each_with_object({})
#=> #<Enumerator: {"size"=>{"default"=>{"jeans"=>"boyfriend"},
# "blue"=>"none"}, "style"=>{"default"=>{"shoes"=>"boots"},
# "blue"=>{"jeans"=>"jeggings"}}}:each_with_object({})>
The first value of this enumerator is generated:
x = enum.next
#=> [["size", {"default"=>{"jeans"=>"boyfriend"}, "blue"=>"none"}], {}]
and passed to the block. The block variables are set equal to x and their values are determined by "disambiguation":
(k,f),g = x
k #=> "size"
f ##=> {"default"=>{"jeans"=>"boyfriend"}, "blue"=>"none"}
g #=> {}
The block calculation is now performed.
c,v = f.find { |kk,_| kk != "default" }
#=> ["blue", "none"]
c #=> "blue"
v #=> "none"
As
c == color
#=> "blue" == "blue" => true
we compute
v.is_a?(Hash)
#=> false
and therefore perform the assignment
g[k] = {}
#=> {}
so that now
g #=> {"size"=>{}}
The second and last element of h is now generated and passed to the block.
x = enum.next
#=> [["style", {"default"=>{"shoes"=>"boots"},
# "blue"=>{"jeans"=>"jeggings"}}], {"style"=>{"jeans"=>"jeggings"}}]
(k,f),g = x
k #=> "style"
f #=> {"default"=>{"shoes"=>"boots"}, "blue"=>{"jeans"=>"jeggings"}}
g #=> {"size"=>"none"}
c,v = f.find { |kk,_| kk != "default" }
#=> ["blue", {"jeans"=>"jeggings"}]
c #=> "blue"
v #=> {"jeans"=>"jeggings"}
c == color
# "blue" == "blue" => true
v.is_a?(Hash)
#=> true
g[k] = v
#=> {"jeans"=>"jeggings"}
g #=> {"size"=>"none", "style"=>{"jeans"=>"jeggings"}}
and g is returned.
Below is what I ended up with after some refactoring. It works and tests all pass. Could do with more refactoring.
class Filterer
def self.filter(facets, color)
acc = {}
facets.each do |k, facets|
facets.each do |_, facet|
acc[k] = color_facets(color, facets)
end
end
acc
end
def self.color_facets(color, facets)
return {} if no_facets?(color, facets)
facets[color] ? facets[color] : facets['default']
end
def self.no_facets?(color, facets)
facets[color] && facets[color] == 'no facet'
end
end

How to turn a ruby hash so that the value becomes a key that points to a group of old keys?

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 sum values in an array with different hash

I want to sum the total values of the same items in an array.
I have an array as
[{"a"=>1},{"b"=>2},{"c"=>3},{"a"=>2},{"b"=>4}]
I want to get the result as
[{"a"=>3},{"b"=>6},{"c"=>3}]
Which method can do it?
if:
array = [{"a"=>1},{"b"=>2},{"c"=>3},{"a"=>2},{"b"=>4}]
then you can do:
array.inject(Hash.new{|h,k| h[k] = 0})
{ |h, a| k, v = a.flatten; h[k] += v; h }.
map{|arr| Hash[*arr] }
#=> [{"a"=>3}, {"b"=>6}, {"c"=>3}]
or:
array.each_with_object(Hash.new{|h,k| h[k] = 0})
{ |a, h| k, v = a.flatten; h[k] += v }.
map{|arr| Hash[*arr] }
#=> [{"a"=>3}, {"b"=>6}, {"c"=>3}]
It can be done as follows
array.group_by { |h| h.keys.first }.
values.
map {|x| x.reduce({}) { |h1, h2| h1.merge(h2) { |_, o, n| o + n } }
#=> [{"a"=>3}, {"b"=>6}, {"c"=>3}]
Every time you want to transform a collection in not a one-to-one way, it's job for #reduce. For one-to-one transformations we use #map.
array.reduce({}) { |h, acc| acc.merge(h) {|_k, o, n| o+n } }.zip.map(&:to_h)
# => [{"b"=>6}, {"a"=>3}, {"c"=>3}]
Here we use reduce with the initial value {}, which is passed to the block as the acc parameter, and then we use #merge with manual "conflicts resolution". It means that the block is called only when the key we're trying to merge is already present in the method receiver, acc. After that we break the hash into an array of hashes.
There are many ways to do this. It is instructive to see a few, even some that may be unusual and/or not especially efficient.
Here is another way:
arr = [{"a"=>1},{"b"=>2},{"c"=>3},{"a"=>2},{"b"=>4}]
arr.flat_map(&:keys)
.uniq
.map { |k| { k=>arr.reduce(0) { |t,g| t + (g.key?(k) ? g[k] : 0) } } }
#=> [{"a"=>3}, {"b"=>6}, {"c"=>3}]
Since nil.to_i => 0, we could instead write reduce's block as:
{ |t,g| t+g[k].to_i }

Ruby: get hash pair with max value

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

frequency of objects in an array using Ruby

If i had a list of balls each of which has a color property. how can i cleanly get the list of balls with the most frequent color.
[m1,m2,m3,m4]
say,
m1.color = blue
m2.color = blue
m3.color = red
m4.color = blue
[m1,m2,m4] is the list of balls with the most frequent color
My Approach is to do:
[m1,m2,m3,m4].group_by{|ball| ball.color}.each do |samecolor|
my_items = samecolor.count
end
where count is defined as
class Array
def count
k =Hash.new(0)
self.each{|x|k[x]+=1}
k
end
end
my_items will be a hash of frequencies foreach same color group. My implementation could be buggy and i feel there must be a better and more smarter way.
any ideas please?
You found group_by but missed max_by
max_color, max_balls = [m1,m2,m3,m4].group_by {|b| b.color}.max_by {|color, balls| balls.length}
Your code isn't bad, but it is inefficient. If I were you I would seek a solution that iterates through your array only once, like this:
balls = [m1, m2, m3, m4]
most_idx = nil
groups = balls.inject({}) do |hsh, ball|
hsh[ball.color] = [] if hsh[ball.color].nil?
hsh[ball.color] << ball
most_idx = ball.color if hsh[most_idx].nil? || hsh[ball.color].size > hsh[most_idx].size
hsh
end
groups[most_idx] # => [m1,m2,m4]
This does basically the same thing as group_by, but at the same time it counts up the groups and keeps a record of which group is largest (most_idx).
How about:
color,balls = [m1,m2,m3,m4].group_by { |b| b.color }.max_by(&:size)
Here's how I'd do it. The basic idea uses inject to accumulate the values into a hash, and comes from "12 - Building a Histogram" in "The Ruby Cookbook".
#!/usr/bin/env ruby
class M
attr_reader :color
def initialize(c)
#color = c
end
end
m1 = M.new('blue')
m2 = M.new('blue')
m3 = M.new('red')
m4 = M.new('blue')
hash = [m1.color, m2.color, m3.color, m4.color].inject(Hash.new(0)){ |h, x| h[x] += 1; h } # => {"blue"=>3, "red"=>1}
hash = [m1, m2, m3, m4].inject(Hash.new(0)){ |h, x| h[x.color] += 1; h } # => {"blue"=>3, "red"=>1}
There are two different ways to do it, depending on how much knowledge you want the inject() to know about your objects.
this produces a reverse sorted list of balls by frequency
balls.group_by { |b| b.color }
.map { |k, v| [k, v.size] }
.sort_by { |k, count| -count}
two parts, I'll use your strange balls example but will also include my own rails example
ary = [m1,m2,m3,m4]
colors = ary.each.map(&:color) #or ary.each.map {|t| t.color }
Hash[colors.group_by(&:w).map {|w, ws| [w, ws.length] }]
#=> {"blue" => 3, "red" => 1 }
my ActiveRecord example
stocks = Sp500Stock.all
Hash[stocks.group_by(&:sector).map {|w, s| [w, s.length] }].sort_by { |k,v| v }
#=> {"Health Care" => 36, etc]
myhash = {}
mylist.each do |ball|
if myhash[ball.color]
myhash[ball.color] += 1
else
myhash[ball.color] = 1
end
end
puts myhash.sort{|a,b| b[1] <=> a[1]}

Resources