Collect values from an array of hashes - ruby

I have a data structure in the following format:
data_hash = [
{ price: 1, count: 3 },
{ price: 2, count: 3 },
{ price: 3, count: 3 }
]
Is there an efficient way to get the values of :price as an array like [1,2,3]?

First, if you are using ruby < 1.9:
array = [
{:price => 1, :count => 3},
{:price => 2, :count => 3},
{:price => 3, :count => 3}
]
Then to get what you need:
array.map{|x| x[:price]}

There is a closed question that redirects here asking about handing map a Symbol to derive a key. This can be done using an Enumerable as a middle-man:
array = [
{:price => 1, :count => 3},
{:price => 2, :count => 3},
{:price => 3, :count => 3}
]
array.each.with_object(:price).map(&:[])
#=> [1, 2, 3]
Beyond being slightly more verbose and more difficult to understand, it also slower.
Benchmark.bm do |b|
b.report { 10000.times { array.map{|x| x[:price] } } }
b.report { 10000.times { array.each.with_object(:price).map(&:[]) } }
end
# user system total real
# 0.004816 0.000005 0.004821 ( 0.004816)
# 0.015723 0.000606 0.016329 ( 0.016334)

Related

How to merge hash of arrays?

I have an array:
[
{a => 1, b => { c => 1, d => 1}},
{a => 1, b => { c => 1, d => 2}},
{a => 1, b => { c => 2, d => 2}},
{a => 2, b => { c => 1, d => 1}},
]
I want to change it to this:
[
{a => 1, b => [{ c => 1, d => [1, 2]}, { c => 2, d => [2]}]},
{a => 2, b=> [ { c=> 1, d => [1] } ]}
]
Rules/Requirements:
Hashes of same value of a go to one hash
b should be an array of {c => , d =>}
d should be an array
d with same value of c go to same array
Here is a solution. It is very explicit, so it would not generalize to other hash structures.
hashes = [
{:a => 1, :b => { :c => 1, :d => 1}},
{:a => 1, :b => { :c => 1, :d => 2}},
{:a => 1, :b => { :c => 2, :d => 2}},
{:a => 2, :b => { :c => 1, :d => 1}},
]
a_values = {}
hashes.each do |hash|
a_value = hash[:a]
a_values[a_value] ||= {}
c_value = hash[:b][:c]
a_values[a_value][c_value] ||= { :c => c_value, :d => [] }
d_value = hash[:b][:d]
a_values[a_value][c_value][:d].push(d_value)
end
# Now aggregate the results
results = a_values.map do |a_value, c_hashes|
b_arr = c_hashes.map { |c_value, c_hash| c_hash }
{ :a => a_value, :b => b_arr }
end
And here is the output:
[
{:a=>1, :b=>[{:c=>1, :d=>[1, 2]}, {:c=>2, :d=>[2]}]},
{:a=>2, :b=>[{:c=>1, :d=>[1]}]}
]

Add element to a nested hash in ruby

I have a hash:
a = { 21 => { 3 => {:x => 5, :y => 6}}}
I want to add another value to the key '21' so that the hash looks like this:
a = { 21 => { 3 => {:x => 5, :y => 6}, 4 => {:x => 8, :y => 7}}}
How can I do that?
You want to add an key-value pair to a hash (a[21]). a[21] will give you the inner hash object.
a = { 21 => { 3 => {:x => 5, :y => 6}}}
a[21]
# => {3=>{:x=>5, :y=>6}}
Associating key, values to the inner hash will solve your problem.
a[21][4] = {:x => 8, :y => 7}
a
# => {21=>{3=>{:x=>5, :y=>6}, 4=>{:x=>8, :y=>7}}}
Another way is:
a[21].update({ 4=>{:x => 8, :y => 7} })
a #=> {21=>{3=>{:x=>5, :y=>6}, 4=>{:x=>8, :y=>7}}}

Sort array using values from separate hash

I have an array of IDs and a separate hash that dictates how those IDs should be ordered. Something like this:
Array:
[
7,
3,
2,
]
Sort ranks:
{
1 => 50,
2 => 70,
3 => 10,
4 => 80,
5 => 60,
6 => 20,
7 => 0,
}
I want to elegantly reverse sort the first array by using the values of the hash. So the result should be this:
[
2,
3,
7,
]
I know there are brute force methods, but is there an easier and more performant way to do this?
assuming you have order_hash and object_array defined, you can sort using following code:
object_array.sort{ |a, b| order_hash[b[:id]] <=> order_hash[a[:id]] }
you can use some constant for instead of order_hash if you want.
I'm not sure about more performant but here's a relatively simple way
arr = [
{
:id => 7,
},
{
:id => 3,
},
{
:id => 2,
}
]
ranks = {
1 => 50,
2 => 70,
3 => 10,
4 => 80,
5 => 60,
6 => 20,
7 => 0,
}
arr2 = arr.sort_by{|elem| -ranks[elem[:id]]}
arr2 # => [{:id=>2}, {:id=>3}, {:id=>7}]
Use Enumerable#sort_by:
input = [
7,
3,
2,
]
ranks = {
1 => 50,
2 => 70,
3 => 10,
4 => 80,
5 => 60,
6 => 20,
7 => 0,
}
result = input.sort_by{|x| ranks[x]} #=> [7, 3, 2]

