Sudoku solver using a concurrent backtracking algorithm in Erlang - algorithm

As my assignment I'm creating a sudoku solver in Erlang using concurrency. My initial idea was to use backtracking algorithm which would spawn new threads whenever it's making a choice.
However, after putting some time and thought into the project I'm starting to think that the way I wanted to solve this is a bit too complicated. Has anyone done something similiar in the past? Would you recommend some different algorithm that would work better with Erlang and concurrency?

BackTracking algorithm is not really adapted to use concurrency. Of course, it is always possible to spawn several processes in parallel that start with different initial conditions (all possible values of the first or 2 first cells to solve). But I don't think this is a true concurent application.
A more suitable algorithm is the propagation of constraints. The idea is to create a process per cell, each cell knowing the 20 "connected cells" processes (8 in the same column, 8 in the same line, and 4 more in the same square). A cell has a state which contain - at least - all the possible values it can take. If a cell has only one single possible value remaining, after initialisation or during the propagation of constraint, it sends a message {remove,Value} to all its connected cells to inform them to remove the value from their list.
It is a true concurrent process, but it has (at least) 2 issues:
- knowing when a solution is found or when the propagation is stuck;
- Only the simplest puzzles will be solved by this algorithm in one shot.
There are some other rules that could be used to solve more complex puzzles. For example look for numbers that have only one remaining possibility, looking for pairs... But these rules are not really easy to implement in parallel, and I don't know the set of rules necessary to solve any puzzle.
Note In the general case the set of rule does not exist since a puzzle may have multiple solutions, although it is not the case for the puzzles we can find in newspapers.
My idea is to complete the constraint propagation algorithm with a search algorithm. A new process, the controller, is in charge to:
- initialize the puzzle
- select the most promissing trial
- ask to make the trial to the cell processes,
- control the end of the propagation process,
- check if it is
- a solution -> print it
- a dead end -> ask to come back to previous state, remove the initial trial number from the list, and select the next most promising trial
- ask to store the current result state and continue to the next trial
So the cells have to complete their state with a stack where they can push and pop their current list of possible value.
The most promising trial can be the selected this way: find the cell with the less remaining possible values, and take the first one.
The next problem is to synchronize everything. The first "simple" solution is to use a timeout. But as always, a timeout is very difficult to define, and at the end very inefficient. I would keep a timeout only for debug purpose, so with a rather big value, because there are some risks that it doesn't work at the first attempt :o).
An alternative to the timeout is to use a counter. Each time the controller sends a message that need synchronization, it increments its counter. Each time a cell has complete the handling of a message that needs synchronization, it returns an {ack_synchro,N} message to the controller, which in turn subtracts N to its counter. Doing this, during the propagation of constraint, when a cell has only one remaining possible value, it can send an {ack_synchro,-20} to the controller before sending the {remove,Value} to its connected cells so the controller "knows" it has to wait for 20 messages more. With this principle, it is possible to synchronize the activity of the cells for the push, pop, {try,Value}, {remove,Value} messages.
I guess it is missing a lot of details, and I am not sure that it will be faster than the imperative backtracking, but it should work at a reasonable coding cost, and it is concurrent.
Edit
I coded this proposal, it is tested with only 2 test cases, one simple puzzle, one complex, and it works fine. Here is the code:
The main module (actually it runs in the shell process) to solve a puzzle use the command: sudo:start(file,Filename) or sudo:start(table,InitialList):
-module (sudo).
-export ([start/2]).
start(file,File) ->
{ok,[Table]} = file:consult(File),
start(table,Table);
start(table,Table) ->
init_cells(),
solve(Table),
print_result(),
stop().
stop() ->
lists:foreach(fun(X) -> cell:stop(X) end ,cells()).
cells() ->
[a1,a2,a3,a4,a5,a6,a7,a8,a9,
b1,b2,b3,b4,b5,b6,b7,b8,b9,
c1,c2,c3,c4,c5,c6,c7,c8,c9,
d1,d2,d3,d4,d5,d6,d7,d8,d9,
e1,e2,e3,e4,e5,e6,e7,e8,e9,
f1,f2,f3,f4,f5,f6,f7,f8,f9,
g1,g2,g3,g4,g5,g6,g7,g8,g9,
h1,h2,h3,h4,h5,h6,h7,h8,h9,
i1,i2,i3,i4,i5,i6,i7,i8,i9].
init_cells() ->
lists:foreach(fun(X) -> cell:start_link(X) end ,cells()),
Zip = lists:zip(cells(),lists:seq(0,80)),
lists:foreach(fun({N,P}) -> cell:init(N,neighbors(P,Zip)) end, Zip),
wait(81).
neighbors(P,Zip) ->
Line = fun(X) -> X div 9 end,
Col = fun(X) -> X rem 9 end,
Square = fun(X) -> {Line(X) div 3, Col(X) div 3} end,
Linked = fun(X) -> (X =/= P) andalso
( (Line(X) == Line(P)) orelse
(Col(X) == Col(P)) orelse
(Square(X) == Square(P))) end,
[Name || {Name,Pos} <- Zip, Linked(Pos)].
solve(Table) ->
Zip = lists:zip(cells(),Table),
test(Zip),
do_solve(is_solved()).
do_solve({true,_,_,_}) ->
done;
do_solve({false,Name,Value,_}) ->
push(),
test(Name,Value),
do_solve(is_solved());
do_solve(error) ->
pop(),
{false,Name,Value,_} = is_solved(),
remove(Name,Value),
do_solve(is_solved()).
print_result() ->
R = get_cells(),
F = fun({_,[I]},Acc) ->
case Acc of
_ when (Acc rem 27) == 0 -> io:format("~n~n ~p",[I]);
_ when (Acc rem 9) == 0 -> io:format("~n ~p",[I]);
_ when (Acc rem 3) == 0 -> io:format(" ~p",[I]);
_ -> io:format(" ~p",[I])
end,
Acc+1
end,
lists:foldl(F,0,R),
io:format("~n").
test(List) ->
F = fun({_,0},Acc) ->
Acc;
({Name,Value},Acc) ->
cell:test(Name,Value),
Acc+1
end,
NbMessages = lists:foldl(F,0,List),
wait(NbMessages).
test(_,0) -> ok;
test(Name,Value) ->
cell:test(Name,Value),
wait(1).
remove(Name,Value) ->
cell:remove(Name,Value),
wait(1).
push() ->
lists:foreach(fun(X) -> cell:push(X) end, cells()),
wait(81).
pop() ->
lists:foreach(fun(X) -> cell:pop(X) end, cells()),
wait(81).
wait(0) ->
done;
wait(NbMessages) ->
receive
{done,N} -> wait(NbMessages-N);
{add,N} -> wait(NbMessages+N)
after 2000 ->
error
end.
get_cells() ->
F = fun(X) -> cell:get_val(X), receive {possible,M} -> M end, {X,M} end,
[F(X) || X <- cells()].
is_solved() ->
State = get_cells(),
F = fun({_,[]},_) -> error;
(_,error) -> error;
({Name,List},Acc = {_,_CurName,_CurVal,Length}) ->
NL = length(List),
case (NL > 1) andalso( NL < Length) of
true -> {false,Name,hd(List),NL};
false -> Acc
end
end,
lists:foldl(F,{true,none,0,10},State).
The Cell server and its interfaces
-module (cell).
-export ([start_link/1,init/2,push/1,pop/1,test/2,remove/2,stop/1,get_val/1]).
% Interfaces
start_link(Name) ->
Pid = spawn_link(fun() -> init() end),
register(Name,Pid).
init(Name,List) ->
Name ! {init,self(),List}.
push(Name) ->
Name ! push.
pop(Name) ->
Name ! pop.
test(Name,Value) ->
Name ! {test,Value}.
remove(Name,Value) ->
Name ! {remove,Value}.
get_val(Name) ->
Name ! get.
stop(Name) ->
Name ! stop.
% private
init() ->
loop(none,[],[],[]).
loop(Report,Possible,Stack,Neighbors) ->
receive
{init,R,List} ->
R ! {done,1},
loop(R,lists:seq(1,9),[],List);
push ->
Report ! {done,1},
loop(Report,Possible,[Possible|Stack],Neighbors);
pop ->
Report ! {done,1},
loop(Report,hd(Stack),tl(Stack),Neighbors);
{test,Value} ->
NewP = test(Report,Possible,Neighbors,Value),
loop(Report,NewP,Stack,Neighbors);
{remove,Value} ->
NewP = remove(Report,Possible,Neighbors,Value),
loop(Report,NewP,Stack,Neighbors);
get ->
Report ! {possible,Possible},
loop(Report,Possible,Stack,Neighbors);
stop ->
ok
end.
test(Report,Possible,Neighbors,Value) ->
true = lists:member(Value,Possible),
Report ! {add,20},
lists:foreach(fun(X) -> remove(X,Value) end, Neighbors),
Report ! {done,1},
[Value].
remove(Report,Possible,Neighbors,Value) ->
case Possible of
[Value,B] ->
remove(Report,B,Neighbors);
[A,Value] ->
remove(Report,A,Neighbors);
_ ->
Report ! {done,1}
end,
lists:delete(Value,Possible).
remove(Report,Value,Neighbors) ->
Report ! {add,20},
lists:foreach(fun(X) -> remove(X,Value) end, Neighbors),
Report ! {done,1}.
a test file:
[
0,0,0,4,0,6,9,0,0,
0,0,0,0,0,0,1,0,0,
0,0,0,3,0,0,0,7,2,
0,0,5,6,4,0,0,0,0,
0,2,3,0,8,0,0,0,1,
0,8,0,0,0,2,4,0,5,
0,7,8,0,0,0,5,0,0,
6,0,1,0,0,7,2,0,0,
0,0,2,0,0,9,0,0,0
].
in action:
1> c(sudo).
{ok,sudo}
2> c(cell).
{ok,cell}
3> timer:tc(sudo,start,[file,"test_hard.txt"]).
1 3 7 4 2 6 9 5 8
2 6 9 7 5 8 1 4 3
8 5 4 3 9 1 6 7 2
7 1 5 6 4 3 8 2 9
4 2 3 9 8 5 7 6 1
9 8 6 1 7 2 4 3 5
3 7 8 2 1 4 5 9 6
6 9 1 5 3 7 2 8 4
5 4 2 8 6 9 3 1 7
{16000,ok}
4>
No comments in the code, but it does exactly what I propose in the first part of the answer.

