Find elements that sum to a given number - ruby-on-rails

I have to find the list of orders that have sum of order amount equal to or greater than a given number. For example,
order # amount
o1 100
o2 50
o3 90
o4 150
o5 20
o6 30
o7 50
And if I need to find the orders in which sum of order amount is equal to 300 or greater than 300, then I should get o5, o6, o2, o7, o3,o1 or o1, o4, o3. It does not matter if order is min to max or max to min. How can I do it in a minimal way? I know first step would be to sort. I can use array sum to get sum of all elements but how do I get the elements that add up to or are just greater than a given number?
I am using Ruby on Rails with Oracle as db.

Your problem is actually quite simple. First, order the orders by decreasing quantity:
orders = [["o1", 100], ["o2", 50], ["o3", 90], ["o4", 150],
["o5", 20], ["o6", 30], ["o7", 50]]
sorted_orders = orders.sort_by(&:last).reverse
#=> [["o4", 150], ["o1", 100], ["o3", 90], ["o7", 50],
# ["o2", 50], ["o6", 30], ["o5", 20]]
Suppose:
min_req = 300
First see if min_req can be achieved by using all the items:
orders.reduce(0) { |tot,(_,qty)| tot+qty } < min_req
#=> false
Had this returned true we'd be finished: since the quantities are all non-negative, we would have computed the largest possible value for the sum of a subset of quantities.
Then simply take items in the sorted order until the quantities sum to at least min_req:
tot = 0
sorted_orders.take_while { |_,qty| tot < min_req && tot += qty }
#=> [["o4", 150], ["o1", 100], ["o3", 90]]
We can wrap this in a method:
def smallest_combination(orders, min_req)
return nil if orders.reduce(0) { |tot,(_,qty)| tot+qty } < min_req
tot = 0
orders.sort_by(&:last)
.reverse
.take_while { |_,qty| tot < min_req && tot += qty }
end
smallest_combination(orders, 300)
#=> [["o4", 150], ["o1", 100], ["o3", 90]]
smallest_combination(orders, 400)
#=> [["o4", 150], ["o1", 100], ["o3", 90], ["o7", 50], ["o2", 50]]
smallest_combination(orders, 500)
#=> nil

Related

How to improve memory usage when generating a contiguous permutation

I have code that needs to generate a contiguous permutation:
(1..n).flat_map {|x| array.map {|y| (x..y) unless x > y } }.compact
Which outputs:
[[1], [1, 2], [1, 2, 3], [1, 2, 3, 4], [1, 2, 3, 4, 5], [2], [2, 3], [2, 3, 4], [2, 3, 4, 5],
[3], [3, 4], [3, 4, 5], [4], [4, 5], [5]]
It works really well with low sizes of n, but when I have n = 100000 I run out of memory. Is there a way to improve this but keeping them contiguous?
I need to perform a reject! iterating over poisonous and allergic arrays:
array = (1..n)
permutations = array.flat_map {|x| array.map {|y| (x..y) unless x > y } }.compact
poisonous.each_with_index do |x, i|
permutations.reject! { |y| y.include?(x) && y.include?(allergic[i]) }
end
The problem:
poisonous = [3,4,6]
allergic = [5,6,7]
These numbers can't be together:
3 -> 4
4 -> 6
6 -> 7
combinations = [[1], [3,4], [4,5]]
So, [3,4] is not a valid combination.
I was able to solve the memory issue by using less variables as possible and doing calculations directly while generating the permutation, although processing time has increased a bit, it is using a lot less memory (dropped from 800mb to 32mb approximately). I'm open for suggestions to improve it even more.
counter = 0
(1..n).each {|x|
(1..n).each {|y|
counter += 1 if !(x > y) && !poisonous.each_with_index.select {|poison, i| (x..y).include?(poison) && (x..y).include?(allergic[i])}.any?
}
}
The following computes the number of "clean combinations". As no large arrays are produced it has modest memory requirements.
require 'set'
def clean_combos(n, poisonous, allergic)
arr = (1..n).to_a
bad_allergies = poisonous.zip(allergic).to_h
arr.sum do |m|
arr.combination(m).sum do |combo|
combo_set = combo.to_set
bad_allergies.any? do |poison, allergy|
combo_set.include?(poison) && combo_set.include?(allergy)
end ? 0 : 1
end
end
end
n = 10
poisonous = [3,4,6]
allergic = [5,6,7]
clean_combos(n, poisonous, allergic)
#=> 479
bad_allergies is found to equal {3=>5, 4=>6, 6=>7}.
This concludes that, for the array [1, 2,..., 10], there are 479 combinations of elements of size between 1 and 10 such that, for each combination, 3 and 5 are not both included, and neither are 4 and 5, and 6 and 7.
See Array#zip, Array#to_h, Array#sum, Array#combination, Hash#any? and Set#include?. Array#to_set is added when include 'set' is executed.
I've converted each combo to a set to speed lookups.
Some tweaks might improve efficiency, so experimentation may be called for. This may depend on the size of the array poisonous (and of allergic) relative to n.

