7

私はすでに Prolog で動作する一般化された口頭算術ソルバーを作成しましたが、遅すぎます。SEND + MORE = MONE Y という単純な式を実行するだけで 8 分かかります。

/* verbalArithmetic(List,Word1,Word2,Word3) where List is the list of all 
   possible letters in the words. The SEND+MORE = MONEY expression would then
   be represented as
    verbalArithmetic([S,E,N,D,M,O,R,Y],[S,E,N,D],[M,O,R,E],[M,O,N,E,Y]). */

validDigit(X) :- member(X,[0,1,2,3,4,5,6,7,8,9]).
validStart(X) :- member(X,[1,2,3,4,5,6,7,8,9]).
assign([H|[]]) :- validDigit(H).         
assign([H|Tail]) :- validDigit(H), assign(Tail), fd_all_different([H|Tail]).

findTail(List,H,T) :- append(H,[T],List).

convert([T],T) :- validDigit(T).
convert(List,Num) :- findTail(List,H,T), convert(H,HDigit), Num is (HDigit*10+T).

verbalArithmetic(WordList,[H1|Tail1],[H2|Tail2],Word3) :- 
    validStart(H1), validStart(H2), assign(WordList), 
    convert([H1|Tail1],Num1),convert([H2|Tail2],Num2), convert(Word3,Num3), 
    Sum is Num1+Num2, Num3 = Sum.
4

6 に答える 6

5

たとえば、SWI-Prolog で有限領域制約を使用することを検討してください。

:- use_module(library(clpfd)).

puzzle([S,E,N,D] + [M,O,R,E] = [M,O,N,E,Y]) :-
        Vars = [S,E,N,D,M,O,R,Y],
        Vars ins 0..9,
        all_different(Vars),
                  S*1000 + E*100 + N*10 + D +
                  M*1000 + O*100 + R*10 + E #=
        M*10000 + O*1000 + N*100 + E*10 + Y,
        M #\= 0, S #\= 0.

クエリの例:

?- time((puzzle(As+Bs=Cs), label(As))).
% 5,803 inferences, 0.002 CPU in 0.002 seconds (98% CPU, 3553582 Lips)
As = [9, 5, 6, 7],
Bs = [1, 0, 8, 5],
Cs = [1, 0, 6, 5, 2] ;
% 1,411 inferences, 0.001 CPU in 0.001 seconds (97% CPU, 2093472 Lips)
false.
于 2012-06-07T09:52:22.203 に答える
5

ここでのパフォーマンスの低下は、実行可能かどうかを確認する前に、すべての可能な文字割り当てを形成するためです。

私のアドバイスは、「早く失敗し、頻繁に失敗すること」です。つまり、失敗のチェックをできるだけ早く割り当てステップにプッシュし、検索ツリーを刈り込みます。

Klas Lindbäck がいくつかの良い提案をしています。一般化として、2 つの数値を加算する場合、キャリーは各場所で最大 1 つです。したがって、左から右への文字への個別の数字の割り当ては、最も右の場所でまだ未決定のキャリーの可能性を考慮してチェックできます。(もちろん、最後の「ユニット」の場所では、キャリーはありません。)

mat が示唆するように (そしてfd_all_different/1で既にブローチした)、制約ロジックが非常に便利な理由です。


追加:補助述語omit/3 を 1 つだけ使用して、制約ロジックを使用しない Prolog ソリューションを次に示します。

omit(H,[H|T],T).
omit(X,[H|T],[H|Y]) :- omit(X,T,Y).

リストからアイテムを選択し、そのアイテムなしで短縮されたリストを生成します。

次に、合計を左から右に評価して検索するsendMoreMoney/3のコードを次に示します。

