Dynamic Programming Rod Cutting
Dynamic Programming Rod Cutting
Assumptions: k ≥ 1, L is positive whole number, li are all positive, whole numbers, and prices pi are positive.
Question: Why are they reasonable assumptions to make? What do we do if these assumptions do not hold? Ask us on the forum.
L = 100
sizes = [ 1, 3, 5, 10, 30, 50, 75]
prices = [ 0.1, 0.2, 0.4, 0.9, 3.1, 5.1, 8.2]
Optimal Substructure(Attempt 1)
Suppose we have length L left for now, we can decide on a possible cut now and then optimally solve for the remaining length of the
rod.
Defining a recurrence
Based on this, we define: maxRevenue(L) as the maximum revenue that can be obtained for length L of rod. Note that sizes and
prices are also arguments to this function but in this formulation they are going to remain fixed. So we do not list them explicitly.
First, we consider the actual recurrence and then write down the base cases (though this is the reverse of how we ought to do things,
technically speaking).
maxRevenue(L) = max
Question: How can \text{maxRevenue}(\hat{L}) end up with an argument \hat{L} < 0? What is the physical meaning in terms of the
cutting a rod? Why is it OK to assign it to -\infty? If you are unable to grok these, ask us on piazza.
3.1
---------------------------------------------------------------------------
KeyboardInterrupt Traceback (most recent call last)
<ipython-input-16-2681916da924> in <module>()
1 # WARNING: Will run for a really long time
----> 2 print(maxRevenue_Recursive(50, sizes, prices))
<ipython-input-4-d11013c46a8d> in <listcomp>(.0)
9 assert len(prices) == k
10 # Let us implement the max of
---> 11 optionValues = [ (prices[i] + maxRevenue_Recursive(L-sizes[i], sizes, prices)) for i in range(k) ]
12 [Link](0) # also add 0 to cover the case where we waste
13 bestValueSoFar = max(optionValues)
<ipython-input-4-d11013c46a8d> in <listcomp>(.0)
9 assert len(prices) == k
10 # Let us implement the max of
---> 11 optionValues = [ (prices[i] + maxRevenue_Recursive(L-sizes[i], sizes, prices)) for i in range(k) ]
12 [Link](0) # also add 0 to cover the case where we waste
13 bestValueSoFar = max(optionValues)
<ipython-input-4-d11013c46a8d> in <listcomp>(.0)
9 assert len(prices) == k
10 # Let us implement the max of
---> 11 optionValues = [ (prices[i] + maxRevenue_Recursive(L-sizes[i], sizes, prices)) for i in range(k) ]
12 [Link](0) # also add 0 to cover the case where we waste
13 bestValueSoFar = max(optionValues)
<ipython-input-4-d11013c46a8d> in <listcomp>(.0)
9 assert len(prices) == k
10 # Let us implement the max of
---> 11 optionValues = [ (prices[i] + maxRevenue_Recursive(L-sizes[i], sizes, prices)) for i in range(k) ]
12 [Link](0) # also add 0 to cover the case where we waste
13 bestValueSoFar = max(optionValues)
<ipython-input-4-d11013c46a8d> in <listcomp>(.0)
9 assert len(prices) == k
10 # Let us implement the max of
---> 11 optionValues = [ (prices[i] + maxRevenue_Recursive(L-sizes[i], sizes, prices)) for i in range(k) ]
12 [Link](0) # also add 0 to cover the case where we waste
13 bestValueSoFar = max(optionValues)
<ipython-input-4-d11013c46a8d> in <listcomp>(.0)
9 assert len(prices) == k
10 # Let us implement the max of
---> 11 optionValues = [ (prices[i] + maxRevenue_Recursive(L-sizes[i], sizes, prices)) for i in range(k) ]
12 [Link](0) # also add 0 to cover the case where we waste
13 bestValueSoFar = max(optionValues)
<ipython-input-4-d11013c46a8d> in <listcomp>(.0)
9 assert len(prices) == k
10 # Let us implement the max of
---> 11 optionValues = [ (prices[i] + maxRevenue_Recursive(L-sizes[i], sizes, prices)) for i in range(k) ]
12 [Link](0) # also add 0 to cover the case where we waste
13 bestValueSoFar = max(optionValues)
<ipython-input-4-d11013c46a8d> in <listcomp>(.0)
9 assert len(prices) == k
10 # Let us implement the max of
---> 11 optionValues = [ (prices[i] + maxRevenue_Recursive(L-sizes[i], sizes, prices)) for i in range(k) ]
12 [Link](0) # also add 0 to cover the case where we waste
13 bestValueSoFar = max(optionValues)
<ipython-input-4-d11013c46a8d> in <listcomp>(.0)
9 assert len(prices) == k
10 # Let us implement the max of
---> 11 optionValues = [ (prices[i] + maxRevenue_Recursive(L-sizes[i], sizes, prices)) for i in range(k) ]
12 [Link](0) # also add 0 to cover the case where we waste
13 bestValueSoFar = max(optionValues)
<ipython-input-4-d11013c46a8d> in <listcomp>(.0)
9 assert len(prices) == k
10 # Let us implement the max of
---> 11 optionValues = [ (prices[i] + maxRevenue_Recursive(L-sizes[i], sizes, prices)) for i in range(k) ]
12 [Link](0) # also add 0 to cover the case where we waste
13 bestValueSoFar = max(optionValues)
<ipython-input-4-d11013c46a8d> in <listcomp>(.0)
9 assert len(prices) == k
10 # Let us implement the max of
---> 11 optionValues = [ (prices[i] + maxRevenue_Recursive(L-sizes[i], sizes, prices)) for i in range(k) ]
12 [Link](0) # also add 0 to cover the case where we waste
13 bestValueSoFar = max(optionValues)
<ipython-input-4-d11013c46a8d> in <listcomp>(.0)
9 assert len(prices) == k
10 # Let us implement the max of
---> 11 optionValues = [ (prices[i] + maxRevenue_Recursive(L-sizes[i], sizes, prices)) for i in range(k) ]
12 [Link](0) # also add 0 to cover the case where we waste
13 bestValueSoFar = max(optionValues)
<ipython-input-4-d11013c46a8d> in <listcomp>(.0)
9 assert len(prices) == k
10 # Let us implement the max of
---> 11 optionValues = [ (prices[i] + maxRevenue_Recursive(L-sizes[i], sizes, prices)) for i in range(k) ]
12 [Link](0) # also add 0 to cover the case where we waste
13 bestValueSoFar = max(optionValues)
<ipython-input-4-d11013c46a8d> in <listcomp>(.0)
9 assert len(prices) == k
10 # Let us implement the max of
---> 11 optionValues = [ (prices[i] + maxRevenue_Recursive(L-sizes[i], sizes, prices)) for i in range(k) ]
12 [Link](0) # also add 0 to cover the case where we waste
13 bestValueSoFar = max(optionValues)
<ipython-input-4-d11013c46a8d> in <listcomp>(.0)
9 assert len(prices) == k
10 # Let us implement the max of
---> 11 optionValues = [ (prices[i] + maxRevenue_Recursive(L-sizes[i], sizes, prices)) for i in range(k) ]
12 [Link](0) # also add 0 to cover the case where we waste
13 bestValueSoFar = max(optionValues)
<ipython-input-4-d11013c46a8d> in <listcomp>(.0)
9 assert len(prices) == k
10 # Let us implement the max of
---> 11 optionValues = [ (prices[i] + maxRevenue_Recursive(L-sizes[i], sizes, prices)) for i in range(k) ]
12 [Link](0) # also add 0 to cover the case where we waste
13 bestValueSoFar = max(optionValues)
<ipython-input-4-d11013c46a8d> in <listcomp>(.0)
9 assert len(prices) == k
10 # Let us implement the max of
---> 11 optionValues = [ (prices[i] + maxRevenue_Recursive(L-sizes[i], sizes, prices)) for i in range(k) ]
12 [Link](0) # also add 0 to cover the case where we waste
13 bestValueSoFar = max(optionValues)
<ipython-input-4-d11013c46a8d> in <listcomp>(.0)
9 assert len(prices) == k
10 # Let us implement the max of
---> 11 optionValues = [ (prices[i] + maxRevenue_Recursive(L-sizes[i], sizes, prices)) for i in range(k) ]
12 [Link](0) # also add 0 to cover the case where we waste
13 bestValueSoFar = max(optionValues)
<ipython-input-4-d11013c46a8d> in <listcomp>(.0)
9 assert len(prices) == k
10 # Let us implement the max of
---> 11 optionValues = [ (prices[i] + maxRevenue_Recursive(L-sizes[i], sizes, prices)) for i in range(k) ]
12 [Link](0) # also add 0 to cover the case where we waste
13 bestValueSoFar = max(optionValues)
<ipython-input-4-d11013c46a8d> in <listcomp>(.0)
9 assert len(prices) == k
10 # Let us implement the max of
---> 11 optionValues = [ (prices[i] + maxRevenue_Recursive(L-sizes[i], sizes, prices)) for i in range(k) ]
12 [Link](0) # also add 0 to cover the case where we waste
13 bestValueSoFar = max(optionValues)
<ipython-input-4-d11013c46a8d> in <listcomp>(.0)
9 assert len(prices) == k
10 # Let us implement the max of
---> 11 optionValues = [ (prices[i] + maxRevenue_Recursive(L-sizes[i], sizes, prices)) for i in range(k) ]
12 [Link](0) # also add 0 to cover the case where we waste
13 bestValueSoFar = max(optionValues)
<ipython-input-4-d11013c46a8d> in <listcomp>(.0)
9 assert len(prices) == k
10 # Let us implement the max of
---> 11 optionValues = [ (prices[i] + maxRevenue_Recursive(L-sizes[i], sizes, prices)) for i in range(k) ]
12 [Link](0) # also add 0 to cover the case where we waste
13 bestValueSoFar = max(optionValues)
<ipython-input-4-d11013c46a8d> in <listcomp>(.0)
9 assert len(prices) == k
10 # Let us implement the max of
---> 11 optionValues = [ (prices[i] + maxRevenue_Recursive(L-sizes[i], sizes, prices)) for i in range(k) ]
12 [Link](0) # also add 0 to cover the case where we waste
13 bestValueSoFar = max(optionValues)
<ipython-input-4-d11013c46a8d> in <listcomp>(.0)
9 assert len(prices) == k
10 # Let us implement the max of
---> 11 optionValues = [ (prices[i] + maxRevenue_Recursive(L-sizes[i], sizes, prices)) for i in range(k) ]
12 [Link](0) # also add 0 to cover the case where we waste
13 bestValueSoFar = max(optionValues)
<ipython-input-4-d11013c46a8d> in <listcomp>(.0)
9 assert len(prices) == k
10 # Let us implement the max of
---> 11 optionValues = [ (prices[i] + maxRevenue_Recursive(L-sizes[i], sizes, prices)) for i in range(k) ]
12 [Link](0) # also add 0 to cover the case where we waste
13 bestValueSoFar = max(optionValues)
KeyboardInterrupt:
Memoization
The recurrences above take too long, unfortunately. Are you wondering why? If so, just add a print statement to see what values of L are
being called. You will find that the same values of L are being called repeatedly billions of time. This causes lots of unnecessary work that
can be avoided through Memoization.
In memoization, we will simply store the results for previously computed lengths so that we can simply retrieve them from our store. To do
so we will use a memo table T.
T[0], ..., T[L] will store the values of maxRevenue for lenghts 0 to L. In particular, T[0] = 0 by the base case.
3.1
5.1000000000000005
32.8
The key is to not just record the maximum but also to record for each l , which choice yields the maximum
(3.1, [30])
(145.00000000000003, [75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 30, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 75, 75, 1])
we have lengths [l1, ..., lk] and prices [p1, ..., pk] . First let us consider the length l1 and make a decision on how
many cuts of length l1 should be made. If L is the current rod length, we can consider making k_1 = 0, \ldots, \lfloor \frac{L}{l_1}
\rfloor cuts of length l1 . The remaining sub problem will have lengths [l2,..., lk] , prices [p2, .. , pk] and remaining rod
length will be L - k1 * l1
Let \text{maxRevenue}(L, i) denote the maximum revenue we obtain if the current remaining length is L and we are allowed to look at
lengths from [li, ..., lk] and prices [pi, ... , pk] . Here L is a positive number and i ranges from 1 to k+1 .
Let us first write down the recurrence and then the base case:
\text{maxRevenue} (L, i) = \left\{ \begin{array}{ll} 0 & \leftarrow \text{Waste the current length} \\ \text{maxRevenue}(L, i+1) & \leftarrow\
\text{Apply the length}\ l_i\ \text{0 times} \\ p_i + \text{maxRevenue}(L - l_i, i+1) & \leftarrow\ \text{Apply the length}\ l_i\ \text{1 times} \\
\vdots & \vdots \\ k_i p_i + \text{maxRevenue}(L - k_i l_i, i+1) & \leftarrow\ \text{Apply the length}\ l_i\ k_i\text{times} \\ \end{array}\right.
3.1
5.1
10.7
32.8
Memoization
To speed up the maxRevenue recurrence, we design a new memo table T with rows labeled l = 0, \ldots, L and columns j = 1, \ldots, k+1.
\begin{array}{|c|l|l|l|l| l| } \hline & j=1 & j=2 & \cdots & j=k & j=k+1 \\ \hline l=0 & 0 & 0 & \cdots & 0 & 0 \\ \hline l=1 & & & \cdots & & 0 \\
\hline \vdots & & & \ddots & & 0 \\ \hline l=L & ? & & \cdots & & 0 \\ \hline \end{array}
The problem seeks the value for l = L and j = 1. The corresponding cell is marked with a ? in the figure above. Once we fill this cell, the
value can be returned as the solution.
Note that each cell (l, j) depends on the value of cells (\hat{l}, j+1) where \hat{l} < l. These values need to be computed before we are
able to compute the value for (l,j). Therefore, we start to fill the cells from l = 0, \ldots, L and j = k+1, \ldots, 0 i.e, right to left and top to
bottom.
k = len(sizes)
assert len(prices) == k
# Build a two dimensional tbl in python
# The entire table is filled with zeros
tbl = []
# Also record which option is best to reconstruct solution
sol = []
for i in range(L+1):
[Link]([0]*(k+1))
[Link]([-1]* (k+1))
for l in range(L+1):
for j in range(k-1, -1, -1): # Iterate from k-1 down to 0
ki = l // sizes[j]
valuesToConsider = [ ((i * prices[j] + tbl[ l-i*sizes[j] ][j+1]), i) for i in range(ki+1) ]
[Link]((0, -1))
(val, option_id) = max(valuesToConsider)
tbl[l][j] = val
sol[l][j] = option_id
# Now retrieve the solution
cuts = []
l = L
j = 0
while l > 0 and j < k:
option_id = sol[l][j]
if option_id == -1:
break
if (option_id > 0):
[Link]('Cut length = %d , %d times' % (sizes[j], option_id))
l = l - option_id * sizes[j]
j = j + 1
return tbl[L][0], cuts
(11.899999999999999,
['Cut length = 1 , 6 times',
'Cut length = 30 , 1 times',
'Cut length = 75 , 1 times'])
(341.2,
['Cut length = 1 , 19 times',
'Cut length = 30 , 1 times',
'Cut length = 75 , 41 times'])