if you install wx , just run sudoku:go(). https://github.com/erlang/otp/blob/86d1fb0865193cce4e308baa6472885a81033f10/lib/wx/examples/sudoku/sudoku.erl
or see this project:
https://github.com/apauley/sudoku-in-erlang

Related

Best way to ensure something happens more than once in an overall sequence, but only once for each subsequence

I have a scenario where a starting action branches out and triggers multiple actions. such as :
A -> B -> D -> F
-> E -> H
-> C -> E -> H
-> F -> G
B and C both started from A, and "DEEF" started from B & C, and so on.
Today, I only allow "E" to run once in the overall sequence. However there is now a requirement to allow "E" to run more than once in the overall sequence, but only for unique originators (so as to avoid any looping). I.E. "E" (or "F" or "G") in above example, can run once in the sequence C -> E -> H and once in A -> B -> E --> H but never A -> B -> E -> H -> E. E can also only always emit H, B can only emit D and E etc. so that set is immutable.
Hopefully I was able to explain the problem.
My initial thought is to have each action output a nonce value - and then store if an action has already run for a nonce value (originator) then it can't run again for that same originator.
In the above example A would create a nonce value "foobar". B and C would not have run for the nonce "foobar" in the flow yet, so they would run the first time.
B would output nonce "boofar" and C would output nonce "oofbar". The next set of actions would check, if they have run for either of these nonces - "E" in particular would now be able to run for each nonce, instead of running only once for the sequence as in the current single per sequence lookup.
I think this might work, but wondering if I'm missing anything. Would appreciate more interesting thoughts.
EDIT: Saw Thomas's comment below - nonce alone would not help me solve the loop issue. I might consider adding a nonce vector that keeps adding ie. foobar.boofar.oofbar and then check that a module ran once for each nonce vector?