How to optimize the solution for Two_sum code in ruby

I am woking on the solution for the following question.
Given an array of integers, return indices of the two numbers such that they add up to a specific target.
You may assume that each input would have exactly one solution.
Example:
Given nums = [2, 7, 11, 15], target = 9,
Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].
This is the solution submitted in ruby after referring the C++ code http://leetcodeunlock.com/2016/05/20/leetcode-1-two-sum-easy/ .
def two_sum(nums, target)
hash = {}
arr = []
nums.each_with_index do |value,index|
y = target - value
if(hash.find{|key,val| key == value})
arr << hash[value]
arr << index
return arr
else
hash[y] = index
end
end
end
My submission failed with the message : Time limit exceeded. Can anyone point out the mistake and help me optimise the code?
nums = [2, 7, 11, 15]
target = 9
# this will find all combinations of 2 elements that add up to 9
results = (0...nums.size).to_a.combination(2).select { |first, last| nums[first] + nums[last] == target }
results.first #=> [0, 1]
Explanation of some parts of the code:
# Get indexes of all elements of nums array
(0...nums.size).to_a #=> [0, 1, 2, 3]
# Generate all combinations of indexes of each 2 elements
(0...nums.size).to_a.combination(2).to_a #=> [[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]]
I have modified the line
if(hash.find{|key,val| key == value})
to
if(hash.key?(value))
to find if a specific key is present in the hash and this solved the issue.
Code
def sum_to_num(arr, num)
return [num/2, num/2] if num.even? && arr.count(num/2) > 1
a = arr.uniq.
group_by { |n| (2*n-num).abs }.
find { |_,a| a.size > 1 }
a.nil? ? nil : a.last
end
This method requires three or four passes through the array, if num is even, one to count the instances of num/2, one to remove duplicate values, one to group_by and one to find the pair of numbers that sum to the desired total. It therefore should be much faster than methods that evaluate every pair of the array's elements, particularly as the size of the array is increased.
Examples
sum_to_num [2, 11, 7, 15], 9
#=> [2, 7]
sum_to_num [2, 5, 2, 6, 1, -5, 4], 10
#=> [6, 4]
sum_to_num [2, 7, 11, -7, 15], 0
#=> [7, -7]
sum_to_num [2, 7, 11, 7, 15], 14 #???
sum_to_num [2, -7, 11, -7, 15], -14 #???
sum_to_num [2, 7, 11, 15], 17
#=> [2, 15]
sum_to_num [2, -11, 8, 15], 4
#=> [-11, 15]
sum_to_num [2, -11, 8, 15], -3
#=> [-11, 8]
sum_to_num [2, -11, 8, 15], 100
#=> nil
Explanation
Assume x and y sum to num. Then
2*x-num + 2*y-num = 2*(x+y) - 2*num
= 2*num - 2*num
= 0
meaning that 2*x-num and 2*y-num are either both zero or they have the opposite signs and the same absolute value. Similarly, if 2*x-num and 2*y-num sum to zero, then
2*x-num + 2*y-num = 0
2*(x+y) - 2*num = 0
meaning that n+m = num (which is hardly surprising considering that 2*x+num is a linear transformation.
Suppose
arr = [2, 5, 2, 6, 1, -5, 4]
num = 10
then
if num.even? && arr.count(num/2) > 1
#=> if 10.even? && arr.count(5) > 1
#=> if true && false
#=> false
Therefore, do not return [5,5].
b = arr.uniq
#=> [2, 5, 6, 1, -5, 4]
c = b.group_by { |n| (2*n-num).abs }
#=> {6=>[2], 0=>[5], 2=>[6, 4], 8=>[1], 20=>[-5]}
a = c.find { |_,a| a.size > 1 }
#=> [2, [6, 4]]
return nil if a.nil?
# do not return
a.last
#=> [6, 4]
I was doing this challenge for fun and wrote a cleaned up ruby solution.
def two_sum(nums, target)
hash = {}
nums.each_with_index { |number, index| hash[number] = index }
nums.each_with_index do |number, index|
difference = target - number
if hash[difference] && hash[difference] != index
return [index, hash[difference]]
end
end
end
# #param {Integer[]} nums
# #param {Integer} target
# #return {Integer[]}
def two_sum(nums, target)
length = nums.length
for i in 0..length
j = i+1
for a in j..length
if j < length
if nums[i] + nums[a] == target
return [i, a]
end
end
j+=1
end
end
[]
end
Well this is my way of solving this
def two_sum(nums, target)
nums.each_with_index do |value, index|
match_index = nums.find_index(target - value)
return [index, match_index] if match_index
end
nil
end
The above has the advantage that it stops execution when a match is found and so hopefully won't time out. :)

Active Record - Where IN with multiple columns

I have a query that needs to fetch from a table that meet two columns requirements exactly. So if I have users table with columns, age and score.
SELECT * FROM users where (age, score) IN ((5,6), (9,12), (22,44)..)
In my web app I am getting this pairs from an ajax request, and the number could be quite big. How do I construct an Active Record query for this?.
I am working on postgres database
Ideally, we will build a query string based on the input. Eg
ages_and_scores = [ [5, 6], [9, 12], [22, 44] ]
query_string = ages_and_scores.map do |pair|
"(age = #{pair[0]} AND score = #{pair[1]})"
end.join(" OR ")
# => (age = 5 AND score = 6) OR (age = 9 AND score = 12) OR (age = 22 AND score = 44)
Finally, your query will be
User.where(query_string)
You may correct the logic of how to build the query string since ages_and_scores is in a different format to my example.
Improvement
ages_and_scores = [ [5, 6], [9, 12], [22, 44] ]
query_params = []
query_template = ages_and_scores.map{ |_| "(age = ? AND score = ?)" }.join(" OR ")
# => (age = ? AND score = ?) OR (age = ? AND score = ?) OR (age = ? AND score = ?)
User.where(query_template, *ages_and_scores.flatten)
Another method, to reconstruct the exact query used by the OP based on a multicolumn IN clause.
A more compact form of SQL, with arguable better semantics:
ages_and_scores = [ [5, 6], [9, 12], [22, 44] ]
query_string = ages_and_scores.map { |pair| "(#{pair[0]},#{pair[1]})" }.join(",")
User.where("(age, score) IN (#{query_string})")
In rails 5, you can use OR, so you can do:
ages_and_scores = [ [5, 6], [9, 12], [22, 44] ]
ages_and_scores.map do |age, score|
User.where(age: age).where(score: score)
end.reduce(&:or)
# => should produce something like:
# SELECT * FROM users WHERE (`users`.`age` = 5 AND `users`.`score` = 6 OR `users`.`age` = 9 AND `users`.`score` = 12 OR `users`.`age` = 22 AND `users`.`score` = 44)
I believe this is sql-injection free and pure ActiveRecord.
Hey you can try this way in mysql:
ages_and_scores = [ [5, 6], [9, 12], [22, 44] ]
User.where("CONCAT(age,',', score) in (?)",ages_and_scores.map{|b| "#{b[0]},#{b[1]}"})
In PG database you can directly Concat using:
(age || ' ,' || score)
Another solution would be to use Arel, which would help keep things DB agnostic.
ages_and_scores = [ [5, 6], [9, 12], [22, 44] ]
first_age, first_score = ages_and_scores.shift
t = User.arel_table
users = ages_and_scores.inject(User.where(t[:age].eq(first_age)).where(t[:score].eq(first_score))) { |query, age_score| query.or(User.where(t[:age].eq(age_score[0])).where(t[:score].eq(age_score[1]))) }
Essentially how this works:
Separate the first value pair from the array
Get the arel table so that you can build the query
Use the inject method from Ruby's Enumerable module to iterate through the rest of the array adding in all of the or conditions ... but starting with the initial two values in the first where query.
Finally, you will have an Arel query which returns all of the users matching your criteria ... and it should work across any DB supported by Rails (Arel).
Base on Yuki Inoue 's comment. I have modified a little bit and it's working smoothly
ages_and_scores = [ [5, 6], [9, 12], [22, 44] ]
ages_and_scores.each do |age, score|
User.where(age: age).map(score: score)
end.reduce(&:or)
# => should produce something like:
# SELECT * FROM users WHERE (`users`.`age` = 5 AND `users`.`score` = 6 OR `users`.`age` = 9 AND `users`.`score` = 12 OR `users`.`age` = 22 AND `users`.`score` = 44)

Ruby sum from nested hash

How can I return the total scores, strokes and rounds from the following array?
players = [{"Angel Cabrera"=>{"score"=>2, "strokes"=>146, "rounds"=>3}},
{"Jason Day"=>{"score"=>1, "strokes"=>145, "rounds"=>3}},
{"Bryson DeChambeau"=>{"score"=>0, "strokes"=>144, "rounds"=>3}},
{"Sergio Garcia"=>{"score"=>0, "strokes"=>144, "rounds"=>3}},
{"Ian Poulter"=>{"score"=>5, "strokes"=>162, "rounds"=>3}},
{"Vijay Singh"=>nil},
{"Jordan Spieth"=>{"score"=>-4, "strokes"=>140, "rounds"=>3}}]
I can get the strokes by doing the following but I know that isn't the best way to do it.
players.each do |x|
x.values()[0]["strokes"]
end
How can I return the sum of the strokes given the array above?
Here are three ways of doing that.
Use the form of Hash#update that employs a block to determine the values of keys that are present in both hashes being merged
players.map { |g| g.first.last }.
compact.
each_with_object({}) { |g,h| h.update(g) { |_,o,v| o+v } }
#=> {"score"=>4, "strokes"=>881, "rounds"=>18}
The steps:
a = players.map { |g| g.first.last }
#=> [{"score"=> 2, "strokes"=>146, "rounds"=>3},
# {"score"=> 1, "strokes"=>145, "rounds"=>3},
# {"score"=> 0, "strokes"=>144, "rounds"=>3},
# {"score"=> 0, "strokes"=>144, "rounds"=>3},
# {"score"=> 5, "strokes"=>162, "rounds"=>3},
# nil,
# {"score"=>-4, "strokes"=>140, "rounds"=>3}]
b = a.compact
#=> [{"score"=> 2, "strokes"=>146, "rounds"=>3},
# {"score"=> 1, "strokes"=>145, "rounds"=>3},
# {"score"=> 0, "strokes"=>144, "rounds"=>3},
# {"score"=> 0, "strokes"=>144, "rounds"=>3},
# {"score"=> 5, "strokes"=>162, "rounds"=>3},
# {"score"=>-4, "strokes"=>140, "rounds"=>3}]
b.each_with_object({}) { |g,h| h.update(g) { |_,o,v| o+v } }
#=> {"score"=>4, "strokes"=>881, "rounds"=>18}
Here, Hash#update (aka merge!) uses the block { |_,o,v| o+v }) to determine the values of keys that are present in both hashes. The first block variable (which is not used, and therefore can be represented by the local variable _) is the key, the second (o, for "old") is the value of the key in h and the third (n, for "new") is the value of the key in g.
Use a counting hash
players.map { |g| g.first.last }.
compact.
each_with_object(Hash.new(0)) { |g,h| g.keys.each { |k| h[k] += g[k] } }
Hash.new(0) creates an empty hash with a default value of zero, represented by the block variable g. This means that if a hash h does not have a key k, h[k] returns the default value (but does not alter the hash). h[k] += g[k] above expands to:
h[k] = h[k] + g[k]
If h does not have a key k, h[k] on the right side is therefore replaced by 0.
Sum values and then convert to a hash
If you are using Ruby v1.9+ and the keys are guaranteed to have the same order in each hash, a third way it could be done is as follows:
["scores", "strokes", "rounds"].zip(
players.map { |g| g.first.last }.
compact.
map(&:values).
transpose.
map { |arr| arr.reduce(:+) }
).to_h
#=> {"scores"=>4, "strokes"=>881, "rounds"=>18}
The steps (starting from b above) are:
c = b.map(&:values)
#=> [[ 2, 146, 3],
# [ 1, 145, 3],
# [ 0, 144, 3],
# [ 0, 144, 3],
# [ 5, 162, 3],
# [-4, 140, 3]]
d = c.transpose
#=> [[ 2, 1, 0, 0, 5, -4],
# [146, 145, 144, 144, 162, 140],
# [ 3, 3, 3, 3, 3, 3]]
totals = d.map { |arr| arr.reduce(:+) }
#=> [4, 881, 18]
e = ["scores", "strokes", "rounds"].zip(totals)
#=> [["scores", 4], ["strokes", 881], ["rounds", 18]]
e.to_h
#=> {"scores"=>4, "strokes"=>881, "rounds"=>18}
Use this code:
#total= 0
players.each do |x|
a= x.values[0]
if a.class == Hash
#total += a["strokes"]
end
end
puts #total

How to sort a ruby array in ascending order but keep zero last

I am trying to sort a Ruby array with the following function
#prices = #item.prices.sort { |x,y| x.total <=> y.total }
Which orders from the lowest to the highest cost. However some products have a total of 0.00 and I want them to appear last rather than at the top.
I have tried a few things but would like some way to modify this block to sort zero at the bottom but keep the rest in ascending order.
Thanks.
Try this out, I think it is doing what you request:
#prices = #item.prices.sort {|a,b| a.total==0 ? 1 : b.total==0 ? -1 : a.total<=>b.total}
Just for the record:
>> a = [0, 1, 3, 0, 2, 5, 0, 9]
=> [0, 1, 3, 0, 2, 5, 0, 9]
>> a.sort_by { |x| x.zero? ? Float::MAX : x }
=> [1, 2, 3, 5, 9, 0, 0, 0]
On most platforms 1.0/0 will evaluate to Infinity, so you can also use this instead of Float::MAX:
>> b = [1,4,2,0,5,0]
=> [1, 4, 2, 0, 5, 0]
>> Inf = 1.0/0
=> Infinity
>> b.sort_by { |x| x.zero? ? Inf : x }
=> [1, 2, 4, 5, 0, 0]
prices = [0, 1, 2, 0,4, 3]
prices = prices.sort_by do |price|
[
if price == 0
1
else
0
end,
price
]
end
p prices
# => [1, 2, 3, 4, 0, 0]
The trick here is that arrays are compared by comparing their first elements, but if those elements are equal, then by comparing their next elements, and so on. So having the sort_by block yield an array lets you determine primary sort order, secondary sort order, and so on in a clean manner.
So devise a comparator to do that ...
if x.total == 0
# always consider 0 "largest" and no 0 can be larger than another
# (make sure 0.0 is 0 and not a number really close to 0)
# perhaps x or y should be first for other reasons as well?
1
else
# otherwise lower to higher as normal
x.total <=> y.total
end
Or without comments:
foo.sort {|x, y| if x.total == 0 then 1 else x.total <=> y.total end}
Happy coding.
This would feel less hacky and less write-only to me:
prices = prices.sort_by do |price|
zero_status = price.zero? ? 1 : 0
[zero_status, price]
end
because it's an idiomatic way of sorting something by two criteria, which is what you're doing here.

Resources