Skip to content

Commit 0ed9ce1

Browse files
pratikglczgdp1807
andauthored
Added next_permutation and prev_permutation algorithms (#405)
Co-authored-by: czgdp1807 <gdp.1807@gmail.com>
1 parent 0edbada commit 0ed9ce1

File tree

3 files changed

+216
-3
lines changed

3 files changed

+216
-3
lines changed

pydatastructs/linear_data_structures/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
is_ordered,
3737
upper_bound,
3838
lower_bound,
39-
longest_increasing_subsequence
39+
longest_increasing_subsequence,
40+
next_permutation,
41+
prev_permutation
4042
)
4143
__all__.extend(algorithms.__all__)

pydatastructs/linear_data_structures/algorithms.py

Lines changed: 163 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
'is_ordered',
1919
'upper_bound',
2020
'lower_bound',
21-
'longest_increasing_subsequence'
21+
'longest_increasing_subsequence',
22+
'next_permutation',
23+
'prev_permutation'
2224
]
2325

2426
def _merge(array, sl, el, sr, er, end, comp):
@@ -1038,3 +1040,163 @@ def longest_increasing_subsequence(array):
10381040
ans[:0] = [array[last_index]]
10391041
last_index = parent[last_index]
10401042
return ans
1043+
1044+
def _permutation_util(array, start, end, comp, perm_comp):
1045+
size = end - start + 1
1046+
permute = OneDimensionalArray(int, size)
1047+
for i, j in zip(range(start, end + 1), range(size)):
1048+
permute[j] = array[i]
1049+
i = size - 1
1050+
while i > 0 and perm_comp(permute[i - 1], permute[i], comp):
1051+
i -= 1
1052+
if i > 0:
1053+
left, right = i, size - 1
1054+
while left <= right:
1055+
mid = left + (right - left) // 2
1056+
if not perm_comp(permute[i - 1], permute[mid], comp):
1057+
left = mid + 1
1058+
else:
1059+
right = mid - 1
1060+
permute[i - 1], permute[left - 1] = \
1061+
permute[left - 1], permute[i - 1]
1062+
left, right = i, size - 1
1063+
while left < right:
1064+
permute[left], permute[right] = permute[right], permute[left]
1065+
left += 1
1066+
right -= 1
1067+
result = True if i > 0 else False
1068+
return result, permute
1069+
1070+
def next_permutation(array, **kwargs):
1071+
"""
1072+
If the function can determine the next higher permutation, it
1073+
returns `True` and the permutation in a new array.
1074+
If that is not possible, because it is already at the largest possible
1075+
permutation, it returns the elements according to the first permutation
1076+
and returns `False` and the permutation in a new array.
1077+
1078+
Parameters
1079+
==========
1080+
1081+
array: OneDimensionalArray
1082+
The array which is to be used for finding next permutation.
1083+
start: int
1084+
The staring index of the considered portion of the array.
1085+
Optional, by default 0
1086+
end: int, optional
1087+
The ending index of the considered portion of the array.
1088+
Optional, by default the index of the last position filled.
1089+
comp: lambda/function
1090+
The comparator which is to be used for specifying the
1091+
desired lexicographical ordering.
1092+
Optional, by default, less than is
1093+
used for comparing two values.
1094+
1095+
1096+
Returns
1097+
=======
1098+
1099+
output: bool, OneDimensionalArray
1100+
First element is `True` if the function can rearrange
1101+
the given portion of the input array as a lexicographically
1102+
greater permutation, otherwise returns `False`.
1103+
Second element is an array having the next permutation.
1104+
1105+
1106+
Examples
1107+
========
1108+
1109+
>>> from pydatastructs import next_permutation, OneDimensionalArray as ODA
1110+
>>> array = ODA(int, [1, 2, 3, 4])
1111+
>>> is_greater, next_permute = next_permutation(array)
1112+
>>> is_greater, str(next_permute)
1113+
(True, '[1, 2, 4, 3]')
1114+
>>> array = ODA(int, [3, 2, 1])
1115+
>>> is_greater, next_permute = next_permutation(array)
1116+
>>> is_greater, str(next_permute)
1117+
(False, '[1, 2, 3]')
1118+
1119+
References
1120+
==========
1121+
1122+
.. [1] http://www.cplusplus.com/reference/algorithm/next_permutation/
1123+
"""
1124+
start = kwargs.get('start', 0)
1125+
end = kwargs.get('end', len(array) - 1)
1126+
comp = kwargs.get('comp', lambda x, y: x < y)
1127+
1128+
def _next_permutation_comp(x, y, _comp):
1129+
if _comp(x, y):
1130+
return False
1131+
else:
1132+
return True
1133+
1134+
return _permutation_util(array, start, end, comp,
1135+
_next_permutation_comp)
1136+
1137+
def prev_permutation(array, **kwargs):
1138+
"""
1139+
If the function can determine the next lower permutation, it
1140+
returns `True` and the permutation in a new array.
1141+
If that is not possible, because it is already at the lowest possible
1142+
permutation, it returns the elements according to the last permutation
1143+
and returns `False` and the permutation in a new array.
1144+
1145+
Parameters
1146+
==========
1147+
1148+
array: OneDimensionalArray
1149+
The array which is to be used for finding next permutation.
1150+
start: int
1151+
The staring index of the considered portion of the array.
1152+
Optional, by default 0
1153+
end: int, optional
1154+
The ending index of the considered portion of the array.
1155+
Optional, by default the index of the last position filled.
1156+
comp: lambda/function
1157+
The comparator which is to be used for specifying the
1158+
desired lexicographical ordering.
1159+
Optional, by default, less than is
1160+
used for comparing two values.
1161+
1162+
1163+
Returns
1164+
=======
1165+
1166+
output: bool, OneDimensionalArray
1167+
First element is `True` if the function can rearrange
1168+
the given portion of the input array as a lexicographically
1169+
smaller permutation, otherwise returns `False`.
1170+
Second element is an array having the previous permutation.
1171+
1172+
1173+
Examples
1174+
========
1175+
1176+
>>> from pydatastructs import prev_permutation, OneDimensionalArray as ODA
1177+
>>> array = ODA(int, [1, 2, 4, 3])
1178+
>>> is_lower, prev_permute = prev_permutation(array)
1179+
>>> is_lower, str(prev_permute)
1180+
(True, '[1, 2, 3, 4]')
1181+
>>> array = ODA(int, [1, 2, 3, 4])
1182+
>>> is_lower, prev_permute = prev_permutation(array)
1183+
>>> is_lower, str(prev_permute)
1184+
(False, '[4, 3, 2, 1]')
1185+
1186+
References
1187+
==========
1188+
1189+
.. [1] http://www.cplusplus.com/reference/algorithm/prev_permutation/
1190+
"""
1191+
start = kwargs.get('start', 0)
1192+
end = kwargs.get('end', len(array) - 1)
1193+
comp = kwargs.get('comp', lambda x, y: x < y)
1194+
1195+
def _prev_permutation_comp(x, y, _comp):
1196+
if _comp(x, y):
1197+
return True
1198+
else:
1199+
return False
1200+
1201+
return _permutation_util(array, start, end, comp,
1202+
_prev_permutation_comp)

