【问题标题】:Prolog - finding the product of a listProlog - 查找列表的产品
【发布时间】:2015-11-11 07:27:34
【问题描述】:

我是 prolog (swi prolog) 的超级新手,如果这是一个愚蠢的问题,我很抱歉。这也是一个单任务。我被要求找到一个列表的产品,这就是我想出的:

product([],1).
product([H|T], Product) :-
    product( T, Product1),
    Product is H * Product1.

我的基本情况最长为:

product([],0).

但这会使一切归零。但是,在测试基本情况时 product([],Product),我得到了一个 - 这是不正确的。任何解决方案的提示将不胜感激。

【问题讨论】:

  • 我不认为这是不正确的。实数或整数在乘法下的单位元确实是 1。
  • 我同意鲍里斯的观点:product([], 1) 应该是正确的基本情况,因为 1 是乘法恒等式。

标签: list prolog


【解决方案1】:

这并没有错:1 是乘法下“数字”的identity element。例如,您可以抽象出操作和循环,以获得以下内容:

list_op_product(L, Op, P) :-
    identity_element(Op, IE),
    reverse(L, R), % no foldr in SWI-Prolog, at least
    foldl(Op, R, IE, P).

所以你只需要定义identity_element/2 和操作本身。因此,加法和乘法将类似于:

identity_element(add, 0).
identity_element(mult, 1).

add(X, Y, R) :- R is X + Y.
mult(X, Y, R) :- R is X * Y.

然后你可以说:

?- list_op_product([2,3,4], add, Sum).
Sum = 9.

?- list_op_product([2,3,4], mult, Product).
Product = 24.

同样,字符串连接的标识元素是空字符串。所以,如果你只是将以下子句添加到identity_element/2

identity_element(atom_concat, '').

你现在可以说:

?- list_op_product([a,b,c], atom_concat, R).
R = abc.

当然,这大部分是不必要的,但它说明了重点。

至于other answer:为了避免错误答案,可能有比剪切更清洁的方法:

product([], 0).
product([H|T], P) :-
    product_1(T, H, P).

product_1([], P, P).
product_1([H|T], H0, P) :-
    product_1(T, H, P0),
    P is P0 * H0.

那么现在:

?- product([2,3,5], P).
P = 30.

?- product([], P).
P = 0.

但这感觉不对。您仍然应该有一个基本案例:

product([], 1).

product/2 的这个子句简单地定义了你的身份元素是什么;这就是为什么最好将 1 留在那里而不用 0 替换它!但是,对于非空列表,您将少做一次乘法(您不会有最后一个 * 1)。这是一个有效且易于进行的优化。如果您尝试查找根本没有标识元素的类型的乘积:那么,product([], X) 未定义。您可以省略该子句,然后 ?- product([], X) 将失败。或者,您可能会抛出错误?

