Skip to content
This repository was archived by the owner on Dec 12, 2023. It is now read-only.
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions solutions/python/string-permutations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#!/usr/bin/env python

"""
This module presents two different solutions for the problem of generating the permutations of a sequence.
"""

from math import factorial
import unittest


def perm_recursive(S):
"""
Return a list with all permutations of the iterable passed as argument.
Uses the simple recursive solution. This Algorithm does not handle repeated elements well.
"""

def expand_inserting(c, L):
return [L[0:i] + [c] + L[i:] for i in range(len(L) + 1)]

if not isinstance(S, list):
S = list(S) # to handle strings
if len(S) == 0:
return []
elif len(S) == 1:
return [S]

c, L = S[0], S[1:]
res = []
for newL in perm_recursive(L):
res.extend(expand_inserting(c, newL))
return res


def perm_generator(A):
"""
Generates all permutations of elements of the iterable passed as argument.
Uses Non-recursive lexicographic order (Knuth's L-Algorithm).
Requires all A elements are comparable to each other (list has to be sortable).
The algorithm handles repeated elements gracefully.
Reference: https://en.wikipedia.org/wiki/Permutation#Permutations_in_computing
"""
n = len(A)
a = sorted(list(A))
ok = False if n == 0 else True

while ok:
yield a[:]
# Find the largest index k such that a[k] < a[k + 1]
k = next((j for j in range(n-2, -1, -1) if a[j] < a[j+1]), -1)
if k == -1: # if no such index, is last permutation
break
# Find the largest index l greater than k such that a[k] < a[l]
l = next((j for j in range(n-1, k, -1) if a[k] < a[j]), -1)
# Swap values
a[k], a[l] = a[l], a[k]
# Reverse the sequence from a[k + 1] up to and including the final element a[n]
a[k+1:] = a[n-1:k:-1]


class Test(unittest.TestCase):

def setUp(self):
self.perm_ABC = (('A', 'B', 'C'), ('B', 'A', 'C'), ('B', 'C', 'A'), ('A', 'C', 'B'), ('C', 'A', 'B'),
('C', 'B', 'A'))

def test_recursive1(self):
""" Permutations of list [A,B,C] correct """
A = ['A', 'B', 'C']
r = perm_recursive(A)
self.assertEqual(set(tuple([tuple(l) for l in r])), set(self.perm_ABC))
self.assertEqual(len(r), factorial(len(A)))

def test_recursive2(self):
""" Permutations of str "ABC" correct """
A = "ABC"
r = perm_recursive(A)
self.assertEqual(set(tuple([tuple(l) for l in r])), set(self.perm_ABC))
self.assertEqual(len(r), factorial(len(A)))

def test_recursive3(self):
""" Empty string produces empty list """
A = ""
r = perm_recursive(A)
self.assertEqual(r, [])

def test_recursive4(self):
""" A sigle letter produces only one permutation """
A = "A"
r = perm_recursive(A)
self.assertEqual(r, [['A']])

def test_recursive5(self):
""" Two repeated letter wrongly produce 2 permutations. Algorithm does not handle repeated elements! """
A = "AA"
r = perm_recursive(A)
self.assertEqual(r, [['A', 'A'], ['A', 'A']])

def test_generator1(self):
""" Generated permutations of list [A,B,C] correct """
A = ['A', 'B', 'C']
r = list(perm_generator(A))
self.assertEqual(set(tuple([tuple(l) for l in r])), set(self.perm_ABC))
self.assertEqual(len(r), factorial(len(A)))

def test_generator2(self):
""" Generated Permutations of str "ABC" correct """
A = "BAC"
r = list(perm_generator(A))
self.assertEqual(set(tuple([tuple(l) for l in r])), set(self.perm_ABC))
self.assertEqual(len(r), factorial(len(A)))

def test_generator3(self):
""" Two repeated letters generate only one permutation. Algorithm handles repeated elements well. """
A = "AA"
r = list(perm_generator(A))
self.assertEqual(r, [['A', 'A']])


if __name__ == '__main__':
unittest.main()