Find hash in another array and substract in Ruby - ruby

I have two arrays of hashes that look like this:
a = [ { car_id: 1, motor_id: 1, quantity: 5 },
{ car_id: 1, motor_id: 2, quantity: 6 },
{ car_id: 5, motor_id: 3, quantity: 3 } ]
b = [ { car_id: 1, motor_id: 1, quantity: 2 },
{ car_id: 1, motor_id: 2, quantity: 3 } ]
I want to substract the quantities from each hash in b from the hashes in a for those hashes that have the same car_id & motor_id. So, my expected result would be:
c = [ {car_id: 1, motor_id: 1, quantity: 3},
{car_id: 1, motor_id: 2, quantity: 3},
{car_id: 5, motor_id: 3, quantity: 3 } ]
What is a good way to do this in Ruby? My thoughts were to iterate over a, and for each element find if there is any in b that have the same car_id and motor_id, if so, substract, and continue.

I suggest you first create a hash bqty for b that, for each element (hash) g of b, maps [g[:car_id], g[:motor_id]] into g[:quantity]:
bqty = b.each_with_object({}) {|g,h| h[[g[:car_id], g[:motor_id]]] = g[:quantity]}
#=> {[1, 1]=>2, [1, 2]=>3}
Next, map each element (hash) g of a to the desired hash. This is done by merging g into an empty hash h (or g.dup), then, if there is an element of bqty with the key key = [h[:car_id], h[:motor_id]], subtract bqty[key] from h[:quantity]. Note this leaves a and b unchanged.
a.map do |g|
{}.merge(g).tap do |h|
key = [h[:car_id], h[:motor_id]]
h[:quantity] -= bqty[key] if bqty.key?(key)
end
end
#=> [{:car_id=>1, :motor_id=>1, :quantity=>3},
# {:car_id=>1, :motor_id=>2, :quantity=>3},
# {:car_id=>5, :motor_id=>3, :quantity=>3}]
An alternative to the antepenultimate1 line is:
h[:quantity] -= bqty[key].to_i
since nil.to_i #=> 0.
1. How can one pass up an opportunity to use such a word?

This is basically what you suggested, except instead of an explicit check for car_id/motor_id it uses a hash instead.
ids = ->(h){h.values_at(:car_id, :motor_id)} # A function for extracting the key values
lookup = Hash[*a.flat_map{|h| [ids[h], h]}] # A map from ids -> hash w/ quantity
b.each{|h| lookup[ids[h]][:quantity] -= h[:quantity]} # Subtract each b from an a (will error if the a doesn't exist)
a == c # == true

Perhaps, you'd find the following more intuitive:
matched_result = []
while current_a = a.shift
if current_b = b.shift
if current_a[:car_id] == current_b[:car_id] && current_a[:motor_id] == current_b[:motor_id]
matched_result << {car_id: current_a[:car_id], motor_id: current_a[:motor_id], quantity: current_a[:quantity] - current_b[:quantity]}
end
else
matched_result << current_a
end
end
p matched_result

Related

Ruby how to reverse display order

I have code below:
def number_loop(n)
puts "#{n}"
while n != 1
if n >1
n -= 1
puts "#{n}"
else
n += 1
puts "#{n}"
end
end
end
number_loop(5)
when I ran the code, it displayed as below:
5
4
3
2
1
how to change the code so that it will display as:
1
2
3
4
5
Using a while loop is rare and almost never seen in Ruby.
When working with numbers use upto and downto methods or a range.
When working with objects use each and reverse_each.
Using Integer methods
1.upto(5).each { |n| puts n } # => 1, 2, 3, 4, 5
5.downto(1).each { |n| puts n } # => 5, 4, 3, 2, 1
1.step(5, 2).each { |n| puts n } # => 1, 3, 5
5.step(1, -2).each { |n| puts n } # => 5, 3, 1
5.times { |n| puts n } # => 0, 1, 2, 3, 4
Using a range
(1..5).each { |n| puts n } # => 1, 2, 3, 4, 5
And if you work with objects use
arr = ["a", "b", "c", "d", "e"]
arr.each { |str| puts str } # => a, b, c, d, e
arr.reverse_each { |str| puts str } # => e, d, c, b, a
And use map if you want to collect the results in an array
squares = (1..5).map { |n| n * n }
# => [1, 4, 9, 16, 25]
For more browse the methods of
Integer class
Enumerable module
And best install pry to explore these interactively with Pry's ls and ri commands.

Ruby sort array of hashes by child-parent relation