【讨论】:

  • 第一句中的错字:...of "numbers" under addition 应该是...of "numbers" under multiply。我同意 (+1):基本情况应该是 product([], 1),因为 1 是乘法恒等式,同样的原因 sum([], 0) 将是 sum 的基本情况。
  • 名称 list_op_product/3 不再适合一般谓词,因为谓词表示 sum 而不是 product,具体取决于范围。此外,很自然地执行像 a*b*c(a*b)*c 这样的乘法运算,因此从左侧折叠实际上非常适合这种情况。从右边开始的折叠对应于a*(b*c),这不是*/2 自然关联的方式,也不是Prolog 中的关联方式。
  • @Boris,我知道,这就是为什么我没有声明:“list_op_product/3 这个名字根本不适合。” ;-) 它不太适合 sum 的情况。
  • 是的,我也注意到了关于串联的观点。在这种情况下,我的首选是交换辅助目标中的参数,例如:ac(A, B, C):- atom_concat(B, A, C).,然后从左侧折叠辅助目标。减法,反社会,有一个右身份,但没有左身份,所以为了进一步推广,你必须区分不同的属性(可交换与否,左身份或右身份等)。
  • @mat 好的,你说的一切都有道理。在我对知识的无尽(并且可能是被误导的)追求中,我一直在尝试阅读由 Alex Stepanov(以 C++ STL 闻名)合着的一本书。他处理这些问题的方式(使用 C++)实际上是定义算法,这些算法取决于为传递给它们的特定类型定义的操作。例如,您不能将半群的元素提升为 0 的幂等(具体示例请参见 power_semigroup(a, n, op)defined here
【解决方案2】:

在描述涉及列表的关系时,通常从描述两种情况开始是有意义的:一种用于空列表,另一种用于涉及至少一个元素的列表。

在第二个子句中,您可以使用foldl/4 和一个小的辅助谓词。例如:

list_product([], 1).
list_product([L|Ls], P) :-
        foldl(product_, Ls, L, P).

product_(A, B, P) :- P is A*B.

实际上你可以等效地写成:

list_product(Ls, P) :- foldl(product_, Ls, 1, P).

空列表的乘积被合理地定义为 1,因此,当在多个列表上构建乘积时,空列表自然地融合在一起,不会改变任何结果。

例子:

?- list_product([1,2,3,4], P).
P = 24.

和:

?- maplist(list_product, [[1,2],[],[3,4]], Ps), list_product(Ps, P)。 P = 24, Ps = [2, 1, 12]。

【讨论】:

    【解决方案3】:

    我同意@Boris 表达的观点,但我认为如果您希望空列表的结果为 0,您可以利用 模式匹配,这是 Prlog 的显着特征之一:

    product([], 0).
    product([N], N).
    product([H|T], Product) :-
        product(T, Product1),
        Product is H * Product1.
    

    由于引入的基本情况与递归子句重叠,因此需要进行剪切,以避免

    ?- product([2,3,5],X).
    X = 30 ;
    X = 0.
    

    所以请根据需要更改我的建议。

    编辑以避免剪切,替代模式可以是

    product([], 0).
    product([N], N).
    product([H,I|T], Product) :-
            product([I|T], Product1),
            Product is H * Product1.
    

    现在第二个和第三个子句不再重叠,但可能是您的 Prolog 不够聪明,无法优化谓词...

    【讨论】:

    • 很好的答案,但请参阅我的答案中的补充内容,以获得避免削减的直接方法(当然你已经知道了)。
    • 虽然这种效率在像 Prolog 这样的语言中很少成为问题,但我猜想。我展示这段代码的原因是因为它没有删减,也没有太大的杀伤力(就像折叠一样)。
    • 我认为需要删减以避免X 的错误答案这一事实强调了基本情况应该是product([], 1) 的事实,因为1 是乘法恒等式。在数学中,恒等元素在某种意义上是什么都没有时的默认值。因此,对于任意数量的 1,您都有诸如 a = a x 1 x 1 x ... x 1 之类的事实,因为我可以在不更改值的情况下继续使用身份。换句话说,当什么都没有时,假设身份(1)。也许product([], X) 可能会失败,但X = 0 是错误的答案,恕我直言。
    • 我认为这并不复杂。 :) 这样想。如果我有列表 L1 和产品 p1,列表 L2 和产品 p2,那么我将列表附加为 L3,L3 的产品应该是什么?我建议它应该是 p1 x p2。但是如果空列表的乘积为 0,它就行不通了。事实product([], 0). 造成了逻辑和数学上的不连续性。
    • OP 说他们不认为这是正确的,这意味着他们不确定。答案应该描述什么是正确的。
    【解决方案4】:

    我建议空列表的乘积应该是 1。

    第一个原因,正如 CapelliC 的回答所示,如果您要求它为 0,那么您就有逻辑不连续性。谓词需要特殊处理来处理 0 产品与 1。

    空列表的乘积为 0 并没有什么特别的原因。事实:

    foo([], X).
    

    描述[]X 之间的关系fooX 应该是什么?仅仅因为列表为空并不意味着逻辑答案是0

    让我们从另一个角度来看。假设我有两个数字列表,L1L2。并假设这些列表的产品分别为p1p2。那么如果我有:

    append(L1, L2, L), product(L, X).
    

    X 应该是什么?我想应该是p1 x p2。如果L1 = [2, 3]L2 = [4, 5],那么我们有p1 = 6p2 = 20L = [2, 3, 4, 5],其乘积为120,即6 x 20。到目前为止一切顺利。

    现在假设L2 = []。然后L = [2, 3]。如果product(L2, 0),那么我将拥有p2 = 0p1 x p2 = 0。但是product(L, 6). 所以逻辑崩溃了,我们有数学或逻辑上的不连续性。但是,如果product([], 1).,那么这一切都按预期工作。

    在算术意义上,如果什么都没有,则默认情况下,代数恒等式就是你所拥有的。对于总和,恒等式为 0,我可以将 a 写为 a + 0 + ... + 0 并且仍然有 a。同样,a 可以写成a x 1 x ... x 1。因此,product([], 1). 成立在数学上是有意义的。

    【讨论】:

    • +1!空积被明智地定义为 1,原因与 0 被明智地定义为空列表的 sum 的原因相同:两者都表示各自的标识元素。将空列表的乘积定义为 0 与将空列表的总和定义为 134 一样有意义。
    猜你喜欢
    • 2017-11-27
    • 1970-01-01
    • 2020-02-20
    • 2017-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-25
    相关资源
    最近更新 更多