Clojure: permutations
I was solving an Advent of Code problem and part of the solution required generating all the permutations of a set of numbers. Permutation is the act of arranging the members of a set into different orders. I wanted to write a recursive solution rather than an imperative solution. After some searching I found a Scheme implementation. As Scheme and Clojure are both dialects of Lisp, translating one into the other wasn't too difficult.
The Scheme implementation:
(define (permutations l)
(cond
((= (length l) 1) (list l))
(else (apply append
(map
(lambda (head)
(map
(lambda (tail) (cons head tail))
(permutations (remove head l))))
l)))))
(permutations '(1 2 3))
=> ((1 2 3) (1 3 2) (2 1 3) (2 3 1) (3 1 2) (3 2 1))
A first pass at the translation sees some minor changes: length
to count
, lambda
to fn
, append
to concat
and finally remove
in Clojure takes a predicate rather than an element to remove. This gives us:
(defn permutations [l]
(cond (= (count l) 1)
(list l)
:else
(apply concat
(map (fn [head]
(map (fn [tail]
(cons head tail))
(permutations (remove #{head} l))))
l))))
(permutations [1 2 3])
=> ((1 2 3) (1 3 2) (2 1 3) (2 3 1) (3 1 2) (3 2 1))
In Clojure, a nested map
can be replaced with a for
comprehension; with the first binding acting as the outer loop and the second binding as the inner loop. l
has been renamed to s
as the fundamental data structure in Clojure is the sequence and not the list:
(defn permutations [s]
(if (= (count s) 1)
(list s)
(for [head s
tail (permutations (remove #{head} s))]
(cons head tail))))
(permutations [1 2 3])
=> ((1 2 3) (1 3 2) (2 1 3) (2 3 1) (3 1 2) (3 2 1))
This recursive solution can be made to use a single stack frame by wrapping it in a lazy-seq
:
(defn permutations [s]
(lazy-seq
(if (= (count s) 1)
(list s)
(for [head s
tail (permutations (remove #{head} s))]
(cons head tail)))))
(permutations [1 2 3])
=> ((1 2 3) (1 3 2) (2 1 3) (2 3 1) (3 1 2) (3 2 1))
count
is replaced with next
to make the solution more idiomatic:
(defn permutations [s]
(lazy-seq
(if (next s)
(for [head s
tail (permutations (remove #{head} s))]
(cons head tail))
[s])))
(permutations [1 2 3])
=> ((1 2 3) (1 3 2) (2 1 3) (2 3 1) (3 1 2) (3 2 1))
The permutation function is working fine. However, when duplicate values are passed, there are unexpected results:
(permutations '(2 2))
=> ((2) (2))
In this case the output is ((2) (2))
, but the expected output should be ((2 2) (2 2))
, as each 2
is a separate item despite having the same value. This can be fixed by replacing remove
with remove-first
. This function removes the first element that matches the predicate rather than all elements that match it:
(defn remove-first [pred s]
(lazy-seq
(when-let [[x & xs] (seq s)]
(cond (pred x) xs
:else (cons x (remove-first pred xs))))))
(defn permutations [s]
(lazy-seq
(if (next s)
(for [head s
tail (permutations (remove-first #{head} s))]
(cons head tail))
[s])))
(permutations [2 2])
=> ((2 2) (2 2))
This works as intended assuming it is desirable for the function to handle duplicate values. If this is not the case, this can be made explicit by passing in a set and using disj
instead of remove
:
(defn permutations [s]
(lazy-seq
(if (next s)
(for [head s
tail (permutations (disj s head))]
(cons head tail))
[s])))
(permutations #{1 2 3})
=> ((1 2 3) (1 3 2) (2 1 3) (2 3 1) (3 1 2) (3 2 1))
This implementation also has a performance advantage over the previous version with remove
. This is because disj
has O(log32N)
(almost O(1)
) performance compared to remove
which has O(N)
performance.
A more comprehensive collection of functions for generating permutations and combinations can be found in the clojure.math.combinatorics library.