I found it helpful to define an auxiliary function, partitionsCap
, which does not let any of the items be larger than a given value. Used recursively, it can be used to only produce the monotonically decreasing results you want (i.e. no [1,3,1]
when you already have [1,1,3]
):
partitions :: Int -> [[Int]]
partitions n = partitionsCap n n
partitionsCap :: Int -> Int -> [[Int]]
partitionsCap cap n
| n < 0 = error "partitions: negative number"
| n == 0 = [[]]
| n > 0 = [i : p | i <- [hi,hi-1..1], p <- partitionsCap i (n-i)]
where hi = min cap n
At the heart of the algorithm is the idea that, when partitioning N, you take i
from n
down to 1, and prepend i
to the partitions of n-i
. Simplified:
concat [map (i:) $ partitions (n-i) | i <- [n,n-1..1]]
but wrong:
> partitions 3
[[3],[2,1],[1,2],[1,1,1]]
We want that [1,2]
to go away. Hence, we need to cap the partitions we're prepending to so they won't go above i
:
concat [map (i:) $ partitionsCap i (n-i) | i <- [hi,hi-1..1]]
where hi = min cap n
Now, to clean it up: that concat and map so close together got my attention. A little background: list comprehensions and the list monad are very closely related, and concatMap is the same as >>=
with its arguments flipped, in the list monad. So I wondered: can those concat and map somehow turn into a >>=
, and can that >>=
somehow sweet-talk its way into the list comprehension?
In this case, the answer is yes :-)
[i : p | i <- [hi,hi-1..1], p <- partitionsCap i (n-i)]
where hi = min cap n