作为我的任务,我使用并发在Erlang中创建了一个数独求解器。我最初的想法是使用回溯算法,只要它做出选择就会产生新的线程。
然而,在花了一些时间和思考到项目后,我开始认为我想要解决这个问题的方式有点过于复杂。过去有没有人做过类似的事情?你会推荐一些能够更好地使用Erlang和并发的算法吗?
答案 0 :(得分:0)
如果你安装wx,只需运行sudoku:go()
。 https://github.com/erlang/otp/blob/86d1fb0865193cce4e308baa6472885a81033f10/lib/wx/examples/sudoku/sudoku.erl
答案 1 :(得分:0)
BackTracking算法并不适合使用并发。当然,始终可以并行生成多个进程,这些进程以不同的初始条件开始(要解决的第一个或第一个单元的所有可能值)。但我不认为这是一个真正的可靠应用。
更合适的算法是约束的传播。我们的想法是为每个细胞创建一个过程,每个细胞都知道20个连接的细胞"进程(同一列中的8个,同一行中的8个,同一个方块中的4个)。单元格的状态包含 - 至少 - 它可以采用的所有可能值。如果一个单元只剩下一个可能的值,则在初始化之后或在约束传播期间,它会向所有连接的单元发送一条消息{remove,Value}
,以通知它们从列表中删除该值。
这是一个真正的并发过程,但它有(至少)2个问题: - 了解何时找到解决方案或何时传播被卡住; - 这个算法一次性只能解决最简单的谜题。
还有一些其他规则可用于解决更复杂的难题。例如,寻找只剩下一种可能性的数字,寻找配对......但这些规则并不是很容易并行实现,而且我也不知道解决任何难题所需的一套规则。
注意在一般情况下,规则集不存在,因为拼图可能有多个解决方案,尽管我们可以在报纸上找到这些拼图。
我的想法是用搜索算法完成约束传播算法。一个新的过程,即控制器,负责: - 初始化拼图 - 选择最有希望的试验 - 要求对细胞过程进行试验, - 控制传播过程的结束, - 检查一下 - 解决方案 - >打印出来 - 死胡同 - >要求回到以前的状态,从列表中删除初始试用号,然后选择下一个最有希望的试验 - 要求存储当前结果状态并继续下一次试用
因此,单元格必须通过堆栈完成其状态,并且可以推送并弹出当前可能值列表。
最有希望的试验可以通过以下方式选择:找到剩余可能值较少的单元格,然后选择第一个单元格。
下一个问题是同步一切。第一个"简单"解决方案是使用超时。但与往常一样,超时很难定义,最后效率很低。我只是为了调试目的而保持超时,所以有一个相当大的值,因为它有一些风险,它在第一次尝试时不起作用:o)。
超时的替代方法是使用计数器。每次控制器发送需要同步的消息时,它都会递增其计数器。每当一个单元完成对需要同步的消息的处理时,它就会向控制器返回一个{ack_synchro,N}
消息,然后控制器将N减去其计数器。这样做,在约束传播期间,当一个单元只有一个剩余的可能值时,它可以在将{ack_synchro,-20}
发送到其连接的单元之前向控制器发送{remove,Value}
,以便控制器"知道"它必须等待20条消息。有了这个原则,就可以同步push
,pop
,{try,Value}
,{remove,Value}
消息的单元格活动。
我想它缺少很多细节,我不确定它会比命令式回溯更快,但它应该以合理的编码成本工作,并且它是并发的。
修改强>
我编写了这个提议,只用了2个测试用例,一个简单的谜题,一个复杂的测试,它运行正常。这是代码:
主要模块(实际上它在shell进程中运行)解决难题使用命令:sudo:start(file,Filename)
或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).
Cell服务器及其接口
-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}.
测试文件:
[
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
].
行动中:
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>
代码中没有任何评论,但它完全符合我在答案第一部分的建议。