From e524bba53630dc00123e191f208b93abca411761 Mon Sep 17 00:00:00 2001 From: pratikgl Date: Sat, 9 Oct 2021 16:05:57 +0530 Subject: [PATCH 1/6] Added next_permutation algorithm --- .../linear_data_structures/__init__.py | 3 +- .../linear_data_structures/algorithms.py | 66 ++++++++++++++++++- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/pydatastructs/linear_data_structures/__init__.py b/pydatastructs/linear_data_structures/__init__.py index 64fbbfb3b..3a89682f6 100644 --- a/pydatastructs/linear_data_structures/__init__.py +++ b/pydatastructs/linear_data_structures/__init__.py @@ -36,6 +36,7 @@ is_ordered, upper_bound, lower_bound, - longest_increasing_subsequence + longest_increasing_subsequence, + next_permutation ) __all__.extend(algorithms.__all__) diff --git a/pydatastructs/linear_data_structures/algorithms.py b/pydatastructs/linear_data_structures/algorithms.py index 0a2d6de7a..28ab4adde 100644 --- a/pydatastructs/linear_data_structures/algorithms.py +++ b/pydatastructs/linear_data_structures/algorithms.py @@ -18,7 +18,8 @@ 'is_ordered', 'upper_bound', 'lower_bound', - 'longest_increasing_subsequence' + 'longest_increasing_subsequence', + 'next_permutation' ] def _merge(array, sl, el, sr, er, end, comp): @@ -1038,3 +1039,66 @@ def longest_increasing_subsequence(array): ans[:0] = [array[last_index]] last_index = parent[last_index] return ans + +def next_permutation(array): + """ + If the function can determine the next higher permutation, it + rearranges the elements as such and returns `True`. If that was + not possible (because it is already at the largest possible permutation), + it rearranges the elements according to the first permutation + (sorted in ascending order) and returns `False`. + + Parameters + ========== + + array: OneDimensionalArray + A given array whose next permutation has to be found. + + Returns + ======= + + output: Bool + Returns `True` if the function can rearrange the given array as a + lexicographicaly greater permutation, otherwise `False`. + + Examples + ======== + + >>> from pydatastructs import next_permutation, OneDimensionalArray as ODA + >>> array = ODA(int, [1, 2, 3, 4]) + >>> next_permute = next_permutation(array) + >>> next_permute + True + >>> print(array) + [1, 2, 4, 3] + >>> array = ODA(int, [3, 2, 1]) + >>> next_permute = next_permutation(array) + >>> next_permute + False + >>> print(array) + [1, 2, 3] + + References + ========== + + .. [1] http://www.cplusplus.com/reference/algorithm/next_permutation/ + """ + n = len(array) + i = n - 1 + while i > 0 and array[i-1] >= array[i]: + i -= 1 + if i > 0: + left, right = i, n - 1 + while left <= right: + mid = left + (right - left) // 2 + if array[i-1] < array[mid]: + left = mid + 1 + else: + right = mid - 1 + array[i-1], array[left-1] = array[left-1], array[i-1] + left, right = i, n - 1 + while left < right: + array[left], array[right] = array[right], array[left] + left += 1 + right -= 1 + return True if i > 0 else False From 933029a4ecc32120dad40f2faa76d7ac94c851a6 Mon Sep 17 00:00:00 2001 From: pratikgl Date: Sat, 9 Oct 2021 16:33:38 +0530 Subject: [PATCH 2/6] Fixed typo --- 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 28ab4adde..e2aa17e95 100644 --- a/pydatastructs/linear_data_structures/algorithms.py +++ b/pydatastructs/linear_data_structures/algorithms.py @@ -1059,7 +1059,7 @@ def next_permutation(array): output: Bool Returns `True` if the function can rearrange the given array as a - lexicographicaly greater permutation, otherwise `False`. + lexicographically greater permutation, otherwise `False`. Examples ======== From 0efc5a82d885dbd1517f21339d6b12bdfff695c2 Mon Sep 17 00:00:00 2001 From: pratikgl Date: Sun, 10 Oct 2021 09:49:24 +0530 Subject: [PATCH 3/6] updated algo to prevent overwriting --- .../linear_data_structures/algorithms.py | 43 ++++++++++--------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/pydatastructs/linear_data_structures/algorithms.py b/pydatastructs/linear_data_structures/algorithms.py index e2aa17e95..29409d41e 100644 --- a/pydatastructs/linear_data_structures/algorithms.py +++ b/pydatastructs/linear_data_structures/algorithms.py @@ -1043,10 +1043,10 @@ def longest_increasing_subsequence(array): def next_permutation(array): """ If the function can determine the next higher permutation, it - rearranges the elements as such and returns `True`. If that was - not possible (because it is already at the largest possible permutation), - it rearranges the elements according to the first permutation - (sorted in ascending order) and returns `False`. + rearranges the elements as such and returns `True` and the permutation. + If that was not possible (because it is already at the largest possible + permutation), it rearranges the elements according to the first permutation + (sorted in ascending order) and returns `False` and the rearranged permutation. Parameters ========== @@ -1057,26 +1057,23 @@ def next_permutation(array): Returns ======= - output: Bool + output: Bool, Array Returns `True` if the function can rearrange the given array as a - lexicographically greater permutation, otherwise `False`. + lexicographically greater permutation, otherwise `False`. And the + rearranged next permutation Examples ======== >>> from pydatastructs import next_permutation, OneDimensionalArray as ODA >>> array = ODA(int, [1, 2, 3, 4]) - >>> next_permute = next_permutation(array) - >>> next_permute - True - >>> print(array) - [1, 2, 4, 3] + >>> is_greater, next_permute = next_permutation(array) + >>> print(is_greater, next_permute) + True [1, 2, 4, 3] >>> array = ODA(int, [3, 2, 1]) - >>> next_permute = next_permutation(array) - >>> next_permute - False - >>> print(array) - [1, 2, 3] + >>> is_greater, next_permute = next_permutation(array) + >>> print(is_greater, next_permute) + False [1, 2, 3] References ========== @@ -1084,21 +1081,25 @@ def next_permutation(array): .. [1] http://www.cplusplus.com/reference/algorithm/next_permutation/ """ n = len(array) + permute = OneDimensionalArray(int, n) + for i in range(n): + permute[i] = array[i] i = n - 1 - while i > 0 and array[i-1] >= array[i]: + while i > 0 and permute[i-1] >= permute[i]: i -= 1 if i > 0: left, right = i, n - 1 while left <= right: mid = left + (right - left) // 2 - if array[i-1] < array[mid]: + if permute[i-1] < permute[mid]: left = mid + 1 else: right = mid - 1 - array[i-1], array[left-1] = array[left-1], array[i-1] + permute[i-1], permute[left-1] = permute[left-1], permute[i-1] left, right = i, n - 1 while left < right: - array[left], array[right] = array[right], array[left] + permute[left], permute[right] = permute[right], permute[left] left += 1 right -= 1 - return True if i > 0 else False + result = True if i > 0 else False + return result, permute From 87006f141c4ab3e6a6047f8ecbe25e9a5f6ecd3f Mon Sep 17 00:00:00 2001 From: pratikgl Date: Sun, 10 Oct 2021 12:50:26 +0530 Subject: [PATCH 4/6] added prev_permutation algo --- .../linear_data_structures/__init__.py | 3 +- .../linear_data_structures/algorithms.py | 67 ++++++++++++++++++- 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/pydatastructs/linear_data_structures/__init__.py b/pydatastructs/linear_data_structures/__init__.py index 3a89682f6..1e338a9b2 100644 --- a/pydatastructs/linear_data_structures/__init__.py +++ b/pydatastructs/linear_data_structures/__init__.py @@ -37,6 +37,7 @@ upper_bound, lower_bound, longest_increasing_subsequence, - next_permutation + next_permutation, + prev_permutation ) __all__.extend(algorithms.__all__) diff --git a/pydatastructs/linear_data_structures/algorithms.py b/pydatastructs/linear_data_structures/algorithms.py index 29409d41e..1d03db126 100644 --- a/pydatastructs/linear_data_structures/algorithms.py +++ b/pydatastructs/linear_data_structures/algorithms.py @@ -19,7 +19,8 @@ 'upper_bound', 'lower_bound', 'longest_increasing_subsequence', - 'next_permutation' + 'next_permutation', + 'prev_permutation' ] def _merge(array, sl, el, sr, er, end, comp): @@ -1103,3 +1104,67 @@ def next_permutation(array): right -= 1 result = True if i > 0 else False return result, permute + +def prev_permutation(array): + """ + If the function can determine the next lower permutation, it + rearranges the elements as such and returns `True` and the permutation. + If that was not possible (because it is already at the lowest possible + permutation), it rearranges the elements according to the last permutation + (sorted in descending order) and returns `False` and the rearranged permutation. + + Parameters + ========== + + array: OneDimensionalArray + A given array whose previous permutation has to be found. + + Returns + ======= + + output: Bool, Array + Returns `True` if the function can rearrange the given array as a + lexicographically lower permutation, otherwise `False`. And the + rearranged previous permutation + + Examples + ======== + + >>> from pydatastructs import prev_permutation, OneDimensionalArray as ODA + >>> array = ODA(int, [1, 2, 4, 3]) + >>> is_lower, prev_permute = prev_permutation(array) + >>> print(is_lower, prev_permute) + True [1, 2, 3, 4] + >>> array = ODA(int, [1, 2, 3, 4]) + >>> is_lower, prev_permute = prev_permutation(array) + >>> print(is_lower, prev_permute) + False [4, 3, 2, 1] + + References + ========== + + .. [1] http://www.cplusplus.com/reference/algorithm/prev_permutation/ + """ + n = len(array) + permute = OneDimensionalArray(int, n) + for i in range(n): + permute[i] = array[i] + i = n - 1 + while i > 0 and permute[i-1] <= permute[i]: + i -= 1 + if i > 0: + left, right = i, n - 1 + while left <= right: + mid = left + (right - left) // 2 + if permute[i-1] > permute[mid]: + left = mid + 1 + else: + right = mid - 1 + permute[i-1], permute[left-1] = permute[left-1], permute[i-1] + left, right = i, n - 1 + while left < right: + permute[left], permute[right] = permute[right], permute[left] + left += 1 + right -= 1 + result = True if i > 0 else False + return result, permute From 4ed8b9a50978cdbd42c6787a6d350bf6b7b08b70 Mon Sep 17 00:00:00 2001 From: czgdp1807 Date: Sun, 10 Oct 2021 16:44:29 +0530 Subject: [PATCH 5/6] Improvements and NITs --- .../linear_data_structures/algorithms.py | 180 +++++++++++------- 1 file changed, 106 insertions(+), 74 deletions(-) diff --git a/pydatastructs/linear_data_structures/algorithms.py b/pydatastructs/linear_data_structures/algorithms.py index 1d03db126..d17cea035 100644 --- a/pydatastructs/linear_data_structures/algorithms.py +++ b/pydatastructs/linear_data_structures/algorithms.py @@ -1041,27 +1041,67 @@ def longest_increasing_subsequence(array): last_index = parent[last_index] return ans -def next_permutation(array): +def _permutation_util(array, start, end, comp, perm_comp): + size = end - start + 1 + permute = OneDimensionalArray(int, size) + for i, j in zip(range(start, end + 1), range(size)): + permute[j] = array[i] + i = size - 1 + while i > 0 and perm_comp(permute[i - 1], permute[i], comp): + i -= 1 + if i > 0: + left, right = i, size - 1 + while left <= right: + mid = left + (right - left) // 2 + if not perm_comp(permute[i - 1], permute[mid], comp): + left = mid + 1 + else: + right = mid - 1 + permute[i - 1], permute[left - 1] = \ + permute[left - 1], permute[i - 1] + left, right = i, size - 1 + while left < right: + permute[left], permute[right] = permute[right], permute[left] + left += 1 + right -= 1 + result = True if i > 0 else False + return result, permute + +def next_permutation(array, **kwargs): """ If the function can determine the next higher permutation, it - rearranges the elements as such and returns `True` and the permutation. - If that was not possible (because it is already at the largest possible - permutation), it rearranges the elements according to the first permutation - (sorted in ascending order) and returns `False` and the rearranged permutation. + returns `True` and the permutation in a new array. + If that is not possible, because it is already at the largest possible + permutation, it returns the elements according to the first permutation + and returns `False` and the permutation in a new array. Parameters ========== array: OneDimensionalArray - A given array whose next permutation has to be found. + The array which is to be used for finding next permutation. + start: int + The staring index of the considered portion of the array. + Optional, by default 0 + end: int, optional + The ending index of the considered portion of the array. + Optional, by default the index of the last position filled. + comp: lambda/function + The comparator which is to be used for specifying the + desired lexicographical ordering. + Optional, by default, less than is + used for comparing two values. + Returns ======= - output: Bool, Array - Returns `True` if the function can rearrange the given array as a - lexicographically greater permutation, otherwise `False`. And the - rearranged next permutation + output: bool, OneDimensionalArray + First element is `True` if the function can rearrange + the given portion of the input array as a lexicographically + greater permutation, otherwise returns `False`. + Second element is an array having the next permutation. + Examples ======== @@ -1069,63 +1109,66 @@ def next_permutation(array): >>> from pydatastructs import next_permutation, OneDimensionalArray as ODA >>> array = ODA(int, [1, 2, 3, 4]) >>> is_greater, next_permute = next_permutation(array) - >>> print(is_greater, next_permute) - True [1, 2, 4, 3] + >>> is_greater, str(next_permute) + (True, '[1, 2, 4, 3]') >>> array = ODA(int, [3, 2, 1]) >>> is_greater, next_permute = next_permutation(array) - >>> print(is_greater, next_permute) - False [1, 2, 3] + >>> is_greater, str(next_permute) + (False, '[1, 2, 3]') References ========== .. [1] http://www.cplusplus.com/reference/algorithm/next_permutation/ """ - n = len(array) - permute = OneDimensionalArray(int, n) - for i in range(n): - permute[i] = array[i] - i = n - 1 - while i > 0 and permute[i-1] >= permute[i]: - i -= 1 - if i > 0: - left, right = i, n - 1 - while left <= right: - mid = left + (right - left) // 2 - if permute[i-1] < permute[mid]: - left = mid + 1 - else: - right = mid - 1 - permute[i-1], permute[left-1] = permute[left-1], permute[i-1] - left, right = i, n - 1 - while left < right: - permute[left], permute[right] = permute[right], permute[left] - left += 1 - right -= 1 - result = True if i > 0 else False - return result, permute + start = kwargs.get('start', 0) + end = kwargs.get('end', len(array) - 1) + comp = kwargs.get('comp', lambda x, y: x < y) -def prev_permutation(array): + def _next_permutation_comp(x, y, _comp): + if _comp(x, y): + return False + else: + return True + + return _permutation_util(array, start, end, comp, + _next_permutation_comp) + +def prev_permutation(array, **kwargs): """ If the function can determine the next lower permutation, it - rearranges the elements as such and returns `True` and the permutation. - If that was not possible (because it is already at the lowest possible - permutation), it rearranges the elements according to the last permutation - (sorted in descending order) and returns `False` and the rearranged permutation. + returns `True` and the permutation in a new array. + If that is not possible, because it is already at the lowest possible + permutation, it returns the elements according to the last permutation + and returns `False` and the permutation in a new array. Parameters ========== array: OneDimensionalArray - A given array whose previous permutation has to be found. + The array which is to be used for finding next permutation. + start: int + The staring index of the considered portion of the array. + Optional, by default 0 + end: int, optional + The ending index of the considered portion of the array. + Optional, by default the index of the last position filled. + comp: lambda/function + The comparator which is to be used for specifying the + desired lexicographical ordering. + Optional, by default, less than is + used for comparing two values. + Returns ======= - output: Bool, Array - Returns `True` if the function can rearrange the given array as a - lexicographically lower permutation, otherwise `False`. And the - rearranged previous permutation + output: bool, OneDimensionalArray + First element is `True` if the function can rearrange + the given portion of the input array as a lexicographically + smaller permutation, otherwise returns `False`. + Second element is an array having the previous permutation. + Examples ======== @@ -1133,38 +1176,27 @@ def prev_permutation(array): >>> from pydatastructs import prev_permutation, OneDimensionalArray as ODA >>> array = ODA(int, [1, 2, 4, 3]) >>> is_lower, prev_permute = prev_permutation(array) - >>> print(is_lower, prev_permute) - True [1, 2, 3, 4] + >>> is_lower, str(prev_permute) + (True, '[1, 2, 3, 4]') >>> array = ODA(int, [1, 2, 3, 4]) >>> is_lower, prev_permute = prev_permutation(array) - >>> print(is_lower, prev_permute) - False [4, 3, 2, 1] + >>> is_lower, str(prev_permute) + (False, '[4, 3, 2, 1]') References ========== .. [1] http://www.cplusplus.com/reference/algorithm/prev_permutation/ """ - n = len(array) - permute = OneDimensionalArray(int, n) - for i in range(n): - permute[i] = array[i] - i = n - 1 - while i > 0 and permute[i-1] <= permute[i]: - i -= 1 - if i > 0: - left, right = i, n - 1 - while left <= right: - mid = left + (right - left) // 2 - if permute[i-1] > permute[mid]: - left = mid + 1 - else: - right = mid - 1 - permute[i-1], permute[left-1] = permute[left-1], permute[i-1] - left, right = i, n - 1 - while left < right: - permute[left], permute[right] = permute[right], permute[left] - left += 1 - right -= 1 - result = True if i > 0 else False - return result, permute + start = kwargs.get('start', 0) + end = kwargs.get('end', len(array) - 1) + comp = kwargs.get('comp', lambda x, y: x < y) + + def _prev_permutation_comp(x, y, _comp): + if _comp(x, y): + return True + else: + return False + + return _permutation_util(array, start, end, comp, + _prev_permutation_comp) From 1f2691672db007bfcdb89047c4d1487edb9b7091 Mon Sep 17 00:00:00 2001 From: czgdp1807 Date: Sun, 10 Oct 2021 17:07:26 +0530 Subject: [PATCH 6/6] Added tests --- .../tests/test_algorithms.py | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/pydatastructs/linear_data_structures/tests/test_algorithms.py b/pydatastructs/linear_data_structures/tests/test_algorithms.py index bd1cad837..503cedd3f 100644 --- a/pydatastructs/linear_data_structures/tests/test_algorithms.py +++ b/pydatastructs/linear_data_structures/tests/test_algorithms.py @@ -3,7 +3,8 @@ OneDimensionalArray, brick_sort, brick_sort_parallel, heapsort, matrix_multiply_parallel, counting_sort, bucket_sort, cocktail_shaker_sort, quick_sort, longest_common_subsequence, is_ordered, - upper_bound, lower_bound, longest_increasing_subsequence) + upper_bound, lower_bound, longest_increasing_subsequence, next_permutation, + prev_permutation) from pydatastructs.utils.raises_util import raises @@ -271,3 +272,51 @@ def test_longest_increasing_subsequence(): output = longest_increasing_subsequence(arr5) expected_result = [3] assert expected_result == output + +def _test_permutation_common(array, expected_perms, func): + num_perms = len(expected_perms) + + output = [] + for _ in range(num_perms): + signal, array = func(array) + output.append(array) + if not signal: + break + + assert len(output) == len(expected_perms) + for perm1, perm2 in zip(output, expected_perms): + assert str(perm1) == str(perm2) + +def test_next_permutation(): + ODA = OneDimensionalArray + + array = ODA(int, [1, 2, 3]) + expected_perms = [[1, 3, 2], [2, 1, 3], + [2, 3, 1], [3, 1, 2], + [3, 2, 1], [1, 2, 3]] + _test_permutation_common(array, expected_perms, next_permutation) + +def test_prev_permutation(): + ODA = OneDimensionalArray + + array = ODA(int, [3, 2, 1]) + expected_perms = [[3, 1, 2], [2, 3, 1], + [2, 1, 3], [1, 3, 2], + [1, 2, 3], [3, 2, 1]] + _test_permutation_common(array, expected_perms, prev_permutation) + +def test_next_prev_permutation(): + ODA = OneDimensionalArray + random.seed(1000) + + for i in range(100): + data = set(random.sample(range(1, 10000), 10)) + array = ODA(int, list(data)) + + _, next_array = next_permutation(array) + _, orig_array = prev_permutation(next_array) + assert str(orig_array) == str(array) + + _, prev_array = prev_permutation(array) + _, orig_array = next_permutation(prev_array) + assert str(orig_array) == str(array)