Changing the order of my code result in error ? - ruby

def stock_picker prices
min_day , max_day , profit = 0 , 0 , 0
i = 1
while i < prices.length
(0...i).each do |day|
if prices[i] - prices[day] > profit
min_day , max_day , profit = day , i , prices[i] - prices[day]
end
#i += 1
end
i += 1
end
return "[#{min_day}, #{max_day}]"
end
prices = [17,3,6,9,15,8,6,1,10]
puts stock_picker prices
My objective is to implement a method #stock_picker that takes in an array of stock prices, one for each hypothetical day. It should return a pair of days representing the best day to buy and the best day to sell. Days start at 0.
My question is why is it that this code wouldn't work if I remove line 11 and wrote it on line 9 instead. Which will then result in the error as follows :
**PS C:\Users\dlim\mystuff> ruby stockpicker.rb
stockpicker.rb:8:in `block in stock_picker': undefined method `-' for nil:NilClass (NoMethodError)
from stockpicker.rb:7:in `each'
from stockpicker.rb:7:in `stock_picker'
from stockpicker.rb:29:in `<main>'

You're basically trying to rewrite combination and max_by :
prices = [17, 3, 6, 9, 15, 8, 6, 1, 10]
days = (0...prices.size).to_a
p days.combination(2).max_by { |day1, day2| prices[day2] - prices[day1] }
# => [1,4]
If you want both the days and the corresponding prices :
[17,3,6,9,15,8,6,1,10].each.with_index.to_a.
combination(2).max_by{|(buy, day1), (sell, day2)|
sell-buy
}
# => [[3, 1], [15, 4]]

Where is this occurring?
Your error
stockpicker.rb:8:in `block in stock_picker': undefined method `-' for nil:NilClass (NoMethodError)
from stockpicker.rb:7:in `each'
from stockpicker.rb:7:in `stock_picker'
from stockpicker.rb:29:in `<main>'
Is occurring on 8th line
if prices[i] - prices[day] > profit
When it tries to access prices[i] when i = 9 and the prices returns nil,
which does not respond to the minus - operator.
Why is this occurring?
You are a loop within a loop
(0...i).each do |day|
if prices[i] - prices[day] > profit
min_day , max_day , profit = day , i , prices[i] - prices[day]
end
#i += 1
end
Increasing the i index counter variable here does not really make sense, since
the day is already iterating over the values in the Range (0...i) and
increasing i inside this loop means its comparing every value in the prices
array once against the next day value in the prices inner array, which
will only include the first three values of the prices (meaning the values at
the end of the prices array, like 1 and 10 will never be compared against
one another); e.g.
i = 1
prices = [17,3,6,9,15,8,6,1,10]
# iteration 0
if prices[i] - prices[day] > profit
# 3 - 17 > 0 # => false
# iteration 1
i += 1 # => 2
day # => 0
if prices[i] - prices[day] > profit
# 6 - 17 > 0 # => false
i += 1 # => 3
day # => 1
# iteration 2
if prices[i] - prices[day] > profit
# 9 - 3 > 0 # => true
min_day, max_day, profit = 1, 3, 6
i += 1 # => 4
day # => 0
# iteration 3
if prices[i] - prices[day] > profit
# 15 - 17 > 0 # => false
i += 1 # => 5
day # => 1
# iteration 4
if prices[i] - prices[day] > profit
# 8 - 3 > 0 # => true
min_day, max_day, profit = 1, 5, 5
i += 1 # => 6
day # => 2
# iteration 5
if prices[i] - prices[day] > profit
# 6 - 6 > 0 # => false
i += 1 # => 7
day # => 3
# iteration 6
if prices[i] - prices[day] > profit
# 1 - 9 > 0 # => false
i += 1 # => 8
day # => 0
# iteration 7
if prices[i] - prices[day] > profit
# 10 - 17 > 0 # => false
i += 1 # => 9
day # => 1
# iteration 8
if prices[i] - prices[day] > profit
# nil - 3 > 0 # => NoMethodError
At the 8th iteration, the outer loop has caused an out of bounds error when
accessing the prices array with prices[i], but is still iterating within the
second loop with the Range of (0...7) that was set after the 5th iteration, so
it is not reaching your while loop's escape clause/expression
while i < prices.length.
Possible solution:
You could keep your working solution or you could simplify your code by using
another Range as the outer loop
(1...prices.length).each do |i|
# ...
end
Instead of increasing an index counter variable within the while loop
i = 1
while i < prices.length
# ...
i +=1
end
Which would look like this
def stock_picker prices
min_day , max_day , profit = 0 , 0 , 0
(1...prices.length).each do |i|
(0...i).each do |day|
if prices[i] - prices[day] > profit
min_day , max_day , profit = day , i , prices[i] - prices[day]
end
end
end
return "[#{min_day}, #{max_day}]"
end
prices = [17,3,6,9,15,8,6,1,10]
puts stock_picker prices
And it will iterate over the following pairs of days, as per your requirements
[i, day]
# => [1, 0], [2, 0], [3, 0], [4, 0], [5, 0], [6, 0], [7, 0], [8, 0],
# [2, 1], [3, 1], [4, 1], [5, 1], [6, 1], [7, 1], [8, 1],
# [3, 2], [4, 2], [5, 2], [6, 2], [7, 2], [8, 2],
# [4, 3], [5, 3], [6, 3], [7, 3], [8, 3],
# [5, 4], [6, 4], [7, 4], [8, 4],
# [6, 5], [7, 5], [8, 5],
# [7, 6], [8, 6],
# [8, 7]
UPDATE:
You could also simplify it again with the Ruby Array combination method
(0...prices.length).to_a.combination(2)
To generate the same unique and non-duplicate pairs of days as iterating over the Ranges implied, which would look like this
def stock_picker prices
min_day , max_day , profit = 0 , 0 , 0
(0...prices.length).to_a.combination(2).each do |day, i|
if prices[i] - prices[day] > profit
min_day , max_day , profit = day , i , prices[i] - prices[day]
end
end
return "[#{min_day}, #{max_day}]"
end
prices = [17,3,6,9,15,8,6,1,10]
puts stock_picker prices
|day, i| will access the first and second variables in the array of day index pairs within array of combinations, whilst reusing the existing variable names you have used.

Related

I'm trying to do a stock picker method on Ruby but i have some issue in my code

I'm trying to do a stock picker method that takes in an array of stock prices, one for each hypothetical day. It should return a pair of days representing the best day to buy and the best day to sell. Days start at 0.
def stock_picker stocks
pair = []
if stocks.size < 2
return "Please enter an array with a valid number of stocks"
else
buy_day = 0
sell_day = 0
profit = 0
stocks.each_with_index do |buy, index|
i = index
while (i < stocks[index..-1].size)
if ((buy - stocks[i]) > profit)
profit = buy - stocks[i]
buy_day = stocks.index(buy)
sell_day = i
end
i+= 1
end
end
pair = [buy_day,sell_day]
return pair.inspect
end
end
stock_picker([17,3,6,9,15,8,6,1,10])
It should return [1,4] instead of [0,7]
Another option is to slice the Array while iterating over it for finding the best profit:
res = ary.each_with_index.with_object([]) do |(buy_val, i), res|
highest_val = ary[i..].max
highest_idx = ary[i..].each_with_index.max[1] + i
res << [highest_val - buy_val, i, highest_idx]
end.max_by(&:first)
#=> [12, 1, 4]
Where 12 is the profit, 1 is the buy index and 4 is the sell index.
To understand how it works, run this extended version, it worth more than any written explanation:
res = []
ary.each_with_index do |buy_val, i|
p buy_val
p ary[i..]
p highest_val = ary[i..].max
p highest_idx = ary[i..].each_with_index.max[1] + i
res << [highest_val - buy_val, i, highest_idx]
p '----'
end
res #=> [[0, 0, 0], [12, 1, 4], [9, 2, 4], [6, 3, 4], [0, 4, 4], [2, 5, 8], [4, 6, 8], [9, 7, 8], [0, 8, 8]]
From the Ruby standard library I used Enumerable#each_with_index, Enumerable#each_with_object, Enumerable#max and Enumerable#max_by.
For getting the index of the max I kindly stole from Chuck (https://stackoverflow.com/a/2149874), thanks and +1. I didn't look for any better option.
As per a comment from Cary Swoveland in the linked post:
[..] a.index(a.max) will return the index of the first and
a.each_with_index.max[1] will return the index of the last [..]
So, maybe you want to use the first option to keep the time between buy and sell shorter.
Use Array#combination:
stocks.
each_with_index.
to_a.
combination(2).
select { |(_, idx1), (_, idx2)| idx2 > idx1 }.
reduce([-1, [-1, -1]]) do |(val, acc), ((v1, idx1), (v2, idx2))|
val < v2 - v1 ? [v2 - v1, [idx1, idx2]] : [val, acc]
end
#⇒ [ 12, [1, 4] ]
You can loop through the stock_prices array selecting for days with greatest positive difference. Your while condition needs to be changed.
#steps
#sets value of biggest_profit to 0(biggest_loss if looking for loss)
#sets most_profitable_days to [nil,nil]
#loops through array
#takes buy day
#loops through remainder of array
#if current day-first day>biggest_profit (first_day-current_day for loss)
#make >= for shortest holding period
#reassign biggest_profit
#most_profitable_days.first=buy_day, most_profitable_days.last=sell_day
#sell_day & buy_day are values of indices
#tests
#must accept only array
#must return array
#must return correct array
def stock_picker(arr)
#checks to make sure array inputs only are given
raise 'Only arrays allowed' unless arr.instance_of?(Array)
#sets value of biggest_profit to 0(biggest_loss if looking for loss)
biggest_profit=0
#sets most_profitable_days to [nil,nil]
most_profitable_days=[nil,nil]
#loops through array
arr.each_with_index do |starting_price, buy_day|
#takes buy day
arr.each_with_index do |final_price,sell_day|
#loops through remainder of array
next if sell_day<=buy_day
#if current day-first day>biggest_profit (first_day-current_day for loss)
#make '>=' for shortest holding period
if final_price-starting_price>=biggest_profit
#reassign biggest_profit
biggest_profit=final_price-starting_price
#most_profitable_days.first=buy_day,
most_profitable_days[0]=buy_day#+1 #to make it more user friendly
#most_profitable_days.last=sell_day
most_profitable_days[-1]=sell_day#+1 #to make it more user friendly
end
end
end
#return most_profitable_days
most_profitable_days
end
p stock_picker([3,2,5,4,12,3]) #[1,4]

find start and end of range given a point

I created this basic class right here:
class TimePeriod
MONTHS_PER_QUARTER = 3
QUARTER_RANGE = {
0 => [1,3],
1 => [4,6],
2 => [7,9],
3 => [10,12]
}
def self.quarter(month_num)
(month_num - 1) / MONTHS_PER_QUARTER
end
def self.quarter_range(month_num)
quarter = quarter month_num
t1,t2 = QUARTER_RANGE[quarter]
[Time.parse(Date::MONTHNAMES[t1]).beginning_of_month, Time.parse(Date::MONTHNAMES[t2]).end_of_month]
end
end
It gives me a time range for a quarter, given a month provided as an integer:
> TimeUtils.quarter_range(Time.now.month)
=> [2015-04-01 00:00:00 -0400, 2015-06-30 23:59:59 -0400]
So it works. However, I have cheated. I had difficulty finding the start and end, given, let's say, the month 6. So I hardcoded the values in the QUARTER_RANGE constant. I want to be able to remove that QUARTER_RANGE constant and find the beginning and end (e.g. [4,6]) without it.
So for example, if the input 3 (March),6 (June),9(September),12(December) is passed, I will know its the end of the quarter, using modulus arithmetic:
3 % 3
=> 0
6 % 3
=> 0
9 % 3
=> 0
12 % 3
=> 0
But the tricky part is given let's say 5 (May), how can I return [4,6]?
You can get the start of the quarter like this:
def qtr_start(mon)
mon - (mon - 1) % MONTHS_PER_QUARTER
end
qtr_start(9) # => 7
The end of the quarter is just that plus two:
def qtr_end(mon)
qtr_start(mon) + 2
end
qtr_end(9) # => 9
Put them together:
def qtr_start_end(mon)
start = mon - (mon - 1) % MONTHS_PER_QUARTER
[ start, start + 2 ]
end
(1..12).each do |mon|
start_end = qtr_start_end(mon)
puts "Month #{mon} is in quarter #{start_end.inspect}"
end
# => Month 1 is in quarter [1, 3]
# Month 2 is in quarter [1, 3]
# Month 3 is in quarter [1, 3]
# Month 4 is in quarter [4, 6]
# Month 5 is in quarter [4, 6]
# Month 6 is in quarter [4, 6]
# Month 7 is in quarter [7, 9]
# Month 8 is in quarter [7, 9]
# Month 9 is in quarter [7, 9]
# Month 10 is in quarter [10, 12]
# Month 11 is in quarter [10, 12]
# Month 12 is in quarter [10, 12]
(1..12).each do |m|
low = 3*((m-1)/3) + 1
p m, [low, low+2]
end
result:
1
[1, 3]
2
[1, 3]
3
[1, 3]
4
[4, 6]
5
[4, 6]
6
...
Well, I'm not sure why you want it but if you want to use modulo this should work out how you want:
month = some_num
quarter_range = []
if month%3 == 0
# end of quarter
quarter_range = [month-2, month]
elsif month%3 == 1
# beginning of quarter
quarter_range = [month, month+2]
else
# middle of quarter
quarter_range = [month-1, month+1]
end
return quarter_range
You could do the following:
MONTHS_PER_PERIOD = 3
PERIODS = (1..12).step(MONTHS_PER_PERIOD).map do |m|
m..(m+MONTHS_PER_PERIOD-1)
end
#=> [1..3, 4..6, 7..9, 10..12]
def month_to_period(m)
PERIODS.find { |p| p.cover?(m) }
end
(1..12).each { |m| puts month_to_period(m) }
1..3
1..3
1..3
4..6
4..6
4..6
7..9
7..9
7..9
10..12
10..12
10..12
MONTHS_PER_PERIOD = 2
PERIODS = (1..12).step(MONTHS_PER_PERIOD).map do |m|
m..(m+MONTHS_PER_PERIOD-1)
end
#=> [1..2, 3..4, 5..6, 7..8, 9..10, 11..12]
(1..12).each { |m| puts month_to_period(m) }
1..2
1..2
3..4
3..4
5..6
5..6
7..8
7..8
9..10
9..10
11..12
11..12

Distributions using nested loops

I would like to write a program which generates all distributions for a given n.
For example, if I enter n equal to 7, the returned result will be:
7
6 1
5 2
5 1 1
4 3
4 2 1
4 1 1 1
3 3 1
3 2 2
3 2 1 1
3 1 1 1 1
2 2 2 1
2 2 1 1 1
2 1 1 1 1 1
1 1 1 1 1 1 1
I wrote the following code:
def sum(a, n)
for i in 1..a.length
a.each do |a|
z = a+i
if z == n
print i
puts a
end
end
end
end
def distribution(n)
numbers_container = []
for i in 1..n-1
numbers_container<<i
end
sum(numbers_container,n)
end
puts "Enter n"
n = gets.chomp.to_i
distribution(n)
I'm stuck in the part where the program needs to check the sum for more than two augends. I don't have an idea how can I write a second loop.
I suggest you use recursion.
Code
def all_the_sums(n, mx=n, p=[])
return [p] if n.zero?
mx.downto(1).each_with_object([]) { |i,a|
a.concat(all_the_sums(n-i, [n-i,i].min, p + [i])) }
end
Example
all_the_sums(7)
#=> [[7],
# [6, 1],
# [5, 2], [5, 1, 1],
# [4, 3], [4, 2, 1], [4, 1, 1, 1],
# [3, 3, 1], [3, 2, 2], [3, 2, 1, 1], [3, 1, 1, 1, 1],
# [2, 2, 2, 1], [2, 2, 1, 1, 1], [2, 1, 1, 1, 1, 1],
# [1, 1, 1, 1, 1, 1, 1]]
Explanation
The argument mx is to avoid the generation of permuations of results. For example, one sequence is [4,2,1]. There are six permutations of the elements of this array (e.g., [4,1,2], [2,4,1] and so on), but we want just one.
Now consider the calculations performed by:
all_the_sums(3)
Each eight-space indentation below reflects a recursive call to the method.
We begin with
n = 3
mx = 3
p = []
return [[]] if 3.zero? #=> no return
# first value passed block by 3.downto(1)..
i = 3
a = []
# invoke all_the_sums(0, [0,3].min, []+[3])
all_the_sums(0, 0, [3])
return [[3]] if 0.zero? #=> return [[3]]
a.concat([[3]]) #=> [].concat([[3]]) => [[3]]
# second value passed block by 3.downto(1)..
i = 2
a = [[3]]
# invoke all_the_sums(1, [1,2].min, []+[2])
all_the_sums(1, 1, [2])
return [[2]] if 1.zero? #=> do not return
# first and only value passed block by 1.downto(1)..
i = 1
a = []
# invoke all_the_sums(0, [0,1].min, [2]+[1])
all_the_sums(0, 0, [2,1])
return [[2,1]] if 0.zero? #=> [[2,1]] returned
a.concat([[2,1]]) #=> [].concat([[2,1]]) => [[2,1]]
return a #=> [[2,1]]
a.concat([[2,1]]) #=> [[3]].concat([[2,1]]) => [[3],[2,1]]
# third and last value passed block by 3.downto(1)..
i = 1
a = [[3],[2,1]]
# invoke all_the_sums(2, [2,1].min, [1])
all_the_sums(2, 1, [1])
return [] if 2.zero? #=> [] not returned
# first and only value passed block by 1.downto(1)..
i = 1
a = []
# invoke all_the_sums(1, [1,1].min, [1]+[1])
all_the_sums(1, 1, [1,1])
return [1,1] if 1.zero? #=> [1,1] not returned
# first and only value passed block by 1.downto(1)..
i = 1
a = []
# invoke all_the_sums(0, [0,1].min, [1,1]+[1]])
all_the_sums(0, 0, [1,1,1])
return [1,1,1] if 1.zero?
#=> return [1,1,1]
a.concat([[1,1,1]]) #=> [[1,1,1]]
return a #=> [[1,1,1]]
a.concat([[1,1,1]]) #=> [].concat([[1,1,1]]) => [[1,1,1]]
return a #=> [[1,1,1]]
a.concat([[1,1,1]]) #=> [[3],[2,1]].concat([[1,1,1]])
return a #=> [[3],[2,1],[1,1,1]]
You can use unary with parameters to have infinite amounts of parameters:
def test_method *parameters
puts parameters
puts parameters.class
end
test_method("a", "b", "c", "d")
So, parameters inside the block becomes an array of parameters. You can then easly loop through them:
parameters.each { |par| p par }
Also, don't use for loops for this as they are less readable than using each methods.
[1..n-1].each do i
# body omitted
end
I think you be able to work it out if you tried to call sum recursively. After this bit:
print i
puts a
Try calling sum again, like this:
sum((1..a).to_a, a)
It won't solve it, but it might lead you in the right direction.

Number of possible equations of K numbers whose sum is N in ruby

I have to create a program in ruby on rails so that it will take less time to solve the particular condition. Now i am to getting the less response time for k=4 but response time is more in case of k>5
Problem:
Problem is response time.
When value of k is more than 5 (k>5) response time is too late for given below equation.
Input: K, N (where 0 < N < ∞, 0 < K < ∞, and K <= N)
Output: Number of possible equations of K numbers whose sum is N.
Example Input:
N=10 K=3
Example Output:
Total unique equations = 8
1 + 1 + 8 = 10
1 + 2 + 7 = 10
1 + 3 + 6 = 10
1 + 4 + 5 = 10
2 + 2 + 6 = 10
2 + 3 + 5 = 10
2 + 4 + 4 = 10
3 + 3 + 4 = 10
For reference, N=100, K=3 should have a result of 833 unique sets
Here is my ruby code
module Combination
module Pairs
class Equation
def initialize(params)
#arr=[]
#n = params[:n]
#k = params[:k]
end
#To create possible equations
def create_equations
return "Please Enter value of n and k" if #k.blank? && #n.blank?
begin
Integer(#k)
rescue
return "Error: Please enter any +ve integer value of k"
end
begin
Integer(#n)
rescue
return "Error: Please enter any +ve integer value of n"
end
return "Please enter k < n" if #n < #k
create_equations_sum
end
def create_equations_sum
aar = []
#arr = []
#list_elements=(1..#n).to_a
(1..#k-1).each do |i|
aar << [*0..#n-1]
end
traverse([], aar, 0)
return #arr.uniq #return result
end
#To check sum
def generate_sum(*args)
new_elements = []
total= 0
args.flatten.each do |arg|
total += #list_elements[arg]
new_elements << #list_elements[arg]
end
if total < #n
new_elements << #n - total
#arr << new_elements.sort
else
return
end
end
def innerloop(arrayOfCurrentValues)
generate_sum(arrayOfCurrentValues)
end
#Recursive method to create dynamic nested loops.
def traverse(accumulated,params, index)
if (index==params.size)
return innerloop(accumulated)
end
currentParam = params[index]
currentParam.each do |currentElementOfCurrentParam|
traverse(accumulated+[currentElementOfCurrentParam],params, index+1)
end
end
end
end
end
run the code using
params = {:n =>100, :k =>4}
c = Combination::Pairs::Equation.new(params)
c.create_equations
Here are two ways to compute your answer. The first is simple but not very efficient; the second, which relies on an optimization technique, is much faster, but requires considerably more code.
Compact but Inefficient
This is a compact way to do the calculation, making use of the method Array#repeated_combination:
Code
def combos(n,k)
[*(1..n-k+1)].repeated_combination(3).select { |a| a.reduce(:+) == n }
end
Examples
combos(10,3)
#=> [[1, 1, 8], [1, 2, 7], [1, 3, 6], [1, 4, 5],
# [2, 2, 6], [2, 3, 5], [2, 4, 4], [3, 3, 4]]
combos(100,4).size
#=> 832
combos(1000,3).size
#=> 83333
Comment
The first two calculations take well under one second, but the third took a couple of minutes.
More efficient, but increased complexity
Code
def combos(n,k)
return nil if k.zero?
return [n] if k==1
return [1]*k if k==n
h = (1..k-1).each_with_object({}) { |i,h| h[i]=[[1]*i] }
(2..n-k+1).each do |i|
g = (1..[n/i,k].min).each_with_object(Hash.new {|h,k| h[k]=[]}) do |m,f|
im = [i]*m
mxi = m*i
if m==k
f[mxi].concat(im) if mxi==n
else
f[mxi] << im if mxi + (k-m)*(i+1) <= n
(1..[(i-1)*(k-m), n-mxi].min).each do |j|
h[j].each do |a|
f[mxi+j].concat([a+im]) if
((a.size==k-m && mxi+j==n) ||
(a.size<k-m && (mxi+j+(k-m-a.size)*(i+1))<=n))
end
end
end
end
g.update({ n=>[[i]*k] }) if i*k == n
h.update(g) { |k,ov,nv| ov+nv }
end
h[n]
end
Examples
p combos(10,3)
#=> [[3, 3, 4], [2, 4, 4], [2, 3, 5], [1, 4, 5],
# [2, 2, 6], [1, 3, 6], [1, 2, 7], [1, 1, 8]]
p combos(10,4)
#=> [[2, 2, 3, 3], [1, 3, 3, 3], [2, 2, 2, 4], [1, 2, 3, 4], [1, 1, 4, 4],
# [1, 2, 2, 5], [1, 1, 3, 5], [1, 1, 2, 6], [1, 1, 1, 7]]
puts "size=#{combos(100 ,3).size}" #=> 833
puts "size=#{combos(100 ,5).size}" #=> 38224
puts "size=#{combos(1000,3).size}" #=> 83333
Comment
The calculation combos(1000,3).size took about five seconds, the others were all well under one second.
Explanation
This method employs dynamic programming to compute a solution. The state variable is the largest positive integer used to compute arrays with sizes no more than k whose elements sum to no more than n. Begin with the largest integer equal to one. The next step is compute all combinations of k or fewer elements that include the numbers 1 and 2, then 1, 2 and 3, and so on, until we have all combinations of k or fewer elements that include the numbers 1 through n. We then select all combinations of k elements that sum to n from the last calculation.
Suppose
k => 3
n => 7
then
h = (1..k-1).each_with_object({}) { |i,h| h[i]=[[1]*i] }
#=> (1..2).each_with_object({}) { |i,h| h[i]=[[1]*i] }
#=> { 1=>[[1]], 2=>[[1,1]] }
This reads, using the only the number 1, [[1]] is the array of all arrays that sum to 1 and [[1,1]] is the array of all arrays that sum to 2.
Notice that this does not include the element 3=>[[1,1,1]]. That's because, already having k=3 elments, if cannot be combined with any other elements, and sums to 3 < 7.
We next execute:
enum = (2..n-k+1).each #=> #<Enumerator: 2..5:each>
We can convert this enumerator to an array to see what values it will pass into its block:
enum.to_a #=> [2, 3, 4, 5]
As n => 7 you may be wondering why this array ends at 5. That's because there are no arrays containing three positive integers, of which at least one is a 6 or a 7, whose elements sum to 7.
The first value enum passes into the block, which is represented by the block variable i, is 2. We will now compute a hash g that includes all arrays that sum to n => 7 or less, have at most k => 3 elements, include one or more 2's and zero or more 1's. (That's a bit of a mouthful, but it's still not precise, as I will explain.)
enum2 = (1..[n/i,k].min).each_with_object(Hash.new {|h,k| h[k]=[]})
#=> (1..[7/2,3].min).each_with_object(Hash.new {|h,k| h[k]=[]})
#=> (1..3).each_with_object(Hash.new {|h,k| h[k]=[]})
Enumerable#each_with_object creates an initially-empty hash that is represented by the block variable f. The default value of this hash is such that:
f[k] << o
is equivalent to
(f[k] |= []) << o
meaning that if f does not have a key k,
f[k] = []
is executed before
f[k] << o
is performed.
enum2 will pass the following elements into its block:
enum2.to_a #=> => [[1, {}], [2, {}], [3, {}]]
(though the hash may not be empty when elements after the first are passed into the block). The first element passed to the block is [1, {}], represented by the block variables:
m => 1
f => Hash.new {|h,k| h[k]=[]}
m => 1 means we will intially construct arrays that contain one (i=) 2.
im = [i]*m #=> [2]*1 => [2]
mxi = m*i #=> 2*1 => 2
As (m == k) #=> (1 == 3) => false, we next execute
f[mxi] << im if mxi + (k-m)*(i+1) <= n
#=> f[2] << [2] if 2 + (3-1)*(1+1) <= 7
#=> f[2] << [2] if 8 <= 7
This considers whether [2] should be added to f[2] without adding any integers j < i = 2. (We have yet to consider the combining of one 2 with integers less than 2 [i.e., 1].) As 8 <= 7, we do not add [2] to f[2]. The reason is that, for this to be part of an array of length k=3, it would be of the form [2,x,y], where x > 2 and y > 2, so 2+x+y >= 2+3+3 = 8 > n = 7. Clear as mud?
Next,
enum3 = (1..[(i-1)*(k-m), n-mxi].min).each
#=> = (1..[2,5].min).each
#=> = (1..2).each
#=> #<Enumerator: 1..2:each>
which passes the values
enum3.to_a #=> [1, 2]
into its block, represented by the block variable j, which is the key of the hash h. What we will be doing here is combine one 2 (m=1) with arrays of elements containing integers up to 1 (i.e., just 1) that sum to j, so the elements of the resulting array will sum to m * i + j => 1 * 2 + j => 2 + j.
The reason enum3 does not pass values of j greater than 2 into its block is that h[l] is empty for l > 2 (but its a little more complicated when i > 2).
For j => 1,
h[j] #=> [[1]]
enum4 = h[j].each #=> #<Enumerator: [[1]]:each>
enum4.to_a #=> [[1]]
a #=> [1]
so
f[mxi+j].concat([a+im]) if
((a.size==k-m && mxi+j==n) || (a.size<k-m && (mxi+j+(k-m-a.size)*(i+1))<=n))
#=> f[2+1].concat([[1]+[2]) if ((1==2 && 2+1==7) || (1<=3-1 && (2+1+(1)*(3)<=7))
#=> f[3].concat([1,2]) if ((false && false) || (1<=2 && (6<=7))
#=> f[3] = [] << [[1,2]] if (false || (true && true)
#=> f[3] = [[1,2]] if true
So the expression on the left is evaluated. Again, the conditional expressions are a little complex. Consider first:
a.size==k-m && mxi+j==n
which is equivalent to:
([2] + f[j]).size == k && ([2] + f[j]).reduce(:+) == n
That is, include the array [2] + f[j] if it has k elements that sum to n.
The second condition considers whether the array the arrays [2] + f[j] with fewer than k elements can be "completed" with integers l > i = 2 and have a sum of n or less.
Now, f #=> {3=>[[1, 2]]}.
We now increment j to 2 and consider arrays [2] + h[2], whose elements will total 4.
For j => 2,
h[j] #=> [[1, 1]]
enum4 = h[j].each #=> #<Enumerator: [[1, 1]]:each>
enum4.to_a #=> [[1, 1]]
a #=> [1, 1]
f[mxi+j].concat([a+im]) if
((a.size==k-m && mxi+j==n) || (a.size<k-m && (mxi+j+(k-m-a.size)*(i+1)<=n))
#=> f[4].concat([1, 1, 2]) if ((2==(3-1) && 2+2 == 7) || (2+2+(3-1-2)*(3)<=7))
#=> f[4].concat([1, 1, 2]) if (true && false) || (false && true))
#=> f[4].concat([1, 1, 2]) if false
so this operation is not performed (since [1,1,2].size => 3 = k and [1,1,2].reduce(:+) => 4 < 7 = n.
We now increment m to 2, meaning that we will construct arrays having two (i=) 2's. After doing so, we see that:
f={3=>[[1, 2]], 4=>[[2, 2]]}
and no other arrays are added when m => 3, so we have:
g #=> {3=>[[1, 2]], 4=>[[2, 2]]}
The statement
g.update({ n=>[i]*k }) if i*k == n
#=> g.update({ 7=>[2,2,2] }) if 6 == 7
adds the element 7=>[2,2,2] to the hash g if the sum of its elements equals n, which it does not.
We now fold g into h, using Hash#update (aka Hash#merge!):
h.update(g) { |k,ov,nv| ov+nv }
#=> {}.update({3=>[[1, 2]], 4=>[[2, 2]]} { |k,ov,nv| ov+nv }
#=> {1=>[[1]], 2=>[[1, 1]], 3=>[[1, 2]], 4=>[[2, 2]]}
Now h contains all the arrays (values) whose keys are the array totals, comprised of the integers 1 and 2, which have at most 3 elements and sum to at most 7, excluding those arrays with fewer than 3 elements which cannot sum to 7 when integers greater than two are added.
The operations performed are as follows:
i m j f
h #=> { 1=>[[1]], 2=>[[1,1]] }
2 1 1 {3=>[[1, 2]]}
2 1 2 {3=>[[1, 2]]}
2 2 1 {3=>[[1, 2]], 4=>[[2, 2]]}
{3=>[[1, 2]], 4=>[[2, 2]]}
3 1 1 {}
3 1 2 {}
3 1 3 {}
3 1 4 {7=>[[2, 2, 3]]}
3 2 1 {7=>[[2, 2, 3], [1, 3, 3]]}
g before g.update: {7=>[[2, 2, 3], [1, 3, 3]]}
g after g.update: {7=>[[2, 2, 3], [1, 3, 3]]}
h after h.update(g): {1=>[[1]],
2=>[[1, 1]],
3=>[[1, 2]],
4=>[[2, 2]],
7=>[[2, 2, 3], [1, 3, 3]]}
4 1 1 {}
4 1 2 {}
4 1 3 {7=>[[1, 2, 4]]}
g before g.update: {7=>[[1, 2, 4]]}
g after g.update: {7=>[[1, 2, 4]]}
h after h.update(g): {1=>[[1]],
2=>[[1, 1]],
3=>[[1, 2]],
4=>[[2, 2]],
7=>[[2, 2, 3], [1, 3, 3], [1, 2, 4]]}
5 1 1 {}
5 1 2 {7=>[[1, 1, 5]]}
g before g.update: {7=>[[1, 1, 5]]}
g after g.update: {7=>[[1, 1, 5]]}
h after h.update(g): {1=>[[1]],
2=>[[1, 1]],
3=>[[1, 2]],
4=>[[2, 2]],
7=>[[2, 2, 3], [1, 3, 3], [1, 2, 4], [1, 1, 5]]}
And lastly,
h[n].select { |a| a.size == k }
#=> h[7].select { |a| a.size == 3 }
#=> [[2, 2, 3], [1, 3, 3], [1, 2, 4], [1, 1, 5]]
#Cary's answer is very in-depth and impressive, but it appears to me that there is a much more naive solution, which proved to be much more efficient as well - good old recursion:
def combos(n,k)
if k == 1
return [n]
end
(1..n-1).flat_map do |i|
combos(n-i,k-1).map { |r| [i, *r].sort }
end.uniq
end
This solution simply reduces the problem each level by taking decreasing the target sum by each number between 1 and the previous target sum, while reducing k by one. Now make sure you don't have duplicates (by sort and uniq) - and you have your answer...
This is great for k < 5, and is much faster than Cary's solution, but as k gets larger, I found that it makes much too many iterations, sort and uniq took a very big toll on the calculation.
So I made sure that won't be needed, by making sure I get only sorted answers - each recursion should check only numbers larger than those already used:
def combos(n,k,min = 1)
if n < k || n < min
return []
end
if k == 1
return [n]
end
(min..n-1).flat_map do |i|
combos(n-i,k-1, i).map { |r| [i, *r] }
end
end
This solution is on par with Cary's on combos(100, 7):
user system total real
My Solution 2.570000 0.010000 2.580000 ( 2.695615)
Cary's 2.590000 0.000000 2.590000 ( 2.609374)
But we can do better: caching! This recursion does many calculations again and again, so caching stuff we already did will save us a lot of work when dealing with long sums:
def combos(n,k,min = 1, cache = {})
if n < k || n < min
return []
end
cache[[n,k,min]] ||= begin
if k == 1
return [n]
end
(min..n-1).flat_map do |i|
combos(n-i,k-1, i, cache).map { |r| [i, *r] }
end
end
end
This solution is mighty fast and passes Cary's solution for large n by light-years:
Benchmark.bm do |bm|
bm.report('Uri') { combos(1000, 3) }
bm.report('Cary') { combos_cary(1000, 3) }
end
user system total real
Uri 0.200000 0.000000 0.200000 ( 0.214080)
Cary 7.210000 0.000000 7.210000 ( 7.220085)
And is on par with k as high as 9, and I believe it is still less complicated than his solution.
You want the number of integer partitions of n into exactly k summands. There is a (computationally) somewhat ugly recurrence for that number.
The idea is this: let P(n,k) be the number of ways to partition n into k nonzero summands; then P(n,k) = P(n-1,k-1) + P(n-k,k). Proof: every partition either contains a 1 or it doesn't contain a 1 as one of the summands. The first case P(n-1,k-1) calculates the number of cases where there is a 1 in the sum; take that 1 away from the sum and partition the remaining n-1 into the now available k-1 summands. The second case P(n-k,k) considers the case where every summand is strictly greater than 1; to do that, reduce all of the k summands by 1 and recurse from there. Obviously, P(n,1) = 1 for all n > 0.
Here's a link that mentions that probably, no closed form is known for general k.

ruby get next value on each loop

Can I get the next value in an each loop?
(1..5).each do |i|
#store = i + (next value of i)
end
where the answer would be..
1 + 2 + 2 + 3 + 3 + 4 + 4 + 5 + 5 = 29
And also can I get the next of the next value?
From as early as Ruby 1.8.7, the Enumerable module has had a method each_cons that does almost exactly what you want:
each_cons(n) { ... } → nil
each_cons(n) → an_enumerator
Iterates the given block for each array of consecutive <n> elements. If no block is given, returns an enumerator.
e.g.:
(1..10).each_cons(3) { |a| p a }
# outputs below
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]
[4, 5, 6]
[5, 6, 7]
[6, 7, 8]
[7, 8, 9]
[8, 9, 10]
The only problem is that it doesn't repeat the last element. But that's trivial to fix. Specifically, you want
store = 0
range = 1..5
range.each_cons(2) do |i, next_value_of_i|
store += i + next_value_of_i
end
store += range.end
p store # => 29
But you could also do this:
range = 1..5
result = range.each_cons(2).reduce(:+).reduce(:+) + range.end
p result # => 29
Alternatively, you may find the following to be more readable:
result = range.end + range.each_cons(2)
.reduce(:+)
.reduce(:+)
Like this:
range = 1..5
store = 0
range.each_with_index do |value, i|
next_value = range.to_a[i+1].nil? ? 0 : range.to_a[i+1]
store += value + next_value
end
p store # => 29
There may be better ways, but this works.
You can get the next of the next value like this:
range.to_a[i+2]
One approach that wouldn't use indexes is Enumerable#zip:
range = 11..15
store = 0 # This is horrible imperative programming
range.zip(range.to_a[1..-1], range.to_a[2..-1]) do |x, y, z|
# nil.to_i equals 0
store += [x, y, z].map(&:to_i).inject(:+)
end
store

Resources