How to resolve the algorithm Nonogram solver step by step in the Haskell programming language
How to resolve the algorithm Nonogram solver step by step in the Haskell programming language
Table of Contents
Problem Statement
A nonogram is a puzzle that provides numeric clues used to fill in a grid of cells, establishing for each cell whether it is filled or not. The puzzle solution is typically a picture of some kind. Each row and column of a rectangular grid is annotated with the lengths of its distinct runs of occupied cells. Using only these lengths you should find one valid configuration of empty and occupied cells, or show a failure message. The problem above could be represented by two lists of lists: A more compact representation of the same problem uses strings, where the letters represent the numbers, A=1, B=2, etc: For this task, try to solve the 4 problems below, read from a “nonogram_problems.txt” file that has this content (the blank lines are separators): Extra credit: generate nonograms with unique solutions, of desired height and width.
This task is the problem n.98 of the "99 Prolog Problems" (archived) by Werner Hett (also thanks to Paul Singleton for the idea and the examples).
Let's start with the solution:
Step by Step solution about How to resolve the algorithm Nonogram solver step by step in the Haskell programming language
Overview
This Haskell code solves a CSP (constraint satisfaction problem) for a crossword puzzle to determine which cells should be filled with the '#' character. The problem is defined in a text file where each line represents either a row or a column of the desired solution, with blanks represented by spaces and filled cells represented by letters (A-Z).
Parsing
First, the code parses the input file to extract the problem definition using the readP
function from the Text.ParserCombinators.ReadP
library. The problemP
parser combines the row and column parsers to create a Problem
data type, which represents the puzzle problem.
CSP Modeling
The heart of the code uses the CSP library to model the problem as a CSP. It creates a set of decision variables (DV
) for each cell, representing whether the cell should be filled (True
) or not (False
). The code then defines a set of constraints to ensure that the solution meets the following criteria:
- Each row or column must have at least one filled cell.
- Filled cells must form blocks separated by at least one empty cell.
- Only one block can be formed in a row or column.
Solving
The code uses the solve
function to find a single solution to the problem. It constructs a CSP using the problemCSP
function and then uses the oneCSPSolution
function to find a solution that satisfies the constraints.
Printing
Finally, the printSolution
function prints the solved crossword puzzle, where #
characters represent filled cells and .
characters represent empty cells.
Example Input and Output
Input (problem.txt):
ABC DEF
GHI JKL
Output:
#######
#@# #@#
#######
Source code in the haskell programming language
import Control.Applicative ((<|>))
import Control.Monad
import Control.Monad.CSP
import Data.List (transpose)
import System.Environment (getArgs)
import Text.ParserCombinators.ReadP (ReadP)
import qualified Text.ParserCombinators.ReadP as P
import Text.Printf (printf)
main :: IO ()
main = do
file <- parseArgs
printf "reading problem file from %s\n" file
ps <- parseProblems file
forM_ ps $ \p -> do
print p
putStrLn ""
printSolution $ solve p
putStrLn ""
-------------------------------------------------------------------------------
-- parsing
-------------------------------------------------------------------------------
parseArgs :: IO FilePath
parseArgs = do
args <- getArgs
case args of
[file] -> return file
_ -> ioError $ userError "expected exactly one command line argument, the name of the problem file"
data Problem = Problem
{ rows :: [[Int]]
, cols :: [[Int]]
} deriving (Show, Read, Eq, Ord)
entryP :: ReadP Int
entryP = do
n <- fromEnum <$> P.get
if n < 65 || n > 90
then P.pfail
else return $ n - 64
blankP, eolP :: ReadP Char
blankP = P.char ' '
eolP = P.char '\n'
entriesP :: ReadP [Int]
entriesP = ([] <$ blankP) <|> P.many1 entryP
lineP :: ReadP [[Int]]
lineP = P.sepBy1 entriesP blankP <* eolP
problemP :: ReadP Problem
problemP = Problem <$> lineP <*> lineP
problemsP :: ReadP [Problem]
problemsP = P.sepBy1 problemP (P.many blankP <* eolP) <* P.eof
parseProblems :: FilePath -> IO [Problem]
parseProblems file = do
s <- readFile file
case P.readP_to_S problemsP s of
[(ps, "")] -> return ps
_ -> ioError $ userError $ "error parsing file " <> file
-------------------------------------------------------------------------------
-- CSP
-------------------------------------------------------------------------------
solve :: Problem -> [[Bool]]
solve = oneCSPSolution . problemCSP
problemCSP :: Problem -> CSP r [[DV r Bool]]
problemCSP p = do
let rowCount = length $ rows p
colCount = length $ cols p
cells <- replicateM rowCount
$ replicateM colCount
$ mkDV [False, True]
forM_ (zip cells $ rows p) $ uncurry rowOrColCSP
forM_ (zip (transpose cells) $ cols p) $ uncurry rowOrColCSP
return cells
rowOrColCSP :: [DV r Bool] -> [Int] -> CSP r ()
rowOrColCSP ws [] = forM_ ws $ constraint1 not
rowOrColCSP ws xs = do
let vs = zip [0 ..] ws
n = length ws
blocks <- forM xs $ \x ->
mkDV [(i, i + x - 1) | i <- [0 .. n - x]] -- the blocks, given by first and last index
-- blocks must be separate and not overlapping
f blocks
-- cells in blocks are set
forM_ blocks $ \x ->
forM_ vs $ \(i, y) ->
constraint2 (\(x1, x2) b -> i < x1 || i > x2 || b) x y
-- cells before the first block are not set
forM_ vs $ \(i, y) ->
constraint2 (\(y', _) b -> i >= y' || not b) (head blocks) y
-- cells after the last block are not set
forM_ vs $ \(i, y) ->
constraint2 (\(_, y') b -> i <= y' || not b) (last blocks) y
-- cells between blocks are not set
forM_ (zip blocks $ tail blocks) $ \(x, y) ->
forM_ vs $ \(i, z) ->
constraint3 (\(_, x') (y', _) b -> i <= x' || i >= y' || not b) x y z
where
f :: [DV r (Int, Int)] -> CSP r ()
f (u : v : bs) = do
constraint2 (\(_, u') (v', _) -> v' >= u' + 2) u v
f $ v : bs
f _ = return ()
-------------------------------------------------------------------------------
-- printing
-------------------------------------------------------------------------------
printSolution :: [[Bool]] -> IO ()
printSolution bss =
forM_ bss $ \bs -> do
forM_ bs $ \b ->
putChar $ if b then '#' else '.'
putChar '\n'
You may also check:How to resolve the algorithm CSV to HTML translation step by step in the Factor programming language
You may also check:How to resolve the algorithm Extensible prime generator step by step in the Fortran programming language
You may also check:How to resolve the algorithm Vector products step by step in the jq programming language
You may also check:How to resolve the algorithm Speech synthesis step by step in the BBC BASIC programming language
You may also check:How to resolve the algorithm Range extraction step by step in the Arturo programming language