Subtract values in hash from corresponding values in another hash

I'd like to be able to subtract two hashes and get a third hash in Ruby.
The two hashes look like this:
h1 = {"Cat" => 100, "Dog" => 5, "Bird" => 2, "Snake" => 10}
h1.default = 0
h2 = {"cat" => 50, "dog" => 3, "BIRD" => 4, "Mouse" => 75, "Snake" => 10}
h2.default = 0
I'd like to be able to call a method on h1 like this:
h1.difference(h2)
and get this hash as a result:
{"Cat" => 50, "Dog" => 2, "BIRD" => -2, "Mouse" => -75}
I'd like to create a new hash with keys from both Hashes and the values of the new hash to be the value of the key in the first hash minus the value of that key in the second hash. The catch is that I'd like this Hash method to work regardless of the case of the keys. In other words, I'd like "Cat" to match up with "cat".
Here's what I have so far:
class Hash
def difference(another_hash)
(keys + another_hash.keys).map { |key| key.strip }.uniq.inject(Hash.new(0)) { |acc, key| acc[key] = (self[key] - another_hash[key]); acc }.delete_if { |key, value| value == 0 }
end
end
This is OK, but, unfortunately, the result isn't what I want.
Any help would be appreciated.
How about converting the hashes to sets.
require 'set'
h1 = {"Cat" => 100, "Dog" => 5, "Bird" => 2, "Snake" => 10}
h1.default = 0
h2 = {"cat" => 50, "dog" => 3, "BIRD" => 4, "Mouse" => 75, "Snake" => 10}
h2.default = 0
p (h1.to_set - h2.to_set)
#=> #<Set: {["Cat", 100], ["Dog", 5], ["Bird", 2]}>
As a recommendation...
I've used something like this in the past:
class Hash
def downcase_keys
Hash[map{ |k,v| [k.downcase, v]}]
end
def difference(other)
Hash[self.to_a - other.to_a]
end
alias :- :difference
end
which lets me do things like:
irb(main):206:0> h1.downcase_keys - h2.downcase_keys
{
"cat" => 100,
"dog" => 5,
"bird" => 2
}
irb(main):207:0> h2.downcase_keys - h1.downcase_keys
{
"cat" => 50,
"dog" => 3,
"bird" => 4,
"mouse" => 75
}
The alias gives you the nice syntax of using - instead of difference, similar to using - for Arrays and Sets. You can still use difference though:
irb(main):210:0> h1.downcase_keys.difference(h2.downcase_keys)
{
"cat" => 100,
"dog" => 5,
"bird" => 2
}
irb(main):211:0> h2.downcase_keys.difference(h1.downcase_keys)
{
"cat" => 50,
"dog" => 3,
"bird" => 4,
"mouse" => 75
}
I always normalize my hash keys, and don't allow variants to leak in. It makes processing the hashes much too difficult when you don't know what the keys are called, so I'd highly recommend doing that as a first step. It's a code-maintenance issue.
Otherwise, you could create a map of the original key names and their normalized names, but you run into problems if your hash contains two unique-case keys, such as 'key' and 'KEY', because normalizing will stomp on one.
Sorry that due to the time limit (I have to take care of my baby boy now), only figured out this stupid but working code:
h1 = {"Cat" => 100, "Dog" => 5, "Bird" => 2, "Snake" => 10}
h1.default = 0
h2 = {"cat" => 50, "dog" => 3, "BIRD" => 4, "Mouse" => 75, "Snake" => 10}
h2.default = 0
h3 = {"Cat" => 50, "Dog" => 2, "BIRD" => -2, "Mouse" => -75}
class Hash
def difference(subtrahend)
diff = {}
self.each_pair do |k1, v1|
flag = false
subtrahend.each_pair do |k2, v2|
if k1.downcase == k2.downcase
flag = true
v_diff = v1 - v2
break if v_diff == 0
v_diff > 0 ? diff[k1] = v_diff : diff[k2] = v_diff
end
end
diff[k1] = v1 unless flag
end
subtrahend.each_pair do |k2, v2|
flag = false
self.each_pair do |k1, v1|
if k1.downcase == k2.downcase
flag = true
break
end
end
diff[k2] = -v2 unless flag
end
return diff
end
end
h1.difference(h2) == h3 ? puts("Pass") : puts("Fail") #=> "Pass"
I got this to the resque https://github.com/junegunn/insensitive_hash
then follow your procedure but slighly tweaked as requirement
require 'insensitive_hash'
h1 = {"Cat" => 100, "Dog" => 5, "Bird" => 2, "Snake" => 10}.insensitive
h1.default = 0
h2 = {"cat" => 50, "dog" => 3, "BIRD" => 4, "Mouse" => 75, "Snake" => 10}.insensitive
h2.default = 0
class Hash
def difference(another_hash)
(keys + another_hash.keys).map { |key|
key.downcase }.uniq.inject(Hash.new(0)) do |acc, key|
val = self[key] - another_hash[key]
acc[key] = val if val!= 0
acc
end
end
end
h1.difference(h2)
# => {"cat"=>50, "dog"=>2, "bird"=>-2, "mouse"=>-75}
This time I would like to provide another solution: normalized -> store original key value pairs -> grab the original key who has larger value as the key for the difference.
h1 = {"Cat" => 100, "Dog" => 5, "Bird" => 2, "Snake" => 10}
h1.default = 0
h2 = {"cat" => 50, "dog" => 3, "BIRD" => 4, "Mouse" => 75, "Snake" => 10}
h2.default = 0
h3 = {"Cat" => 50, "Dog" => 2, "BIRD" => -2, "Mouse" => -75}
class Hash
def difference(subtrahend)
# create a hash which contains all normalized keys
all_pairs = (self.keys.map{|x| x.downcase} + subtrahend.keys.map{|x| x.downcase}).uniq.inject({}) do |pairs, key|
pairs[key] = []
pairs
end
#=> {"mouse"=>[], "cat"=>[], "snake"=>[], "bird"=>[], "dog"=>[]}
# push original key value pairs into array which is the value of just created hash
[self, subtrahend].each_with_index do |hsh, idx|
hsh.each_pair { |k, v| all_pairs[k.downcase].push([k, v]) }
all_pairs.each_value { |v| v.push([nil, 0]) if v.size == idx }
end
#=> {"mouse"=>[[nil, 0], ["Mouse", 75]], "cat"=>[["Cat", 100], ["cat", 50]], "snake"=>[["Snake", 10], ["Snake", 10]], "bird"=>[["Bird", 2], ["BIRD", 4]], "dog"=>[["Dog", 5], ["dog", 3]]}
results = {}
all_pairs.each_value do |values|
diff = values[0][1] - values[1][1]
# always take the key whose value is larger
if diff > 0
results[values[0][0]] = diff
elsif diff < 0
results[values[1][0]] = diff
end
end
return results
end
end
puts h1.difference(h2).inspect #=> {"Cat" => 50, "Dog" => 2, "BIRD" => -2, "Mouse" => -75}
h1.difference(h2) == h3 ? puts("Pass") : puts("Fail") #=> "Pass"
According to what you described, this one does a pretty good job. The result is exactly what you've shown (key is not normalized in the final result, but depends on whose value is bigger).