how to do this without "match ... with"

For a better understanding, I try to rewrite this code without "... with" but I struggle:
let rec blast list =
list with
| x :: y :: [] -> x
| hd :: tl -> blast tl
| _ -> fail "not enough";;
Any ideas? Thanks!
Sure we could "manually" try to match each pattern.
The first applies when there is exactly 2 elements, the second when there is more than 1 (but not 2) and the third in all other cases (0 elements).
The second case can be folded into the last case (As when there is 1 element, the recursive call just fails).
So now we have 3 cases: exactly 2, more than 2 and less than 2.
Perfect for List.compare_length_with: 'a list -> int -> int:
let rec beforelast list =
let cmp = List.compare_length_with list 2 in
if cmp = 0 then (* Exactly 2 elements *)
List.hd list
else if cmp > 0 then (* More than 2 elements *)
beforelast (List.tl list)
else (* 1 or 0 elements *)
failwith "not enough"
Though note that you are still pattern matching under the hood, because that's what OCaml data types are made for. For example, List.hd might be implemented like:
let hd = function
| head :: _ -> head
| [] -> raise (Failure "hd")
So the match ... with way should be the way that leads to a better understanding.

Algorithm for condensing/consolidating number combinations

Using a horse race betting scenario, say I have a number of separate bets for predicting the first 4 finishers of the race (superfecta).
The bets are as follows...
1/2/3/4
1/2/3/5
1/2/4/3
1/2/4/5
1/2/5/3
1/2/5/4
What I want to do is combine or condense these separate combinations as much as possible. For the bets above, they can be all condensed into 1 line...
1/2/3,4,5/3,4,5
But if I removed the last bet from the list: 1/2/5/4 ...
1/2/3/4
1/2/3/5
1/2/4/3
1/2/4/5
1/2/5/3
The condensed bets would instead have to now be 2 lines:
1/2/3,4/3,4,5
1/2/5/3
What would algorithm look like for this?
I find it easiest to think about this kind of thing with pretty pictures. How about we try to build some graphs?
First example
1/2/3/4
1/2/3/5
1/2/4/3
1/2/4/5
1/2/5/3
1/2/5/4
...could look like this, in graph form:
Each path from top to bottom (e.g. 1->2->4->3) corresponds to a row in your initial format.
If we start with that graph, then (perhaps) we can run a little algorithm on the graph that will simplify it in the way you're looking for. Here's what we'll try:
Start at the top of the graph, and move down level by level. (The first level contains only the blue node 1.)
For each node in the current level, count the number of children. If there is only one child, skip the node. (Since blue node 1 only has one child, we'll skip to green node 2.)
For each of the multiple children, construct a set that contains that child and its grandchildren. (The red node 3 has a set {3,4,5}, red 4 has a set {3,4,5}, and red 5 has a set {3,4,5}.)
If any of these sets are identical, replace the associated children/grandchildren with a single node containing the children, pointing to a grandchild that contains the set. (Since all three red nodes have identical sets, they all get replaced.)
Second example
1/2/3/4
1/2/3/5
1/2/4/3
1/2/4/5
1/2/5/3
...could look like this, in graph form:
The red nodes 3 and 4 have identical sets (i.e. {3,4,5}), so they get replaced. Red node 5 doesn't have the same set as red nodes 3 and 4, so we leave it alone.
As before, each path through the simplified tree represents one row of your output.
(I haven't covered what happens if you replace children/grandchildren when there are great-grandchildren. It could be that you should actually start at the bottom row and work your way upwards.)
by F#
open System
open System.Collections.Generic
let data =
[|
"1/2/3/4"
"1/2/3/5"
"1/2/4/3"
"1/2/4/5"
"1/2/5/3"
"1/2/5/4"
|]
let conv (xs:string []) =
let xs = xs |> Array.map (fun x -> x.Split('/'))
let len = xs.[0] |> Array.length
let sa = Array.init len (fun _ -> new SortedSet<string>())
xs |> Array.iter (fun xa -> xa |> Array.iteri (fun i x -> sa.[i].Add(x) |>ignore))
String.Join("/", sa |> Array.map (fun x -> if Seq.length x = 1 then Seq.head x else String.Join(",", x |> Seq.toArray)))
let _ =
conv data |> printfn "%s"
//result:1/2/3,4,5/3,4,5
//use 0-3 and 4 element of data
[|data.[0..3]; data.[4..4] |]
|> Array.map (fun x -> conv x)
|> Array.iter (printfn "%s")
(* result:
1/2/3,4/3,4,5
1/2/5/3
*)

Transform one word into another by changing, inserting, or deleting one character at a time

Given a finite dictionary of words and a start-end pair (e.g. "hands" and "feet" in the example below), find the shortest sequence of words such that any word in the sequence can be formed from either of its neighbors by either 1) inserting one character, 2) deleting one character, or 3) changing one character.
hands ->
hand ->
and ->
end ->
fend ->
feed ->
feet
For those who may be wondering - this is not a homework problem that was assigned to me or a question I was asked in an interview; it is simply a problem that interests me.
I am looking for a one- or two- sentence "top down view" of what approach you would take -- and for the daring, a working implementation in any language.
Instead of turning the dictionary into a full graph, use something with a little less structure:
For each word in the dictionary, you get a shortened_word by deleting character number i for each i in len(word). Map the pair (shortened_word, i) to a list of all the words.
This helps looking up all words with one replaced letter (because they must be in the same (shortened_word, i) bin for some i, and words with one more letter (because they must be in some (word, i) bin for some i.
The Python code:
from collections import defaultdict, deque
from itertools import chain
def shortened_words(word):
for i in range(len(word)):
yield word[:i] + word[i + 1:], i
def prepare_graph(d):
g = defaultdict(list)
for word in d:
for short in shortened_words(word):
g[short].append(word)
return g
def walk_graph(g, d, start, end):
todo = deque([start])
seen = {start: None}
while todo:
word = todo.popleft()
if word == end: # end is reachable
break
same_length = chain(*(g[short] for short in shortened_words(word)))
one_longer = chain(*(g[word, i] for i in range(len(word) + 1)))
one_shorter = (w for w, i in shortened_words(word) if w in d)
for next_word in chain(same_length, one_longer, one_shorter):
if next_word not in seen:
seen[next_word] = word
todo.append(next_word)
else: # no break, i.e. not reachable
return None # not reachable
path = [end]
while path[-1] != start:
path.append(seen[path[-1]])
return path[::-1]
And the usage:
dictionary = ispell_dict # list of 47158 words
graph = prepare_graph(dictionary)
print(" -> ".join(walk_graph(graph, dictionary, "hands", "feet")))
print(" -> ".join(walk_graph(graph, dictionary, "brain", "game")))
Output:
hands -> bands -> bends -> bents -> beets -> beet -> feet
brain -> drain -> drawn -> dawn -> damn -> dame -> game
A word about speed: building the 'graph helper' is fast (1 second), but hands -> feet takes 14 seconds, and brain --> game takes 7 seconds.
Edit: If you need more speed, you can try using a graph or network library. Or you actually build the full graph (slow) and then find paths much faster. This mostly consists of moving the look-up of edges from the walking function to the graph-building function:
def prepare_graph(d):
g = defaultdict(list)
for word in d:
for short in shortened_words(word):
g[short].append(word)
next_words = {}
for word in d:
same_length = chain(*(g[short] for short in shortened_words(word)))
one_longer = chain(*(g[word, i] for i in range(len(word) + 1)))
one_shorter = (w for w, i in shortened_words(word) if w in d)
next_words[word] = set(chain(same_length, one_longer, one_shorter))
next_words[word].remove(word)
return next_words
def walk_graph(g, start, end):
todo = deque([start])
seen = {start: None}
while todo:
word = todo.popleft()
if word == end: # end is reachable
break
for next_word in g[word]:
if next_word not in seen:
seen[next_word] = word
todo.append(next_word)
else: # no break, i.e. not reachable
return None # not reachable
path = [end]
while path[-1] != start:
path.append(seen[path[-1]])
return path[::-1]
Usage: Build the graph first (slow, all timings on some i5 laptop, YMMV).
dictionary = ispell_dict # list of 47158 words
graph = prepare_graph(dictionary) # more than 6 minutes!
Now find the paths (much faster than before, times without printing):
print(" -> ".join(walk_graph(graph, "hands", "feet"))) # 10 ms
print(" -> ".join(walk_graph(graph, "brain", "game"))) # 6 ms
print(" -> ".join(walk_graph(graph, "tampering", "crunchier"))) # 25 ms
Output:
hands -> lands -> lends -> lens -> lees -> fees -> feet
brain -> drain -> drawn -> dawn -> damn -> dame -> game
tampering -> tapering -> capering -> catering -> watering -> wavering -> havering -> hovering -> lovering -> levering -> leering -> peering -> peeping -> seeping -> seeing -> sewing -> swing -> swings -> sings -> sines -> pines -> panes -> paces -> peaces -> peaches -> beaches -> benches -> bunches -> brunches -> crunches -> cruncher -> crunchier
A naive approach could be to turn the dictionary into a graph, with the words as nodes and the edges connecting "neighbors" (i.e. words that can be turned into one another via one operation). Then you could use a shortest-path algorithm to find the distance between word A and word B.
The hard part about this approach would be finding a way to efficiently turn the dictionary into a graph.
Quick answer. You can compute for the Levenshtein distance, the "common" edit distance in most dynamic programming texts, and, from the computation table generated, try to build that path.
From the Wikipedia link:
d[i, j] := minimum
(
d[i-1, j] + 1, // a deletion
d[i, j-1] + 1, // an insertion
d[i-1, j-1] + 1 // a substitution
)
You can take note of when these happens in your code (maybe, in some auxiliary table) and, surely, it'd be easy reconstructing a solution path from there.

Forcing "main line" nodes into a straight line in Graphviz (or alternatives)

I'm trying to use Graphviz dot (but am willing to use something else) to generate a graph with a long "main line" of nodes, and many small branches. I'd like the main line to be straight from left to right, with the small branches above or below it. However, Graphviz "balances" the two branches, so I end up with a crooked graph.
To illustrate, here's a sketch similar to what I currently get:
And this is what I actually want:
Is there any way to force or encourage Graphviz to generate a graph like the second one? I may be able to use "dummy" second branches to have it do a 3-way layout, and then hide/delete the dummies afterward, but if there's a better option that would be preferable.
Here is a solution using the weight attribute of edges:
digraph G {
rankdir="LR";
node[width=0.15, height=0.15, shape=point];
edge[weight=2, arrowhead=none];
1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8;
edge[weight=1];
2 -> 9 -> 10 ;
5-> 11 -> 12;
}
Result:
A second solution using the group attribute of nodes - again a directed graph:
From the graphviz dot reference:
If the end points of an edge belong to
the same group, i.e., have the same
group attribute, parameters are set to
avoid crossings and keep the edges
straight.
So here we go:
digraph g{
rankdir="LR";
node[width=0.15, height=0.15, shape=point, group=main];
edge[arrowhead=none];
1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8;
node[group=branches];
2 -> 9 -> 10;
5 -> 11 -> 12;
}
Output is exactly the same as in my first answer.

Resources