Thanks to Andre for his talk on Friday; I spent Saturday writing a Sudoku solver in Erlang. You can suck it down using bzr from http://repo.spacepants.org/sudoku/sudoku.dev (and note it's a bzr repo, so you can't browse the source at that url).
I'd like some feedback on my code, so I've attached it here. Both seasoned Erlang professionals and green newbs are welcome to comment and ask questions. (I'm also taking the opportunity to use the [RFR] tag in the subject as a Request for Review ;-) (I'm more interested in coding style, or suggestions on ways to make things more concise, rather than comments on algorithm or telling me that it doesn't actually solve anything harder than an "easy" sudoku, though you're welcome to pass judgement on that, too.) Particular things that I'm sure I'm doing wrong is excessive use of anonymous functions (Funs) but apparently I can't use a real function as the argument to, say, lists:map? Does Erlang support currying or is this hacked through the use of Funs? Also I'm sure I'm using 'case' too much, and should be using guards or pattern matches, but everything I tried ended up causing the compiler to complain ;-)
-module(sudoku). %-export([solve/1]). -compile(export_all). %solve(Grid) -> % Daily Sudoku 28/6/2005 (medium) -define(test_grid, [[x,x,3,x,x,x,x,1,x], [9,x,1,x,3,x,8,x,7], [x,x,x,9,5,x,x,3,4], [1,5,7,x,x,x,x,x,x], [x,x,x,x,7,x,x,x,x], [x,x,x,x,x,x,7,8,6], [3,9,x,x,8,6,x,x,x], [6,x,5,x,4,x,1,x,8], [x,1,x,x,x,x,2,x,x]]). %%% -record(cell, {x, y, block, possibles}). calc_possibles(V) when V == x -> lists:seq(1, 9); calc_possibles(V) -> [V]. test_calc_possibles() -> [1,2,3,4,5,6,7,8,9] = calc_possibles(x), [1] = calc_possibles(1), ok. calc_block(X, Y) -> (X-1) div 3 + ((Y-1) div 3) * 3 + 1. test_calc_block() -> 1 = calc_block(1, 1), 1 = calc_block(2, 1), 1 = calc_block(3, 1), 2 = calc_block(4, 1), 5 = calc_block(5, 5), 9 = calc_block(9, 9), ok. compile_cell(X, Y, V) -> Block = calc_block(X, Y), Possibles = calc_possibles(V), #cell{x=X, y=Y, block=Block, possibles=Possibles}. test_compile_cell() -> #cell{x=1, y=1, block=1, possibles=[1]} = compile_cell(1, 1, 1), #cell{x=1, y=1, block=1, possibles=[1,2,3,4,5,6,7,8,9]} = compile_cell(1, 1, x), ok. compile_row([], _) -> []; compile_row(Row, Y) -> EnumeratedRow = lists:zip(lists:seq(1, length(Row)), Row), lists:map(fun({X, V}) -> compile_cell(X, Y, V) end, EnumeratedRow). test_compile_row() -> [] = compile_row([], 0), [{cell, 1, 1, 1, [1]}] = compile_row([1], 1), [{cell, 1, 1, 1, [1]}, {cell, 2, 1, 1, [2]}] = compile_row([1, 2], 1), [{cell, 1, 4, 4, [1,2,3,4,5,6,7,8,9]}] = compile_row([x], 4), ok. compile_sudoku([]) -> []; compile_sudoku(Grid) -> EnumeratedGrid = lists:zip(lists:seq(1, length(Grid)), Grid), lists:flatmap(fun({Y, V}) -> compile_row(V, Y) end, EnumeratedGrid). test_compile_sudoku() -> [] = compile_sudoku([]), [{cell,1,1,1,[1]}] = compile_sudoku([[1]]), ok. % pull out the definitely solved cells from the neighbour list definites(Cells) -> lists:filter(fun(C) -> length(C#cell.possibles) == 1 end, Cells). test_definites() -> [{cell,1,1,1,[1]}] = definites([{cell,1,2,1,[1,2]},{cell,1,1,1,[1]}]), ok. % not defined in lists module! keyfilter(Key, N, List) -> lists:filter(fun(T) -> Key == element(N, T) end, List). test_keyfilter() -> [{1,1}] = keyfilter(1, 1, [{2,2},{2,1},{1,1}]), ok. % find all affecting neighbours and recalculate this cells' possibles % this one does all the grunt work of solving the sudoku, add % new operations on the list of possibles here collapse_possibles(Cell, Sudoku) -> % neighbours in the same column that are solved Cols = lists:flatmap(fun(C) -> C#cell.possibles end, definites(keyfilter(Cell#cell.x, 2, Sudoku))), %io:format("Cols: ~p~n", [Cols]), Rows = lists:flatmap(fun(C) -> C#cell.possibles end, definites(keyfilter(Cell#cell.y, 3, Sudoku))), %io:format("Rows: ~p~n", [Rows]), Blocks = lists:flatmap(fun(C) -> C#cell.possibles end, definites(keyfilter(Cell#cell.block, 4, Sudoku))), %io:format("Blocks: ~p~n", [Blocks]), ((Cell#cell.possibles -- Cols) -- Rows) -- Blocks. test_collapse_possibles() -> Sudoku = compile_sudoku([[x,x,3,x,x,x,x,1,x], [9,x,1,x,3,x,8,x,7], [x,x,x,9,5,x,x,3,4], [1,5,7,x,x,x,x,x,x], [x,x,x,x,7,x,x,x,x], [x,x,x,x,x,x,7,8,6], [3,9,x,x,8,6,x,x,x], [6,x,5,x,4,x,1,x,8], [x,1,x,x,x,x,2,x,x]]), Cell = lists:nth(1, Sudoku), P = collapse_possibles(Cell, Sudoku), %io:format("Poss: ~p~n", [P]), P = [2,4,5,7,8], ok. % look at the neighbours of Cell and work out if Cell can only have one % value from it's list of possibles % e.g. if Cell is the only unsolved cell in a row that has a particular % value, then Cell must be that value deduce_possibles(Cell, Sudoku) -> Unsolveds = lists:filter(fun(C) -> ((C#cell.x == Cell#cell.x) or (C#cell.y == Cell#cell.y) or (C#cell.block == Cell#cell.block)) and (length(C#cell.possibles) > 1) and (C /= Cell) end, Sudoku), %io:format("Unsolveds: ~p~n", [Unsolveds]), % remove all possibles that other cells can also be Shareds = lists:flatmap(fun(C) -> C#cell.possibles end, Unsolveds), %io:format("Shareds: ~p~n", [Shareds]), Possibles = Cell#cell.possibles -- Shareds, %io:format("Possibles: ~p~n", [Possibles]), Possibles. test_deduce_possibles() -> Sudoku = [{cell,9,1,3,[2,9]}, {cell,9,2,3,[7]}, {cell,9,3,3,[4]}, {cell,9,4,6,[2,9]}, {cell,9,5,6,[1,2,9]}, {cell,9,6,6,[6]}, {cell,9,7,9,[5]}, {cell,9,8,9,[8]}, {cell,9,9,9,[3]}], Cell = {cell,9,5,6,[1,2,9]}, [1] = deduce_possibles(Cell, Sudoku), ok. do_solve(Solveds, []) -> Solveds; do_solve(Solveds, Unsolveds) -> {Changed, Unchanged} = iter_unsolved(Unsolveds, Solveds ++ Unsolveds), %io:format("Changed: ~p~n", [Changed]), case length(Changed) == 0 of true -> Solveds ++ Unsolveds; false -> {NewSolveds, NewUnsolveds} = lists:partition(fun(C) -> length(C#cell.possibles) == 1 end, Changed), %io:format("NewSolveds: ~p~n", [NewSolveds]), do_solve(Solveds ++ NewSolveds, NewUnsolveds ++ Unchanged) end. iter_unsolved([], _) -> {[], []}; iter_unsolved([Cell|Rest], Sudoku) -> {Changed, Unchanged} = iter_unsolved(Rest, Sudoku), Deduceds = deduce_possibles(Cell, Sudoku), case length(Deduceds) == 1 of true -> {[Cell#cell{possibles=Deduceds}] ++ Changed, Unchanged}; false -> NewPossibles = collapse_possibles(Cell, Sudoku), case (length(NewPossibles) == 0) or (Cell#cell.possibles == NewPossibles) of true -> {Changed, [Cell] ++ Unchanged}; false -> {[Cell#cell{possibles=NewPossibles}] ++ Changed, Unchanged} end end. test_do_solve() -> % solved [{cell,1,1,1,[1]}] = do_solve([], [{cell,1,1,1,[1]}]), % unsolved Sudoku = compile_sudoku([[x,x,3,x,x,x,x,1,x], [9,x,1,x,3,x,8,x,7], [x,x,x,9,5,x,x,3,4], [1,5,7,x,x,x,x,x,x], [x,x,x,x,7,x,x,x,x], [x,x,x,x,x,x,7,8,6], [3,9,x,x,8,6,x,x,x], [6,x,5,x,4,x,1,x,8], [x,1,x,x,x,x,2,x,x]]), S = do_solve([], Sudoku), io:format("S: ~p~n", [format_solution(S)]), ok. split_rows([], _) -> []; split_rows(Cells, Y) -> {This, Rest} = lists:partition(fun(C) -> C#cell.y == Y end, Cells), %io:format("this: ~p, rest: ~p~n", [This, Rest]), Row = lists:map(fun(C) -> C#cell.possibles end, lists:keysort(2, This)), %io:format("Row: ~p~n", [Row]), [Row] ++ split_rows(Rest, Y + 1). extract_values([]) -> []; extract_values([Row|Rest]) -> ORow = lists:flatmap(fun(C) -> %io:format("C: ~p~n", [C]), if length(C) == 1 -> C; true -> [x] end end, Row), %io:format("ORow: ~p~n", [ORow]), [ORow] ++ extract_values(Rest). split_rows_and_extract_value(Cells, N) -> extract_values(split_rows(Cells, N)). test_split_rows_and_extract_value() -> [] = split_rows_and_extract_value([], 0), [[x]] = split_rows_and_extract_value([{cell,1,1,1,[1,2]}], 1), [[1,2],[3,4]] = split_rows_and_extract_value([{cell,1,1,1,[1]},{cell,2,1,1,[2]},{cell,1,2,1,[3]},{cell,2,2,1,[4]}], 1), [[1,2],[3,4],[5,6]] = split_rows_and_extract_value([{cell,2,3,1,[6]},{cell,2,2,1,[4]},{cell,1,2,1,[3]},{cell,2,1,1,[2]},{cell,1,3,1,[5]},{cell,1,1,1,[1]}], 1), ok. format_solution(Solution) -> split_rows_and_extract_value(Solution, 1). test_format_solution() -> [[1]] = format_solution([{cell,1,1,1,[1]}]), [[1,2]] = format_solution([{cell,1,1,1,[1]}]), % perverse test [[1,2,3,4], [2,3,4,5], [3,4,5,6], [4,5,6,7]] = format_solution([{cell,4,4,x,[7]}, {cell,1,4,x,[4]}, {cell,1,1,x,[1]}, {cell,4,1,x,[4]}, {cell,2,2,x,[3]}, {cell,3,3,x,[5]}, {cell,2,1,x,[2]}, {cell,3,1,x,[3]}, {cell,1,2,x,[2]}, {cell,1,3,x,[3]}, {cell,2,3,x,[4]}, {cell,3,2,x,[4]}, {cell,2,4,x,[5]}, {cell,3,4,x,[6]}, {cell,4,2,x,[5]}, {cell,4,3,x,[6]}]), ok. pretty_print_cell(x) -> " "; pretty_print_cell(C) -> integer_to_list(C). pretty_print_row([]) -> io:format("~n"), ok; pretty_print_row([Cell|Rest]) -> case (length(Rest) + 1) rem 3 == 0 of true -> io:format("|"); false -> ok end, io:format(" ~s ", [pretty_print_cell(Cell)]), pretty_print_row(Rest). pretty_print([]) -> ok; pretty_print([Row|Rest]) -> case (length(Rest) + 1) rem 3 == 0 of true -> io:format("~s~n", [lists:map(fun(_) -> "-" end, lists:seq(1, length(Row) * 3 + 2) )]); false -> ok end, pretty_print_row(Row), io:format("~n"), pretty_print(Rest). solve(Grid) -> Sudoku = compile_sudoku(Grid), format_solution(do_solve([], Sudoku)). test_solve() -> solve(?test_grid). %% tests test_all() -> %ok = test_row(), %ok = test_col(), %ok = test_box(), %ok = test_cell(), %ok = test_is_empty(), ok = test_calc_block(), ok = test_calc_possibles(), ok = test_compile_cell(), ok = test_compile_row(), ok = test_compile_sudoku(), ok = test_split_rows_and_extract_value(), ok = test_definites(), ok = test_collapse_possibles(), ok = test_keyfilter(), ok = test_do_solve(), ok.
_______________________________________________ coders mailing list coders@slug.org.au http://lists.slug.org.au/listinfo/coders