From fb1f3809da2dd01f8612540cb1d61ebd4a4e0335 Mon Sep 17 00:00:00 2001 From: Maxwell Aladago Date: Wed, 4 Sep 2019 13:41:56 -0400 Subject: [PATCH 1/4] changing typo --- divide_and_conquer/convex_hull.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/divide_and_conquer/convex_hull.py b/divide_and_conquer/convex_hull.py index 42219794aed1..a0c319e766da 100644 --- a/divide_and_conquer/convex_hull.py +++ b/divide_and_conquer/convex_hull.py @@ -7,7 +7,7 @@ Two algorithms have been implemented for the convex hull problem here. 1. A brute-force algorithm which runs in O(n^3) -2. A divide-and-conquer algorithm which runs in O(n^3) +2. A divide-and-conquer algorithm which runs in O(n log(n)) There are other several other algorithms for the convex hull problem which have not been implemented here, yet. From ab09a861d88acfccc0f5539845e3bb7fa4fedaeb Mon Sep 17 00:00:00 2001 From: Maxwell Aladago Date: Wed, 4 Sep 2019 15:48:39 -0400 Subject: [PATCH 2/4] fully refactored the rod-cutting module --- dynamic_programming/rod_cutting.py | 220 +++++++++++++++++++++++------ 1 file changed, 173 insertions(+), 47 deletions(-) diff --git a/dynamic_programming/rod_cutting.py b/dynamic_programming/rod_cutting.py index c3111dcfc8a1..46d5d62b408b 100644 --- a/dynamic_programming/rod_cutting.py +++ b/dynamic_programming/rod_cutting.py @@ -1,57 +1,183 @@ -from typing import List +""" +This module provides two implementations for the rod-cutting problem: +1. A naive recursive implementation which has an exponential runtime +2. Two dynamic programming implementations which have quadratic runtime -def rod_cutting(prices: List[int],length: int) -> int: +The rod-cutting problem is the problem of finding the maximum possible revenue +obtainable from a rod of length ``n`` given a list of prices for each integral piece +of the rod. The maximum revenue can thus be obtained by cutting the rod and selling the +pieces separately or not cutting it at all if the price of it is the maximum obtainable. + +""" + + +def naive_cut_rod_recursive(n: int, prices: list): """ - Given a rod of length n and array of prices that indicate price at each length. - Determine the maximum value obtainable by cutting up the rod and selling the pieces - - >>> rod_cutting([1,5,8,9],4) + Solves the rod-cutting problem via naively without using the benefit of dynamic programming. + The results is the same sub-problems are solved several times leading to an exponential runtime + + Runtime: O(2^n) + + Arguments + ------- + n: int, the length of the rod + prices: list, the prices for each piece of rod. ``p[i-i]`` is the + price for a rod of length ``i`` + + Returns + ------- + The maximum revenue obtainable for a rod of length n given the list of prices for each piece. + + Examples + -------- + >>> naive_cut_rod_recursive(4, [1, 5, 8, 9]) 10 - >>> rod_cutting([1,1,1],3) - 3 - >>> rod_cutting([1,2,3], -1) - Traceback (most recent call last): - ValueError: Given integer must be greater than 1, not -1 - >>> rod_cutting([1,2,3], 3.2) - Traceback (most recent call last): - TypeError: Must be int, not float - >>> rod_cutting([], 3) - Traceback (most recent call last): - AssertionError: prices list is shorted than length: 3 - - - - Args: - prices: list indicating price at each length, where prices[0] = 0 indicating rod of zero length has no value - length: length of rod - - Returns: - Maximum revenue attainable by cutting up the rod in any way. - """ - - prices.insert(0, 0) - if not isinstance(length, int): - raise TypeError('Must be int, not {0}'.format(type(length).__name__)) - if length < 0: - raise ValueError('Given integer must be greater than 1, not {0}'.format(length)) - assert len(prices) - 1 >= length, "prices list is shorted than length: {0}".format(length) - - return rod_cutting_recursive(prices, length) - -def rod_cutting_recursive(prices: List[int],length: int) -> int: - #base case - if length == 0: + >>> naive_cut_rod_recursive(10, [1, 5, 8, 9, 10, 17, 17, 20, 24, 30]) + 30 + """ + + _enforce_args(n, prices) + if n == 0: return 0 - value = float('-inf') - for firstCutLocation in range(1,length+1): - value = max(value, prices[firstCutLocation]+rod_cutting_recursive(prices,length - firstCutLocation)) - return value + max_revue = float("-inf") + for i in range(1, n + 1): + max_revue = max(max_revue, prices[i - 1] + naive_cut_rod_recursive(n - i, prices)) + + return max_revue + + +def top_down_cut_rod(n: int, prices: list): + """ + Constructs a top-down dynamic programming solution for the rod-cutting problem + via memoization. This function serves as a wrapper for _top_down_cut_rod_recursive + + Runtime: O(n^2) + + Arguments + -------- + n: int, the length of the rod + prices: list, the prices for each piece of rod. ``p[i-i]`` is the + price for a rod of length ``i`` + + Note + ---- + For convenience and because Python's lists using 0-indexing, length(max_rev) = n + 1, + to accommodate for the revenue obtainable from a rod of length 0. + + Returns + ------- + The maximum revenue obtainable for a rod of length n given the list of prices for each piece. + + Examples + ------- + >>> top_down_cut_rod(4, [1, 5, 8, 9]) + 10 + >>> top_down_cut_rod(10, [1, 5, 8, 9, 10, 17, 17, 20, 24, 30]) + 30 + """ + _enforce_args(n, prices) + max_rev = [float("-inf") for _ in range(n + 1)] + return _top_down_cut_rod_recursive(n, prices, max_rev) + + +def _top_down_cut_rod_recursive(n: int, prices: list, max_rev: list): + """ + Constructs a top-down dynamic programming solution for the rod-cutting problem + via memoization. + + Runtime: O(n^2) + + Arguments + -------- + n: int, the length of the rod + prices: list, the prices for each piece of rod. ``p[i-i]`` is the + price for a rod of length ``i`` + max_rev: list, the computed maximum revenue for a piece of rod. + ``max_rev[i]`` is the maximum revenue obtainable for a rod of length ``i`` + + Returns + ------- + The maximum revenue obtainable for a rod of length n given the list of prices for each piece. + """ + if max_rev[n] >= 0: + return max_rev[n] + elif n == 0: + return 0 + else: + max_revenue = float("-inf") + for i in range(1, n + 1): + max_revenue = max(max_revenue, prices[i - 1] + _top_down_cut_rod_recursive(n - i, prices, max_rev)) + + max_rev[n] = max_revenue + + return max_rev[n] + + +def bottom_up_cut_rod(n: int, prices: list): + """ + Constructs a bottom-up dynamic programming solution for the rod-cutting problem + + Runtime: O(n^2) + + Arguments + ---------- + n: int, the maximum length of the rod. + prices: list, the prices for each piece of rod. ``p[i-i]`` is the + price for a rod of length ``i`` + + Returns + ------- + The maximum revenue obtainable from cutting a rod of length n given + the prices for each piece of rod p. + + Examples + ------- + >>> bottom_up_cut_rod(4, [1, 5, 8, 9]) + 10 + >>> bottom_up_cut_rod(10, [1, 5, 8, 9, 10, 17, 17, 20, 24, 30]) + 30 + """ + _enforce_args(n, prices) + + # length(max_rev) = n + 1, to accommodate for the revenue obtainable from a rod of length 0. + max_rev = [float("-inf") for _ in range(n + 1)] + max_rev[0] = 0 + + for i in range(1, n + 1): + max_revenue_i = max_rev[i] + for j in range(1, i + 1): + max_revenue_i = max(max_revenue_i, prices[j - 1] + max_rev[i - j]) + + max_rev[i] = max_revenue_i + + return max_rev[n] + + +def _enforce_args(n: int, prices: list): + if n < 0: + raise ValueError(f"n must be greater than or equal to 0. Got n = {n}") + + if n > len(prices): + raise ValueError(f"Each integral piece of road must have a corresponding " + f"price. Got n = {n} but length of prices = {len(prices)}") def main(): - assert rod_cutting([1,5,8,9,10,17,17,20,24,30],10) == 30 - # print(rod_cutting([],0)) + prices = [6, 10, 12, 15, 20, 23] + n = len(prices) + + # the best revenue comes from cutting 6 pieces of the road, each + # of length 1. Thus, yielding 6 * 6 = 36. + expected_max_revenue = 36 + + max_rev_top_down = top_down_cut_rod(n, prices) + max_rev_bottom_up = bottom_up_cut_rod(n, prices) + max_rev_naive = naive_cut_rod_recursive(n, prices) + + assert expected_max_revenue == max_rev_top_down + assert max_rev_top_down == max_rev_bottom_up + assert max_rev_bottom_up == max_rev_naive + if __name__ == '__main__': main() - From 5bd088783eb12d143d80e293eff1873491674547 Mon Sep 17 00:00:00 2001 From: Maxwell Aladago Date: Wed, 4 Sep 2019 15:55:08 -0400 Subject: [PATCH 3/4] more documentations --- divide_and_conquer/convex_hull.py | 2 +- dynamic_programming/rod_cutting.py | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/divide_and_conquer/convex_hull.py b/divide_and_conquer/convex_hull.py index a0c319e766da..42219794aed1 100644 --- a/divide_and_conquer/convex_hull.py +++ b/divide_and_conquer/convex_hull.py @@ -7,7 +7,7 @@ Two algorithms have been implemented for the convex hull problem here. 1. A brute-force algorithm which runs in O(n^3) -2. A divide-and-conquer algorithm which runs in O(n log(n)) +2. A divide-and-conquer algorithm which runs in O(n^3) There are other several other algorithms for the convex hull problem which have not been implemented here, yet. diff --git a/dynamic_programming/rod_cutting.py b/dynamic_programming/rod_cutting.py index 46d5d62b408b..ccf1002011cf 100644 --- a/dynamic_programming/rod_cutting.py +++ b/dynamic_programming/rod_cutting.py @@ -154,11 +154,21 @@ def bottom_up_cut_rod(n: int, prices: list): def _enforce_args(n: int, prices: list): + """ + Basic checks on the arguments to the rod-cutting algorithms + + n: int, the length of the rod + prices: list, the price list for each piece of rod. + + Throws ValueError: + + if n is negative or there are fewer items in the price list than the length of the rod + """ if n < 0: raise ValueError(f"n must be greater than or equal to 0. Got n = {n}") if n > len(prices): - raise ValueError(f"Each integral piece of road must have a corresponding " + raise ValueError(f"Each integral piece of rod must have a corresponding " f"price. Got n = {n} but length of prices = {len(prices)}") @@ -166,7 +176,7 @@ def main(): prices = [6, 10, 12, 15, 20, 23] n = len(prices) - # the best revenue comes from cutting 6 pieces of the road, each + # the best revenue comes from cutting 6 pieces of the rod, each # of length 1. Thus, yielding 6 * 6 = 36. expected_max_revenue = 36 From 2af5bf9e7274dbcc1e570c9e268b9edf0c34bb5b Mon Sep 17 00:00:00 2001 From: Maxwell Aladago Date: Wed, 4 Sep 2019 15:56:56 -0400 Subject: [PATCH 4/4] rewording --- dynamic_programming/rod_cutting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dynamic_programming/rod_cutting.py b/dynamic_programming/rod_cutting.py index ccf1002011cf..5b52eaca7c89 100644 --- a/dynamic_programming/rod_cutting.py +++ b/dynamic_programming/rod_cutting.py @@ -176,8 +176,8 @@ def main(): prices = [6, 10, 12, 15, 20, 23] n = len(prices) - # the best revenue comes from cutting 6 pieces of the rod, each - # of length 1. Thus, yielding 6 * 6 = 36. + # the best revenue comes from cutting the rod into 6 pieces, each + # of length 1 resulting in a revenue of 6 * 6 = 36. expected_max_revenue = 36 max_rev_top_down = top_down_cut_rod(n, prices)