sendMoreMoney([S,E,N,D],[M,O,R,E],[M,O,N,E,Y]) :-
    M = 1,
    omit(S,[2,3,4,5,6,7,8,9],PoolO),
    (CarryS = 0 ; CarryS = 1),
    %% CarryS + S + M =      M*10 + O
    O is (CarryS + S + M) - (M*10), 
    omit(O,[0|PoolO],PoolE),
    omit(E,PoolE,PoolN),
    (CarryE = 0 ; CarryE = 1),
    %% CarryE + E + O = CarryS*10 + N
    N is (CarryE + E + O) - (CarryS*10),
    omit(N,PoolN,PoolR),
    (CarryN = 0 ; CarryN = 1),
    %% CarryN + N + R = CarryE*10 + E
    R is (CarryE*10 + E) - (CarryN + N),
    omit(R,PoolR,PoolD),
    omit(D,PoolD,PoolY),
    %%          D + E = CarryN*10 + Y
    Y is (D + E) - (CarryN*10),
    omit(Y,PoolY,_).

M は左端の桁の合計からゼロ以外の桁上げ、つまり 1 でなければならず、S はその他のゼロ以外の桁でなければならないことを確認することから始めます。コメントは、追加の文字が、既に行われた選択に基づいて決定論的に値を割り当てられる可能性がある手順を示しています。


追加 (2):これは、2 つの被加数の「一般的な」暗号ソルバーであり、同じ長さ/「桁数」である必要はありません。length/2のコードはかなり一般的な組み込み述語として省略されており、Will Ness の提案を取り入れて、omit/3の呼び出しは SWI-Prolog ユーザーの便宜のためにselect/3に置き換えられています。

Amziでこれをテストしました!Cryptarithms.comの英字の例を使用した SWI-Prologには、2 つの加数が含まれており、それぞれに固有の解があります。また、適切なバックトラッキングをテストするために、I + AM = BEN という 12 のソリューションを使用して例を作成しました。

solveCryptarithm([H1|T1],[H2|T2],Sum) :-
    operandAlign([H1|T1],[H2|T2],Sum,AddTop,AddPad,Carry,TSum,Pool),
    solveCryptarithmAux(H1,H2,AddTop,AddPad,Carry,TSum,Pool).

operandAlign(Add1,Add2,Sum,AddTop,AddPad,Carry,TSum,Pool) :-
    operandSwapPad(Add1,Add2,Length,AddTop,AddPad),
    length(Sum,Size),
    (   Size = Length
     -> ( Carry = 0, Sum = TSum , Pool = [1|Peel] )
     ;  ( Size is Length+1, Carry = 1, Sum = [Carry|TSum], Pool = Peel )
    ),
    Peel = [2,3,4,5,6,7,8,9,0].

operandSwapPad(List1,List2,Length,Longer,Padded) :-
    length(List1,Length1),
    length(List2,Length2),
    (   Length1 >= Length2
     -> ( Length = Length1, Longer = List1, Shorter = List2, Pad is Length1 - Length2 )
     ;  ( Length = Length2, Longer = List2, Shorter = List1, Pad is Length2 - Length1 )
    ),
    zeroPad(Shorter,Pad,Padded).

zeroPad(L,0,L).
zeroPad(L,K,P) :-
    K > 0,
    M is K-1,
    zeroPad([0|L],M,P).

solveCryptarithmAux(_,_,[],[],0,[],_).
solveCryptarithmAux(NZ1,NZ2,[H1|T1],[H2|T2],CarryOut,[H3|T3],Pool) :-
    ( CarryIn = 0 ; CarryIn = 1 ),   /* anticipatory carry */
    (   var(H1)
     -> select(H1,Pool,P_ol)
     ;  Pool = P_ol
    ),
    (   var(H2)
     -> select(H2,P_ol,P__l)
     ;  P_ol = P__l
    ),
    (   var(H3)
     -> ( H3 is H1 + H2 + CarryIn - 10*CarryOut, select(H3,P__l,P___) )
     ;  ( H3 is H1 + H2 + CarryIn - 10*CarryOut, P__l = P___ )
    ),
    NZ1 \== 0,
    NZ2 \== 0,
    solveCryptarithmAux(NZ1,NZ2,T1,T2,CarryIn,T3,P___).

