mirror of
https://github.com/KevinMidboe/linguist.git
synced 2025-10-29 09:40:21 +00:00
What is Frege? ------------- Frege is a non-strict, pure functional programming language in the spirit of Haskell for the JVM. It enjoys a strong static type system with type inference. Higher rank types are supported, though type annotations are required for that. Frege programs are compiled to Java and run in a JVM. Existing Java Classes and Methods can be used seamlessly from Frege. The Frege programming language is named after and in honor of Gottlob Frege. Project State: ------------- The compiler, an Eclipse plugin and a provisional version of the documentation can be downloaded from here https://github.com/Frege/frege/releases. The REPL can be downloaded from here https://github.com/Frege/frege-repl/releases. An online REPL is running here http://try.frege-lang.org/. Examples: -------- 1) Command Line Clock: https://github.com/Frege/frege/blob/master/examples/CommandLineClock.fr 2) Brainfuck: https://github.com/Frege/frege/blob/master/examples/Brainfuck.fr 3) Concurrency: https://github.com/Frege/frege/blob/master/examples/Concurrent.fr 4) Sudoku: https://github.com/Frege/frege/blob/master/examples/Sudoku.fr 5) Java Swing examples: https://github.com/Frege/frege/blob/master/examples/SwingExamples.fr
562 lines
23 KiB
Plaintext
562 lines
23 KiB
Plaintext
package examples.Sudoku where
|
|
|
|
import Data.TreeMap (Tree, keys)
|
|
import Data.List as DL hiding (find, union)
|
|
|
|
|
|
type Element = Int -- 1,2,3,4,5,6,7,8,9
|
|
type Zelle = [Element] -- set of candidates
|
|
type Position = Int -- 0..80
|
|
type Feld = (Position, Zelle)
|
|
type Brett = [Feld]
|
|
|
|
--- data type for assumptions and conclusions
|
|
data Assumption =
|
|
!ISNOT Position Element
|
|
| !IS Position Element
|
|
|
|
|
|
derive Eq Assumption
|
|
derive Ord Assumption
|
|
instance Show Assumption where
|
|
show (IS p e) = pname p ++ "=" ++ e.show
|
|
show (ISNOT p e) = pname p ++ "/" ++ e.show
|
|
|
|
showcs cs = joined " " (map Assumption.show cs)
|
|
|
|
elements :: [Element] -- all possible elements
|
|
elements = [1 .. 9]
|
|
|
|
{-
|
|
a b c d e f g h i
|
|
0 1 2 | 3 4 5 | 6 7 8 1
|
|
9 10 11 |12 13 14 |15 16 17 2
|
|
18 19 20 |21 22 23 |24 25 26 3
|
|
---------|---------|--------
|
|
27 28 29 |30 31 32 |33 34 35 4
|
|
36 37 38 |39 40 41 |42 43 44 5
|
|
45 46 47 |48 49 50 |51 52 53 6
|
|
---------|---------|--------
|
|
54 55 56 |57 58 59 |60 61 62 7
|
|
63 64 65 |66 67 68 |69 70 71 8
|
|
72 73 74 |75 76 77 |78 79 80 9
|
|
-}
|
|
|
|
positions :: [Position] -- all possible positions
|
|
positions = [0..80]
|
|
rowstarts :: [Position] -- all positions where a row is starting
|
|
rowstarts = [0,9,18,27,36,45,54,63,72]
|
|
colstarts :: [Position] -- all positions where a column is starting
|
|
colstarts = [0,1,2,3,4,5,6,7,8]
|
|
boxstarts :: [Position] -- all positions where a box is starting
|
|
boxstarts = [0,3,6,27,30,33,54,57,60]
|
|
boxmuster :: [Position] -- pattern for a box, by adding upper left position results in real box
|
|
boxmuster = [0,1,2,9,10,11,18,19,20]
|
|
|
|
|
|
--- extract field for position
|
|
getf :: Brett -> Position -> Feld
|
|
getf (f:fs) p
|
|
| fst f == p = f
|
|
| otherwise = getf fs p
|
|
getf [] p = (p,[])
|
|
|
|
|
|
--- extract cell for position
|
|
getc :: Brett -> Position -> Zelle
|
|
getc b p = snd (getf b p)
|
|
|
|
--- compute the list of all positions that belong to the same row as a given position
|
|
row :: Position -> [Position]
|
|
row p = [z..(z+8)] where z = (p `quot` 9) * 9
|
|
|
|
--- compute the list of all positions that belong to the same col as a given position
|
|
col :: Position -> [Position]
|
|
col p = map (c+) rowstarts where c = p `mod` 9
|
|
|
|
--- compute the list of all positions that belong to the same box as a given position
|
|
box :: Position -> [Position]
|
|
box p = map (z+) boxmuster where
|
|
ri = p `div` 27 * 27 -- 0, 27 or 54, depending on row
|
|
ci = p `mod` 9 -- column index 0..8, 0,1,2 is left, 3,4,5 is middle, 6,7,8 is right
|
|
cs = ci `div` 3 * 3 -- 0, 3 or 6
|
|
z = ri + cs
|
|
|
|
--- check if candidate set has exactly one member, i.e. field has been solved
|
|
single :: Zelle -> Bool
|
|
single [_] = true
|
|
single _ = false
|
|
|
|
unsolved :: Zelle -> Bool
|
|
unsolved [_] = false
|
|
unsolved _ = true
|
|
|
|
-- list of rows, cols, boxes
|
|
allrows = map row rowstarts
|
|
allcols = map col colstarts
|
|
allboxs = map box boxstarts
|
|
allrcb = zip (repeat "row") allrows
|
|
++ zip (repeat "col") allcols
|
|
++ zip (repeat "box") allboxs
|
|
|
|
|
|
containers :: [(Position -> [Position], String)]
|
|
containers = [(row, "row"), (col, "col"), (box, "box")]
|
|
|
|
-- ----------------- PRINTING ------------------------------------
|
|
-- printable coordinate of field, upper left is a1, lower right is i9
|
|
pname p = packed [chr (ord 'a' + p `mod` 9), chr (ord '1' + p `div` 9)]
|
|
|
|
-- print board
|
|
printb b = mapM_ p1line allrows >> println ""
|
|
where
|
|
p1line row = do
|
|
print (joined "" (map pfld line))
|
|
where line = map (getc b) row
|
|
|
|
-- print field (brief)
|
|
-- ? = no candidate
|
|
-- 5 = field is 5
|
|
-- . = some candidates
|
|
pfld [] = "?"
|
|
pfld [x] = show x
|
|
pfld zs = "0"
|
|
|
|
-- print initial/final board
|
|
result msg b = do
|
|
println ("Result: " ++ msg)
|
|
print ("Board: ")
|
|
printb b
|
|
return b
|
|
|
|
res012 b = case concatMap (getc b) [0,1,2] of
|
|
[a,b,c] -> a*100+b*10+c
|
|
_ -> 9999999
|
|
|
|
-- -------------------------- BOARD ALTERATION ACTIONS ---------------------------------
|
|
-- print a message about what is done to the board and return the new board
|
|
turnoff1 :: Position -> Zelle -> Brett -> IO Brett
|
|
turnoff1 i off b
|
|
| single nc = do
|
|
-- print (pname i)
|
|
-- print ": set to "
|
|
-- print (head nc)
|
|
-- println " (naked single)"
|
|
return newb
|
|
| otherwise = return newb
|
|
where
|
|
cell = getc b i
|
|
nc = filter (`notElem` off) cell
|
|
newb = (i, nc) : [ f | f <- b, fst f != i ]
|
|
|
|
turnoff :: Int -> Zelle -> String -> Brett -> IO Brett
|
|
turnoff i off msg b = do
|
|
-- print (pname i)
|
|
-- print ": set to "
|
|
-- print nc
|
|
-- print " by clearing "
|
|
-- print off
|
|
-- print " "
|
|
-- println msg
|
|
return newb
|
|
where
|
|
cell = getc b i
|
|
nc = filter (`notElem` off) cell
|
|
newb = (i, nc) : [ f | f <- b, fst f != i ]
|
|
|
|
turnoffh ps off msg b = foldM toh b ps
|
|
where
|
|
toh b p = turnoff p off msg b
|
|
|
|
setto :: Position -> Element -> String -> Brett -> IO Brett
|
|
setto i n cname b = do
|
|
-- print (pname i)
|
|
-- print ": set to "
|
|
-- print n
|
|
-- print " (hidden single in "
|
|
-- print cname
|
|
-- println ")"
|
|
return newb
|
|
where
|
|
nf = [n]
|
|
newb = (i, nf) : [ f | f <- b, fst f != i ]
|
|
|
|
|
|
-- ----------------------------- SOLVING STRATEGIES ---------------------------------------------
|
|
-- reduce candidate sets that contains numbers already in same row, col or box
|
|
-- This finds (and logs) NAKED SINGLEs in passing.
|
|
reduce b = [ turnoff1 p sss | (p,cell) <- b, -- for each field
|
|
unsolved cell, -- with more than 1 candidate
|
|
-- single fields in containers that are candidates of that field
|
|
sss = [ s | (rcb, _) <- containers, [s] <- map (getc b) (rcb p), s `elem` cell],
|
|
sss != [] ] -- collect field index, elements to remove from candidate set
|
|
|
|
-- look for a number that appears in exactly 1 candidate set of a container
|
|
-- this number can go in no other place (HIDDEN SINGLE)
|
|
hiddenSingle b = [ setto i n cname | -- select index, number, containername
|
|
(cname, rcb) <- allrcb, -- FOR rcb IN allrcb
|
|
n <- elements, -- FOR n IN elements
|
|
fs = filter (unsolved • snd) (map (getf b) rcb),
|
|
occurs = filter ((n `elem`) • snd) fs,
|
|
length occurs == 1,
|
|
(i, _) <- occurs ]
|
|
|
|
-- look for NAKED PAIRS, TRIPLES, QUADS
|
|
nakedPair n b = [ turnoff p t ("(naked tuple in " ++ nm ++ ")") | -- SELECT pos, tuple, name
|
|
-- n <- [2,3,4], // FOR n IN [2,3,4]
|
|
(nm, rcb) <- allrcb, -- FOR rcb IN containers
|
|
fs = map (getf b) rcb, -- let fs = fields for rcb positions
|
|
u = (fold union [] . filter unsolved . map snd) fs, -- let u = union of non single candidates
|
|
t <- n `outof` u, -- FOR t IN n-tuples
|
|
hit = (filter ((`subset` t) . snd) . filter (unsolved . snd)) fs,
|
|
length hit == n,
|
|
(p, cell) <- fs,
|
|
p `notElem` map fst hit,
|
|
any (`elem` cell) t
|
|
]
|
|
|
|
-- look for HIDDEN PAIRS, TRIPLES or QUADS
|
|
hiddenPair n b = [ turnoff p off ("(hidden " ++ show t ++ " in " ++ nm ++ ")") | -- SELECT pos, tuple, name
|
|
-- n <- [2,3,4], // FOR n IN [2,3,4]
|
|
(nm, rcb) <- allrcb, -- FOR rcb IN containers
|
|
fs = map (getf b) rcb, -- let fs = fields for rcb positions
|
|
u = (fold union [] . filter ((>1) . length) . map snd) fs, -- let u = union of non single candidates
|
|
t <- n `outof` u, -- FOR t IN n-tuples
|
|
hit = (filter (any ( `elem` t) . snd) . filter (unsolved . snd)) fs,
|
|
length hit == n,
|
|
off = (fold union [] . map snd) hit `minus` t,
|
|
off != [],
|
|
(p, cell) <- hit,
|
|
! (cell `subset` t)
|
|
]
|
|
|
|
a `subset` b = all (`elem` b) a
|
|
a `union` b = uniq (sort (a ++ b))
|
|
a `minus` b = filter (`notElem` b) a
|
|
a `common` b = filter (`elem` b) a
|
|
n `outof` as
|
|
| length as < n = []
|
|
| [] <- as = []
|
|
| 1 >= n = map (:[]) as
|
|
| (a:bs) <- as = map (a:) ((n-1) `outof` bs) ++ (n `outof` bs)
|
|
| otherwise = undefined -- cannot happen because either as is empty or not
|
|
|
|
same f a b = b `elem` f a
|
|
|
|
intersectionlist = [(allboxs, row, "box/row intersection"), (allboxs, col, "box/col intersection"),
|
|
(allrows ++ allcols, box, "line/box intersection")]
|
|
intersections b = [
|
|
turnoff pos [c] reason | -- SELECT position, candidate, reson
|
|
(from, container, reason) <- intersectionlist,
|
|
rcb <- from,
|
|
fs = (filter (unsolved . snd) . map (getf b)) rcb, -- fs = fields in from with more than 1 candidate
|
|
c <- (fold union [] • map snd) fs, -- FOR c IN union of candidates
|
|
cpos = (map fst • filter ((c `elem`) • snd)) fs, -- cpos = positions where c occurs
|
|
cpos != [], -- WHERE cpos is not empty
|
|
all (same container (head cpos)) (tail cpos), -- WHERE all positions are in the intersection
|
|
-- we can remove all occurences of c that are in container, but not in from
|
|
(pos, cell) <- map (getf b) (container (head cpos)),
|
|
c `elem` cell,
|
|
pos `notElem` rcb ]
|
|
|
|
|
|
-- look for an XY Wing
|
|
-- - there exists a cell A with candidates X and Y
|
|
-- - there exists a cell B with candidates X and Z that shares a container with A
|
|
-- - there exists a cell C with candidates Y and Z that shares a container with A
|
|
-- reasoning
|
|
-- - if A is X, B will be Z
|
|
-- - if A is Y, C will be Z
|
|
-- - since A will indeed be X or Y -> B or C will be Z
|
|
-- - thus, no cell that can see B and C can be Z
|
|
xyWing board = [ turnoff p [z] ("xy wing " ++ pname b ++ " " ++ pname c ++ " because of " ++ pname a) |
|
|
(a, [x,y]) <- board, -- there exists a cell a with candidates x and y
|
|
rcba = map (getf board) (row a ++ col a ++ box a), -- rcba = all fields that share a container with a
|
|
(b, [b1, b2]) <- rcba,
|
|
b != a,
|
|
b1 == x && b2 != y || b2 == x && b1 != y, -- there exists a cell B with candidates x and z
|
|
z = if b1 == x then b2 else b1,
|
|
(c, [c1, c2]) <- rcba,
|
|
c != a, c!= b,
|
|
c1 == y && c2 == z || c1 == z && c2 == y, -- there exists a cell C with candidates y and z
|
|
ps = (uniq . sort) ((row b ++ col b ++ box b) `common` (row c ++ col c ++ box c)),
|
|
-- remove z in ps
|
|
(p, cs) <- map (getf board) ps,
|
|
p != b, p != c,
|
|
z `elem` cs ]
|
|
|
|
-- look for a N-Fish (2: X-Wing, 3: Swordfish, 4: Jellyfish)
|
|
-- When all candidates for a particular digit in N rows are located
|
|
-- in only N columns, we can eliminate all candidates from those N columns
|
|
-- which are not located on those N rows
|
|
fish n board = fish "row" allrows row col ++ fish "col" allcols col row where
|
|
fishname 2 = "X-Wing"
|
|
fishname 3 = "Swordfish"
|
|
fishname 4 = "Jellyfish"
|
|
fishname _ = "unknown fish"
|
|
fish nm allrows row col = [ turnoff p [x] (fishname n ++ " in " ++ nm ++ " " ++ show (map (pname . head) rset)) |
|
|
rset <- n `outof` allrows, -- take n rows (or cols)
|
|
x <- elements, -- look for certain number
|
|
rflds = map (filter ((>1) . length . snd) . map (getf board)) rset, -- unsolved fields in the rowset
|
|
colss = (map (map (head . col . fst) . filter ((x `elem`) . snd)) rflds), -- where x occurs in candidates
|
|
all ((>1) . length) colss, -- x must appear in at least 2 cols
|
|
cols = fold union [] colss,
|
|
length cols == n,
|
|
cstart <- cols,
|
|
(p, cell) <- map (getf board) (col cstart),
|
|
x `elem` cell,
|
|
all (p `notElem`) rset]
|
|
|
|
|
|
-- compute immediate consequences of an assumption of the form (p `IS` e) or (p `ISNOT` e)
|
|
conseq board (IS p e) = uniq (sort ([ p `ISNOT` x | x <- getc board p, x != e ] ++
|
|
[ a `ISNOT` e |
|
|
(a,cs) <- map (getf board) (row p ++ col p ++ box p),
|
|
a != p,
|
|
e `elem` cs
|
|
]))
|
|
conseq board (ISNOT p e) = uniq (sort ([ p `IS` x | cs = getc board p, length cs == 2, x <- cs, x != e ] ++
|
|
[ a `IS` e |
|
|
cp <- [row p, box p, col p],
|
|
as = (filter ((e `elem`) . getc board) . filter (p!=)) cp,
|
|
length as == 1,
|
|
a = head as
|
|
]))
|
|
|
|
-- check if two assumptions contradict each other
|
|
contradicts (IS a x) (IS b y) = a==b && x!=y
|
|
contradicts (IS a x) (ISNOT b y) = a==b && x==y
|
|
contradicts (ISNOT a x) (IS b y) = a==b && x==y
|
|
contradicts (ISNOT _ _) (ISNOT _ _) = false
|
|
|
|
-- get the Position of an Assumption
|
|
aPos (IS p _) = p
|
|
aPos (ISNOT p _) = p
|
|
|
|
-- get List of elements that must be turned off when assumption is true/false
|
|
toClear board true (IS p x) = filter (x!=) (getc board p)
|
|
toClear board false (IS p x) = [x]
|
|
toClear board true (ISNOT p x) = [x]
|
|
toClear board false (ISNOT p x) = filter (x!=) (getc board p)
|
|
|
|
|
|
-- look for assumptions whose implications contradict themself
|
|
chain board paths = [ solution a (head cs) (reverse cs) |
|
|
(a, css) <- paths,
|
|
cs <- take 1 [ cs | cs <- css, contradicts a (head cs) ]
|
|
]
|
|
where
|
|
solution a c cs = turnoff (aPos a) (toClear board false a) reason where
|
|
reason = "Assumption " ++ show a ++ " implies " ++ show c ++ "\n\t"
|
|
++ showcs cs ++ "\n\t"
|
|
++ "Therefore, " ++ show a ++ " must be false."
|
|
|
|
-- look for an assumption that yields to contradictory implications
|
|
-- this assumption must be false
|
|
chainContra board paths = [ solution a (reverse pro) (reverse contra) |
|
|
(a, css) <- paths, -- FOR ALL assumptions "a" with list of conlusions "css"
|
|
(pro, contra) <- take 1 [ (pro, contra) |
|
|
pro <- (uniqBy (using head) . sortBy (comparing head)) css, -- FOR ALL conslusion chains "pro"
|
|
c = head pro, -- LET "c" BE the final conclusion
|
|
contra <- take 1 (filter ((contradicts c) . head) css) -- THE FIRST conclusion that contradicts c
|
|
]
|
|
]
|
|
where
|
|
solution a pro con = turnoff (aPos a) (toClear board false a) reason where
|
|
reason = ("assumption " ++ show a ++ " leads to contradictory conclusions\n\t"
|
|
++ showcs pro ++ "\n\t" ++ showcs con)
|
|
|
|
|
|
|
|
-- look for a common implication c of some assumptions ai, where at least 1 ai is true
|
|
-- so that (a0 OR a1 OR a2 OR ...) IMPLIES c
|
|
-- For all cells pi in same container that have x as candidate, we can construct (p0==x OR p1==x OR ... OR pi==x)
|
|
-- For a cell p with candidates ci, we can construct (p==c0 OR p==c1)
|
|
cellRegionChain board paths = [ solution b as (map head os) |
|
|
as <- cellas ++ regionas, -- one of as must be true
|
|
iss = filter ((`elem` as) . fst) paths, -- the implications for as
|
|
(a, ass) <- take 1 iss, -- implications for first assumption
|
|
fs <- (uniqBy (using head) . sortBy (comparing head)) ass,
|
|
b = head fs, -- final conclusions of first assumption
|
|
os = [fs] : map (take 1 . filter ((b==) . head) . snd) (tail iss), -- look for implications with same conclusion
|
|
all ([]!=) os]
|
|
where
|
|
cellas = [ map (p `IS`) candidates | (p, candidates@(_:_:_)) <- board ]
|
|
regionas = [ map (`IS` e) ps |
|
|
region <- map (map (getf board)) (allrows ++ allcols ++ allboxs),
|
|
e <- elements,
|
|
ps = map fst (filter ((e `elem`) . snd) region),
|
|
length ps > 1 ]
|
|
solution b as oss = turnoff (aPos b) (toClear board true b) reason where
|
|
reason = "all of the assumptions " ++ joined ", " (map show as) ++ " imply " ++ show b ++ "\n\t"
|
|
++ joined "\n\t" (map (showcs . reverse) oss) ++ "\n\t"
|
|
++ "One of them must be true, so " ++ show b ++ " must be true."
|
|
|
|
|
|
{-
|
|
Wir brauchen für einige Funktionen eine Datenstruktur wie
|
|
[ (Assumption, [[Assumption]]) ]
|
|
d.i. eine Liste von möglichen Annahmen samt aller Schlußketten.
|
|
Idealerweise sollte die Schlußkette in umgekehrter Reihenfolge vorliegen,
|
|
dann kann man einfach finden:
|
|
- Annahmen, die zum Selbstwiderspruch führen.
|
|
- alles, was aus einer bestimmten Annahme folgt (map (map head) [[a]])
|
|
-...
|
|
-}
|
|
--- Liste aller Annahmen für ein bestimmtes Brett
|
|
assumptions :: Brett -> [Assumption]
|
|
assumptions board = [ a |
|
|
(p, cs) <- board,
|
|
!(single cs),
|
|
a <- map (ISNOT p) cs ++ map (IS p) cs ]
|
|
|
|
consequences :: Brett -> [Assumption] -> [[Assumption]]
|
|
consequences board as = map (conseq board) as
|
|
|
|
acstree :: Brett -> Tree Assumption [Assumption]
|
|
acstree board = Tree.fromList (zip as cs)
|
|
where
|
|
as = assumptions board
|
|
cs = consequences board as
|
|
|
|
-- bypass maybe on tree lookup
|
|
find :: Tree Assumption [Assumption] -> Assumption -> [Assumption]
|
|
find t a
|
|
| Just cs <- t.lookup a = cs
|
|
| otherwise = error ("no consequences for " ++ show a)
|
|
|
|
-- for performance resons, we confine ourselves to implication chains of length 20 per assumption
|
|
mkPaths :: Tree Assumption [Assumption] -> [ (Assumption, [[Assumption]]) ]
|
|
mkPaths acst = map impl (keys acst) -- {[a1], [a2], [a3] ]
|
|
where
|
|
-- [Assumption] -> [(a, [chains, ordered by length]
|
|
impl a = (a, impls [[a]])
|
|
impls ns = (take 1000 • concat • takeUntil null • iterate expandchain) ns
|
|
-- expandchain :: [[Assumption]] -> [[Assumption]]
|
|
expandchain css = [ (n:a:as) |
|
|
(a : as) <- css, -- list of assumptions
|
|
n <- find acst a, -- consequences of a
|
|
n `notElem` as -- avoid loops
|
|
]
|
|
-- uni (a:as) = a : uni (filter ((head a !=) • head) as)
|
|
-- uni [] = empty
|
|
-- empty = []
|
|
|
|
|
|
-- ------------------ SOLVE A SUDOKU --------------------------
|
|
-- Apply all available strategies until nothing changes anymore
|
|
-- Strategy functions are supposed to return a list of
|
|
-- functions, which, when applied to a board, give a changed board.
|
|
-- When a strategy does not find anything to alter,
|
|
-- it returns [], and the next strategy can be tried.
|
|
solve b
|
|
| all (single . snd) b = result "Solved" b
|
|
| any (([]==) . snd) b = result "not solvable" b
|
|
| res@(_:_) <- reduce b = apply b res >>=solve -- compute smallest candidate sets
|
|
-- comment "candidate sets are up to date" = ()
|
|
| res@(_:_) <- hiddenSingle b = apply b res >>= solve -- find HIDDEN SINGLES
|
|
-- comment "no more hidden singles" = ()
|
|
| res@(_:_) <- intersections b = apply b res >>= solve -- find locked candidates
|
|
-- comment "no more intersections" = ()
|
|
| res@(_:_) <- nakedPair 2 b = apply b res >>= solve -- find NAKED PAIRS, TRIPLES or QUADRUPELS
|
|
-- comment "no more naked pairs" = ()
|
|
| res@(_:_) <- hiddenPair 2 b = apply b res >>= solve -- find HIDDEN PAIRS, TRIPLES or QUADRUPELS
|
|
-- comment "no more hidden pairs" = ()
|
|
-- res@(_:_) <- nakedPair 3 b = apply b res >>= solve // find NAKED PAIRS, TRIPLES or QUADRUPELS
|
|
-- | comment "no more naked triples" = ()
|
|
-- res@(_:_) <- hiddenPair 3 b = apply b res >>= solve // find HIDDEN PAIRS, TRIPLES or QUADRUPELS
|
|
-- | comment "no more hidden triples" = ()
|
|
-- res@(_:_) <- nakedPair 4 b = apply b res >>=solve // find NAKED PAIRS, TRIPLES or QUADRUPELS
|
|
-- | comment "no more naked quadruples" = ()
|
|
-- res@(_:_) <- hiddenPair 4 b = apply b res >>=solve // find HIDDEN PAIRS, TRIPLES or QUADRUPELS
|
|
-- | comment "no more hidden quadruples" = ()
|
|
| res@(_:_) <- xyWing b = apply b res >>=solve -- find XY WINGS
|
|
-- comment "no more xy wings" = ()
|
|
| res@(_:_) <- fish 2 b = apply b res >>=solve -- find 2-FISH
|
|
-- comment "no more x-wings" = ()
|
|
-- res@(_:_) <- fish 3 b = apply b res >>=solve // find 3-FISH
|
|
-- | comment "no more swordfish" = ()
|
|
-- res@(_:_) <- fish 4 b = apply b res >>=solve // find 4-FISH
|
|
-- | comment "no more jellyfish" = ()
|
|
-- | comment pcomment = ()
|
|
| res@(_:_) <- chain b paths = apply b (take 9 res) >>= solve -- find forcing chains
|
|
| res@(_:_) <- cellRegionChain b paths = apply b (take 9 res) >>= solve -- find common conclusion for true assumption
|
|
| res@(_:_) <- chainContra b paths = apply b (take 9 res) >>= solve -- find assumptions that allow to infer both a and !a
|
|
-- comment "consistent conclusions only" = ()
|
|
|
|
| otherwise = result "ambiguous" b
|
|
where
|
|
apply brd fs = foldM (\b\f -> f b) brd fs
|
|
paths = mkPaths (acstree b)
|
|
-- pcomment = show (length paths) ++ " assumptions with " ++ show (fold (+) 0 (map (length <~ snd) paths))
|
|
-- ++ " implication chains"
|
|
|
|
-- comment com = do stderr << com << "\n" for false
|
|
-- log com = do stderr << com << "\n" for true
|
|
|
|
--- turn a string into a row
|
|
mkrow :: String -> [Zelle]
|
|
mkrow s = mkrow1 xs
|
|
where
|
|
xs = s ++ "---------" -- make sure at least 9 elements
|
|
mkrow1 xs = (take 9 • filter ([]!=) • map f • unpacked) xs
|
|
f x | x >= '1' && x <= '9' = [ord x - ord '0']
|
|
| x == ' ' = [] -- ignored
|
|
| otherwise = elements
|
|
|
|
main ["-h"] = main []
|
|
main ["-help"] = main []
|
|
main [] = do
|
|
mapM_ stderr.println [
|
|
"usage: java Sudoku file ...",
|
|
" java Sudoku position",
|
|
"where position is a 81 char string consisting of digits",
|
|
"One can get such a string by going to",
|
|
"http://www.sudokuoftheday.com/pages/s-o-t-d.php",
|
|
"Right click on the puzzle and open it in new tab",
|
|
"Copy the 81 digits from the URL in the address field of your browser.",
|
|
"",
|
|
"There is also a file with hard sudokus in examples/top95.txt\n"]
|
|
return ()
|
|
|
|
|
|
main [s@#^[0-9\W]{81}$#] = solve board >> return ()
|
|
where
|
|
board = zip positions felder
|
|
felder = decode s
|
|
|
|
main files = forM_ files sudoku
|
|
where
|
|
sudoku file = do
|
|
br <- openReader file
|
|
lines <- BufferedReader.getLines br
|
|
bs <- process lines
|
|
ss <- mapM (\b -> print "Puzzle: " >> printb b >> solve b) bs
|
|
println ("Euler: " ++ show (sum (map res012 ss)))
|
|
return ()
|
|
|
|
-- "--3-" => [1..9, 1..9, [3], 1..9]
|
|
decode s = map candi (unpacked s) where
|
|
candi c | c >= '1' && c <= '9' = [(ord c - ord '0')]
|
|
| otherwise = elements
|
|
process [] = return []
|
|
process (s:ss)
|
|
| length s == 81 = consider b1
|
|
| length s == 9,
|
|
length acht == 8,
|
|
all ((9==) • length) acht = consider b2
|
|
| otherwise = do
|
|
stderr.println ("skipped line: " ++ s)
|
|
process ss
|
|
where
|
|
acht = take 8 ss
|
|
neun = fold (++) "" (s:acht)
|
|
b1 = zip positions (decode s)
|
|
b2 = zip positions (decode neun)
|
|
consider b = do
|
|
-- print "Puzzle: "
|
|
-- printb b
|
|
bs <- process ss
|
|
return (b:bs)
|
|
|