From 49268171425e80a1921085d7b244a0bdb029b238 Mon Sep 17 00:00:00 2001 From: Shivangi Nayak Date: Tue, 23 Mar 2021 21:37:08 +0530 Subject: [PATCH 1/5] longest increasing subsequence added --- .../linear_data_structures/__init__.py | 5 +- .../linear_data_structures/algorithms.py | 172 +++++++++++++++++- .../tests/test_algorithms.py | 118 +++++++++++- 3 files changed, 292 insertions(+), 3 deletions(-) diff --git a/pydatastructs/linear_data_structures/__init__.py b/pydatastructs/linear_data_structures/__init__.py index 67d700eba..64fbbfb3b 100644 --- a/pydatastructs/linear_data_structures/__init__.py +++ b/pydatastructs/linear_data_structures/__init__.py @@ -33,6 +33,9 @@ cocktail_shaker_sort, quick_sort, longest_common_subsequence, - is_ordered + is_ordered, + upper_bound, + lower_bound, + longest_increasing_subsequence ) __all__.extend(algorithms.__all__) diff --git a/pydatastructs/linear_data_structures/algorithms.py b/pydatastructs/linear_data_structures/algorithms.py index c390c8036..01035d3ee 100644 --- a/pydatastructs/linear_data_structures/algorithms.py +++ b/pydatastructs/linear_data_structures/algorithms.py @@ -15,7 +15,10 @@ 'cocktail_shaker_sort', 'quick_sort', 'longest_common_subsequence', - 'is_ordered' + 'is_ordered', + 'upper_bound', + 'lower_bound', + 'longest_increasing_subsequence' ] def _merge(array, sl, el, sr, er, end, comp): @@ -843,3 +846,170 @@ def is_ordered(array, **kwargs): if comp(array[i], array[i - 1]): return False return True + + +def upper_bound(array, start, end, value, comp): + """ + Finds the index of the first occurence of an element greater than value according + to an order defined,in the given sorted OneDimensionalArray + + Parameters + ======== + array: OneDimensionalArray + The sorted array (sorted according to a custom comparator function) in which the + upper bound has to be found + start: int + The staring index of the portion of the array in which the upper bound + of a given value has to be looked for + end: int + The ending index of the portion of the array in which the upper bound + of a given value has to be looked for + + Returns + ======= + output: int + Index of the upper bound of the given value in the given sorted OneDimensionalArray + + Examples + ======== + >>> from pydatastructs import upper_bound, OneDimensionalArray as ODA + >>> arr = ODA(int, [4, 5, 5, 6, 7]) + >>> upperBound = upper_bound(arr, 0, 4, 5, None) + >>> upperBound + 3 + >>> arr = ODA(int, [7, 6, 5, 5, 4]) + >>> upperBound = upper_bound(arr, 0, 4, 5, lambda x, y: x > y) + >>> upperBound + 4 + + Note + ==== + The OneDimensionalArray must be sorted beforehand + """ + if comp is None: + def comp(a, b): return (a < b) + index = end + inclusive_end = end - 1 + if comp(value, array[start]): + index = start + while start <= inclusive_end: + mid = (start + inclusive_end)//2 + if not comp(value, array[mid]): + start = mid + 1 + else: + index = mid + inclusive_end = mid - 1 + return index + + +def lower_bound(array, start, end, value, comp): + """ + Finds the the index of the first occurence of an element which is not + less than value according to an order defined, in the given OneDimensionalArray + + Parameters + ======== + array: OneDimensionalArray + The sorted array (sorted according to a custom comparator function) + in which the lower bound has to be found + start: int + The staring index of the portion of the array in which the lower + bound of a given value has to be looked for + end: int + The ending index of the portion of the array in which the lower + bound of a given value has to be looked for + + Returns + ======= + output: int + Index of the lower bound of the given value in the given sorted OneDimensionalArray + + Examples + ======== + >>> from pydatastructs import lower_bound, OneDimensionalArray as ODA + >>> arr = ODA(int, [4, 5, 5, 6, 7]) + >>> lowerBound = lower_bound(arr, 0, 4, 5, lambda x, y : x < y) + >>> lowerBound + 1 + >>> arr = ODA(int, [7, 6, 5, 5, 4]) + >>> lowerBound = lower_bound(arr, 0, 4, 5, lambda x, y : x > y) + >>> lowerBound + 2 + Note + ==== + The OneDimensionalArray must be sorted beforehand + """ + if comp is None: + def comp(a, b): return (a < b) + index = end + inclusive_end = end - 1 + if not comp(array[start], value): + index = start + while start <= inclusive_end: + mid = (start + inclusive_end)//2 + if comp(array[mid], value): + start = mid + 1 + else: + index = mid + inclusive_end = mid - 1 + return index + + +def longest_increasing_subsequence(array): + """ + Returns the longest increasing subsequence (as a OneDimensionalArray) that + can be obtained from a given OneDimensionalArray. A subsequence + of an array is an ordered subset of the array's elements having the same + sequential ordering as the original array. Here, an increasing + sequence stands for a strictly increasing sequence of numbers. + + Parameters + ======== + + array: OneDimensionalArray + The given array in the form of a OneDimensionalArray + + Returns + ======= + + output: OneDimensionalArray + Returns the longest increasing subsequence that can be obtained + from the given array + + Examples + ======== + + >>> from pydatastructs import lower_bound, OneDimensionalArray as ODA + >>> from pydatastructs import longest_increasing_subsequence as LIS + >>> array = ODA(int, [2, 5, 3, 7, 11, 8, 10, 13, 6]) + >>> longestIncreasingSubsequence = LIS(array) + >>> longestIncreasingSubsequence + [2, 3, 7, 8, 10, 13] + >>> array2 = ODA(int, [3, 4, -1, 5, 8, 2, 2 ,2, 3, 12, 7, 9, 10]) + >>> longestIncreasingSubsequence = LIS(array2) + >>> longestIncreasingSubsequence + [-1, 2, 3, 7, 9, 10] + """ + n = len(array) + dp = [0]*n + parent = [-1]*n + length = 0 + for i in range(1, n): + if array[i] <= array[dp[0]]: + dp[0] = i + elif array[dp[length]] < array[i]: + length += 1 + dp[length] = i + parent[i] = dp[length - 1] + else: + curr_array = [array[dp[i]] for i in range(length)] + ceil = lower_bound(curr_array, 0, length, array[i], None) + dp[ceil] = i + parent[i] = dp[ceil - 1] + ans = [] + + last_index = dp[length] + while last_index != -1: + ans[:0] = [array[last_index]] + last_index = parent[last_index] + return ans diff --git a/pydatastructs/linear_data_structures/tests/test_algorithms.py b/pydatastructs/linear_data_structures/tests/test_algorithms.py index 94e954c7f..63c7672f5 100644 --- a/pydatastructs/linear_data_structures/tests/test_algorithms.py +++ b/pydatastructs/linear_data_structures/tests/test_algorithms.py @@ -2,7 +2,8 @@ merge_sort_parallel, DynamicOneDimensionalArray, OneDimensionalArray, brick_sort, brick_sort_parallel, heapsort, matrix_multiply_parallel, counting_sort, bucket_sort, - cocktail_shaker_sort, quick_sort, longest_common_subsequence, is_ordered) + cocktail_shaker_sort, quick_sort, longest_common_subsequence, is_ordered, + upper_bound, lower_bound, longest_increasing_subsequence) from pydatastructs.utils.raises_util import raises @@ -157,3 +158,118 @@ def test_is_ordered(): arr4.delete(0) output = is_ordered(arr4) assert output == expected_result + + +def test_upper_bound(): + ODA = OneDimensionalArray + arr1 = ODA(int, [3, 3, 3]) + output = upper_bound(arr1, 0, len(arr1), 3, None) + expected_result = 3 + assert expected_result == output + + arr2 = ODA(int, [4, 4, 5, 6]) + output = upper_bound(arr2, 0, 3, 4, None) + expected_result = 2 + assert expected_result == output + + arr3 = ODA(int, [6, 6, 7, 8, 9]) + output = upper_bound(arr3, 2, 4, 5, None) + expected_result = 2 + assert expected_result == output + + arr4 = ODA(int, [3, 4, 4, 6]) + output = upper_bound(arr4, 1, 3, 5, None) + expected_result = 3 + assert expected_result == output + + arr5 = ODA(int, [7, 6, 6, 6, 6, 5, 4, 3]) + output = upper_bound(arr5, 0, len(arr5), 6, lambda x, y: x > y) + expected_result = 5 + assert expected_result == output + + arr6 = ODA(int, [7, 6, 6, 6, 6, 5, 4, 3]) + output = upper_bound(arr6, 2, len(arr6), 2, lambda x, y: x > y) + expected_result = 8 + assert expected_result == output + + arr7 = ODA(int, [7, 6, 6, 6, 6, 5, 4, 3]) + output = upper_bound(arr7, 3, 7, 9, lambda x, y: x > y) + expected_result = 3 + assert expected_result == output + + arr8 = ODA(int, [7, 6, 6, 6, 6, 5, 4, 3]) + output = upper_bound(arr8, 0, 3, 6, lambda x, y: x > y) + expected_result = 3 + assert expected_result == output + + +def test_lower_bound(): + ODA = OneDimensionalArray + arr1 = ODA(int, [3, 3, 3]) + output = lower_bound(arr1, 1, len(arr1), 3, None) + expected_result = 1 + assert expected_result == output + + arr2 = ODA(int, [4, 4, 4, 4, 5, 6]) + output = lower_bound(arr2, 0, 3, 5, None) + expected_result = 3 + assert expected_result == output + + arr3 = ODA(int, [6, 6, 7, 8, 9]) + output = lower_bound(arr3, 0, 3, 5, None) + expected_result = 0 + assert expected_result == output + + arr4 = ODA(int, [3, 4, 4, 4]) + output = lower_bound(arr4, 0, 4, 5, None) + expected_result = 4 + assert expected_result == output + + arr5 = ODA(int, [7, 6, 6, 6, 6, 5, 4, 3]) + output = lower_bound(arr5, 0, len(arr5), 5, lambda x, y: x > y) + expected_result = 5 + assert expected_result == output + + arr6 = ODA(int, [7, 6, 6, 6, 6, 5, 4, 3]) + output = lower_bound(arr6, 4, len(arr6), 2, lambda x, y: x > y) + expected_result = 8 + assert expected_result == output + + arr7 = ODA(int, [7, 6, 6, 6, 6, 5, 4, 3]) + output = lower_bound(arr7, 0, 5, 9, lambda x, y: x > y) + expected_result = 0 + assert expected_result == output + + arr8 = ODA(int, [7, 6, 6, 6, 6, 5, 4, 3]) + output = lower_bound(arr8, 0, 3, 6, lambda x, y: x > y) + expected_result = 1 + assert expected_result == output + + +def test_longest_increasing_subsequence(): + ODA = OneDimensionalArray + + arr1 = ODA(int, [2, 5, 3, 7, 11, 8, 10, 13, 6]) + output = longest_increasing_subsequence(arr1) + expected_result = [2, 3, 7, 8, 10, 13] + assert expected_result == output + + arr2 = ODA(int, [3, 4, -1, 5, 8, 2, 2, 2, 3, 12, 7, 9, 10]) + output = longest_increasing_subsequence(arr2) + expected_result = [-1, 2, 3, 7, 9, 10] + assert expected_result == output + + arr3 = ODA(int, [6, 6, 6, 19, 9]) + output = longest_increasing_subsequence(arr3) + expected_result = [6, 9] + assert expected_result == output + + arr4 = ODA(int, [5, 4, 4, 3, 3, 6, 6, 8]) + output = longest_increasing_subsequence(arr4) + expected_result = [3, 6, 8] + assert expected_result == output + + arr5 = ODA(int, [7, 6, 6, 6, 5, 4, 3]) + output = longest_increasing_subsequence(arr5) + expected_result = [3] + assert expected_result == output From 0e9af27bba8a12c026f8fffcc2da1ed2ec496f6b Mon Sep 17 00:00:00 2001 From: Shivangi Nayak Date: Tue, 23 Mar 2021 21:57:05 +0530 Subject: [PATCH 2/5] minor bug fixed --- pydatastructs/linear_data_structures/algorithms.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pydatastructs/linear_data_structures/algorithms.py b/pydatastructs/linear_data_structures/algorithms.py index 01035d3ee..f95d0054e 100644 --- a/pydatastructs/linear_data_structures/algorithms.py +++ b/pydatastructs/linear_data_structures/algorithms.py @@ -935,6 +935,7 @@ def lower_bound(array, start, end, value, comp): >>> lowerBound = lower_bound(arr, 0, 4, 5, lambda x, y : x > y) >>> lowerBound 2 + Note ==== The OneDimensionalArray must be sorted beforehand From 228b3f63cd59e3b50f6beaeb843c0a7cc70d076e Mon Sep 17 00:00:00 2001 From: Shivangi Nayak Date: Wed, 24 Mar 2021 20:41:57 +0530 Subject: [PATCH 3/5] optional arguements added --- .../linear_data_structures/algorithms.py | 59 +++++++++++++------ .../tests/test_algorithms.py | 32 +++++----- 2 files changed, 58 insertions(+), 33 deletions(-) diff --git a/pydatastructs/linear_data_structures/algorithms.py b/pydatastructs/linear_data_structures/algorithms.py index f95d0054e..3b0e02e37 100644 --- a/pydatastructs/linear_data_structures/algorithms.py +++ b/pydatastructs/linear_data_structures/algorithms.py @@ -848,46 +848,58 @@ def is_ordered(array, **kwargs): return True -def upper_bound(array, start, end, value, comp): +def upper_bound(array, value, **kwargs): """ Finds the index of the first occurence of an element greater than value according to an order defined,in the given sorted OneDimensionalArray Parameters ======== + array: OneDimensionalArray The sorted array (sorted according to a custom comparator function) in which the upper bound has to be found - start: int + + start: int, optional The staring index of the portion of the array in which the upper bound of a given value has to be looked for - end: int + + end: int, optional The ending index of the portion of the array in which the upper bound of a given value has to be looked for + comp: boolean function, optional + A function that specifies the ordering of elements. By default, it takes two + parameters and returns True if 1st parameter is strictly smaller than + 2nd parameter + Returns ======= + output: int Index of the upper bound of the given value in the given sorted OneDimensionalArray Examples ======== + >>> from pydatastructs import upper_bound, OneDimensionalArray as ODA >>> arr = ODA(int, [4, 5, 5, 6, 7]) - >>> upperBound = upper_bound(arr, 0, 4, 5, None) + >>> upperBound = upper_bound(arr, 5, start = 0, end = 4) >>> upperBound 3 >>> arr = ODA(int, [7, 6, 5, 5, 4]) - >>> upperBound = upper_bound(arr, 0, 4, 5, lambda x, y: x > y) + >>> upperBound = upper_bound(arr, 5, comp = lambda x, y: x > y) >>> upperBound 4 Note ==== + The OneDimensionalArray must be sorted beforehand """ - if comp is None: - def comp(a, b): return (a < b) + start = kwargs.get('start', 0) + end = kwargs.get('end', len(array)) + comp = kwargs.get('comp', lambda x, y: x < y) index = end inclusive_end = end - 1 if comp(value, array[start]): @@ -902,46 +914,59 @@ def comp(a, b): return (a < b) return index -def lower_bound(array, start, end, value, comp): +def lower_bound(array, value, **kwargs): """ Finds the the index of the first occurence of an element which is not less than value according to an order defined, in the given OneDimensionalArray Parameters ======== + array: OneDimensionalArray The sorted array (sorted according to a custom comparator function) in which the lower bound has to be found - start: int + + start: int, optional The staring index of the portion of the array in which the lower - bound of a given value has to be looked for - end: int + bound of a given value has to be looked for. Default value is set to 0. + + end: int, optional The ending index of the portion of the array in which the lower - bound of a given value has to be looked for + bound of a given value has to be looked for. Default value is set to + end of array, i.e., len(arr) + + comp: boolean function, optional + A function that specifies the ordering of elements. By default, it takes two + parameters and returns True if 1st parameter is strictly smaller than + 2nd parameter Returns ======= + output: int Index of the lower bound of the given value in the given sorted OneDimensionalArray Examples ======== + >>> from pydatastructs import lower_bound, OneDimensionalArray as ODA >>> arr = ODA(int, [4, 5, 5, 6, 7]) - >>> lowerBound = lower_bound(arr, 0, 4, 5, lambda x, y : x < y) + >>> lowerBound = lower_bound(arr, 5, end = 4, comp = lambda x, y : x < y) >>> lowerBound 1 >>> arr = ODA(int, [7, 6, 5, 5, 4]) - >>> lowerBound = lower_bound(arr, 0, 4, 5, lambda x, y : x > y) + >>> lowerBound = lower_bound(arr, 5, start = 0, comp = lambda x, y : x > y) >>> lowerBound 2 Note ==== + The OneDimensionalArray must be sorted beforehand """ - if comp is None: - def comp(a, b): return (a < b) + start = kwargs.get('start', 0) + end = kwargs.get('end', len(array)) + comp = kwargs.get('comp', lambda x, y: x < y) index = end inclusive_end = end - 1 if not comp(array[start], value): @@ -1004,7 +1029,7 @@ def longest_increasing_subsequence(array): parent[i] = dp[length - 1] else: curr_array = [array[dp[i]] for i in range(length)] - ceil = lower_bound(curr_array, 0, length, array[i], None) + ceil = lower_bound(curr_array, array[i]) dp[ceil] = i parent[i] = dp[ceil - 1] ans = [] diff --git a/pydatastructs/linear_data_structures/tests/test_algorithms.py b/pydatastructs/linear_data_structures/tests/test_algorithms.py index 63c7672f5..60652a263 100644 --- a/pydatastructs/linear_data_structures/tests/test_algorithms.py +++ b/pydatastructs/linear_data_structures/tests/test_algorithms.py @@ -163,42 +163,42 @@ def test_is_ordered(): def test_upper_bound(): ODA = OneDimensionalArray arr1 = ODA(int, [3, 3, 3]) - output = upper_bound(arr1, 0, len(arr1), 3, None) + output = upper_bound(arr1, 3) expected_result = 3 assert expected_result == output arr2 = ODA(int, [4, 4, 5, 6]) - output = upper_bound(arr2, 0, 3, 4, None) + output = upper_bound(arr2, 4, end=3) expected_result = 2 assert expected_result == output arr3 = ODA(int, [6, 6, 7, 8, 9]) - output = upper_bound(arr3, 2, 4, 5, None) + output = upper_bound(arr3, 5, start=2, end=4) expected_result = 2 assert expected_result == output arr4 = ODA(int, [3, 4, 4, 6]) - output = upper_bound(arr4, 1, 3, 5, None) + output = upper_bound(arr4, 5, start=1, end=3) expected_result = 3 assert expected_result == output arr5 = ODA(int, [7, 6, 6, 6, 6, 5, 4, 3]) - output = upper_bound(arr5, 0, len(arr5), 6, lambda x, y: x > y) + output = upper_bound(arr5, 6, comp=lambda x, y: x > y) expected_result = 5 assert expected_result == output arr6 = ODA(int, [7, 6, 6, 6, 6, 5, 4, 3]) - output = upper_bound(arr6, 2, len(arr6), 2, lambda x, y: x > y) + output = upper_bound(arr6, 2, start=2, comp=lambda x, y: x > y) expected_result = 8 assert expected_result == output arr7 = ODA(int, [7, 6, 6, 6, 6, 5, 4, 3]) - output = upper_bound(arr7, 3, 7, 9, lambda x, y: x > y) + output = upper_bound(arr7, 9, start=3, end=7, comp=lambda x, y: x > y) expected_result = 3 assert expected_result == output arr8 = ODA(int, [7, 6, 6, 6, 6, 5, 4, 3]) - output = upper_bound(arr8, 0, 3, 6, lambda x, y: x > y) + output = upper_bound(arr8, 6, end=3, comp=lambda x, y: x > y) expected_result = 3 assert expected_result == output @@ -206,42 +206,42 @@ def test_upper_bound(): def test_lower_bound(): ODA = OneDimensionalArray arr1 = ODA(int, [3, 3, 3]) - output = lower_bound(arr1, 1, len(arr1), 3, None) + output = lower_bound(arr1, 3, start=1) expected_result = 1 assert expected_result == output arr2 = ODA(int, [4, 4, 4, 4, 5, 6]) - output = lower_bound(arr2, 0, 3, 5, None) + output = lower_bound(arr2, 5, end=3) expected_result = 3 assert expected_result == output arr3 = ODA(int, [6, 6, 7, 8, 9]) - output = lower_bound(arr3, 0, 3, 5, None) + output = lower_bound(arr3, 5, end=3) expected_result = 0 assert expected_result == output arr4 = ODA(int, [3, 4, 4, 4]) - output = lower_bound(arr4, 0, 4, 5, None) + output = lower_bound(arr4, 5) expected_result = 4 assert expected_result == output arr5 = ODA(int, [7, 6, 6, 6, 6, 5, 4, 3]) - output = lower_bound(arr5, 0, len(arr5), 5, lambda x, y: x > y) + output = lower_bound(arr5, 5, comp=lambda x, y: x > y) expected_result = 5 assert expected_result == output arr6 = ODA(int, [7, 6, 6, 6, 6, 5, 4, 3]) - output = lower_bound(arr6, 4, len(arr6), 2, lambda x, y: x > y) + output = lower_bound(arr6, 2, start=4, comp=lambda x, y: x > y) expected_result = 8 assert expected_result == output arr7 = ODA(int, [7, 6, 6, 6, 6, 5, 4, 3]) - output = lower_bound(arr7, 0, 5, 9, lambda x, y: x > y) + output = lower_bound(arr7, 9, end=5, comp=lambda x, y: x > y) expected_result = 0 assert expected_result == output arr8 = ODA(int, [7, 6, 6, 6, 6, 5, 4, 3]) - output = lower_bound(arr8, 0, 3, 6, lambda x, y: x > y) + output = lower_bound(arr8, 6, end=3, comp=lambda x, y: x > y) expected_result = 1 assert expected_result == output From 6492063b8b70f8dd72cde0b50c799333d240ef30 Mon Sep 17 00:00:00 2001 From: Shivangi Nayak Date: Tue, 18 May 2021 04:20:59 +0530 Subject: [PATCH 4/5] case changed in docstring --- pydatastructs/linear_data_structures/algorithms.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pydatastructs/linear_data_structures/algorithms.py b/pydatastructs/linear_data_structures/algorithms.py index 86938fc03..ef4fe3537 100644 --- a/pydatastructs/linear_data_structures/algorithms.py +++ b/pydatastructs/linear_data_structures/algorithms.py @@ -1007,12 +1007,12 @@ def longest_increasing_subsequence(array): >>> from pydatastructs import lower_bound, OneDimensionalArray as ODA >>> from pydatastructs import longest_increasing_subsequence as LIS >>> array = ODA(int, [2, 5, 3, 7, 11, 8, 10, 13, 6]) - >>> longestIncreasingSubsequence = LIS(array) - >>> longestIncreasingSubsequence + >>> longest_inc_subsequence = LIS(array) + >>> longest_inc_subsequence [2, 3, 7, 8, 10, 13] >>> array2 = ODA(int, [3, 4, -1, 5, 8, 2, 2 ,2, 3, 12, 7, 9, 10]) - >>> longestIncreasingSubsequence = LIS(array2) - >>> longestIncreasingSubsequence + >>> longest_inc_subsequence = LIS(array2) + >>> longest_inc_subsequence [-1, 2, 3, 7, 9, 10] """ n = len(array) From cb73fc28de16bd6765272404a46e66128f3201c4 Mon Sep 17 00:00:00 2001 From: Gagandeep Singh Date: Sun, 6 Jun 2021 19:12:29 +0530 Subject: [PATCH 5/5] Apply suggestions from code review --- pydatastructs/linear_data_structures/algorithms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydatastructs/linear_data_structures/algorithms.py b/pydatastructs/linear_data_structures/algorithms.py index ef4fe3537..0a2d6de7a 100644 --- a/pydatastructs/linear_data_structures/algorithms.py +++ b/pydatastructs/linear_data_structures/algorithms.py @@ -989,7 +989,7 @@ def longest_increasing_subsequence(array): sequence stands for a strictly increasing sequence of numbers. Parameters - ======== + ========== array: OneDimensionalArray The given array in the form of a OneDimensionalArray