ruby - group by repeating key of multiple hashes

I have the following array
t = [
{nil => 1, 10 => 2, 16 => 4, 5=> 10},
{nil => 9, 5 => 2, 17 => 3, 10 => 2},
{10 => 4, 5 => 9, 17 => 1}
]
how can I get this as result?
{nil => [1,9,0],10 => [2,2,4], 16 => [4,0,0], 5 => [10,2,9], 17=>[0,3,1]}
I've seen that I can use something like this
t.group_by{|h| h['key']}
but I'm not sure if I can put a regexp inside the brackets
Thanks in advance
Javier
EDIT:
Is just want to group by each key of each hash inside the array, if the key is not present then the value is 0 for that hash
How about this one for illegibility:
t = [
{nil => 1, 10 => 2, 16 => 4, 5=> 10},
{nil => 9, 5 => 2, 17 => 3, 10 => 2},
{10 => 4, 5 => 9, 17 => 1}
]
# Create hash of possible keys
keys = t.reduce({}) { |m, h| h.each_key { |k| m[k] = [] }; m }
# Iterate through array, for each hash, for each key, append the
# value if key is in hash or zero otherwise
t.reduce(keys) { |m, h| m.each_key { |k| m[k] << (h[k] || 0) }; m }
puts keys
#=> {nil=>[1, 9, 0], 10=>[2, 2, 4], 16=>[4, 0, 0], 5=>[10, 2, 9], 17=>[0, 3, 1]}
Not the most elegant code I've ever written, but it does the job and is easy to understand:
def jqq(a)
keys = []
result = {}
a.each do |h|
keys += h.keys
end
keys.uniq.each do |key|
result[key] = []
a.each do |h|
h.default = 0
result[key] << h[key]
end
end
result
end
t = [
{nil => 1, 10 => 2, 16 => 4, 5=> 10},
{nil => 9, 5 => 2, 17 => 3, 10 => 2},
{10 => 4, 5 => 9, 17 => 1}
]
puts jqq(t)
# {nil=>[1, 9, 0], 10=>[2, 2, 4], 16=>[4, 0, 0], 5=>[10, 2, 9], 17=>[0, 3, 1]}
I do not think there is any any function available
Just gave a try with hash
def do_my_work(data)
hash = {}
#get all keys first
arr.map{|m| m.keys}.flatten.uniq.each {|a| hash[a]=[]}
# Now iterate and fill the values
arr.each do |elm|
hash.each do |k,v|
hash[k] << (elm[k].nil? ? 0 : elm[k])
end
end
end
hash = do_my_work(t)
puts hash
# => {nil=>[1, 9, 0], 10=>[2, 2, 4], 16=>[4, 0, 0], 5=>[10, 2, 9], 17=>[0, 3, 1]}

Resources