A permutation of bag
is a sequence with the same elements as bag
, though possibly in a different order.
If we write bag as (e1 ... en)
, then the set of all permutations contains all permutations where e1
is in first position, all the permutations where e2
is in first positions etc. and all the permutations where en
is the first element.
This way of partitioning the permutations is what the algorithm exploits, by recursively computing all the permutations where each element is in first position.
(defun permutations (bag)
(if (null bag)
'(())
(mapcan #'(lambda (e)
(mapcar #'(lambda (p) (cons e p))
(permutations (remove e bag))))
bag)))
The innermost lambda
:
#'(lambda (p) (cons e p)
is interpreted as:
Given a permutation p
, which is a list of values, return a new permutation with element e
in front.
It is passed to mapcar
for a list of permutations:
(mapcar #'(lambda (p) (cons e p))
(permutations (remove e bag)))
The meaning of the call to mapcar
is thus:
Compute all the permutations of the subset obtained by removing e
from bag
. This gives a list of permutations, each one being a list of values that do not contain e
. For all of those lists, add e
in front. The result of mapcar
is a list of permutations where e
is the element in front of each permutation.
More concretely, if you have a bag (1 2 3)
, and if e
is 1
, then first you remove 1
from the bag, which is (2 3)
, you compute all the permutations recursively, which is ((2 3) (3 2))
, and for all the permutations in that list you add 1
in front of the permutations; you obtain ((1 2 3) (1 3 2))
.
Now, notice that this does not contain all the possible permutations of (1 2 3)
.
You want also to compute the permutations where e
is 2, so you remove 2
and compute the permutations, which are ((1 3) (3 1))
, and you add 2
in front, for another list of permutations, namely ((2 1 3) (2 3 1))
.
Finally, you also want to do the same when e
is 3
. Let's skip the intermediate computations, the result is ((3 1 2) (3 2 1))
.
All the intermediate results are different permutations of (1 2 3)
that cover, without duplicates, all the permutations of the initial bag:
e = 1 : ((1 2 3) (1 3 2))
e = 2 : ((2 1 3) (2 3 1))
e = 3 : ((3 1 2) (3 2 1))
When we append all the lists together, we obtain the list of all the permutations of (1 2 3)
:
((1 2 3) (1 3 2)
(2 1 3) (2 3 1)
(3 1 2) (3 2 1))
That's the purpose of the call to mapcan
: (mapcan ... bag)
computes lists of permutations of bag
for each element of bag
and append them to compute the complete set of permutations.
I don't know how Norvig thought about writing this code in particular, but the recursive algorithms for computing permutations were already documented.
See for example Permutation Generation Methods (R. Sedgewick, 1977). This paper is mostly concerned about computing permutations over vectors, not linked lists, and one of the best algorithm in this category (that minimizes swaps) is Heap's algorithm.
For linked lists, I found this paper Functional Programs for Generating Permutations. by Topor in 1982 (PAIP was published in 1991).
mapcar
you will see the difference pretty clearly. Furthermore if youtrace
permutations
you see thelambda
s working as described in the comment. – Oriyamapcan
doesn't help much because it doesn't show any real-life use cases of it.trace
doesn't help much either because it shows only 2 invocations of thepermutations
one with the input and one with the final output, i.e. it doesn't show the individual progressions of thosemapcan
andmapcar
The only helpful thing is to replace themapcan
withmapcar
as it shows the progression but again it doesn't show clearly how those 2 lambdas work in perfect harmony to produce the correct output, nor explain when to usemapcan
. – Gibert(a b)
and then increase to(a b c)
it should show a difference in the trace. – Oriya'(a b)
trace output is:1. Trace: (PERMUTATION '(A B))
1. Trace: PERMUTATION ==> ((A B) (B A))
i.e. only the input and output repeating. – Gibertclisp
which gave only 2 trace outputs for a 2 elements input'(a b)
Obviously something was wrong with it so I've tried it onsbcl
with 3 elements input'(a b c)
and it gave 31 lines of trace output and it's quite informative. Here it is Thank you for the comment, it's helpful. – Gibert