From 2f95e05bbca4ffb7eba095e8c704a5b92e656354 Mon Sep 17 00:00:00 2001 From: Alfredo Valles Valdes Date: Wed, 3 Aug 2016 00:59:24 +0200 Subject: [PATCH] Add string permutations solution in Python with 2 algorithms Lint with pep8 and flake --- solutions/python/string-permutations.py | 120 ++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 solutions/python/string-permutations.py diff --git a/solutions/python/string-permutations.py b/solutions/python/string-permutations.py new file mode 100644 index 0000000..d613fc9 --- /dev/null +++ b/solutions/python/string-permutations.py @@ -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()