Skip to content

Commit 9fdbb66

Browse files
alfre2vblakeembrey
authored andcommitted
Add string permutations solution in Python with 2 algorithms (blakeembrey#170)
Lint with pep8 and flake
1 parent 58d64b2 commit 9fdbb66

File tree

1 file changed

+120
-0
lines changed

1 file changed

+120
-0
lines changed
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#!/usr/bin/env python
2+
3+
"""
4+
This module presents two different solutions for the problem of generating the permutations of a sequence.
5+
"""
6+
7+
from math import factorial
8+
import unittest
9+
10+
11+
def perm_recursive(S):
12+
"""
13+
Return a list with all permutations of the iterable passed as argument.
14+
Uses the simple recursive solution. This Algorithm does not handle repeated elements well.
15+
"""
16+
17+
def expand_inserting(c, L):
18+
return [L[0:i] + [c] + L[i:] for i in range(len(L) + 1)]
19+
20+
if not isinstance(S, list):
21+
S = list(S) # to handle strings
22+
if len(S) == 0:
23+
return []
24+
elif len(S) == 1:
25+
return [S]
26+
27+
c, L = S[0], S[1:]
28+
res = []
29+
for newL in perm_recursive(L):
30+
res.extend(expand_inserting(c, newL))
31+
return res
32+
33+
34+
def perm_generator(A):
35+
"""
36+
Generates all permutations of elements of the iterable passed as argument.
37+
Uses Non-recursive lexicographic order (Knuth's L-Algorithm).
38+
Requires all A elements are comparable to each other (list has to be sortable).
39+
The algorithm handles repeated elements gracefully.
40+
Reference: https://en.wikipedia.org/wiki/Permutation#Permutations_in_computing
41+
"""
42+
n = len(A)
43+
a = sorted(list(A))
44+
ok = False if n == 0 else True
45+
46+
while ok:
47+
yield a[:]
48+
# Find the largest index k such that a[k] < a[k + 1]
49+
k = next((j for j in range(n-2, -1, -1) if a[j] < a[j+1]), -1)
50+
if k == -1: # if no such index, is last permutation
51+
break
52+
# Find the largest index l greater than k such that a[k] < a[l]
53+
l = next((j for j in range(n-1, k, -1) if a[k] < a[j]), -1)
54+
# Swap values
55+
a[k], a[l] = a[l], a[k]
56+
# Reverse the sequence from a[k + 1] up to and including the final element a[n]
57+
a[k+1:] = a[n-1:k:-1]
58+
59+
60+
class Test(unittest.TestCase):
61+
62+
def setUp(self):
63+
self.perm_ABC = (('A', 'B', 'C'), ('B', 'A', 'C'), ('B', 'C', 'A'), ('A', 'C', 'B'), ('C', 'A', 'B'),
64+
('C', 'B', 'A'))
65+
66+
def test_recursive1(self):
67+
""" Permutations of list [A,B,C] correct """
68+
A = ['A', 'B', 'C']
69+
r = perm_recursive(A)
70+
self.assertEqual(set(tuple([tuple(l) for l in r])), set(self.perm_ABC))
71+
self.assertEqual(len(r), factorial(len(A)))
72+
73+
def test_recursive2(self):
74+
""" Permutations of str "ABC" correct """
75+
A = "ABC"
76+
r = perm_recursive(A)
77+
self.assertEqual(set(tuple([tuple(l) for l in r])), set(self.perm_ABC))
78+
self.assertEqual(len(r), factorial(len(A)))
79+
80+
def test_recursive3(self):
81+
""" Empty string produces empty list """
82+
A = ""
83+
r = perm_recursive(A)
84+
self.assertEqual(r, [])
85+
86+
def test_recursive4(self):
87+
""" A sigle letter produces only one permutation """
88+
A = "A"
89+
r = perm_recursive(A)
90+
self.assertEqual(r, [['A']])
91+
92+
def test_recursive5(self):
93+
""" Two repeated letter wrongly produce 2 permutations. Algorithm does not handle repeated elements! """
94+
A = "AA"
95+
r = perm_recursive(A)
96+
self.assertEqual(r, [['A', 'A'], ['A', 'A']])
97+
98+
def test_generator1(self):
99+
""" Generated permutations of list [A,B,C] correct """
100+
A = ['A', 'B', 'C']
101+
r = list(perm_generator(A))
102+
self.assertEqual(set(tuple([tuple(l) for l in r])), set(self.perm_ABC))
103+
self.assertEqual(len(r), factorial(len(A)))
104+
105+
def test_generator2(self):
106+
""" Generated Permutations of str "ABC" correct """
107+
A = "BAC"
108+
r = list(perm_generator(A))
109+
self.assertEqual(set(tuple([tuple(l) for l in r])), set(self.perm_ABC))
110+
self.assertEqual(len(r), factorial(len(A)))
111+
112+
def test_generator3(self):
113+
""" Two repeated letters generate only one permutation. Algorithm handles repeated elements well. """
114+
A = "AA"
115+
r = list(perm_generator(A))
116+
self.assertEqual(r, [['A', 'A']])
117+
118+
119+
if __name__ == '__main__':
120+
unittest.main()

0 commit comments

Comments
 (0)