pydatastructs/linear_data_structures/tests/test_algorithms.py

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
OneDimensionalArray, brick_sort, brick_sort_parallel,
44
heapsort, matrix_multiply_parallel, counting_sort, bucket_sort,
55
cocktail_shaker_sort, quick_sort, longest_common_subsequence, is_ordered,
6-
upper_bound, lower_bound, longest_increasing_subsequence)
6+
upper_bound, lower_bound, longest_increasing_subsequence, next_permutation,
7+
prev_permutation)
78

89

910
from pydatastructs.utils.raises_util import raises
@@ -271,3 +272,51 @@ def test_longest_increasing_subsequence():
271272
output = longest_increasing_subsequence(arr5)
272273
expected_result = [3]
273274
assert expected_result == output
275+
276+
def _test_permutation_common(array, expected_perms, func):
277+
num_perms = len(expected_perms)
278+
279+
output = []
280+
for _ in range(num_perms):
281+
signal, array = func(array)
282+
output.append(array)
283+
if not signal:
284+
break
285+
286+
assert len(output) == len(expected_perms)
287+
for perm1, perm2 in zip(output, expected_perms):
288+
assert str(perm1) == str(perm2)
289+
290+
def test_next_permutation():
291+
ODA = OneDimensionalArray
292+
293+
array = ODA(int, [1, 2, 3])
294+
expected_perms = [[1, 3, 2], [2, 1, 3],
295+
[2, 3, 1], [3, 1, 2],
296+
[3, 2, 1], [1, 2, 3]]
297+
_test_permutation_common(array, expected_perms, next_permutation)
298+
299+
def test_prev_permutation():
300+
ODA = OneDimensionalArray
301+
302+
array = ODA(int, [3, 2, 1])
303+
expected_perms = [[3, 1, 2], [2, 3, 1],
304+
[2, 1, 3], [1, 3, 2],
305+
[1, 2, 3], [3, 2, 1]]
306+
_test_permutation_common(array, expected_perms, prev_permutation)
307+
308+
def test_next_prev_permutation():
309+
ODA = OneDimensionalArray
310+
random.seed(1000)
311+
312+
for i in range(100):
313+
data = set(random.sample(range(1, 10000), 10))
314+
array = ODA(int, list(data))
315+
316+
_, next_array = next_permutation(array)
317+
_, orig_array = prev_permutation(next_array)
318+
assert str(orig_array) == str(array)
319+
320+
_, prev_array = prev_permutation(array)
321+
_, orig_array = next_permutation(prev_array)
322+
assert str(orig_array) == str(array)

0 commit comments

Comments
 (0)