选择加起来等于特定数字的数字(扭曲)

Choose numbers that add up to a specific number (with a twist)

我想在 Prolog 的帮助下解决冒险游戏 'Book of Unwritten Tales 2' 中的一个谜语,以加深我对这种特定语言的了解。

从 9 个数字中找出 3 个相加等于 41。

你必须选择一个数字三次。每次选择都是从 9 个数字中的 3 个固定的不同分组中选择的。组的顺序是固定的。这些选项与减号或加号运算符组合 ,这不是数字本身的符号 ,而是说明它将如何与下一个数字(扭曲)进行代数联系。

可供选择的示例组:

  1. (65+) (17 -) (37 +)

  2. (50-) (23 -) (27 +)

  3. (33) (47) (45)

正确的解决方案是

(65+) (23-) (47) = 41
i.e. 65 + 23 - 47 = 41

我下面的程序只找到以下不正确的解决方案

(17 -) (23 +) (47) = 41

(17 -) (23 -) (47) = -53

这是不正确的,因为在分组 #2 中只有 (23 -) 可用,而不是 (23 +)。

addX(X,Y,S) :- X is S - Y.

e(X,S,R) :- ( (addX( X,65,      S),R='65 +') ; (addX( X,17, -1 * S),R = '17 -') ; (addX( X,37,S),R = '37 +') ),X=0.
d(X,S,R) :-   (addX( X,50, -1 * S),R='50 -') ; (addX( X,23, -1 * S),R = '23 -') ; (addX( X,27,S),R = '27 +').
c(X,S,R) :-   (addX( X,33,      S),R='33  ') ; (addX( X,47,      S),R = '47  ') ; (addX( X,45,S),R = '45  ').

solve(Sum,Res5,Res4,Res3) :- c(Xc,Sum,Res3),d(Xd,Xc,Res4),e(Xe,Xd,Res5).

?- solve(41,X1,X2,X3).
X1 = '17 -',
X2 = '23 -',
X3 = 47 ;
false.

问题是运算符应用不正确。 所以我尝试通过嵌套调用返回符号来修复它。

addX(X,Y,S) :- X is S - Y.

e(X,S,R,SI,SO) :- ( (addX( X,65,S),R=65,SO= 1) ; (addX( X,17,S),R = 17,SO= -1) ; (addX( X,37,S),R = 37,SO= 1) ),X=0.
d(X,S,R,SI,SO) :-   (addX( X,SI*50,S),R=50,SO= -1) ; (addX( X,SI*23,S),R = 23,SO= -1) ; (addX( X,SI*27,S),R = 27,SO= 1).
c(X,S,R,SI,SO) :-   (addX( X,SI*33,S),R=33,SO= 1) ; (addX( X,SI*47,S),R = 47,SO= 1) ; (addX( X,SI*45,S),R = 45,SO= 1).

solve(Sum,Res3,Res4,Res5) :- e(Xe,Xd,Res5,1,SO5),d(Xd,Xc,Res4,SO5,SO4),c(Xc,Sum,Res3,SO4,SO3).

不幸的是,这会导致以下运行时错误:

?- solve(41,X1,X2,X3).
ERROR: is/2: Arguments are not sufficiently instantiated

感谢任何帮助!

我会采取不同的方法。首先,我会选择一种更易于操作的数据表示形式。每对数字与操作:

  1. [(65,(+)), (17,(-)), (37, (+))]

  2. [(50, (-)), (23, (-)), (27, (+))]

  3. [33, 47, 45]

在这里,number/operation 对是 (N, (op))。这个词可以让我们很容易地找出号码和操作员。由于 Prolog 中的句法原因,运算符周围需要额外的括号。您也可以选择 [[65,+], [17,-], [37,+]][t(65,+), t(17,-), t(37,+)].

等形式

然后,为了查询问题,我选择将这组信息作为上述列表的列表传递。查询看起来像:

solve(41, [[(65,(+)),(17,(-)),(37,(+))],[(50,(-)),(23,(-)),(27,(+))],[(33),(47),(45)]], Result).

我可以选择将以上 3 个列表作为单独的参数传递,但如果您想更改列表的数量,列表的列表更具可扩展性。我希望看到的 Result 是从上述 3 个列表中的每一个列表中按顺序选择一个项目的列表,使得该列表的评估为 41。

