diff --git a/pydatastructs/linear_data_structures/__init__.py b/pydatastructs/linear_data_structures/__init__.py index 64fbbfb3b..1e338a9b2 100644 --- a/pydatastructs/linear_data_structures/__init__.py +++ b/pydatastructs/linear_data_structures/__init__.py @@ -36,6 +36,8 @@ is_ordered, upper_bound, lower_bound, - longest_increasing_subsequence + longest_increasing_subsequence, + 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 0a2d6de7a..d17cea035 100644 --- a/pydatastructs/linear_data_structures/algorithms.py +++ b/pydatastructs/linear_data_structures/algorithms.py @@ -18,7 +18,9 @@ 'is_ordered', 'upper_bound', 'lower_bound', - 'longest_increasing_subsequence' + 'longest_increasing_subsequence', + 'next_permutation', + 'prev_permutation' ] def _merge(array, sl, el, sr, er, end, comp): @@ -1038,3 +1040,163 @@ def longest_increasing_subsequence(array): ans[:0] = [array[last_index]] last_index = parent[last_index] return ans + +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 + 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 + 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, 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 + ======== + + >>> from pydatastructs import next_permutation, OneDimensionalArray as ODA + >>> array = ODA(int, [1, 2, 3, 4]) + >>> is_greater, next_permute = next_permutation(array) + >>> is_greater, str(next_permute) + (True, '[1, 2, 4, 3]') + >>> array = ODA(int, [3, 2, 1]) + >>> is_greater, next_permute = next_permutation(array) + >>> is_greater, str(next_permute) + (False, '[1, 2, 3]') + + References + ========== + + .. [1] http://www.cplusplus.com/reference/algorithm/next_permutation/ + """ + start = kwargs.get('start', 0) + end = kwargs.get('end', len(array) - 1) + comp = kwargs.get('comp', lambda x, y: x < y) + + 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 + 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 + 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, 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 + ======== + + >>> from pydatastructs import prev_permutation, OneDimensionalArray as ODA + >>> array = ODA(int, [1, 2, 4, 3]) + >>> is_lower, prev_permute = prev_permutation(array) + >>> is_lower, str(prev_permute) + (True, '[1, 2, 3, 4]') + >>> array = ODA(int, [1, 2, 3, 4]) + >>> is_lower, prev_permute = prev_permutation(array) + >>> is_lower, str(prev_permute) + (False, '[4, 3, 2, 1]') + + References + ========== + + .. [1] http://www.cplusplus.com/reference/algorithm/prev_permutation/ + """ + 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) 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)