If you were on the math team during high school, you may remember integer partitions not too fondly. They're not particularly easy to get a grip on: even counting the partitions of an integer requires generating functions (which are scary when you're in high school).
But they're easily tractable with a computer, and so here we present functional
algorithms for integer partitions, written in Haskell. It does use recursion-schemes
,
but this is more than justified by the
added eloquence.
import Numeric.Natural (Natural)
import Control.Monad (join)
import Data.List (nub)
import Data.Functor.Foldable (ListF (..), para)
partitions :: Natural -> [[Natural]]
partitions = para algebra
where algebra Nothing = []
algebra (Just (0,_)) = [[1]]
algebra (Just (_, past)) = (nub . (getAll =<<)) (fmap (1:) past)
getAll :: [Natural] -> [[Natural]]
getAll = fmap (dropWhile (==0) . sort) . subsets
where subsets xs = flip sumIndicesAt xs <$> indices xs
indices :: [Natural] -> [[Natural]]
indices = join . para algebra
where algebra Nil = []
algebra (Cons x (xs, [])) = [[x:xs]]
algebra (Cons x (xs, past)) = (:) <$> [x:xs,[]] <*> past
sumIndicesAt :: [Natural] -> [Natural] -> [Natural]
sumIndicesAt ix = (\(a, b) -> sum a : b) . partition (`elem` ix)
While this is beautiful on its own (not to mention an instructive use of
paramorphisms), we also get a very satisfying application of
QuickCheck
using Ramanujan's congruences, viz.
$$p(5k + 4) \equiv 0 (\text{mod } 5)$$ $$p(7k + 5) \equiv 0 (\text{mod } 7)$$ $$p(11 k + 6) \equiv 0 (\text{mod } 11)$$
where \(p(n)\) is the number of ways to partition some integer \(n\).
This leads to the following property test using hspec:
import Test.Hspec
import Test.QuickCheck
import Numeric.Naturals
main :: IO ()
main = hspec $
describe "partitions" $
it "should satisfy Ramanujan's identities (1/3)" $
property $ \n -> (length . partitions) (5 * n + 4) mod
5 == (0 :: Natural)
it "should satisfy Ramanujan's identities (2/3)" $
property $ \n -> (length . partitions) (7 * n + 5) mod
7 == (0 :: Natural)
it "should satisfy Ramanujan's identities (3/3)" $
property $ \n -> (length . partitions) (11 * n + 6) mod
11 == (0 :: Natural)
From which we can confirm that our function does what we desire with a cosmic level of confidence.