考虑括号问题而不实际放置任何括号的一种方法是使用后缀表示法。换句话说:
(a + b) * c
变成:
a b + c *
这是规范 Prolog 表示法中的以下树:
*(+(a, b), c)
同样:
a + (b * c) ---> a b c * + ---> +(a, *(b, c))
举个完整的例子,有三个操作数,1、2 和 3,并且只有 + 和 * 作为运算符,为了简短起见,您会得到:
1 2 + 3 + ---> (1 + 2) + 3 = 6
1 2 + 3 * ---> (1 + 2) * 3 = 9
1 2 * 3 + ---> (1 * 2) + 3 = 6
1 2 * 3 * ---> (1 * 2) * 3 = 6
1 2 3 + + ---> 1 + (2 + 3) = 6
1 2 3 + * ---> 1 * (2 + 3) = 5
1 2 3 * + ---> 1 + (2 * 3) = 7
1 2 3 * * ---> 1 * (2 * 3) = 6
查看第一列,我得到以下一般概念:您从 n 操作数和 n-1 二元运算符开始。您将前两个操作数压入堆栈,并且需要再执行 2*n-3 步。在每一步,您要么推送一个操作数,要么应用一个运算符。如果你还有剩余,你总是可以压入一个操作数。仅当堆栈上有两个或多个操作数时才能应用运算符;那时您将不得不减少堆栈。
回溯将负责枚举所有可能性(因此这是对解决方案空间的典型暴力穷举搜索)。您将有两个选择点来源:选择一个运算符;以及推动或减少。
考虑到这一点,我得出了一个谓词的以下实现,它接受一个操作数列表、一个二元运算符列表,并为您提供一个“带括号”的表达式:
expr(Operands, Operators, E) :-
Operands = [A, B|Rest],
length(Operands, N),
Steps is 2*N - 3,
expr(Steps, Rest, [B, A], Operators, E).
这会将前两个操作数压入堆栈并计算剩余步数。
expr(Steps, Operands, Stack, Operators, E) :-
( succ(Steps0, Steps) ->
next(Steps0, Operands, Stack, Operators, E)
; Stack = [E]
).
这里我用succ/2倒数到0然后停止;最后,堆栈中唯一的元素就是您的表达式。
next(Steps, Operands, Stack, Operators, E) :-
push(Operands, Stack, Operands_next, Stack_next),
expr(Steps, Operands_next, Stack_next, Operators, E).
next(Steps, Operands, Stack, Operators, E) :-
member(Op, Operators),
reduce(Stack, Op, Stack_next),
expr(Steps, Operands, Stack_next, Operators, E).
这是您推动或减少的地方。两个单独的子句是选择点的第一来源;使用member/2 从列表中获取一个运算符是另一个。
push([X|Xs], S0, Xs, [X|S0]).
reduce([A,B|Stack], Op, [X|Stack]) :-
X =.. [Op, B, A].
实现推送和缩减是微不足道的。我使用“univ”运算符=.. 从[+, 1, 2] 之类的列表中生成+(1, 2) 之类的术语。
有了这个,你已经可以问“我如何使用 +、* 和括号将 [1,2,3] 变成 7”:
?- expr([1,2,3], [+,*], E), E =:= 7.
E = 1+2*3 ;
false.
这是最基本的“生成和测试”:生成算术表达式,然后测试它们是否计算为一个值。如果省略测试,则可以看到所有表达式:
?- expr([1,2,3], [+,*], E).
E = 1+(2+3) ;
E = 1*(2+3) ;
E = 1+2*3 ;
E = 1*(2*3) ;
E = 1+2+3 ;
E = (1+2)*3 ;
E = 1*2+3 ;
E = 1*2*3 ;
false.
一个奇怪的细节是,因为+ 和* 已经被定义为中缀运算符,Prolog 会为您编写它们,甚至为它们加上括号。我不知道像E = (1+2)*3 这样的解决方案对您来说是否足够好,或者您真的需要['(', 1, +, 2, ')', *, 3]。 The other answer 似乎已经有一个可行的解决方案。因为这里的表达式已经是一个有效的算术表达式,你必须稍微调整一下。我可能会这样写:
infix(N) -->
{ number(N)
}, !,
[N].
infix(E) -->
{ compound(E),
E =.. [Op, A, B]
}, !,
['('], infix(A), [Op], infix(B), [')'].
我也不知道 1+2+3 = 3+3 = 6 是否和 1+(2+3) = 1+5 = 6 一样:需要考虑关联性吗?
无论哪种方式,您都可以将expr/3 包装在这样的谓词中:
equals_k(Numbers, K, E) :-
expr(Numbers, [+,-,*,/], E0),
K =:= E0,
phrase(infix(E0), E).
PS:除以零异常很容易,例如:
?- expr([1,0], [/], E), R is E.