packages feed

set-cover-0.0.5: example/Nonogram.hs

{-
* <https://en.wikipedia.org/wiki/Nonogram>
* <https://de.wikipedia.org/wiki/Datei:Paint_by_numbers_Animation.gif>

The solver works but is pretty slow.
I assume that a faster solution can be achieved
if we succeed in splitting bricks into single squares.
-}
module Main where

import qualified Math.SetCover.Exact as ESC

import Control.Monad (liftM2)

import qualified Data.Set as Set
import qualified Data.List.Match as Match
import qualified Data.List.HT as ListHT
import qualified Data.List as List
import Data.Foldable (foldMap)
import Data.Char (isSpace)
import Data.Set (Set)


data X = X Orientation Int Item
        deriving (Eq, Ord, Show)

data Item = Brick Int | Position Int | Reserve Int Int
        deriving (Eq, Ord, Show)

data Orientation = Horizontal | Vertical
        deriving (Eq, Ord, Show)


type Assign = ESC.Assign (Set (Int, Int)) (Set X)

assignsFromBrick ::
   Orientation -> Int -> Int ->
   Maybe Int -> Int -> Maybe Int -> Int -> [Assign]
assignsFromBrick orient width line prevBrick thisBrick maybeThisBrick size =
   flip map [0 .. width-size] $ \col ->
   ESC.assign
      (case orient of
          Horizontal -> Set.fromList $ take size $ map ((,) line) [col ..]
          Vertical -> Set.empty) $
   Set.fromList $ map (X orient line) $
   Brick thisBrick
   :
   (map Position $ take size [col ..])
   ++
   maybe []
      (\brick -> map (Reserve brick) [col .. pred width])
      prevBrick
   ++
   maybe []
      (\brick -> map (Reserve brick) [0 .. min (pred width) (col+size)])
      maybeThisBrick

assignsFromLine ::
   Orientation -> Int -> Int -> [Int] -> [Assign]
assignsFromLine orient width line xs =
--   let bricks = Match.take (ListHT.laxTail xs) [0..]
   let bricks = Match.take (drop 1 xs) [0..]
   in  concat
          (List.zipWith4
              (assignsFromBrick orient width line)
              (Nothing : map Just bricks) [0..] (map Just bricks ++ [Nothing]) xs)
       ++
       liftM2
          (\brick c ->
             ESC.assign Set.empty $ Set.singleton $
             X orient line (Reserve brick c))
          bricks [0 .. width-1]

assignsFromLines :: [[Int]] -> [[Int]] -> [Assign]
assignsFromLines rows columns =
   concat (zipWith (assignsFromLine Horizontal (length columns)) [0..] rows)
   ++
   concat (zipWith (assignsFromLine Vertical (length rows)) [0..] columns)
   ++
   liftM2
      (\r c ->
         ESC.assign Set.empty $
         Set.fromList
            [X Horizontal r (Position c),
             X Vertical c (Position r)])
      (Match.take rows [0..])
      (Match.take columns [0..])

decode :: [[Int]] -> [[Int]] -> [Set (Int, Int)]
decode rows columns =
   map Set.unions $ ESC.partitions $ assignsFromLines rows columns

encodeLines :: [String] -> [[Int]]
encodeLines =
   map (filter (>0) . map length . ListHT.chop isSpace)

encodeStrings :: [String] -> ([[Int]], [[Int]])
encodeStrings xs =
   (encodeLines xs, encodeLines $ List.transpose xs)


testRows, testColumns :: [[Int]]
testRows =
   [1,1] :
   [1] :
   [1,1] :
   []

testColumns =
   [1,1] :
   [1] :
   [1,1] :
   []

testRhombus, testCircle, testP, testBigCircle :: [String]
testRhombus =
   "  X  " :
   " X X " :
   "X   X" :
   " X X " :
   "  X  " :
   []

testCircle =
   " XXX " :
   "XX XX" :
   "X   X" :
   "XX XX" :
   " XXX " :
   []

testP =
   "XXXX  " :
   "XXXXXX" :
   "XX  XX" :
   "XX  XX" :
   "XXXXXX" :
   "XXXX  " :
   "XX    " :
   "XX    " :
   "XX    " :
   []

-- cannot solve this one within 30 minutes
testBigCircle =
   "   XXXXX   " :
   " XXX   XXX " :
   " X       X " :
   "XX       XX" :
   "X         X" :
   "X         X" :
   "X         X" :
   "XX       XX" :
   " X       X " :
   " XXX   XXX " :
   "   XXXXX   " :
   []

soccerRows, soccerColumns :: [[Int]]
soccerRows =
   [3] :
   [5] :
   [3, 1] :
   [2, 1] :
   [3, 3, 4] :
   [2, 2, 7] :
   [6, 1, 1] :
   [4, 2, 2] :
   [1, 1] :
   [3, 1] :
   [6] :
   [2, 7] :
   [6, 3, 1] :
   [1, 2, 2, 1, 1] :
   [4, 1, 1, 3] :
   [4, 2, 2] :
   [3, 3, 1] :
   [3, 3] :
   [3] :
   [2, 1] :
   []

soccerColumns =
   [2] :
   [1, 2] :
   [2, 3] :
   [2, 3] :
   [3, 1, 1] :
   [2, 1, 1] :
   [1, 1, 1, 2, 2] :
   [1, 1, 3, 1, 3] :
   [2, 6, 4] :
   [3, 3, 9, 1] :
   [5, 3, 2] :
   [3, 1, 2, 2] :
   [2, 1, 7] :
   [3, 3, 2] :
   [2, 4] :
   [2, 1, 2] :
   [2, 2, 1] :
   [2, 2] :
   [1] :
   [1] :
   []


format :: Int -> Int -> Set (Int, Int) -> String
format rows columns set =
   unlines $
   ListHT.outerProduct
      (\r c -> if Set.member (r,c) set then 'X' else '.')
      (take rows [0..])
      (take columns [0..])

testSimple :: IO ()
testSimple = do
   let assigns = assignsFromLines testRows testColumns
   mapM_ (print . ESC.labeledSet) assigns
   putStrLn "set union:"
   print $ foldMap ESC.labeledSet assigns
   mapM_
      (putStrLn .
       format (length testRows) (length testColumns) .
       Set.unions) $
      ESC.partitions assigns

testImage :: IO ()
testImage =
   let (rows, columns) = encodeStrings testP
   in  mapM_ (putStrLn . format (length rows) (length columns)) $
       decode rows columns

-- too slow to complete
main :: IO ()
main =
   mapM_ (putStrLn . format (length soccerRows) (length soccerColumns)) $
   decode soccerRows soccerColumns