So we have and array of hashes
array = [
{id: 1, parent_id: 0},
{id: 2, parent_id: 1},
{id: 3, parent_id: 0},
{id: 4, parent_id: 2}
]
target_array = []
What is the most efficient and ruby way to map/sort that array to the following result:
target_array = [
{id:1,children:
[{id: 2, children: [
{id:4, children:[]}]}]},
{id: 3, children:[]}
]
p.s.The most I am capable of is iterating whole thing for each item and excluding from array hash that is already mapped to target_array.
You can solve this with recursion :
#array = [
{id: 1, parent_id: 0},
{id: 2, parent_id: 1},
{id: 3, parent_id: 0},
{id: 4, parent_id: 2}
]
def build_hierarchy target_array, n
#array.select { |h| h[:parent_id] == n }.each do |h|
target_array << {id: h[:id], children: build_hierarchy([], h[:id])}
end
target_array
end
build_hierarchy [], 0
Output :
=> [{"id"=>1, "children"=>[{"id"=>2, "children"=>[{"id"=>4, "children"=>[]}]}]}, {"id"=>3, "children"=>[]}]
Live example in this ruby fiddle http://rubyfiddle.com/riddles/9b643
I would use recursion, but the following could easily be converted to a non-recursive method.
First construct a hash linking parents to their children (p2c). For this, use the form of Hash#update (aka merge!) that uses a block to determine the values of keys that are present in both hashes being merged:
#p2c = array.each_with_object({}) { |g,h|
h.update(g[:parent_id]=>[g[:id]]) { |_,ov,nv| ov+nv } }
#=> {0=>[1, 3], 1=>[2], 2=>[4]}
There are many other ways to construct this hash. Here's another:
#p2c = Hash[array.group_by { |h| h[:parent_id] }
.map { |k,v| [k, v.map { |g| g[:id] }] }]
Now construct a recursive method whose lone argument is a parent:
def family_tree(p=0)
return [{ id: p, children: [] }] unless #p2c.key?(p)
#p2c[p].each_with_object([]) { |c,a|
a << { id:c, children: family_tree(c) } }
end
We obtain:
family_tree
#=> [ { :id=>1, :children=>
# [
# { :id=>2, :children=>
# [
# { :id=>4, :children=>[] }
# ]
# }
# ]
# },
# { :id=>3, :children=>[] }
# ]
Constructing the hash #p2c initially should make it quite efficient.
This is what I tried my way using Hash
array = [
{id: 1, parent_id: 0},
{id: 2, parent_id: 1},
{id: 3, parent_id: 0},
{id: 4, parent_id: 2}
]
target_hash = Hash.new { |h,k| h[k] = { id: nil, children: [ ] } }
array.each do |n|
id, parent_id = n.values_at(:id, :parent_id)
target_hash[id][:id] = n[:id]
target_hash[parent_id][:children].push(target_hash[id])
end
puts target_hash[0]
Output:
{:id=>nil, :children=>[{:id=>1, :children=>[{:id=>2, :children=>[{:id=>4, :children=>[]}]}]}, {:id=>3, :children=>[]}]}
I think the best one will have O(nlog(n)) time complexity at most. I'm giving my non-hash one :
array = [
{id: 1, parent_id: 0},
{id: 2, parent_id: 1},
{id: 3, parent_id: 0},
{id: 4, parent_id: 2}
]
# This takes O(nlog(n)).
array.sort! do |a, b|
k = (b[:parent_id] <=> b[:parent_id])
k == 0 ? b[:id] <=> a[:id] : k
end
# This takes O(n)
target_array = array.map do |node|
{ id: node[:id], children: [] }
end
# This takes O(nlog(n))
target_array.each_with_index do |node, index|
parent = target_array[index + 1...target_array.size].bsearch do |target_node|
target_node[:id] == array[index][:parent_id]
end
if parent
parent[:children] << node
target_array[index] = nil
end
end
# O(n)
target_array.reverse.compact
# =>
# [{:id => 1, :children =>[{:id=>2,:children=> [ {:id=>4,
# :children=>[]}]}]},
# {:id=>3, :children=>[]} ]
So mine uses O(nlog(n)) in general.
By the way, when I simply tested out the existing solutions I found Gagan Gami's to be most efficient (slightly ahead of mine), I believe it's O(nlog(n)) too, though not obvious. But the currently accepted solution takes O(n^2) time.

Access hash values via database request-esque statements

