Permutations in Haskell or "I feel old ..."

I was actually looking for someone who have made a simulation of the gravitational force between particles, but instead I came across another topic concerning haskell.

Haskell - I feel old...

The author had tried to grok haskell a few times and was stuck again on his third attempt. He had a problem with understanding an implementation of calculating all possible permutations of a list of items.

His friend had given him one implementation which was difficult to grasp.

permute :: [a] -> [[a]]
permute = foldr (concatMap . ins) [[]]
   where ins x []     = [[x]]
         ins x (y:ys) = (x:y:ys):[ y:res | res <- ins x ys]

So I thought I would try implement a variant of permute. To understand this explanation it is useful to know basic haskell concepts including lists, functions and pattern matching and list comprehension.

First I tried some examples of permute on paper.

                    [a] 
        [a,b]                  [b,a]              -- insert b in [a]
[c,a,b][a,c,b][a,b,c]  [c,b,a][b,c,a][b,a,c]      -- insert c in [a,b] and [b,a]

So for one item you are done. For two items you can switch the order to get the second permutation. For three items you can insert the third item (c) at three location in both of the permutations of the previous set of two permutations. This is actually what you do when going from one to two elements as well i.e. inserting b before and after a.

So it would be useful to have a function insert (ins) which insert an item at all possible locations in a list and return all these lists in a list. So how do you do that. Imagine the following function. We have a single item x and a list where the first item is y (head) and the rest of the list is ys (tail).

ins x (y:ys)

One list where x is inserted in (y:ys) is x:y:ys i.e. x inserted first in the list y:ys. ins x ys is x inserted in ys at all possible locations. Lets take a concrete example.

x = 'c'
(y:ys) = ['a','b']    -- y = 'a' and ys = ['b']

In this case x:y:ys would compute to the following.

x:y:ys        --  ['c','a','b']
ins x ys      --  ins 'c' ['b'] ==> [['c','b'],['b','c']]

So if you take y in front of each of ins x ys results you will have the rest of the results from ins x (y:ys). This can be expressed as list comprehension.

[y:res | res <- ins x ys]

So ins can really be implemented by taking x:y:ys as a first result and then insert y in front of the recursive result from ins x ys i.e. [y:res | res <- ins x ys]. The only thing to do is to define a base case for the recursion to finish. Insert x in a empty list is the most basic case which is trivially implemented.

ins x [] = [[x]]

So now we can understand why ins can be implemented as follows.

ins x []     = [[x]]
ins x (y:ys) = (x:y:ys):[y:res | res <- ins x ys]

From the examples of permutations above I would implement permute as follows. I also added a small main to test the function.

permute :: [a] -> [[a]]
permute []       = [[]]
permute (x:[])   = [[x]]
permute (x:y:[]) = [[x,y],[y,x]]
permute (x:ys)   = concatMap (ins x) (permute ys)

main = do print $ permute ['a','b','c']

concatMap applies ins x on each list element of permute ys and concatenates its results. As ins x on a list produce a list of lists the concatenation of concatMap is used to collect the results as a single list.

runhaskell Permute.hs 
["abc","bac","bca","acb","cab","cba"]

Because ins can handle the case where ys is empty in permute ys, the cases where the list parameter to permute has one or two elements is not required for the recursion to finalize. The simplified solution is as follows. ins is also implemented as a sub-function of permute.

permute :: [a] -> [[a]]
permute []       = [[]]
permute (x:ys)   = concatMap (ins x) (permute ys) where
  ins x []     = [[x]]
  ins x (y:ys) = (x:y:ys):[y:res | res <- ins x ys]
    
main = do print $ permute ['a','b','c']

The original solution where foldr is used, instead of a recursive call to permute, is bit more difficult to grasp, but equivalent.