これは、左から右への検索/評価の利点が「一般化された」ソルバーで達成できることを示していると思います。以前の「調整された」コードと比較して、推論の数が約 2 倍増加します。

于 2012-06-13T00:09:58.167 に答える
3

注: この回答では、試行する必要がある組み合わせの数を減らすためのアルゴリズムについて説明しています。私は Prolog を知らないので、コード スニペットを提供することはできません。

ブルート フォース ソリューションを高速化する秘訣はショートカットです。無効な組み合わせの範囲を特定できれば、組み合わせの数を大幅に減らすことができます。

例を手に取ってください。人間がそれを解くと、彼女はすぐに、MONEY が 5 桁であるのに対し、SEND と MORE は 4 桁しかないことに気付きます。したがって、MONEY の M は桁 1 でなければなりません。組み合わせの 90% がなくなりました!

コンピューターのアルゴリズムを構築するとき、最初に可能なすべての入力に適用されるショートカットを使用しようとします。必要なパフォーマンスが得られない場合は、入力の特定の組み合わせにのみ適用されるショートカットを検討し始めます。したがって、ここでは M=1 ショートカットを残します。

代わりに、最後の桁に焦点を当てます。(D+E) mod 10 = Y であることがわかっています。これは、試行する組み合わせの数を 90% 削減したことになります。

そのステップは、実行を 1 分足らずで行う必要があります。

それでも不十分な場合はどうすればよいでしょうか。次のステップ: 最後から 2 番目の数字を見てください。(N+R+D+E からのキャリー) mod 10 = E.

最後の桁のすべての有効な組み合わせをテストしているため、各テストでキャリーが 0 か 1 かがわかります。テストする組み合わせの数をさらに減らす (コードの) 複雑な点は、重複が発生することです。 (文字は、別の文字に既に割り当てられている番号にマップされます)。重複に遭遇すると、チェーンをさらに下ることなく次の組み合わせに進むことができます。

任務頑張ってください!

于 2012-06-07T07:24:43.483 に答える
2

これが私の見解です。、および を使用しますmapfoldl/5

:- meta_predicate mapfoldl(4,?,?,?,?).
mapfoldl(P_4,Xs,Zs, S0,S) :-
   list_mapfoldl_(Xs,Zs, S0,S, P_4).

:- meta_predicate list_mapfoldl_(?,?,?,?,4).
list_mapfoldl_([],[], S,S, _).
list_mapfoldl_([X|Xs],[Y|Ys], S0,S, P_4) :-
   call(P_4,X,Y,S0,S1),
   list_mapfoldl_(Xs,Ys, S1,S, P_4).

mapfoldl/5うまく活用して口頭算術をやってみましょう!

:- use_module(library(clpfd)).
:- use_module(library(lambda)).

