Justin Pombrio

What we preceive as reality is a construct of the mind.

Carpenter's Ruler

import Prelude hiding (length)
import Random
import Control.Monad
import Data.List (minimumBy)
import Debug.Trace

{-
 - A better-than-bruteforce approach to the NP-Complete
 - Carpenter's Ruler problem
 -}

problemSize = 1000
problemLen = 40


type Length = Integer

data Solution = Solution Length Length deriving Show

solution :: Length -> Length -> Solution
solution x y = Solution (min x y) (max x y)

length :: Solution -> Length
length (Solution small large) = small + large

instance Eq Solution where
    Solution s l == Solution s' l' =
        s == s' && l == l'

compareLengths :: Solution -> Solution -> Ordering
compareLengths (Solution s l) (Solution s' l') =
    compare (s + l) (s' + l')

type SolutionSet = [Solution]

better :: Solution -> Solution -> Bool
-- When is one solution _guarenteed_ to outperform another?
better (Solution small large) (Solution small' large') =
    (small < small' && large <= large')
    || (small <= small' && large < large')

insert :: Solution -> SolutionSet -> SolutionSet
insert solution [] = [solution]
insert solution (s : ss)
    | better solution s = insert solution ss
    | better s solution = s : ss
    | s == solution = s : ss
    | otherwise = s : insert solution ss

merge :: SolutionSet -> SolutionSet -> SolutionSet
merge [] set = set
merge (s : ss) set = merge ss (insert s set)

addJoint :: Length -> Solution -> SolutionSet
addJoint len (Solution small large)
    | len <= small = [solution (small - len) (large + len),
                      solution (small + len) (large - len)]
    | len >= large = [solution 0 (small + len)]
    | otherwise = [solution 0 (large + len),
                   solution (small + len) (large - len)]

iteration :: SolutionSet -> Length -> SolutionSet
iteration solutions len =
    trace (show solutions) $
    foldl1 merge $ map (addJoint len) solutions

bestSolution :: SolutionSet -> Length
bestSolution = length . minimumBy compareLengths

solve :: [Length] -> Length
solve (len : lens) = bestSolution $ foldl iteration [solution 0 len] lens

left :: Solution -> Length -> Solution
left (Solution small large) len
    | len <= small = solution (small - len) (large + len)
    | otherwise = solution 0 (large + len)

right :: Solution -> Length -> Solution
right (Solution small large) len
    | len <= large = solution (small + len) (large - len)
    | otherwise = solution 0 (small + len)

both :: Length -> Solution -> [Solution]
both len solution = [left solution len, right solution len]

allSolutions :: SolutionSet -> [Length] -> SolutionSet
allSolutions solutions [] = solutions
allSolutions solutions (len : lens) =
    allSolutions (concat $ map (both len) solutions) lens

bruteForce :: [Length] -> Length
bruteForce (len : lens) =
    bestSolution $ allSolutions [Solution 0 len] lens

generateLength :: IO Length
generateLength = do
  len <- randomIO
  return (mod len problemSize)

generateProblem :: IO [Length]
generateProblem = replicateM problemLen generateLength

main = do
  problem <- generateProblem
  putStrLn $ "Problem: " ++ show problem
  putStrLn $ "Quick Solution: " ++ show (solve problem)
  --putStrLn $ "Brute Solution: " ++ show (bruteForce problem)