Where'd you get this code from? I'd say this is a bad example of code for learning python, complex one-liners like that return statement are often a bad idea. Most python code I can understand what it's doing just looking at it, but that line of code is completely meaningless to me.
–
davrApr 6 '10 at 0:39

1

Agreed. 'sz', 'perm', 'l', 'p', etc., do very little to help the learning process as well.
–
Chris CooperApr 6 '10 at 1:09

5 Answers
5

This return is returning a list comprehension whose items are made by inserting the first item of l into each position of p, from the first to the last -- p in turn is a list of lists, obtained by a recursive call to perm which excludes the first item of l (and thus permutes all other items in all possible ways).

If you don't understand recursion, it's not really trivial to explain;-). If you don't understand list comprehensions, they are trivial to explain -- that return is semantically equivalent to

result = []
for i in range(sz):
for p in perm(l[1:]):
result.append(p[:i]+[l[0]]+p[i:])
return result

this also shows how inefficient this code is: it's calling perm recursively sz times, and obviously there's no need for it. Much better would be to simply swap the two for loops:

result = []
for p in perm(l[1:]):
for i in range(sz):
result.append(p[:i]+[l[0]]+p[i:])
return result

and the equivalent of this, much better code, is a list comprehension with the two for clauses swapped:

The return statement is using a list comprehension. It's a bit easier to understand if you put it into actual loops:

value = []
for i in range(sz):
# call this function using all but the first item in l
for p in perm(l[1:]):
# now place the first item in l between index i-1 and index i in p
value.append(p[:i] + [l[0]] + p[i:])
return value

So, here's the thing, p stands for all permutations of l[1:] (ie, l minus the first element). Next, i is range(sz), which means it varies from 0 to the length of l. That will split p in two lists of all possible sizes (0 and sz, 1 and sz -1, 2 and sz - 2, etc), and insert the first element of l -- the one that didn't get permuted -- between these two lists.

Starting code

Revision 1

def perm(s):
# Base case: an empty list or a list with only one item has only one
# permutation
if len(s) <= 1:
return [s]
return [p[:i] + [s[0]] + p[i:]
for i in range(len(s)) for p in perm(s[1:])]

Rename l to s

Remove sz, instead using len(s) directly. We might lose a tiny bit of efficiency, but we gain a huge amount of readability

Fix spacing in list comprehension

Revision 2

def perm(s):
# Base case: an empty list or a list with only one item has only one
# permutation
if len(s) <= 1:
return [s]
# A list of permutations
permutations = []
for i in range(len(s)):
# Recursively find all permutations of s[1:]
for p in perm(s[1:]):
# Insert s[0] in position i
permutations.append(p[:i] + [s[0]] + p[i:])
return permutations

Break apart the list comprehension

Revision 3

def perm(s):
# Base case: an empty list or a list with only one item has only one
# permutation
if len(s) <= 1:
return [s]
# A list of permutations
permutations = []
# Recursively find all permutations of s[1:]
for p in perm(s[1:]):
for i in range(len(s)):
# Insert s[0] in position i
permutations.append(p[:i] + [s[0]] + p[i:])
return permutations

Change the nesting of the for loops. Now, you can say: for each permutation, take each position i, and add a copy of that permutation with s[0] inserted in each position i. This gets clearer in the next few revisions.

Revision 4

def perm(s):
# Base case: an empty list or a list with only one item has only one
# permutation
if len(s) <= 1:
return [s]
# Recursively find all permutations of s[1:]
shortperms = perm(s[1:])
# A list of permutations
permutations = []
for shortperm in shortperms:
for i in range(len(s)):
# Make a copy of shortperm
spcopy = shortperm[:]
# Insert s[0] in position i
spcopy.insert(s[0], i)
# Add this to the list of permutations
permutations.append(spcopy)
return permutations

Moved the perm function call. Now, the shortperms variable will contain all the permutations of s[1:], which is s minus the first item.

Changed the list addition into three operations:

Make a copy of shortperm

Insert the first item in s

Add that list to permutations

Revision 5

def perm(s):
# Base case: an empty list or a list with only one item has only one
# permutation
if len(s) <= 1:
return [s]
# Recursively find all permutations of s[1:]
shortperms = perm(s[1:])
# A list of permutations
permutations = []
for shortperm in shortperms:
for i in range(len(shortperm) + 1):
# Make a copy of shortperm
spcopy = shortperm[:]
# Insert s[0] in position i
spcopy.insert(s[0], i)
# Add this to the list of permutations
permutations.append(spcopy)
return permutations

len(s) is the same as len(shortperm) + 1, because each shortperm is a permutation of the items in s, minus the first one. However, this is probably more readable.

Final code

With a docstring comment

def perm(s):
"""Return a list of all permutations of the items in the input
sequence."""
# Base case: an empty list or a list with only one item has only one
# permutation
if len(s) <= 1:
return [s]
# Recursively find all permutations of s[1:]
shortperms = perm(s[1:])
# A list of permutations
permutations = []
for shortperm in shortperms:
for i in range(len(shortperm) + 1):
# Make a copy of shortperm
spcopy = shortperm[:]
# Insert s[0] in position i
spcopy.insert(s[0], i)
# Add this to the list of permutations
permutations.append(spcopy)
return permutations