然后解决方案将遍历列表列表的每个元素,select 每个元素的一个成员,评估该 selection 的结果,并将该结果与 sum 参数进行比较(在这种情况下,41)。

%  Solve the summation problem

solve(Sum, [C|Choices], [A|Results]) :-
    member(A, C),
    solve(Sum, A, Choices, Results).
solve(Sum, (N1,Op1), [C|Choices], [(N2,Op2)|Results]) :-
    member((N2,Op2), C),
    Term =.. [Op1, N1, N2],
    S is Term,
    solve(Sum, (S,Op2), Choices, Results).
solve(Sum, (N1,Op1), [C], [N2]) :-
    member(N2, C),
    Term =.. [Op1, N1, N2],
    Sum is Term.

运行 这个查询:

| ?-  solve(41, [[(65,(+)),(17,(-)),(37,(+))],[(50,(-)),(23,(-)),(27,(+))],[(33),(47),(45)]], R).

R = [(65,(+)),(23,(-)),47] ? ;

no

通过为第一个参数使用变量,您可以看到可能的结果组合:

| ?-  solve(S, [[(65,(+)),(17,(-)),(37,(+))],[(50,(-)),(23,(-)),(27,(+))],[(33),(47),(45)]], R).

R = [(65,(+)),(50,(-)),33]
S = 82 ? ;

R = [(65,(+)),(50,(-)),47]
S = 68 ? ;

R = [(65,(+)),(50,(-)),45]
S = 70 ? ;
...

只需更改上面 1、2 和 3 中的列表,即可轻松扩展此解决方案。您可以有两个或更多列表,或不同长度的列表。


如果我可以窃取一个生物学家 maplist 的想法,这个解决方案的变体将是:

solve(Sum, Choices, Results) :-
    maplist(member, Results, Choices),
    evaluate(Results, Sum).

evaluate([(N1,Op1), (N2,Op2)|Ops], Sum) :-
    Term =.. [Op1, N1, N2],              % form a term with first two
    S is Term,                           % Evaluate the Term
    evaluate([(S,Op2)|Ops], Sum).        % Evaluate the remaining terms
evaluate([(N1,Op),N2], Sum) :-           % Evaluate last term
    Term =.. [Op, N1, N2],               % Form a term
    Sum is Term.                         % Check final sum

一个 'declarative' 这个漂亮谜题的解决方案(或者是一个丑陋的 hack?)

:- op(100, xf, +).
:- op(100, xf, -).

test(X,Y,Z) :-
    member(X, [65 +, 17 -, 37 +]),
    member(Y, [50 -, 23 -, 27 +]),
    member(Z, [33  , 47  , 45  ]),
    combine(X, Y, Z, E), 41 is E.

combine(X -, Y -, Z, X - Y - Z).
combine(X -, Y +, Z, X - Y + Z).
combine(X +, Y -, Z, X + Y - Z).
combine(X +, Y +, Z, X + Y + Z).

更通用的解决方案出奇地困难,尽管更短...

test(X,Y,Z) :-
    member(X, [65 +, 17 -, 37 +]),
    member(Y, [50 -, 23 -, 27 +]),
    member(Z, [33  , 47  , 45  ]),
    eval([X, Y, Z], E), 41 is E.
    %combine(X, Y, Z, E), 41 is E.

eval([H|T], E) :- H =.. [Op,N], eval(Op,N,T,E).
eval(Op,X,[H|T], E) :- 
   H =.. [Op1,M], Q =.. [Op,X,M], eval(Op1,Q,T,E)
 ; E =.. [Op,X,H].

由于@CapelliC 的解决方案并不是一个丑陋的 hack,而且他和@lurker 的解决方案都足够优雅和有用,我想我会冒昧地用一个真正丑陋的 hack 来回答。

这肯定不是解决问题的好方法。但它确实有效!

puzzle(P) :-
    P = [
            ['65 +', '17 -', '37 +'],
            ['50 -', '23 -', '27 +'],
            ['33', '47', '45']
        ].

puzzle_solution(Puzzle, Sum, Expr) :-
    maplist(member, Ps, Puzzle),
    atomic_list_concat(Ps, ' ', ExprAtom),
    term_to_atom(Expr, ExprAtom),
    Sum is Expr.