From 5d81c55a5695503cb61bc3fca90b3a0133015102 Mon Sep 17 00:00:00 2001 From: Maxwell Aladago Date: Sun, 18 Aug 2019 17:20:05 -0400 Subject: [PATCH 1/8] function for the knapsack problem which returns one of the optimal subsets --- dynamic_programming/knapsack.py | 92 ++++++++++++++++++++++++++++++--- 1 file changed, 84 insertions(+), 8 deletions(-) diff --git a/dynamic_programming/knapsack.py b/dynamic_programming/knapsack.py index 27d1cfed799b..ea0080dd79ea 100644 --- a/dynamic_programming/knapsack.py +++ b/dynamic_programming/knapsack.py @@ -1,7 +1,8 @@ """ Given weights and values of n items, put these items in a knapsack of capacity W to get the maximum total value in the knapsack. """ -def MF_knapsack(i,wt,val,j): + +def MF_knapsack(i, wt, val, j): ''' This code involves the concept of memory functions. Here we solve the subproblems which are needed unlike the below example @@ -12,10 +13,12 @@ def MF_knapsack(i,wt,val,j): if j < wt[i - 1]: val = MF_knapsack(i - 1,wt,val,j) else: - val = max(MF_knapsack(i - 1,wt,val,j),MF_knapsack(i - 1,wt,val,j - wt[i - 1]) + val[i - 1]) + val = max(MF_knapsack(i - 1, wt, val, j), + MF_knapsack(i - 1, wt, val, j - wt[i - 1]) + val[i - 1]) F[i][j] = val return F[i][j] + def knapsack(W, wt, val, n): dp = [[0 for i in range(W+1)]for j in range(n+1)] @@ -26,17 +29,90 @@ def knapsack(W, wt, val, n): else: dp[i][w] = dp[i-1][w] - return dp[n][w] + return dp[n][W], dp + + +def knapsack_with_example_solution(W, wt, val): + """ + Solves the integer weights knapsack problem returns one of + the several possible optimal subsets. + + Parameters + --------- + + W: int, the total maximum weight for the given knapsack problem. + wt: list, the vector of weights for all items where wt[i] is the weight + of the ith item. + val: list, the vector of values for all items where val[i] is the value + of te ith item + + Returns + ------- + optimal_val: float, the optimal value for the given knapsack problem + example_optional_set: set, the indices of one of the optimal subsets + which gave rise to the optimal value. + + Examples + ------- + >>> knapsack_with_example_solution(10, [1, 3, 5, 2], [10, 20, 100, 22]) + (142, {2, 3, 4}) + >>> knapsack_with_example_solution(6, [4, 3, 2, 3], [3, 2, 4, 4]) + (8, {3, 4}) + >>> knapsack_with_example_solution(6, [4, 3, 2, 3], [3, 2, 4]) + Traceback (most recent call last): + ... + ValueError: The number of weights must be the same as the number of values. + But got 4 weights and 3 values + """ + if not (isinstance(wt, (list, tuple)) and isinstance(val, (list, tuple))): + raise ValueError("Both the weights and values vectors must be either lists or tuples") + + num_items = len(wt) + if num_items != len(val): + raise ValueError("The number of weights must be the " + "same as the number of values.\nBut " + "got {} weights and {} values".format(num_items, len(val))) + for i in range(num_items): + if not isinstance(wt[i], int): + raise TypeError("All weights must be integers but " + "got weight of type {} at index {}".format(type(wt[i]), i)) + + optimal_val, dp_table = knapsack(W, wt, val, num_items) + example_optional_set = set() + _construct_solution(dp_table, wt, num_items, W, example_optional_set) + + return optimal_val, example_optional_set + + +def _construct_solution(dp, wt, i, j, optimal_set): + if i > 0 and j > 0: + if dp[i - 1][j] == dp[i][j]: + _construct_solution(dp, wt, i - 1, j, optimal_set) + else: + # item i is certainly part of the solution + optimal_set.add(i) + _construct_solution(dp, wt, i - 1, j - wt[i-1], optimal_set) + if __name__ == '__main__': ''' Adding test case for knapsack ''' - val = [3,2,4,4] - wt = [4,3,2,3] + val = [3, 2, 4, 4] + wt = [4, 3, 2, 3] n = 4 w = 6 F = [[0]*(w + 1)] + [[0] + [-1 for i in range(w + 1)] for j in range(n + 1)] - print(knapsack(w,wt,val,n)) - print(MF_knapsack(n,wt,val,w)) # switched the n and w - + + optimal_solution, _ = knapsack(w,wt,val, n) + print(optimal_solution) + print(MF_knapsack(n,wt,val,w)) # switched the n and w + + # testing the dynamic programming problem with example + # the optimal subset for the above example are items 3 and 4 + optimal_solution, optimal_subset = knapsack_with_example_solution(w, wt, val) + assert optimal_solution == 8 + assert optimal_subset == {3, 4} + print("optimal_value = ", optimal_solution) + print("An optimal subset corresponding to the optimal value", optimal_subset) + From f2994cfa895ff037b20a4be60a3ec8f5aa89a39e Mon Sep 17 00:00:00 2001 From: Maxwell Aladago Date: Sun, 18 Aug 2019 17:30:30 -0400 Subject: [PATCH 2/8] function for the knapsack problem which returns one of the optimal subsets --- dynamic_programming/knapsack.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/dynamic_programming/knapsack.py b/dynamic_programming/knapsack.py index ea0080dd79ea..d419325f80a5 100644 --- a/dynamic_programming/knapsack.py +++ b/dynamic_programming/knapsack.py @@ -85,11 +85,32 @@ def knapsack_with_example_solution(W, wt, val): def _construct_solution(dp, wt, i, j, optimal_set): + """ + Recursively reconstructs one of the optimal subsets given + a filled DP table and the vector of weights + + Parameters + --------- + + dp: list of list, the table of a solved integer weight dynamic programming problem + + wt: list or tuple, the vector of weights of the items + i: int, the index of the item under consideration + j: int, the current possible maximum weight + optimal_set: set, the optimal subset so far. This gets modified by the function. + + Returns + ------- + None + + """ + # for the current item i at a maximum weight j to be part of an optimal subset, + # the optimal value at (i, j) must be greater than the optimal value at (i-1, j). + # where i - 1 means considering only the previous items at the given maximum weight if i > 0 and j > 0: if dp[i - 1][j] == dp[i][j]: _construct_solution(dp, wt, i - 1, j, optimal_set) else: - # item i is certainly part of the solution optimal_set.add(i) _construct_solution(dp, wt, i - 1, j - wt[i-1], optimal_set) From 85b9c59c6ec8de0452aaaae990d0a86bf6219dd0 Mon Sep 17 00:00:00 2001 From: Maxwell Aladago Date: Sun, 18 Aug 2019 17:34:05 -0400 Subject: [PATCH 3/8] function for the knapsack problem which returns one of the optimal subsets --- dynamic_programming/knapsack.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/dynamic_programming/knapsack.py b/dynamic_programming/knapsack.py index d419325f80a5..10770ffcc11b 100644 --- a/dynamic_programming/knapsack.py +++ b/dynamic_programming/knapsack.py @@ -1,8 +1,12 @@ """ Given weights and values of n items, put these items in a knapsack of capacity W to get the maximum total value in the knapsack. + +Note that only the integer weights 0-1 knapsack problem is solvable using dynamic programming. """ +from __future__ import print_function, division, absolute_import + -def MF_knapsack(i, wt, val, j): +def MF_knapsack(i,wt,val,j): ''' This code involves the concept of memory functions. Here we solve the subproblems which are needed unlike the below example @@ -13,8 +17,7 @@ def MF_knapsack(i, wt, val, j): if j < wt[i - 1]: val = MF_knapsack(i - 1,wt,val,j) else: - val = max(MF_knapsack(i - 1, wt, val, j), - MF_knapsack(i - 1, wt, val, j - wt[i - 1]) + val[i - 1]) + val = max(MF_knapsack(i - 1,wt,val,j),MF_knapsack(i - 1,wt,val,j - wt[i - 1]) + val[i - 1]) F[i][j] = val return F[i][j] @@ -123,8 +126,7 @@ def _construct_solution(dp, wt, i, j, optimal_set): wt = [4, 3, 2, 3] n = 4 w = 6 - F = [[0]*(w + 1)] + [[0] + [-1 for i in range(w + 1)] for j in range(n + 1)] - + F = [[0] * (w + 1)] + [[0] + [-1 for i in range(w + 1)] for j in range(n + 1)] optimal_solution, _ = knapsack(w,wt,val, n) print(optimal_solution) print(MF_knapsack(n,wt,val,w)) # switched the n and w From 43fefa196d0fa2623bc8190f6684978fabe37506 Mon Sep 17 00:00:00 2001 From: Maxwell Aladago Date: Sun, 18 Aug 2019 18:03:42 -0400 Subject: [PATCH 4/8] function for the knapsack problem which returns one of the optimal subsets --- dynamic_programming/knapsack.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/dynamic_programming/knapsack.py b/dynamic_programming/knapsack.py index 10770ffcc11b..04a6c0630ea2 100644 --- a/dynamic_programming/knapsack.py +++ b/dynamic_programming/knapsack.py @@ -3,8 +3,7 @@ Note that only the integer weights 0-1 knapsack problem is solvable using dynamic programming. """ -from __future__ import print_function, division, absolute_import - +from typing import Union def MF_knapsack(i,wt,val,j): ''' @@ -35,7 +34,7 @@ def knapsack(W, wt, val, n): return dp[n][W], dp -def knapsack_with_example_solution(W, wt, val): +def knapsack_with_example_solution(W: int, wt: list, val:list): """ Solves the integer weights knapsack problem returns one of the several possible optimal subsets. @@ -87,7 +86,7 @@ def knapsack_with_example_solution(W, wt, val): return optimal_val, example_optional_set -def _construct_solution(dp, wt, i, j, optimal_set): +def _construct_solution(dp:list, wt:list, i:int, j:int, optimal_set:set): """ Recursively reconstructs one of the optimal subsets given a filled DP table and the vector of weights From 47f76ebe22b533817a846a1c21a37b4bede246aa Mon Sep 17 00:00:00 2001 From: Maxwell Aladago Date: Sun, 18 Aug 2019 18:51:48 -0400 Subject: [PATCH 5/8] function for the knapsack problem which returns one of the optimal subsets --- dynamic_programming/knapsack.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dynamic_programming/knapsack.py b/dynamic_programming/knapsack.py index 04a6c0630ea2..70c6d3eca78d 100644 --- a/dynamic_programming/knapsack.py +++ b/dynamic_programming/knapsack.py @@ -3,7 +3,6 @@ Note that only the integer weights 0-1 knapsack problem is solvable using dynamic programming. """ -from typing import Union def MF_knapsack(i,wt,val,j): ''' From 5e828fcab9912c45b86a3504af54556f28cc21ef Mon Sep 17 00:00:00 2001 From: Maxwell Aladago Date: Mon, 19 Aug 2019 01:19:48 -0400 Subject: [PATCH 6/8] some pep8 cleanup too --- dynamic_programming/knapsack.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/dynamic_programming/knapsack.py b/dynamic_programming/knapsack.py index 70c6d3eca78d..488059d6244d 100644 --- a/dynamic_programming/knapsack.py +++ b/dynamic_programming/knapsack.py @@ -1,10 +1,13 @@ """ -Given weights and values of n items, put these items in a knapsack of capacity W to get the maximum total value in the knapsack. +Given weights and values of n items, put these items in a knapsack of + capacity W to get the maximum total value in the knapsack. -Note that only the integer weights 0-1 knapsack problem is solvable using dynamic programming. +Note that only the integer weights 0-1 knapsack problem is solvable + using dynamic programming. """ -def MF_knapsack(i,wt,val,j): + +def MF_knapsack(i, wt, val, j): ''' This code involves the concept of memory functions. Here we solve the subproblems which are needed unlike the below example @@ -12,10 +15,11 @@ def MF_knapsack(i,wt,val,j): ''' global F # a global dp table for knapsack if F[i][j] < 0: - if j < wt[i - 1]: - val = MF_knapsack(i - 1,wt,val,j) + if j < wt[i-1]: + val = MF_knapsack(i-1, wt, val, j) else: - val = max(MF_knapsack(i - 1,wt,val,j),MF_knapsack(i - 1,wt,val,j - wt[i - 1]) + val[i - 1]) + val = max(MF_knapsack(i-1, wt, val, j), + MF_knapsack(i-1, wt, val, j - wt[i-1]) + val[i-1]) F[i][j] = val return F[i][j] @@ -24,9 +28,9 @@ def knapsack(W, wt, val, n): dp = [[0 for i in range(W+1)]for j in range(n+1)] for i in range(1,n+1): - for w in range(1,W+1): - if(wt[i-1]<=w): - dp[i][w] = max(val[i-1]+dp[i-1][w-wt[i-1]],dp[i-1][w]) + for w in range(1, W+1): + if wt[i-1] <= w: + dp[i][w] = max(val[i-1] + dp[i-1][w-wt[i-1]], dp[i-1][w]) else: dp[i][w] = dp[i-1][w] From eba05a935d130196dbba38ae7ac0fb1fca46f33f Mon Sep 17 00:00:00 2001 From: Maxwell Aladago Date: Mon, 19 Aug 2019 02:30:53 -0400 Subject: [PATCH 7/8] ENH: refactored longest common subsequence, also fixed a bug with the sequence returned --- .../longest_common_subsequence.py | 90 ++++++++++++++----- 1 file changed, 68 insertions(+), 22 deletions(-) diff --git a/dynamic_programming/longest_common_subsequence.py b/dynamic_programming/longest_common_subsequence.py index 0a4771cb2efd..f04e2dea7c85 100644 --- a/dynamic_programming/longest_common_subsequence.py +++ b/dynamic_programming/longest_common_subsequence.py @@ -1,37 +1,83 @@ """ LCS Problem Statement: Given two sequences, find the length of longest subsequence present in both of them. -A subsequence is a sequence that appears in the same relative order, but not necessarily continious. +A subsequence is a sequence that appears in the same relative order, but not necessarily continuous. Example:"abc", "abg" are subsequences of "abcdefgh". """ from __future__ import print_function -try: - xrange # Python 2 -except NameError: - xrange = range # Python 3 -def lcs_dp(x, y): +def lcs_dp(x: str, y: str): + """ + Finds the longest common subsequence between two strings. Also returns the + The subsequence found + + Parameters + ---------- + + x: str, one of the strings + y: str, the other string + + Returns + ------- + L[m][n]: int, the length of the longest subsequence. Also equal to len(seq) + Seq: str, the subsequence found + + >>> lcs_dp("programming", "gaming") + (6, 'gaming') + >>> lcs_dp("physics", "smartphone") + (2, 'ph') + >>> lcs_dp("computer", "food") + (1, 'o') + """ # find the length of strings + + assert x is not None + assert y is not None + m = len(x) n = len(y) # declaring the array for storing the dp values - L = [[None] * (n + 1) for i in xrange(m + 1)] - seq = [] - - for i in range(m + 1): - for j in range(n + 1): - if i == 0 or j == 0: - L[i][j] = 0 - elif x[i - 1] == y[ j - 1]: - L[i][j] = L[i - 1][j - 1] + 1 - seq.append(x[i -1]) + L = [[0] * (n + 1) for _ in range(m + 1)] + + for i in range(1, m + 1): + for j in range(1, n + 1): + if x[i-1] == y[j-1]: + match = 1 else: - L[i][j] = max(L[i - 1][j], L[i][j - 1]) - # L[m][n] contains the length of LCS of X[0..n-1] & Y[0..m-1] + match = 0 + + L[i][j] = max(L[i-1][j], L[i][j-1], L[i-1][j-1] + match) + + seq = "" + i, j = m, n + while i > 0 and i > 0: + if x[i - 1] == y[j - 1]: + match = 1 + else: + match = 0 + + if L[i][j] == L[i - 1][j - 1] + match: + if match == 1: + seq = x[i - 1] + seq + i -= 1 + j -= 1 + elif L[i][j] == L[i - 1][j]: + i -= 1 + else: + j -= 1 + return L[m][n], seq -if __name__=='__main__': - x = 'AGGTAB' - y = 'GXTXAYB' - print(lcs_dp(x, y)) + +if __name__ == '__main__': + a = 'AGGTAB' + b = 'GXTXAYB' + expected_ln = 4 + expected_subseq = "GTAB" + + ln, subseq = lcs_dp(a, b) + assert expected_ln == ln + assert expected_subseq == subseq + print("len =", ln, ", sub-sequence =", subseq) + From 6c493196ea90ed1fab839d7890bb47151dd19b1a Mon Sep 17 00:00:00 2001 From: Maxwell Aladago Date: Mon, 19 Aug 2019 03:06:35 -0400 Subject: [PATCH 8/8] renamed function --- dynamic_programming/longest_common_subsequence.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dynamic_programming/longest_common_subsequence.py b/dynamic_programming/longest_common_subsequence.py index f04e2dea7c85..7836fe303688 100644 --- a/dynamic_programming/longest_common_subsequence.py +++ b/dynamic_programming/longest_common_subsequence.py @@ -6,7 +6,7 @@ from __future__ import print_function -def lcs_dp(x: str, y: str): +def longest_common_subsequence(x: str, y: str): """ Finds the longest common subsequence between two strings. Also returns the The subsequence found @@ -22,11 +22,11 @@ def lcs_dp(x: str, y: str): L[m][n]: int, the length of the longest subsequence. Also equal to len(seq) Seq: str, the subsequence found - >>> lcs_dp("programming", "gaming") + >>> longest_common_subsequence("programming", "gaming") (6, 'gaming') - >>> lcs_dp("physics", "smartphone") + >>> longest_common_subsequence("physics", "smartphone") (2, 'ph') - >>> lcs_dp("computer", "food") + >>> longest_common_subsequence("computer", "food") (1, 'o') """ # find the length of strings @@ -76,7 +76,7 @@ def lcs_dp(x: str, y: str): expected_ln = 4 expected_subseq = "GTAB" - ln, subseq = lcs_dp(a, b) + ln, subseq = longest_common_subsequence(a, b) assert expected_ln == ln assert expected_subseq == subseq print("len =", ln, ", sub-sequence =", subseq)