Мне показалось полезным определить вспомогательную функцию partitionsCap
, которая не позволяет ни одному из элементов быть больше заданного значения. Используемый рекурсивно, он может использоваться только для монотонного получения желаемых результатов с уменьшением (т. Е. Нет [1,3,1]
, когда у вас уже есть [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
В основе алгоритма лежит идея, что при разбиении N вы берете i
с n
до 1 и добавляете i
к разделам n-i
. Упрощенная:
concat [map (i:) $ partitions (n-i) | i <- [n,n-1..1]]
но неправильно:
> partitions 3
[[3],[2,1],[1,2],[1,1,1]]
Мы хотим, чтобы [1,2]
ушел. Следовательно, нам нужно ограничить разделы, к которым мы добавляем, чтобы они не превышали i
:
concat [map (i:) $ partitionsCap i (n-i) | i <- [hi,hi-1..1]]
where hi = min cap n
Теперь, чтобы очистить это: это concat и map так близко друг к другу привлекли мое внимание. Немного предыстории: списочные выражения и монада списка очень тесно связаны , а concatMap - это то же самое, что >>=
с перевернутыми аргументами в монаде списка. Так что я подумал: могут ли эти concat и map каким-то образом превратиться в >>=
, и может ли это >>=
каким-то сладким образом проникнуть в понимание списка?
В этом случае ответ - да: -)
[i : p | i <- [hi,hi-1..1], p <- partitionsCap i (n-i)]
where hi = min cap n