Let's say I have two arrays, each containing any number of hashes with identical keys but differing values:
ArrayA = [{value: "abcd", value_length: 4, type: 0},{value: "abcdefgh", value_length: 8, type: 1}]
ArrayB = [{value: "ab", value_length: 2, type: 0},{value: "abc", value_length: 3, type: 1}]
Despite having any number, the number of hashes will always be equal.
How could I find the largest :value_length for every hash of a certain type?
For instance, the largest :value_length for a hash with a :type of 0 would be 4. The largest :value_length for a hash with a :type of 1 would be 8.
I just can't get my head around this problem.
It would be great if I could get the answer in the way I described above, in a database request-esque fashion.
Currently, I'm trying to do it like this:
# place all :value_length values in an array
flat_A = ArrayA.flatten.map{|h| h[:value_length]} #=> [4,8]
flat_B = ArrayB.flatten.map{|h| h[:value_length]} #=> [2,3]
But I don't know how I could compare the parallel results of separate arrays. i.e. (4 with 2, 8 with 3)
loop_A = 0
loop_B = 0
flat_A.each do |a|
flat_B each do |b|
if loop_A == loop_B
comparisson_array << a << b
#something like this I just can't think!!!!
comparisson_array.index comparisson_array.max #=> 1
end
loop_B += 1
end
loop_A += 1
end
Using Array#zip:
ArrayA = [{value_length: 4, type: 0},{value_length: 8, type: 1}]
ArrayB = [{value_length: 2, type: 0},{value_length: 3, type: 1}]
ArrayA.zip(ArrayB).map {
|a, b| [a[:value_length], b[:value_length]].max
} # => [4, 8]
NOTE: I assumed that ArrayA and ArrayB are sorted by type.

Iterating over hash of arrays

I have the following:
#products = {
2 => [
#<Review id: 9, answer01: 3, score: 67, style_id: 2, consumer_id: 2,
branch_id: 2, business_id: 2>
],
15 => [
#<Review id: 10, answer01: 3, score: 67, style_id: 2, consumer_id: 2,
branch_id: 2, business_id: 2>,
#<Review id: 11, answer01: 3, score: 67, style_id: 2, consumer_id: 2,
branch_id: 2, business_id: 2>
]
}
I want to average the scores for all reviews associated with each product's hash key. How can I do this?
To iterate over a hash:
hash = {}
hash.each_pair do |key,value|
#code
end
To iterate over an array:
arr=[]
arr.each do |x|
#code
end
So iterating over a hash of arrays (let's say we're iterating over each array in each point in the hash) would be done like so:
hash = {}
hash.each_pair do |key,val|
hash[key].each do |x|
#your code, for example adding into count and total inside program scope
end
end
Yes, just use map to make and array of the scores for each product and then take the average of the array.
average_scores = {}
#products.each_pair do |key, product|
scores = product.map{ |p| p.score }
sum = scores.inject(:+) # If you are using rails, you can also use scores.sum
average = sum.to_f / scores.size
average_scores[key] = average
end
Thanks for the answer Shingetsu, I will certainly upvote it. I accidentally figured the answer out myself.
trimmed_hash = #products.sort.map{|k, v| [k, v.map{|a| a.score}]}
trimmed_hash.map{|k, v| [k, v.inject(:+).to_f/v.length]}

Ruby array subtraction without removing items more than once

The canonical Array difference example in Ruby is:
[ 1, 1, 2, 2, 3, 3, 4, 5 ] - [ 1, 2, 4 ] #=> [ 3, 3, 5 ]
What's the best way to get the following behavior instead?
[ 1, 1, 2, 2, 3, 3, 4, 5 ].subtract_once([ 1, 2, 4 ]) #=> [ 1, 2, 3, 3, 5 ]
That is, only the first instance of each matching item in the second array is removed from the first array.
Subtract values as many times as they appear in the other array, or any Enumerable:
class Array
# Subtract each passed value once:
# %w(1 2 3 1).subtract_once %w(1 1 2) # => ["3"]
# [ 1, 1, 2, 2, 3, 3, 4, 5 ].subtract_once([ 1, 2, 4 ]) => [1, 2, 3, 3, 5]
# Time complexity of O(n + m)
def subtract_once(values)
counts = values.inject(Hash.new(0)) { |h, v| h[v] += 1; h }
reject { |e| counts[e] -= 1 unless counts[e].zero? }
end
Subtract each unique value once:
require 'set'
class Array
# Subtract each unique value once:
# %w(1 2 2).subtract_once_uniq %w(1 2 2) # => [2]
# Time complexity of O((n + m) * log m)
def subtract_once_uniq(values)
# note that set is implemented
values_set = Set.new values.to_a
reject { |e| values_set.delete(e) if values_set.include?(e) }
end
end
class Array
def subtract_once(b)
h = b.inject({}) {|memo, v|
memo[v] ||= 0; memo[v] += 1; memo
}
reject { |e| h.include?(e) && (h[e] -= 1) >= 0 }
end
end
I believe this does what I want. Many thanks to #glebm
This is all I can think of so far:
[1, 2, 4].each { |x| ary.delete_at ary.index(x) }
Similar to #Jeremy Ruten's answer but accounting for the fact that some elements may not be present:
# remove each element of y from x exactly once
def array_difference(x, y)
ret = x.dup
y.each do |element|
if index = ret.index(element)
ret.delete_at(index)
end
end
ret
end
This answer also won't modify the original array as it operates, so:
x = [1,2,3]
y = [3,4,5]
z = array_difference(x, y) # => [1,2]
x == [1,2,3] # => [1,2,3]

Resources