Skip to content

Commit

Permalink
Use atoms as square names. Thanks to Kresten Krab Thorup for the idea:
Browse files Browse the repository at this point in the history
  • Loading branch information
Andreas Pauley committed Mar 29, 2011
1 parent f092e55 commit 9c69a9d
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 51 deletions.
20 changes: 7 additions & 13 deletions sudoku.erl
Expand Up @@ -9,22 +9,16 @@

squares() ->
%% Returns a list of 81 square names, including "a1" etc.
ct_expand:term([[X,Y] || X <- ?rows, Y <- ?cols]).
col_squares() ->
%% All the square names for each column.
ct_expand:term([[[X,Y] || X <- ?rows, Y <- [C]] || C <- ?cols]).
row_squares() ->
%% All the square names for each row.
ct_expand:term([[[X,Y] || X <- [R], Y <- ?cols] || R <- ?rows]).
box_squares() ->
%% All the square names for each box.
ct_expand:term([[[X,Y] || X <- R, Y <- C] ||
R <- ["abc", "def", "ghi"],
C <- ["123", "456", "789"]]).
ct_expand:term([list_to_atom([X,Y]) || X <- ?rows, Y <- ?cols]).

unitlist() ->
%% A list of all units (columns, rows, boxes) in a grid.
col_squares() ++ row_squares() ++ box_squares().
ct_expand:term([[list_to_atom([X,Y]) || X <- ?rows, Y <- [C]] || C <- ?cols]
++ [[list_to_atom([X,Y])
|| X <- [R], Y <- ?cols] || R <- ?rows]
++ [[list_to_atom([X,Y]) || X <- R, Y <- C] ||
R <- ["abc", "def", "ghi"],
C <- ["123", "456", "789"]]).

