Reputation: 11
I was trying to write my own code to print all the permutations of a string(cause I'm a beginner and want to practice and get familiar). But when I convert the string into a list outside the function it doesn't work, while if I convert it inside and convert it back to a string before the recursion, it works...
def permutations(mesg, l, r):
msg = list(mesg)
if l == r-1:
print("".join(msg))
else:
for i in range(l, r):
msg[i], msg[l] = msg[l], msg[i]
permutations("".join(msg), l+1, r)
message = "ABC"
permutations(message, 0, len(message))
input("press X to exti: ")
and the output is: ABC ACB BAC BCA CAB CBA
def permutations(msg, l, r):
if l == r-1:
print("".join(msg))
else:
for i in range(l, r):
msg[i], msg[l] = msg[l], msg[i]
permutations(msg, l+1, r)
message = "ABC"
permutations(list(message), 0, len(message))
input("press X to exti: ")
and the output is: ABC ACB CAB CBA ABC ACB
the l and i variable progress how they should: # 00,11,12,01,11,12,02,11,12. The first number is l and the other is i. each pair is printed inside the for loop before the recursion.
Upvotes: 1
Views: 52
Reputation: 57204
The reason for this behavior is that lists are mutable, yet recursion requires a property of self-similarity: the input to a recursive call cannot mess with its parent state or you'll wind up with a different list than the caller originally was working on in its for
loop once the child finishes working and control returns to the parent.
The top code breaches this rule: the each trip through the loop swaps elements in the list but nothing is ever restored, so all of the recursive manipulations down through the calls to a leaf node pollute future trips through the parent loop.
list()
solves the problem by achieving immutability, but it's not the most performance-conscious approach because you're copying the whole list on every call frame. The other typical approach is usually to "undo" the swap operation after each child call returns:
def permutations(lst, left, right):
if left == right - 1:
print("".join(lst))
else:
for i in range(left, right):
lst[i], lst[left] = lst[left], lst[i]
permutations(lst, left + 1, right)
# undo the swap to restore the list
lst[i], lst[left] = lst[left], lst[i]
if __name__ == "__main__":
permutations(list("abc"), 0, 3)
A few additional suggestions:
l
as a variable name because it's too easy to confuse for a 1 or an i.right
is just an alias for len(lst)
. Just call len
instead and simplify your state. Only use right
if it's a movable index, like binary search.print("".join(lst))
is better as yield lst[:]
. Any permutation function should basically return a generator as itertools.permutations
does. Returning a list of lists is also much better than a print
side effect that basically makes the function single-purpose and unuseful for anything programmatic that you want to apply more operations to (and you usually do; print
is a dead end here).[1, 2, 3]
as the argument, and the result should still be a list instead of "123", ...
.left
(really i
) can be a default argument.Putting it together:
def permutations(lst, i=0):
if i == len(lst) - 1:
yield lst[:]
else:
for j in range(i, len(lst)):
lst[i], lst[j] = lst[j], lst[i]
yield from permutations(lst, i + 1)
lst[i], lst[j] = lst[j], lst[i]
if __name__ == "__main__":
for p in permutations(list("abc")):
print("".join(p))
(needless to say, never actually use this code, use itertools.permutations
-- it's much faster)
Upvotes: 0