digits_number(Ds,Z) :-
   Ds = [D0|_],
   Ds ins 0..9,
   D0 #\= 0,           % most-significant digit must not equal 0
   reverse(Ds,Rs),
   length(Ds,N),
   numlist(1,N,Es),    % exponents (+1)
   maplist(\E1^V^(V is 10**(E1-1)),Es,Ps),
   scalar_product(Ps,Rs,#=,Z).

list([]) --> [].
list([E|Es]) --> [E], list(Es).

cryptarithexpr_value([V|Vs],X) -->
   { digits_number([V|Vs],X) },
   list([V|Vs]).
cryptarithexpr_value(T0,T) -->
   { functor(T0,F,A)  },
   { dif(F-A,'.'-2)   },
   { T0 =.. [F|Args0] },
   mapfoldl(cryptarithexpr_value,Args0,Args),
   { T  =.. [F|Args] }.

crypt_arith_(Expr,Zs) :-
   phrase(cryptarithexpr_value(Expr,Goal),Zs0),
   (  member(Z,Zs0), \+var(Z)
   -> throw(error(uninstantiation_error(Expr),crypt_arith_/2)) 
   ;  true 
   ),
   sort(Zs0,Zs),
   all_different(Zs),
   call(Goal).

見つかったすべてのソリューションをダンプするための迅速で汚いハック:

solve_n_dump(Opts,Eq) :-
   (  crypt_arith_(Eq,Zs),
      labeling(Opts,Zs),
      format('Eq = (~q), Zs = ~q.~n',[Eq,Zs]),
      false
   ;  true
   ).

solve_n_dump(Eq) :- solve_n_dump([],Eq).

試してみよう!

?- solve_n_dump([S,E,N,D]+[M,O,R,E] #= [M,O,N,E,Y])。
Eq = ([9,5,6,7]+[1,0,8,5]#=[1,0,6,5,2])、Zs = [9,5,6,7,1, 0,8,2]。
真実。

?- solve_n_dump([C,R,O,S,S]+[R,O,A,D,S] #= [D,A,N,G,E,R])。
Eq = ([9,6,2,3,3]+[6,2,5,1,3]#=[1,5,8,7,4,6​​])、Zs = [9,6, 2,3,5,1,8,7,4]。
真実。

?- solve_n_dump([F,O,R,T,Y]+[T,E,N]+[T,E,N] #= [S,I,X,T,Y])。
Eq = ([2,9,7,8,6]+[8,5,0]+[8,5,0]#=[3,1,4,8,6])、Zs = [2, 9,7,8,6,5,0,3,1,4]。
真実。

?- solve_n_dump([E,A,U]*[E,A,U] #= [O,C,E,A,N])。
式 = ([2,0,3]*[2,0,3]#=[4,1,2,0,9])、Zs = [2,0,3,4,1,9]。
真実。

?- solve_n_dump([N、U、M、B、E、R] #= 3*[P、R、I、M、E])。
% 以下と同じ: [N,U,M,B,E,R] #= [P,R,I,M,E]+[P,R,I,M,E]+[P,R,I,自分]
Eq = (3*[5,4,3,2,8]#=[1,6,2,9,8,4])、Zs = [5,4,3,2,8,1,6, 9]。
真実。

?- solve_n_dump(3*[C,O,F,F,E,E] #= [T,H,E,O,R,E,M])。
Eq = (3*[8,3,1,1,9,9]#=[2,4,9,3,5,9,7])、Zs = [8,3,1,9,2, 4,5,7]。
真実。

もう少しやって、いくつかの異なるラベル付けオプションを試してみましょう:

?- time(solve_n_dump( [] ,[D,O,N,A,L,D]+[G,E,R,A,L,D] #= [R,O,B,E,R,T]) ])))。
Eq = ([5,2,6,4,8,5]+[1,9,7,4,8,5]#=[7,2,3,9,7,0]), Zs = [ 5,2,6,4,8,1,9,7,3,0]。
% 35,696,801 推論、3.928 秒で 3.929 CPU (100% CPU、9085480 リップ)
真実。

?- time(solve_n_dump( [ff] ,[D,O,N,A,L,D]+[G,E,R,A,L,D] #= [R,O,B,E,R, T])))。
Eq = ([5,2,6,4,8,5]+[1,9,7,4,8,5]#=[7,2,3,9,7,0]), Zs = [ 5,2,6,4,8,1,9,7,3,0]。
% 2,902,871 推論、0.340で 0.340 CPU (100% CPU、8533271 リップ)
真実。
于 2015-05-20T10:14:04.797 に答える
2

あなたが持っている

convert([A,B,C,D]) => convert([A,B,C])*10 + D 
 => (convert([A,B])*10+C)*10+D => ... 
 => ((A*10+B)*10+C)*10+D

したがって、単純な線形再帰でこれを表現できます。

さらに重要なことは、 domain から 1 つの可能な数字を選択した場合、0..9その数字を以降の選択に使用しないでください。

selectM([A|As],S,Z):- select(A,S,S1),selectM(As,S1,Z).
selectM([],Z,Z). 

select/3SWI Prolog で利用できます。このツールを使用すると、このように狭められたドメインから徐々に数字を選択できます。

money_puzzle( [[S,E,N,D],[M,O,R,E],[M,O,N,E,Y]]):-
  Dom = [0,1,2,3,4,5,6,7,8,9],
  selectM([D,E],  Dom,Dom1),   add(D,E,0, Y,C1),   % D+E=Y
  selectM([Y,N,R],Dom1,Dom2),  add(N,R,C1,E,C2),   % N+R=E
  select(  O,     Dom2,Dom3),  add(E,O,C2,N,C3),   % E+O=N
  selectM([S,M],  Dom3,_),     add(S,M,C3,O,M),    % S+M=MO
  S \== 0, M \== 0.

桁上げで 2 桁を加算し、新しい桁上げで結果の桁を生成できます (たとえば、4+8 (0) = 2 (1)12 など)。

add(A,B,C1,D,C2):- N is A+B+C1, D is N mod 10, C2 is N // 10 .

このように実装され、数字が選択されてすぐmoney_puzzle/1にテストされる段階的な性質のおかげで、瞬時に実行されます。

?- time( money_puzzle(X) ).
% 27,653 inferences, 0.02 CPU in 0.02 seconds (100% CPU, 1380662 Lips)
X = [[9, 5, 6, 7], [1, 0, 8, 5], [1, 0, 6, 5, 2]] ;
No
?- time( (money_puzzle(X),fail) ).
% 38,601 inferences, 0.02 CPU in 0.02 seconds (100% CPU, 1927275 Lips)

課題は、それを汎用にすることになります。

于 2012-06-13T09:13:25.823 に答える
2

Will Ness style, generalized (but assuming length(A) <= length(B)) solver:

money_puzzle(A, B, C) :-
    maplist(reverse, [A,B,C], [X,Y,Z]),
    numlist(0, 9, Dom),
    swc(0, Dom, X,Y,Z),
    A \= [0|_], B \= [0|_].

swc(C, D0, [X|Xs], [Y|Ys], [Z|Zs]) :-
    peek(D0, X, D1),
    peek(D1, Y, D2),
    peek(D2, Z, D3),
    S is X+Y+C,
    ( S > 9 -> Z is S - 10, C1 = 1 ; Z = S, C1 = 0 ),
    swc(C1, D3, Xs, Ys, Zs).
swc(C, D0, [], [Y|Ys], [Z|Zs]) :-
    peek(D0, Y, D1),
    peek(D1, Z, D2),
    S is Y+C,
    ( S > 9 -> Z is S - 10, C1 = 1 ; Z = S, C1 = 0 ),
    swc(C1, D2, [], Ys, Zs).
swc(0, _, [], [], []).
swc(1, _, [], [], [1]).

peek(D, V, R) :- var(V) -> select(V, D, R) ; R = D.

performance:

?- time(money_puzzle([S,E,N,D],[M,O,R,E],[M,O,N,E,Y])).
% 38,710 inferences, 0.016 CPU in 0.016 seconds (100% CPU, 2356481 Lips)
S = 9,
E = 5,
N = 6,
D = 7,
M = 1,
O = 0,
R = 8,
Y = 2 ;
% 15,287 inferences, 0.009 CPU in 0.009 seconds (99% CPU, 1685686 Lips)
false.

?-  time(money_puzzle([D,O,N,A,L,D],[G,E,R,A,L,D],[R,O,B,E,R,T])).
% 14,526 inferences, 0.008 CPU in 0.008 seconds (99% CPU, 1870213 Lips)
D = 5,
O = 2,
N = 6,
A = 4,
L = 8,
G = 1,
E = 9,
R = 7,
B = 3,
T = 0 ;
% 13,788 inferences, 0.009 CPU in 0.009 seconds (99% CPU, 1486159 Lips)
false.
于 2015-09-16T18:57:15.943 に答える