├── .gitignore ├── README.txt ├── priv ├── easy50.txt ├── hardest.txt └── top95.txt ├── rebar ├── rebar.config ├── src ├── ct_expand.erl ├── sudoku.app.src └── sudoku.erl └── sudoku /.gitignore: -------------------------------------------------------------------------------- 1 | *.beam 2 | *.app 3 | *.out 4 | *~ 5 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | This is an Erlang version of a sudoku solver, mostly based on the 2 | solution by Peter Norvig. 3 | For comparison, see my slightly modified version of Norvig's Python code: 4 | https://github.com/apauley/sudoku-by-norvig 5 | 6 | To try it out, simply compile and run the sudoku script: 7 | $ ./rebar compile 8 | $ ./sudoku 9 | 10 | The solution for file.txt will be saved as file.out in your current directory. 11 | 12 | You will need to have Erlang installed with escript in your path. 13 | 14 | To only run the tests: 15 | $ ./sudoku runtests 16 | 17 | To solve the sample files without running tests: 18 | $ ./sudoku solve 19 | 20 | To solve a specific file (e.g. hardest.txt): 21 | $ ./sudoku solve hardest.txt 22 | -------------------------------------------------------------------------------- /priv/easy50.txt: -------------------------------------------------------------------------------- 1 | ..3.2.6..9..3.5..1..18.64....81.29..7.......8..67.82....26.95..8..2.3..9..5.1.3.. 2 | 2...8.3...6..7..84.3.5..2.9...1.54.8.........4.27.6...3.1..7.4.72..4..6...4.1...3 3 | ......9.7...42.18....7.5.261..9.4....5.....4....5.7..992.1.8....34.59...5.7...... 4 | .3..5..4...8.1.5..46.....12.7.5.2.8....6.3....4.1.9.3.25.....98..1.2.6...8..6..2. 5 | .2.81.74.7....31...9...28.5..9.4..874..2.8..316..3.2..3.27...6...56....8.76.51.9. 6 | 1..92....524.1...........7..5...81.2.........4.27...9..6...........3.945....71..6 7 | .43.8.25.6.............1.949....4.7....6.8....1.2....382.5.............5.34.9.71. 8 | 48...69.2..2..8..19..37..6.84..1.2....37.41....1.6..49.2..85..77..9..6..6.92...18 9 | ...9....2.5.1234...3....16.9.8.......7.....9.......2.5.91....5...7439.2.4....7... 10 | ..19....39..7..16..3...5..7.5......9..43.26..2......7.6..1...3..42..7..65....68.. 11 | ...1254....84.....42.8......3.....95.6.9.2.1.51.....6......3.49.....72....1298... 12 | .6234.75.1....56..57.....4.....948..4.......6..583.....3.....91..64....7.59.8326. 13 | 3..........5..9...2..5.4....2....7..16.....587.431.6.....89.1......67.8......5437 14 | 63..........5....8..5674.......2......34.1.2.......345.....7..4.8.3..9.29471...8. 15 | ....2..4...8.35.......7.6.2.31.4697.2...........5.12.3.49...73........1.8....4... 16 | 361.259...8.96..1.4......57..8...471...6.3...259...8..74......5.2..18.6...547.329 17 | .5.8.7.2.6...1..9.7.254...6.7..2.3.15.4...9.81.3.8..7.9...762.5.6..9...3.8.1.3.4. 18 | .8...5........3457....7.8.9.6.4..9.3..7.1.5..4.8..7.2.9.1.2....8423........1...8. 19 | ..35.29......4....1.6...3.59..251..8.7.4.8.3.8..763..13.8...1.4....2......51.48.. 20 | ...........98.51...519.742.29.4.1.65.........14.5.8.93.267.958...51.36........... 21 | .2..3..9....9.7...9..2.8..5..48.65..6.7...2.8..31.29..8..6.5..7...3.9....3..2..5. 22 | ..5.....6.7...9.2....5..1.78.415.......8.3.......928.59.7..6....3.4...1.2.....6.. 23 | .4.....5...19436....9...3..6...5...21.3...5.68...2...7..5...2....24367...3.....4. 24 | ..4..........3...239.7...8.4....9..12.98.13.76..2....8.1...8.539...4..........8.. 25 | 36..2..89...361............8.3...6.24..6.3..76.7...1.8............418...97..3..14 26 | 5..4...6...9...8..64..2.........1..82.8...5.17..5.........9..84..3...6...6...3..2 27 | ..72564..4.......5.1..3..6....5.8.....8.6.2.....1.7....3..7..9.2.......4..63127.. 28 | ..........79.5.18.8.......7..73.68..45.7.8.96..35.27..7.......5.16.3.42.......... 29 | .3.....8...9...5....75.92..7..1.5..8.2..9..3.9..4.2..1..42.71....2...8...7.....9. 30 | 2..17.6.3.5....1.......6.79....4.7.....8.1.....9.5....31.4.......5....6.9.6.37..2 31 | .......8.8..7.1.4..4..2..3.374...9......3......5...321.1..6..5..5.8.2..6.8....... 32 | .......85...21...996..8.1..5..8...16.........89...6..7..9.7..523...54...48....... 33 | 6.8.7.5.2.5.6.8.7...2...3..5...9...6.4.3.2.5.8...5...3..5...2...1.7.4.9.4.9.6.7.1 34 | .5..1..4.1.7...6.2...9.5...2.8.3.5.1.4..7..2.9.1.8.4.6...4.1...3.4...7.9.2..6..1. 35 | .53...79...97534..1.......2.9..8..1....9.7....8..3..7.5.......3..76412...61...94. 36 | ..6.8.3...49.7.25....4.5...6..317..4..7...8..1..826..9...7.2....75.4.19...3.9.6.. 37 | ..5.8.7..7..2.4..532.....84.6.1.5.4...8...5...7.8.3.1.45.....916..5.8..7..3.1.6.. 38 | ...9..8..128..64...7.8...6.8..43...75.......96...79..8.9...4.1...36..284..1..7... 39 | ....8....27.....54.95...81...98.64...2.4.3.6...69.51...17...62.46.....38....9.... 40 | ...6.2...4...5...1.85.1.62..382.671...........194.735..26.4.53.9...2...7...8.9... 41 | ...9....2.5.1234...3....16.9.8.......7.....9.......2.5.91....5...7439.2.4....7... 42 | 38..........4..785..9.2.3...6..9....8..3.2..9....4..7...1.7.5..495..6..........92 43 | ...158.....2.6.8...3.....4..27.3.51...........46.8.79..5.....8...4.7.1.....325... 44 | .1.5..2..9....1.....2..8.3.5...3...7..8...5..6...8...4.4.1..7.....7....6..3..4.5. 45 | .8.....4....469...4.......7..59.46...7.6.8.3...85.21..9.......5...781....6.....1. 46 | 9.42....7.1..........7.65.....8...9..2.9.4.6..4...2.....16.7..........3.3....57.2 47 | ...7..8....6....31.4...2....24.7.....1..3..8.....6.29....8...7.86....5....2..6... 48 | ..1..7.9.59..8...1.3.....8......58...5..6..2...41......8.....3.1...2..79.2.7..4.. 49 | .....3.17.15..9..8.6.......1....7.....9...2.....5....4.......2.5..6..34.34.2..... 50 | 3..2........1.7...7.6.3.5...7...9.8.9...2...4.1.8...5...9.4.3.1...7.2........8..6 51 | -------------------------------------------------------------------------------- /priv/hardest.txt: -------------------------------------------------------------------------------- 1 | 85...24..72......9..4.........1.7..23.5...9...4...........8..7..17..........36.4. 2 | ..53.....8......2..7..1.5..4....53...1..7...6..32...8..6.5....9..4....3......97.. 3 | 12..4......5.69.1...9...5.........7.7...52.9..3......2.9.6...5.4..9..8.1..3...9.4 4 | ...57..3.1......2.7...234......8...4..7..4...49....6.5.42...3.....7..9....18..... 5 | 7..1523........92....3.....1....47.8.......6............9...5.6.4.9.7...8....6.1. 6 | 1....7.9..3..2...8..96..5....53..9...1..8...26....4...3......1..4......7..7...3.. 7 | 1...34.8....8..5....4.6..21.18......3..1.2..6......81.52..7.9....6..9....9.64...2 8 | ...92......68.3...19..7...623..4.1....1...7....8.3..297...8..91...5.72......64... 9 | .6.5.4.3.1...9...8.........9...5...6.4.6.2.7.7...4...5.........4...8...1.5.2.3.4. 10 | 7.....4...2..7..8...3..8.799..5..3...6..2..9...1.97..6...3..9...3..4..6...9..1.35 11 | ....7..2.8.......6.1.2.5...9.54....8.........3....85.1...3.2.8.4.......9.7..6.... 12 | -------------------------------------------------------------------------------- /priv/top95.txt: -------------------------------------------------------------------------------- 1 | 4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4...... 2 | 52...6.........7.13...........4..8..6......5...........418.........3..2...87..... 3 | 6.....8.3.4.7.................5.4.7.3..2.....1.6.......2.....5.....8.6......1.... 4 | 48.3............71.2.......7.5....6....2..8.............1.76...3.....4......5.... 5 | ....14....3....2...7..........9...3.6.1.............8.2.....1.4....5.6.....7.8... 6 | ......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3. 7 | 6.2.5.........3.4..........43...8....1....2........7..5..27...........81...6..... 8 | .524.........7.1..............8.2...3.....6...9.5.....1.6.3...........897........ 9 | 6.2.5.........4.3..........43...8....1....2........7..5..27...........81...6..... 10 | .923.........8.1...........1.7.4...........658.........6.5.2...4.....7.....9..... 11 | 6..3.2....5.....1..........7.26............543.........8.15........4.2........7.. 12 | .6.5.1.9.1...9..539....7....4.8...7.......5.8.817.5.3.....5.2............76..8... 13 | ..5...987.4..5...1..7......2...48....9.1.....6..2.....3..6..2.......9.7.......5.. 14 | 3.6.7...........518.........1.4.5...7.....6.....2......2.....4.....8.3.....5..... 15 | 1.....3.8.7.4..............2.3.1...........958.........5.6...7.....8.2...4....... 16 | 6..3.2....4.....1..........7.26............543.........8.15........4.2........7.. 17 | ....3..9....2....1.5.9..............1.2.8.4.6.8.5...2..75......4.1..6..3.....4.6. 18 | 45.....3....8.1....9...........5..9.2..7.....8.........1..4..........7.2...6..8.. 19 | .237....68...6.59.9.....7......4.97.3.7.96..2.........5..47.........2....8....... 20 | ..84...3....3.....9....157479...8........7..514.....2...9.6...2.5....4......9..56 21 | .98.1....2......6.............3.2.5..84.........6.........4.8.93..5...........1.. 22 | ..247..58..............1.4.....2...9528.9.4....9...1.........3.3....75..685..2... 23 | 4.....8.5.3..........7......2.....6.....5.4......1.......6.3.7.5..2.....1.9...... 24 | .2.3......63.....58.......15....9.3....7........1....8.879..26......6.7...6..7..4 25 | 1.....7.9.4...72..8.........7..1..6.3.......5.6..4..2.........8..53...7.7.2....46 26 | 4.....3.....8.2......7........1...8734.......6........5...6........1.4...82...... 27 | .......71.2.8........4.3...7...6..5....2..3..9........6...7.....8....4......5.... 28 | 6..3.2....4.....8..........7.26............543.........8.15........8.2........7.. 29 | .47.8...1............6..7..6....357......5....1..6....28..4.....9.1...4.....2.69. 30 | ......8.17..2........5.6......7...5..1....3...8.......5......2..4..8....6...3.... 31 | 38.6.......9.......2..3.51......5....3..1..6....4......17.5..8.......9.......7.32 32 | ...5...........5.697.....2...48.2...25.1...3..8..3.........4.7..13.5..9..2...31.. 33 | .2.......3.5.62..9.68...3...5..........64.8.2..47..9....3.....1.....6...17.43.... 34 | .8..4....3......1........2...5...4.69..1..8..2...........3.9....6....5.....2..... 35 | ..8.9.1...6.5...2......6....3.1.7.5.........9..4...3...5....2...7...3.8.2..7....4 36 | 4.....5.8.3..........7......2.....6.....5.8......1.......6.3.7.5..2.....1.8...... 37 | 1.....3.8.6.4..............2.3.1...........958.........5.6...7.....8.2...4....... 38 | 1....6.8..64..........4...7....9.6...7.4..5..5...7.1...5....32.3....8...4........ 39 | 249.6...3.3....2..8.......5.....6......2......1..4.82..9.5..7....4.....1.7...3... 40 | ...8....9.873...4.6..7.......85..97...........43..75.......3....3...145.4....2..1 41 | ...5.1....9....8...6.......4.1..........7..9........3.8.....1.5...2..4.....36.... 42 | ......8.16..2........7.5......6...2..1....3...8.......2......7..3..8....5...4.... 43 | .476...5.8.3.....2.....9......8.5..6...1.....6.24......78...51...6....4..9...4..7 44 | .....7.95.....1...86..2.....2..73..85......6...3..49..3.5...41724................ 45 | .4.5.....8...9..3..76.2.....146..........9..7.....36....1..4.5..6......3..71..2.. 46 | .834.........7..5...........4.1.8..........27...3.....2.6.5....5.....8........1.. 47 | ..9.....3.....9...7.....5.6..65..4.....3......28......3..75.6..6...........12.3.8 48 | .26.39......6....19.....7.......4..9.5....2....85.....3..2..9..4....762.........4 49 | 2.3.8....8..7...........1...6.5.7...4......3....1............82.5....6...1....... 50 | 6..3.2....1.....5..........7.26............843.........8.15........8.2........7.. 51 | 1.....9...64..1.7..7..4.......3.....3.89..5....7....2.....6.7.9.....4.1....129.3. 52 | .........9......84.623...5....6...453...1...6...9...7....1.....4.5..2....3.8....9 53 | .2....5938..5..46.94..6...8..2.3.....6..8.73.7..2.........4.38..7....6..........5 54 | 9.4..5...25.6..1..31......8.7...9...4..26......147....7.......2...3..8.6.4.....9. 55 | ...52.....9...3..4......7...1.....4..8..453..6...1...87.2........8....32.4..8..1. 56 | 53..2.9...24.3..5...9..........1.827...7.........981.............64....91.2.5.43. 57 | 1....786...7..8.1.8..2....9........24...1......9..5...6.8..........5.9.......93.4 58 | ....5...11......7..6.....8......4.....9.1.3.....596.2..8..62..7..7......3.5.7.2.. 59 | .47.2....8....1....3....9.2.....5...6..81..5.....4.....7....3.4...9...1.4..27.8.. 60 | ......94.....9...53....5.7..8.4..1..463...........7.8.8..7.....7......28.5.26.... 61 | .2......6....41.....78....1......7....37.....6..412....1..74..5..8.5..7......39.. 62 | 1.....3.8.6.4..............2.3.1...........758.........7.5...6.....8.2...4....... 63 | 2....1.9..1..3.7..9..8...2.......85..6.4.........7...3.2.3...6....5.....1.9...2.5 64 | ..7..8.....6.2.3...3......9.1..5..6.....1.....7.9....2........4.83..4...26....51. 65 | ...36....85.......9.4..8........68.........17..9..45...1.5...6.4....9..2.....3... 66 | 34.6.......7.......2..8.57......5....7..1..2....4......36.2..1.......9.......7.82 67 | ......4.18..2........6.7......8...6..4....3...1.......6......2..5..1....7...3.... 68 | .4..5..67...1...4....2.....1..8..3........2...6...........4..5.3.....8..2........ 69 | .......4...2..4..1.7..5..9...3..7....4..6....6..1..8...2....1..85.9...6.....8...3 70 | 8..7....4.5....6............3.97...8....43..5....2.9....6......2...6...7.71..83.2 71 | .8...4.5....7..3............1..85...6.....2......4....3.26............417........ 72 | ....7..8...6...5...2...3.61.1...7..2..8..534.2..9.......2......58...6.3.4...1.... 73 | ......8.16..2........7.5......6...2..1....3...8.......2......7..4..8....5...3.... 74 | .2..........6....3.74.8.........3..2.8..4..1.6..5.........1.78.5....9..........4. 75 | .52..68.......7.2.......6....48..9..2..41......1.....8..61..38.....9...63..6..1.9 76 | ....1.78.5....9..........4..2..........6....3.74.8.........3..2.8..4..1.6..5..... 77 | 1.......3.6.3..7...7...5..121.7...9...7........8.1..2....8.64....9.2..6....4..... 78 | 4...7.1....19.46.5.....1......7....2..2.3....847..6....14...8.6.2....3..6...9.... 79 | ......8.17..2........5.6......7...5..1....3...8.......5......2..3..8....6...4.... 80 | 963......1....8......2.5....4.8......1....7......3..257......3...9.2.4.7......9.. 81 | 15.3......7..4.2....4.72.....8.........9..1.8.1..8.79......38...........6....7423 82 | ..........5724...98....947...9..3...5..9..12...3.1.9...6....25....56.....7......6 83 | ....75....1..2.....4...3...5.....3.2...8...1.......6.....1..48.2........7........ 84 | 6.....7.3.4.8.................5.4.8.7..2.....1.3.......2.....5.....7.9......1.... 85 | ....6...4..6.3....1..4..5.77.....8.5...8.....6.8....9...2.9....4....32....97..1.. 86 | .32.....58..3.....9.428...1...4...39...6...5.....1.....2...67.8.....4....95....6. 87 | ...5.3.......6.7..5.8....1636..2.......4.1.......3...567....2.8..4.7.......2..5.. 88 | .5.3.7.4.1.........3.......5.8.3.61....8..5.9.6..1........4...6...6927....2...9.. 89 | ..5..8..18......9.......78....4.....64....9......53..2.6.........138..5....9.714. 90 | ..........72.6.1....51...82.8...13..4.........37.9..1.....238..5.4..9.........79. 91 | ...658.....4......12............96.7...3..5....2.8...3..19..8..3.6.....4....473.. 92 | .2.3.......6..8.9.83.5........2...8.7.9..5........6..4.......1...1...4.22..7..8.9 93 | .5..9....1.....6.....3.8.....8.4...9514.......3....2..........4.8...6..77..15..6. 94 | .....2.......7...17..3...9.8..7......2.89.6...13..6....9..5.824.....891.......... 95 | 3...8.......7....51..............36...2..4....7...........6.13..452...........8.. 96 | -------------------------------------------------------------------------------- /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apauley/sudoku-in-erlang/1f7b472050c272cef24bdcfd7725cf56712ffa18/rebar -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | %%-*- mode: erlang -*- 2 | {erl_opts, [warnings_as_errors]}. 3 | -------------------------------------------------------------------------------- /src/ct_expand.erl: -------------------------------------------------------------------------------- 1 | %%% The contents of this file are subject to the Erlang Public License, 2 | %%% Version 1.0, (the "License"); you may not use this file except in 3 | %%% compliance with the License. You may obtain a copy of the License at 4 | %%% http://www.erlang.org/license/EPL1_0.txt 5 | %%% 6 | %%% Software distributed under the License is distributed on an "AS IS" 7 | %%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 8 | %%% the License for the specific language governing rights and limitations 9 | %%% under the License. 10 | %%% 11 | %%% The Original Code is ___ 12 | %%% 13 | %%% The Initial Developer of the Original Code is Ericsson Telecom 14 | %%% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson 15 | %%% Telecom AB. All Rights Reserved. 16 | %%% 17 | %%% Contributor(s): ______________________________________. 18 | %%% 19 | %%% ------------------------------------------------------------------ 20 | -module(ct_expand). 21 | -id(''). 22 | -vsn(''). 23 | -date('2006-08-22'). 24 | -author('ulf.wiger@ericsson.com'). 25 | 26 | %%% This module is a quick hack to allow for compile-time expansion of 27 | %%% complex expressions. To trigger the expansion, wrap the expression 28 | %%% inside a call to ct_expand:term/1. 29 | %%% 30 | %%% A simple example: 31 | %%% 32 | %%% f() -> 33 | %%% ct_expand:term( 34 | %%% ordsets:from_list([this, is, a, list, 'of', atoms]) 35 | %%% ). 36 | %%% 37 | %%% The parse_transform evaluates this into: 38 | %%% 39 | %%% f() -> [a, atoms, is, list, 'of', this]. 40 | %%% 41 | 42 | 43 | -export([function/4, 44 | format_error/1]). 45 | -export([parse_transform/2]). 46 | 47 | 48 | 49 | -define(ERROR(R, T, F, I), 50 | begin 51 | rpt_error(R, T, F, I), 52 | throw({error,erl_syntax:get_pos( 53 | proplists:get_value(form,I)),{unknown,R}}) 54 | end). 55 | 56 | -import(erl_syntax, [clause/3, 57 | clause_patterns/1, 58 | clause_body/1, 59 | clause_guard/1, 60 | match_expr/2, 61 | function_clauses/1, 62 | get_pos/1, 63 | add_ann/2, 64 | get_ann/1]). 65 | 66 | %%% ================================================================== 67 | %%% This is the custom code that's been added. The rest of the module 68 | %%% is 'library code' (or should be...) 69 | %%% Any expression wrapped inside a call to ct_expand:term(Expr) is 70 | %%% replaced at compile-time with the results from erl_eval([Expr], []) 71 | %%% (note: no bindings) 72 | %%% 73 | parse_transform(Forms, Options) -> 74 | function({?MODULE, term, 1}, 75 | fun(Form, _Context) -> 76 | io:format("expanding...~n", []), 77 | case erl_syntax:application_arguments(Form) of 78 | [Expr] -> 79 | case erl_eval:exprs(revert_tree([Expr]), []) of 80 | {value, Value, _} -> 81 | erl_syntax:abstract(Value); 82 | Other -> 83 | erlang:error({cannot_evaluate, 84 | [Expr, Other]}) 85 | end; 86 | _Args -> 87 | erlang:error(illegal_form) 88 | end 89 | end, Forms, Options). 90 | 91 | %%% End custom code. 92 | %%% ================================================================== 93 | 94 | 95 | %%% API: function({Module, Function, Arity}, Fun, Forms, Options) -> 96 | %%% NewForms 97 | %%% 98 | %%% Forms and Options are the arguments passed to the parse_transform/2 99 | %%% function. 100 | %%% {Module, Function, Arity} is the function call to transform 101 | %%% Fun(Form, Context) -> NewForm is the fun provided by the caller. 102 | %%% 103 | %%% Context is a property list, containing the following properties: 104 | %%% - {file, Filename} 105 | %%% - {module, ModuleName} 106 | %%% - {function, FunctionName} % name of the enclosing function 107 | %%% - {arity, Arity :: integer()} % arity of same 108 | %%% - {var_names, Vars :: [atom()]} % generated variables binding the 109 | %%% % function arguments. 110 | %%% % length(Vars) == Arity 111 | %%% 112 | function({_Module, _Function, _Arity} = MFA, F, 113 | Forms, Options) when is_function(F) -> 114 | parse_transform(MFA, F, Forms, Options). 115 | 116 | parse_transform(MFA, Fun, Forms, _Options) -> 117 | [File|_] = [F || {attribute,_,file,{F,_}} <- Forms], 118 | try begin 119 | NewTree = xform(MFA, Fun, Forms, [{file, File}]), 120 | revert_tree(NewTree) 121 | end 122 | catch 123 | throw:{error,Ln,What} -> 124 | {error, [{File, [{Ln,?MODULE,What}]}], []} 125 | end. 126 | 127 | revert_tree(Tree) -> 128 | [erl_syntax:revert(T) || T <- lists:flatten(Tree)]. 129 | 130 | 131 | format_error(Other) -> 132 | lists:flatten( 133 | io_lib:format("unknown error in parse_transform: ~p", [Other])). 134 | 135 | 136 | 137 | 138 | xform({M,F,A}, Fun, Forms, Context0) -> 139 | Bef = fun(function, Form, Ctxt) -> 140 | {Fname, Arity} = erl_syntax_lib:analyze_function(Form), 141 | VarNames = erl_syntax_lib:new_variable_names( 142 | Arity, 143 | erl_syntax_lib:variables(Form)), 144 | {Form, [{function, Fname}, 145 | {arity, Arity}, 146 | {var_names, VarNames}|Ctxt]}; 147 | (_, Form, Context) -> 148 | {Form, Context} 149 | end, 150 | Aft = fun(application, Form, Context) -> 151 | case erl_syntax_lib:analyze_application(Form) of 152 | {M, {F, A}} -> 153 | add_ann( 154 | bind_state, 155 | Fun(Form, Context)); 156 | _ -> 157 | Form 158 | end; 159 | (function, Form, Context) -> 160 | Form1 = 161 | erl_syntax_lib:map_subtrees( 162 | fun(Clause) -> 163 | case should_i_bind(Clause) of 164 | true -> 165 | Pats = clause_patterns(Clause), 166 | CBod = clause_body(Clause), 167 | CGd = clause_guard(Clause), 168 | Pats1 = 169 | lists:zipwith( 170 | fun(V, P) -> 171 | match_expr(v(V), P) 172 | end, 173 | proplists:get_value( 174 | var_names, Context), 175 | Pats), 176 | clause(Pats1, CGd, CBod); 177 | false -> 178 | Clause 179 | end 180 | end, Form), 181 | Form1; 182 | (_, Form, _Context) -> 183 | Form 184 | end, 185 | [Module] = [Mx || {attribute, _, module, Mx} <- Forms], 186 | transform(Forms, Bef, Aft, [{module, Module}|Context0]). 187 | 188 | 189 | transform(Forms, Before, After, Context) -> 190 | F1 = 191 | fun(Form) -> 192 | Type = erl_syntax:type(Form), 193 | {Form1, Context1} = 194 | try Before(Type, Form, Context) 195 | catch 196 | error:Reason -> 197 | ?ERROR(Reason, 'before', Before, 198 | [{type, Type}, 199 | {context, Context}, 200 | {form, Form}]) 201 | end, 202 | Form2 = 203 | case erl_syntax:subtrees(Form1) of 204 | [] -> 205 | Form1; 206 | List -> 207 | NewList = 208 | transform( 209 | List, Before, After, Context1), 210 | erl_syntax:update_tree(Form, NewList) 211 | end, 212 | Type2 = erl_syntax:type(Form2), 213 | try After(Type2, Form2, Context1) 214 | catch 215 | error:Reason2 -> 216 | ?ERROR(Reason2, 'after', After, 217 | [{type, Type2}, 218 | {context, Context1}, 219 | {form, Form2}]) 220 | end 221 | end, 222 | F2 = fun(List) when is_list(List) -> 223 | map(F1, List); 224 | (Form) -> 225 | F1(Form) 226 | end, 227 | map(F2, Forms). 228 | 229 | %%% Slightly modified version of lists:mapfoldl/3 230 | %%% Here, F/2 is able to insert forms before and after the form 231 | %%% in question. The inserted forms are not transformed afterwards. 232 | map(F, [Hd|Tail]) -> 233 | {Before, Res, After} = 234 | case F(Hd) of 235 | {Be, _, Af} = Result when is_list(Be), is_list(Af) -> 236 | Result; 237 | R1 -> 238 | {[], R1, []} 239 | end, 240 | Rs = map(F, Tail), 241 | Before ++ [Res| After ++ Rs]; 242 | map(F, []) when is_function(F, 1) -> []. 243 | 244 | 245 | 246 | rpt_error(Reason, BeforeOrAfter, Fun, Info) -> 247 | Fmt = lists:flatten( 248 | ["*** ERROR in parse_transform function:~n" 249 | "*** Reason = ~p~n" 250 | "*** applying ~w fun (~p)~n", 251 | ["*** ~10w = ~p~n" || _ <- Info]]), 252 | Args = [Reason, BeforeOrAfter, Fun | 253 | lists:foldr( 254 | fun({K,V}, Acc) -> 255 | [K, V | Acc] 256 | end, [], Info)], 257 | io:format(Fmt, Args). 258 | 259 | 260 | should_i_bind(Tree) -> 261 | erl_syntax_lib:fold( 262 | fun(T, Flag) -> 263 | lists:member(bind_state, get_ann(T)) or Flag 264 | end, false, Tree). 265 | 266 | 267 | 268 | v(V) -> 269 | erl_syntax:variable(V). 270 | -------------------------------------------------------------------------------- /src/sudoku.app.src: -------------------------------------------------------------------------------- 1 | {application, sudoku, 2 | [ 3 | {description, "An Erlang implementation of Norvig's Sudoku solver"}, 4 | {vsn, "0.9.0"}, 5 | {registered, []}, 6 | {applications, [ 7 | kernel, 8 | stdlib 9 | ]}, 10 | {env, []} 11 | ]}. 12 | -------------------------------------------------------------------------------- /src/sudoku.erl: -------------------------------------------------------------------------------- 1 | -module(sudoku). 2 | 3 | -export([solve_all/1, 4 | solve_file/1, 5 | solve_file/2, 6 | solve_binary_string/1, 7 | print_results/1]). 8 | 9 | -export([test/0]). 10 | 11 | -compile({parse_transform, ct_expand}). 12 | 13 | -define(digits, "123456789"). 14 | -define(rows, "abcdefghi"). 15 | -define(cols, ?digits). 16 | 17 | solve_all(GridList) -> 18 | SolutionDicts = solve_all_return_dicts(GridList), 19 | [to_string(S) || S <- SolutionDicts]. 20 | 21 | solve_file(Filename) -> 22 | solve_file(Filename, "\n"). 23 | 24 | solve_file(Filename, Seperator) -> 25 | {ok, BinString} = file:read_file(Filename), 26 | Solutions = solve_binary_string_return_dicts(BinString, Seperator), 27 | OutFilename = [filename:basename(Filename, ".txt") 28 | | ".out"], 29 | ok = to_file(OutFilename, Solutions), 30 | Solutions. 31 | 32 | solve_binary_string(BinString) -> 33 | SolutionDicts = solve_binary_string_return_dicts(BinString, "\n"), 34 | [to_string(S) || S <- SolutionDicts]. 35 | 36 | solve_binary_string_return_dicts(BinString, Seperator) -> 37 | PuzzleGridList = string:tokens(binary_to_list(BinString), Seperator), 38 | SolutionDicts = solve_all_return_dicts(PuzzleGridList), 39 | SolutionDicts. 40 | 41 | print_results(Filename) -> 42 | print_results(Filename, "\n"). 43 | 44 | print_results(Filename, Seperator) -> 45 | {Time, Solutions} = timer:tc(sudoku, solve_file, 46 | [Filename, Seperator]), 47 | Solved = [Puzzle 48 | || Puzzle <- Solutions, is_solved(Puzzle)], 49 | TimeInSeconds = Time / 1000000, 50 | Eliminations = [Count || {_, Count} <- Solutions], 51 | {Total, Avg, Med, Max, Min, NumberPuzzles} = 52 | stats(Eliminations), 53 | Hz = NumberPuzzles / TimeInSeconds, 54 | Msg = "Solved ~p of ~p puzzles from ~s in ~f " 55 | "secs (~.2f Hz)\n (~p total eliminations, " 56 | "avg ~.2f, median ~p, max ~p, min ~p).~n", 57 | io:format(Msg, 58 | [length(Solved), NumberPuzzles, Filename, TimeInSeconds, 59 | Hz, Total, Avg, Med, Max, Min]). 60 | 61 | stats(List) -> 62 | Total = lists:sum(List), 63 | Length = length(List), 64 | Avg = Total / Length, 65 | Med = lists:nth(round(Length / 2), lists:sort(List)), 66 | Max = lists:max(List), 67 | Min = lists:min(List), 68 | {Total, Avg, Med, Max, Min, Length}. 69 | 70 | squares() -> 71 | %% Returns a list of 81 square names, including 'a1' etc. 72 | ct_expand:term([list_to_atom([X, Y]) 73 | || X <- ?rows, Y <- ?cols]). 74 | 75 | unitlist() -> 76 | %% A list of all units (columns, rows, boxes) in a grid. 77 | ct_expand:term([[list_to_atom([X, Y]) 78 | || X <- ?rows, Y <- [C]] 79 | || C <- ?cols] 80 | ++ 81 | [[list_to_atom([X, Y]) || X <- [R], Y <- ?cols] 82 | || R <- ?rows] 83 | ++ 84 | [[list_to_atom([X, Y]) || X <- R, Y <- C] 85 | || R <- ["abc", "def", "ghi"], 86 | C <- ["123", "456", "789"]]). 87 | 88 | units(Square) -> 89 | %% A list of units for a specific square 90 | [S || S <- unitlist(), lists:member(Square, S)]. 91 | 92 | peers(Square) -> 93 | %% A unique list of squares (excluding this one) 94 | %% that are also part of the units for this square. 95 | NonUniquePeers = lists:flatten([S || S <- units(Square)]), 96 | lists:delete(Square, lists:usort(NonUniquePeers)). 97 | 98 | values(Puzzle, Square) -> 99 | %% Returns the digit values for a given square 100 | {Dict, _} = Puzzle, 101 | dict:fetch(Square, Dict). 102 | 103 | parse_grid(GridString) -> 104 | CleanGrid = clean_grid(GridString), 105 | 81 = length(CleanGrid), 106 | parse_puzzle(empty_puzzle(), squares(), CleanGrid). 107 | 108 | clean_grid(GridString) -> 109 | %% Return a string with only digits, 0 and . 110 | ValidChars = (?digits) ++ "0.", 111 | [E || E <- GridString, lists:member(E, ValidChars)]. 112 | 113 | parse_puzzle(Puzzle, [], []) -> Puzzle; 114 | parse_puzzle(Puzzle, [Square | Squares], 115 | [Value | GridString]) -> 116 | IsDigit = lists:member(Value, ?digits), 117 | NewPuzzle = assign_if_digit(Puzzle, Square, Value, 118 | IsDigit), 119 | parse_puzzle(NewPuzzle, Squares, GridString). 120 | 121 | assign_if_digit(Puzzle, Square, Value, true) -> 122 | %% Value is a Digit, possible to assign 123 | assign(Puzzle, Square, Value); 124 | assign_if_digit(Puzzle, _, _, false) -> 125 | %% Not possible to assign 126 | Puzzle. 127 | 128 | empty_puzzle() -> {empty_dict(), 0}. 129 | 130 | empty_dict() -> 131 | dict:from_list([{Square, ?digits} 132 | || Square <- squares()]). 133 | 134 | assign(Puzzle, Square, Digit) -> 135 | %% Assign by eliminating all values except the assigned value. 136 | OtherValues = exclude_from(values(Puzzle, Square), 137 | Digit), 138 | eliminate_digits(Puzzle, Square, OtherValues). 139 | 140 | eliminate_digits({false, _Count} = False, _, _) -> 141 | False; 142 | eliminate_digits(Puzzle, _, []) -> Puzzle; 143 | eliminate_digits(Puzzle, Square, [Digit | T]) -> 144 | PuzzleOrFalse = eliminate(Puzzle, [Square], Digit), 145 | eliminate_digits(PuzzleOrFalse, Square, T). 146 | 147 | eliminate({false, _Count} = False, _, _) -> False; 148 | eliminate(Puzzle, [], _) -> Puzzle; 149 | eliminate(Puzzle, [Square | T], Digit) -> 150 | %% Eliminate the specified Digit from all specified Squares. 151 | OldValues = values(Puzzle, Square), 152 | NewValues = exclude_from(OldValues, Digit), 153 | NewPuzzle = eliminate(Puzzle, Square, Digit, NewValues, 154 | OldValues), 155 | eliminate(NewPuzzle, T, Digit). 156 | 157 | eliminate({_, Count}, _, _, [], _) -> 158 | %% Contradiction: removed last value 159 | {false, Count}; 160 | eliminate(Puzzle, _, _, Vs, Vs) -> 161 | %% NewValues and OldValues are the same, already eliminated. 162 | Puzzle; 163 | eliminate({ValuesDict, Eliminations}, Square, Digit, 164 | NewValues, _) -> 165 | NewDict = dict:store(Square, NewValues, ValuesDict), 166 | NewPuzzle = peer_eliminate({NewDict, Eliminations + 1}, 167 | Square, NewValues), 168 | %% Digit have been eliminated from this Square. 169 | %% Now see if the elimination has created a unique place for a digit 170 | %% to live in the surrounding units of this Square. 171 | assign_unique_place(NewPuzzle, units(Square), Digit). 172 | 173 | peer_eliminate(Puzzle, Square, [AssignedValue]) -> 174 | %% If there is only one value left, we can also 175 | %% eliminate that value from the peers of Square 176 | eliminate(Puzzle, peers(Square), AssignedValue); 177 | peer_eliminate(Puzzle, _, _) -> 178 | %% Multiple values, cannot eliminate from peers. 179 | Puzzle. 180 | 181 | assign_unique_place({false, _Count} = False, _, _) -> 182 | False; 183 | assign_unique_place(Puzzle, [], _) -> Puzzle; 184 | assign_unique_place(Puzzle, [Unit | T], Digit) -> 185 | %% If a certain digit can only be in one place in a unit, 186 | %% assign it. 187 | Places = places_for_value(Puzzle, Unit, Digit), 188 | NewPuzzle = assign_unique_place_for_digit(Puzzle, 189 | Places, Digit), 190 | assign_unique_place(NewPuzzle, T, Digit). 191 | 192 | assign_unique_place_for_digit({_, Count}, [], _) -> 193 | %% Contradiction: no place for Digit found 194 | {false, Count}; 195 | assign_unique_place_for_digit(Puzzle, [Square], 196 | Digit) -> 197 | %% Unique place for Digit found, assign 198 | assign(Puzzle, Square, Digit); 199 | assign_unique_place_for_digit(Puzzle, _, _) -> 200 | %% Mutlitple palces (or none) found for Digit 201 | Puzzle. 202 | 203 | places_for_value(Puzzle, Unit, Digit) -> 204 | [Square 205 | || Square <- Unit, 206 | lists:member(Digit, values(Puzzle, Square))]. 207 | 208 | solve(GridString) -> search(parse_grid(GridString)). 209 | 210 | search({false, _Count} = False) -> False; 211 | search(Puzzle) -> search(Puzzle, is_solved(Puzzle)). 212 | 213 | search(Puzzle, true) -> 214 | %% Searching an already solved puzzle should just return it unharmed. 215 | Puzzle; 216 | search(Puzzle, false) -> 217 | {Square, Values} = 218 | least_valued_unassigned_square(Puzzle), 219 | first_valid_result(Puzzle, Square, Values). 220 | 221 | %% Returns the first valid puzzle, otherwise the last puzzle 222 | first_valid_result({_, Count}, _, []) -> {false, Count}; 223 | first_valid_result(Puzzle, Square, 224 | [Digit | _T] = Digits) -> 225 | PuzzleOrFalse = search(assign(Puzzle, Square, Digit)), 226 | first_valid_result(Puzzle, Square, Digits, 227 | PuzzleOrFalse). 228 | 229 | first_valid_result({Dict, ValidCount}, Square, [_ | T], 230 | {false, InvalidCount}) -> 231 | first_valid_result({Dict, 232 | ValidCount + (InvalidCount - ValidCount)}, 233 | Square, T); 234 | first_valid_result(_, _, _, Puzzle) -> Puzzle. 235 | 236 | least_valued_unassigned_square({ValuesDict, _}) -> 237 | Lengths = [values_length(V) || V <- dict:to_list(ValuesDict)], 238 | Unassigned = [L || L <- Lengths, unassigned(L)], 239 | {_, Square, Values} = lists:min(Unassigned), 240 | {Square, Values}. 241 | 242 | unassigned({Length, _, _}) -> Length > 1. 243 | 244 | values_length({S, Values}) -> 245 | {length(Values), S, Values}. 246 | 247 | solve_all_return_dicts(GridList) -> 248 | PidGrids = [{spawn(fun server/0), Grid} 249 | || Grid <- GridList], 250 | lists:foreach(fun send_puzzle/1, PidGrids), 251 | [receive_solution(V) || V <- PidGrids]. 252 | 253 | send_puzzle({Pid, Grid}) -> Pid ! {self(), solve, Grid}. 254 | 255 | receive_solution({Pid, Grid}) -> 256 | receive {Pid, Grid, Solution} -> Solution end. 257 | 258 | server() -> 259 | receive 260 | {From, solve, GridString} -> 261 | From ! {self(), GridString, solve(GridString)} 262 | end. 263 | 264 | is_solved(Puzzle) -> 265 | lists:all(fun (Unit) -> 266 | is_unit_solved(Puzzle, Unit) 267 | end, 268 | unitlist()). 269 | 270 | is_unit_solved(Puzzle, Unit) -> 271 | UnitValues = lists:flatmap(fun (S) -> values(Puzzle, S) 272 | end, 273 | Unit), 274 | lists:sort(UnitValues) =:= (?digits). 275 | 276 | to_string(Puzzle) -> 277 | {ValuesDict, _} = Puzzle, 278 | Fun = fun ({_, [V]}) -> [V]; 279 | ({_, _}) -> "." 280 | end, 281 | lists:flatmap(Fun, 282 | lists:sort(dict:to_list(ValuesDict))). 283 | 284 | to_file(Filename, Solutions) -> 285 | GridStrings = [to_string(S) ++ "\n" || S <- Solutions], 286 | ok = file:write_file(Filename, 287 | list_to_binary(GridStrings)). 288 | 289 | exclude_from(Values, Digit) -> 290 | lists:delete(Digit, Values). 291 | 292 | %%%==================================================================================== 293 | %%% Micro Unit Tests 294 | %%% ---------------- 295 | %%% 296 | %%% Test the functionality using Joe Armstrong's Micro Lightweight Unit Testing: 297 | %%% http://armstrongonsoftware.blogspot.com/2009/01/micro-lightweight-unit-testing.html 298 | %%% 299 | %%% This was moved into the sudoku module in order to get rid of export_all 300 | %%%==================================================================================== 301 | 302 | test() -> 303 | ok = test_squares(), 304 | ok = test_unitlist(), 305 | ok = test_units(), 306 | ok = test_peers(), 307 | ok = test_empty_puzzle(), 308 | ok = test_clean_grid(), 309 | ok = test_parse_grid(), 310 | ok = test_least_valued_unassigned_square(), 311 | ok = test_eliminate(), 312 | ok = test_search_bails_out_early(), 313 | ok = test_search_solves_grid(), 314 | ok = test_assign(), 315 | ok = test_assign_eliminates_from_peers(), 316 | ok = test_recursive_peer_elimination(), 317 | ok = test_automatically_assign_unique_places(), 318 | ok = test_places_for_value(), 319 | ok = test_is_solved(), 320 | ok = test_to_string(), 321 | ok = test_stats(), 322 | ok. 323 | 324 | test_squares() -> 325 | 81 = length(squares()), 326 | ok. 327 | 328 | test_unitlist() -> 329 | 27 = length(unitlist()), 330 | ok. 331 | 332 | test_units() -> 333 | [[a2,b2,c2,d2,e2,f2,g2,h2,i2]|_] = units(c2), 334 | 335 | %% Each square should have exactly 3 units 336 | true = lists:all(fun(Units) -> length(Units) =:= 3 end, 337 | [units(Square) || Square <- squares()]), 338 | 339 | %% Each unit should contain exactly nine squares 340 | TruthValues = [lists:all(fun(Unit) -> length(Unit) =:= 9 end, 341 | units(Square)) || Square <- squares()], 342 | 343 | %% Each square should be part of all its units 344 | TruthValues = [lists:all(fun(Unit) -> lists:member(Square, Unit) end, 345 | units(Square)) || Square <- squares()], 346 | 347 | true = allTrue(TruthValues), 348 | ok. 349 | 350 | test_peers() -> 351 | Peers = lists:sort([c8, f2, g2, h2, c7, 352 | i2, a3, a1, c9, a2, 353 | b1, b2, b3, c3, c1, 354 | c4, d2, c6, c5, e2]), 355 | Peers = lists:sort(peers(c2)), 356 | 357 | %% Each square should have exactly 20 squares as its peers 358 | true = lists:all(fun(Units) -> length(Units) =:= 20 end, 359 | [peers(Square) || Square <- squares()]), 360 | ok. 361 | 362 | test_empty_puzzle() -> 363 | Puzzle = empty_puzzle(), 364 | {_, Eliminations} = Puzzle, 365 | 0 = Eliminations, 366 | true = is_sudoku_puzzle(Puzzle), 367 | 368 | %% The values of all keys should start with all possible values. 369 | Squares = squares(), 370 | Digits = "123456789", 371 | true = lists:all(fun(Values) -> Values =:= Digits end, 372 | [values(Puzzle, Square) || Square <- Squares]), 373 | ok. 374 | 375 | test_clean_grid() -> 376 | GridString = "|4..-...-805| 377 | .3.+...+...", 378 | "4.....805.3......." = clean_grid(GridString), 379 | ok. 380 | 381 | test_parse_grid() -> 382 | GridString = "4.....8.5 383 | .3....... 384 | ...7..... 385 | .2.....6. 386 | ....8.4.. 387 | ....1.... 388 | ...6.3.7. 389 | 5..2..... 390 | 1.4......", 391 | 392 | %% A parsed grid will already have eliminated the values of some squares 393 | Puzzle = parse_grid(GridString), 394 | "4" = values(Puzzle, f2), 395 | ok. 396 | 397 | test_least_valued_unassigned_square() -> 398 | %% Assign something to A1 and eliminate another from A2. 399 | %% A1 should not be considered, it's already assigned. 400 | Puzzle = assign(eliminate_digits(empty_puzzle(), a2, "234"), a1, $1), 401 | false = is_solved(Puzzle), 402 | {a2, _} = least_valued_unassigned_square(Puzzle), 403 | 404 | %% Any square can be returned when all values are equally unassigned 405 | {a1, _} = least_valued_unassigned_square(empty_puzzle()), 406 | ok. 407 | 408 | test_eliminate() -> 409 | Puzzle = eliminate(empty_puzzle(), [a2], $3), 410 | "12456789" = values(Puzzle, a2), 411 | NewPuzzle = eliminate_digits(Puzzle, a2, "13689"), 412 | "2457" = values(NewPuzzle, a2), 413 | 414 | %% Eliminating the last value from a square should indicate an error 415 | {false, _} = eliminate_digits(Puzzle, a2, "123456789"), 416 | ok. 417 | 418 | test_search_bails_out_early() -> 419 | %% Searching an already solved puzzle should just return it unharmed. 420 | true = solved_puzzle() =:= search(solved_puzzle()), 421 | 422 | %% Searching a previous failure should return the same failure 423 | {false, 1} = search({false, 1}), 424 | ok. 425 | 426 | test_search_solves_grid() -> 427 | GridString = "4.....8.5.3..........7......2... 428 | ..6.....8.4......1.......6.3.7.5..2.....1.4......", 429 | ValuesTuple = parse_grid(GridString), 430 | false = is_solved(ValuesTuple), 431 | true = is_solved(search(ValuesTuple)), 432 | ok. 433 | 434 | test_assign() -> 435 | Puzzle = assign(empty_puzzle(), a2, $1), 436 | "1" = values(Puzzle, a2), 437 | 438 | %% Assigning a different value to an already assigned square should 439 | %% indicate an error. 440 | {false, _} = assign(Puzzle, a2, $3), 441 | ok. 442 | 443 | test_assign_eliminates_from_peers() -> 444 | NonPeerValues = values(empty_puzzle(), d1), 445 | Puzzle = assign(empty_puzzle(), a3, $7), 446 | 447 | %% Now 7 may not be a possible value in any of a3's peers 448 | Fun = fun(Square) -> not (lists:member($7, values(Puzzle, Square))) end, 449 | true = lists:all(Fun, peers(a3)), 450 | 451 | %% After assignment, the non-peers remain unchanged: 452 | NonPeerValues = values(Puzzle, d1), 453 | ok. 454 | 455 | test_recursive_peer_elimination() -> 456 | %% Eliminate all but two values from a peer of a3: 457 | SetupPuzzle = eliminate_digits(empty_puzzle(), a2, "2345689"), 458 | "17" = values(SetupPuzzle, a2), 459 | 460 | %% Assigning one of the above two values in a3 should trigger 461 | %% peer elimination in a2 as well. 462 | Puzzle = assign(SetupPuzzle, a3, $7), 463 | "1" = values(Puzzle, a2), 464 | Fun = fun(Square) -> not (lists:member($1, values(Puzzle, Square))) end, 465 | true = lists:all(Fun, peers(a2)), 466 | ok. 467 | 468 | test_automatically_assign_unique_places() -> 469 | %% This grid was chosen so that C9 is a unique place for the digit 2 470 | GridString = ".....3.17.15..9..8.6.......1.... 471 | 7.....9...2.....5....4.......2.5..6..34.34.2.....", 472 | Puzzle = parse_grid(GridString), 473 | "2" = values(Puzzle, c9), 474 | ok. 475 | 476 | test_places_for_value() -> 477 | GridString = ".45.81376....................... 478 | .................................................", 479 | Puzzle = parse_grid(GridString), 480 | "29" = values(Puzzle, a1), 481 | "29" = values(Puzzle, a4), 482 | Unit = [a1,a2,a3,a4,a5,a6,a7,a8,a9], 483 | [a1,a4] = places_for_value(Puzzle, Unit, $9), 484 | [a1,a4] = places_for_value(Puzzle, Unit, $2), 485 | ok. 486 | 487 | test_is_solved() -> 488 | true = is_solved(solved_puzzle()), 489 | false = is_solved(empty_puzzle()), 490 | ok. 491 | 492 | test_to_string() -> 493 | GridString = ".1736982563215894795872431682543 494 | 7169791586432346912758289643571573291684164875293", 495 | Puzzle = eliminate_digits(parse_grid(GridString), a1, "12356789"), 496 | [$4|T] = to_string(Puzzle), 497 | [$.|T] = clean_grid(GridString), 498 | ok. 499 | 500 | test_stats() -> 501 | {Total, Avg, Med, Max, Min, Length} = stats([2, 9, 4]), 502 | 15 = Total, 503 | 5.0 = Avg, 504 | 4 = Med, 505 | 9 = Max, 506 | 2 = Min, 507 | 3 = Length, 508 | ok. 509 | 510 | is_sudoku_puzzle(Puzzle) -> 511 | {ValuesDict, Eliminations} = Puzzle, 512 | true = is_integer(Eliminations), 513 | is_sudoku_dict(ValuesDict). 514 | 515 | is_sudoku_dict(ValuesDict) -> 516 | lists:sort(dict:fetch_keys(ValuesDict)) =:= squares(). 517 | 518 | solved_puzzle() -> 519 | GridString = "41736982563215894795872431682543 520 | 7169791586432346912758289643571573291684164875293", 521 | parse_grid(GridString). 522 | 523 | allTrue(Booleans) -> 524 | %% Test support function: 525 | %% Returns true if the list of booleans are all true. 526 | %% I expect there should already be such a function, 527 | %% please point me to it if you can. 528 | lists:all(fun(Bool) -> Bool end, Booleans). 529 | -------------------------------------------------------------------------------- /sudoku: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | %%% -*- mode:erlang -*- 3 | 4 | main(Args) -> 5 | true = code:add_pathz("./ebin"), 6 | run_solver(Args). 7 | 8 | run_solver([]) -> 9 | run_solver(["runtests"]), 10 | run_solver(["solve"]); 11 | run_solver(["runtests"]) -> 12 | ok = sudoku:test(), 13 | io:format("All tests passed :-)~n"); 14 | run_solver(["solve"]) -> 15 | sudoku:print_results("./priv/easy50.txt"), 16 | sudoku:print_results("./priv/top95.txt"), 17 | sudoku:print_results("./priv/hardest.txt"); 18 | run_solver(["solve", Filename]) -> 19 | sudoku:print_results(Filename). 20 | --------------------------------------------------------------------------------