Anonymous user
Sudoku: Difference between revisions
m
→Constraint Satisfaction (Norvig)
Line 4,139:
<!-- By Martin Freedman, 27/11/2021 -->
<lang fsharp>// https://norvig.com/sudoku.html
module SudokuCPS
open System
// helpers
let tuple2 a b = a,b
let (>>=) f g =
/// folds folder returning Some on
let rec all folder state =
| [] -> state
| hd::rest -> state >>= (fun st -> folder st hd) |> (fun st1 -> all folder st1 rest)
/// "A1" to "I9" squares as key in values dictionary
Line 4,189 ⟶ 4,182:
/// Eliminate d from values[s] and propagate when values = 1.
/// Return Some values, except return None if a contradiction is detected.
let rec eliminate (values:Map<
let peerElim (vx:Map<
match Seq.length vx[s] with // (1) If a square s is reduced to one value d, then eliminate d from the peers.
| 0 ->
| 1 -> peers[s] |> List.ofArray |> all (fun st s2 -> eliminate st s2 (vx[s] |> Seq.head) ) (
| _ ->
let unitsElim vx =
units[s] ▼
|> List.ofArray |> all (fun (st:Map<
let dKeys = [for s in u do if st[s] |> Seq.contains d then s]
match Seq.length dKeys with
| 0 ->
| 1 -> assign st (Seq.head dKeys) d
| _ ->
match values[s] |> Seq.contains d with
| false ->
let values2 = values |> Map.change s (Option.map (fun dx -> dx |> Array.filter ((<>)d)))
peerElim values2
▲ |> function // (2) If a unit u is reduced to only one place for a value d, then put it there.
|
|
/// Eliminate all the other values (except d) from values[s] and propagate.
Line 4,219 ⟶ 4,212:
values[s]
|> Seq.filter ((<>)d)
|> List.ofSeq |> all (fun st d2 -> eliminate st s d2) (
/// Convert grid into a Map of {square: char} with "0","."or"," for empties.
let parseGrid grid =
let cells = [for c in grid do if valid |> Seq.contains c then if empty |> Seq.contains c then 0 else ((string>>int) c)]
if Seq.length cells = 81 then cells |> Seq.zip squares |> Map.ofSeq |>
/// Convert grid to a Map of constraint popagated possible values, or return None if a contradiction is detected.
let applyCPS
let values = [for s in squares do s, digits]|> Map.ofList
|>
>> List.ofSeq >> all (fun vx sd -> assign vx sd.Key sd.Value) (
/// Calculate string centre for each square - which can contain more than 1 digit when debugging
Line 4,242 ⟶ 4,235:
/// Display these values as a 2-D grid. Used for debugging
let prettyPrint (
let asString = Seq.map string >> String.concat ""
let width = 1 + ([for s in squares do Seq.length values[s]] |> List.max)
let line = sprintf "%s\n" ((String('-',width*3) |> Seq.replicate 3) |> String.concat "+")
▲ |> String.concat ""
/// Outputs single line puzzle with 0 as empty squares
▲ | Ok values -> values |> Map.toSeq |> Seq.map (snd>>string) |> String.concat ""
/// Using depth-first search and propagation, try all possible values.
let rec search (values:Map<_,_>)
let rec some sx =
|> Seq.tryPick (fun d -> assign values
▲ | s::sx ->
▲ |> Seq.tryPick (fun d -> assign values s d >>= search |> function Error _ -> None | Ok seq -> Some seq)
|> function
| Some seqx when Seq.isEmpty seqx -> some (Seq.tail sx)
| Some seq ->
| _ ->
// Choose the unfilled square(s) s with the fewest possibilities
|> function
|
|
// Option functions glue/ Command Line Helpers (Could be clearer!)
let optionFuncToString error applyF stringF = function None -> error | Some m -> applyF m |> stringF
let solveNoSearch =
optionFuncToString "Parse Error" applyCPS (Option.fold (fun _ t -> t |> prettyPrint) "No Solution")
let solve =
optionFuncToString "Parse Error" (applyCPS >> (Option.bind search)) (Option.fold (fun _ t -> t |> prettyPrint) "No Solution") </lang>
'''Usage'''<lang fsharp>open System
open SudokuCPS
open System.Diagnostics
open System.IO
[<EntryPoint>]
let main argv =
printfn "Easy board solution automatic with constraint propagation"
let easy = "..3.2.6..9..3.5..1..18.64....81.29..7.......8..67.82....26.95..8..2.3..9..5.1.3.."
easy |>
printfn "Simple elimination not possible"
let simple = "4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......"
simple |> parseGrid |> optionFuncToString "Parse Error" id asString |> printfn "%s
simple |>
printfn "Try again with search:"
simple |> parseGrid |>
simple |> parseGrid |> solve |> printfn "%s"
let watch = Stopwatch()
Line 4,302 ⟶ 4,294:
printfn "Hard"
watch.Start()
hard |>
watch.Stop()
printfn $"Elapsed milliseconds = {watch.ElapsedMilliseconds } ms"
watch.Reset()
▲ printfn $"First {num} puzzles in sudoku17"
▲ let puzzles = File.ReadLines(@"sudoku17.txt") |> Seq.take num |> List.ofSeq
watch.Start()
let result = puzzles |>
watch.Stop()
let total = watch.ElapsedMilliseconds
Console.ReadKey() |> ignore
0</lang>
{{Output}}Timings run on i7500U @2.75Ghz CPU, 16GB RAM<pre>Easy board solution automatic with constraint propagation▼
</lang>▼
▲{{Output}}<pre>Easy board solution automatic with constraint propagation
4 8 3 |9 2 1 |6 5 7
9 6 7 |3 4 5 |8 2 1
Line 4,336 ⟶ 4,324:
Simple elimination not possible
400000805030000000000700000020000060000080400000010000000603070500200000104000000
4 1679 12679 | 139 2369 269 | 8 1239 5
26789 3 1256789 | 14589 24569 245689 | 12679 1249 124679
Line 4,387 ⟶ 4,374:
5 9 8 |7 3 6 |2 4 1
Elapsed milliseconds =
Puzzles:49151, Total:184642 ms, Average:3.7566275355537018 ms
===SLPsolve===
|