在Erlang中使用并发回溯算法的数独求解器

时间:2017-01-14 13:38:23

标签: algorithm concurrency erlang backtracking sudoku

作为我的任务,我使用并发在Erlang中创建了一个数独求解器。我最初的想法是使用回溯算法,只要它做出选择就会产生新的线程。

然而,在花了一些时间和思考到项目后,我开始认为我想要解决这个问题的方式有点过于复杂。过去有没有人做过类似的事情?你会推荐一些能够更好地使用Erlang和并发的算法吗?

2 个答案:

答案 0 :(得分:0)

答案 1 :(得分:0)

BackTracking算法并不适合使用并发。当然,始终可以并行生成多个进程,这些进程以不同的初始条件开始(要解决的第一个或第一个单元的所有可能值)。但我不认为这是一个真正的可靠应用。

更合适的算法是约束的传播。我们的想法是为每个细胞创建一个过程,每个细胞都知道20个连接的细胞"进程(同一列中的8个,同一行中的8个,同一个方块中的4个)。单元格的状态包含 - 至少 - 它可以采用的所有可能值。如果一个单元只剩下一个可能的值,则在初始化之后或在约束传播期间,它会向所有连接的单元发送一条消息{remove,Value},以通知它们从列表中删除该值。

这是一个真正的并发过程,但它有(至少)2个问题:   - 了解何时找到解决方案或何时传播被卡住;   - 这个算法一次性只能解决最简单的谜题。

还有一些其他规则可用于解决更复杂的难题。例如,寻找只剩下一种可能性的数字,寻找配对......但这些规则并不是很容易并行实现,而且我也不知道解决任何难题所需的一套规则。

注意在一般情况下,规则集不存在,因为拼图可能有多个解决方案,尽管我们可以在报纸上找到这些拼图。

我的想法是用搜索算法完成约束传播算法。一个新的过程,即控制器,负责:   - 初始化拼图   - 选择最有希望的试验   - 要求对细胞过程进行试验,   - 控制传播过程的结束,   - 检查一下      - 解决方案 - >打印出来      - 死胡同 - >要求回到以前的状态,从列表中删除初始试用号,然后选择下一个最有希望的试验      - 要求存储当前结果状态并继续下一次试用

因此,单元格必须通过堆栈完成其状态,并且可以推送并弹出当前可能值列表。

最有希望的试验可以通过以下方式选择:找到剩余可能值较少的单元格,然后选择第一个单元格。

下一个问题是同步一切。第一个"简单"解决方案是使用超时。但与往常一样,超时很难定义,最后效率很低。我只是为了调试目的而保持超时,所以有一个相当大的值,因为它有一些风险,它在第一次尝试时不起作用:o)。

超时的替代方法是使用计数器。每次控制器发送需要同步的消息时,它都会递增其计数器。每当一个单元完成对需要同步的消息的处理时,它就会向控制器返回一个{ack_synchro,N}消息,然后控制器将N减去其计数器。这样做,在约束传播期间,当一个单元只有一个剩余的可能值时,它可以在将{ack_synchro,-20}发送到其连接的单元之前向控制器发送{remove,Value},以便控制器"知道"它必须等待20条消息。有了这个原则,就可以同步pushpop{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>

代码中没有任何评论,但它完全符合我在答案第一部分的建议。