units(Square) ->
%% A list of units for a specific square
Expand Down
73 changes: 35 additions & 38 deletions unittests.erl
@@ -1,7 +1,7 @@
-module(unittests).
-import(lists, [all/2, member/2, sort/1]).
-import(sudoku, [cross/2, values/2,
squares/0, col_squares/0, row_squares/0, box_squares/0,
squares/0,
unitlist/0, units/1, peers/1, search/1,
least_valued_unassigned_square/1, stats/1,
clean_grid/1, is_solved/1, eliminate_digits/3,
Expand Down Expand Up @@ -38,14 +38,11 @@ test_squares() ->
ok.

test_unitlist() ->
[["a1","b1","c1","d1","e1","f1","g1","h1","i1"]|_] = col_squares(),
[["a1","a2","a3","a4","a5","a6","a7","a8","a9"]|_] = row_squares(),
[["a1","a2","a3","b1","b2","b3","c1","c2","c3"]|_] = box_squares(),
27 = length(unitlist()),
ok.

test_units() ->
[["a2","b2","c2","d2","e2","f2","g2","h2","i2"]|_] = units("c2"),
[[a2,b2,c2,d2,e2,f2,g2,h2,i2]|_] = units(c2),

%% Each square should have exactly 3 units
true = all(fun(Units) -> length(Units) == 3 end,
Expand All @@ -63,11 +60,11 @@ test_units() ->
ok.

test_peers() ->
Peers = sort(["c8", "f2", "g2", "h2", "c7",
"i2", "a3", "a1", "c9", "a2",
"b1", "b2", "b3", "c3", "c1",
"c4", "d2", "c6", "c5", "e2"]),
Peers = sort(peers("c2")),
Peers = sort([c8, f2, g2, h2, c7,
i2, a3, a1, c9, a2,
b1, b2, b3, c3, c1,
c4, d2, c6, c5, e2]),
Peers = sort(peers(c2)),

%% Each square should have exactly 20 squares as its peers
true = all(fun(Units) -> length(Units) == 20 end,
Expand Down Expand Up @@ -106,28 +103,28 @@ test_parse_grid() ->

%% A parsed grid will already have eliminated the values of some squares
Puzzle = parse_grid(GridString),
"4" = values(Puzzle, "f2"),
"4" = values(Puzzle, f2),
ok.

test_least_valued_unassigned_square() ->
%% Assign something to A1 and eliminate another from A2.
%% A1 should not be considered, it's already assigned.
Puzzle = assign(eliminate_digits(empty_puzzle(), "a2", "234"), "a1", $1),
Puzzle = assign(eliminate_digits(empty_puzzle(), a2, "234"), a1, $1),
false = is_solved(Puzzle),
{"a2", _} = least_valued_unassigned_square(Puzzle),
{a2, _} = least_valued_unassigned_square(Puzzle),

%% Any square can be returned when all values are equally unassigned
{"a1", _} = least_valued_unassigned_square(empty_puzzle()),
{a1, _} = least_valued_unassigned_square(empty_puzzle()),
ok.

test_eliminate() ->
Puzzle = eliminate(empty_puzzle(), ["a2"], $3),
"12456789" = values(Puzzle, "a2"),
NewPuzzle = eliminate_digits(Puzzle, "a2", "13689"),
"2457" = values(NewPuzzle, "a2"),
Puzzle = eliminate(empty_puzzle(), [a2], $3),
"12456789" = values(Puzzle, a2),
NewPuzzle = eliminate_digits(Puzzle, a2, "13689"),
"2457" = values(NewPuzzle, a2),

%% Eliminating the last value from a square should indicate an error
{false, _} = eliminate_digits(Puzzle, "a2", "123456789"),
{false, _} = eliminate_digits(Puzzle, a2, "123456789"),
ok.

test_search_bails_out_early() ->
Expand All @@ -147,56 +144,56 @@ test_search_solves_grid() ->
ok.

test_assign() ->
Puzzle = assign(empty_puzzle(), "a2", $1),
"1" = values(Puzzle, "a2"),
Puzzle = assign(empty_puzzle(), a2, $1),
"1" = values(Puzzle, a2),

%% Assigning a different value to an already assigned square should
%% indicate an error.
{false, _} = assign(Puzzle, "a2", $3),
{false, _} = assign(Puzzle, a2, $3),
ok.

test_assign_eliminates_from_peers() ->
NonPeerValues = values(empty_puzzle(), "d1"),
Puzzle = assign(empty_puzzle(), "a3", $7),
NonPeerValues = values(empty_puzzle(), d1),
Puzzle = assign(empty_puzzle(), a3, $7),

%% Now 7 may not be a possible value in any of a3's peers
Fun = fun(Square) -> not (member($7, values(Puzzle, Square))) end,
true = all(Fun, peers("a3")),
true = all(Fun, peers(a3)),

%% After assignment, the non-peers remain unchanged:
NonPeerValues = values(Puzzle, "d1"),
NonPeerValues = values(Puzzle, d1),
ok.

test_recursive_peer_elimination() ->
%% Eliminate all but two values from a peer of a3:
SetupPuzzle = eliminate_digits(empty_puzzle(), "a2", "2345689"),
"17" = values(SetupPuzzle, "a2"),
SetupPuzzle = eliminate_digits(empty_puzzle(), a2, "2345689"),
"17" = values(SetupPuzzle, a2),

%% Assigning one of the above two values in a3 should trigger
%% peer elimination in a2 as well.
Puzzle = assign(SetupPuzzle, "a3", $7),
"1" = values(Puzzle, "a2"),
Puzzle = assign(SetupPuzzle, a3, $7),
"1" = values(Puzzle, a2),
Fun = fun(Square) -> not (member($1, values(Puzzle, Square))) end,
true = all(Fun, peers("a2")),
true = all(Fun, peers(a2)),
ok.

test_automatically_assign_unique_places() ->
%% This grid was chosen so that C9 is a unique place for the digit 2
GridString = ".....3.17.15..9..8.6.......1....
7.....9...2.....5....4.......2.5..6..34.34.2.....",
Puzzle = parse_grid(GridString),
"2" = values(Puzzle, "c9"),
"2" = values(Puzzle, c9),
ok.

test_places_for_value() ->
GridString = ".45.81376.......................
.................................................",
Puzzle = parse_grid(GridString),
"29" = values(Puzzle, "a1"),
"29" = values(Puzzle, "a4"),
Unit = ["a1","a2","a3","a4","a5","a6","a7","a8","a9"],
["a1","a4"] = places_for_value(Puzzle, Unit, $9),
["a1","a4"] = places_for_value(Puzzle, Unit, $2),
"29" = values(Puzzle, a1),
"29" = values(Puzzle, a4),
Unit = [a1,a2,a3,a4,a5,a6,a7,a8,a9],
[a1,a4] = places_for_value(Puzzle, Unit, $9),
[a1,a4] = places_for_value(Puzzle, Unit, $2),
ok.

test_is_solved() ->
Expand All @@ -207,7 +204,7 @@ test_is_solved() ->
test_to_string() ->
GridString = ".1736982563215894795872431682543
7169791586432346912758289643571573291684164875293",
Puzzle = eliminate_digits(parse_grid(GridString), "a1", "12356789"),
Puzzle = eliminate_digits(parse_grid(GridString), a1, "12356789"),
[$4|T] = to_string(Puzzle),
[$.|T] = clean_grid(GridString),
ok.
Expand Down

0 comments on commit 9c69a9d

Please sign in to comment.