我知道这个问题已被提出,但我只是想问一下我的具体实施情况。我正在编写这个函数只是为了练习Prolog并更好地理解Prolog。这就是我所拥有的:
del(Ele, [H], [H]) :- Ele \= H.
del(Ele, [H|T], [H|New]) :-
Ele \= H,
del(Ele, T, [New]).
我的想法是,如果我要删除的元素不等于New
,我会将一个元素添加到名为H
的新列表中。根据我的理解,我的代码无效,因为当我到达Ele \= H
的元素时,我的代码停止了。任何想法如何解决这个问题?
例如,del(5, [3,4,5,6,7], X)
将返回false。
此外,还有更好的解决方案吗?将列表中的每个元素添加到新列表似乎是一个糟糕的解决方案,因为这对于大型列表来说会很慢。我宁愿只保留当前列表中的元素,找到与Ele
匹配的元素,删除该元素,然后返回列表。
答案 0 :(得分:3)
您已经描述了del/3
应该保留的一些个案。但这些只是Ele
与列表中的元素不相等/不可统一的情况。缺少一些东西:
空列表怎么样?
如果Ele
等于元素,该怎么办?
所以你需要添加更多的子句。 (稍后您可能会因冗余原因删除一些内容。)
如果您使用的是SWI,B,SICStus或YAP,请考虑使用dif/2
代替(\=)/2
。 prolog-dif
以下是dif/2
在这种情况下如此有用的原因。使用dif/2
,您将获得纯粹的单调计划。你可以直接尝试一下:
?- del(5, [3,4,5,6,7], X). false.
你遇到了同样的问题。让我重申一下问题是什么:你期望某些东西应该成立,但事实并非如此。所以这种关系定义得太狭隘了。如果我概括了查询,我可能会得到更好的答案。请尝试del(E, [3,4,5,6,7], X).
但同样false
。所以我会尝试更一般的查询:
?- del(E, Xs, Ys). Xs = Ys, Ys = [_G1076], dif(E, _G1076) ...
对我来说很完美!也许是另一个答案:
?- del(E, Xs, Ys). Xs = Ys, Ys = [_G1076], dif(E, _G1076) ; Xs = [_G1133, _G1136], Ys = [_G1133|_G1136], dif(E, _G1136), dif(E, _G1133) ...
现在我们得到了错误的答案。我将稍微实例化它以使其更易读:
?- del(e, [a,b], Ys). Ys = [a|b] ; false.
这个答案显然不正确,因为[a|b]
不是列表。而且,这可能是最小的错误答案......
我想通过此方式向您展示的是,您通常可以找到问题,而不是一步一步地完成问题。一旦获得更复杂的控制流(如并发),逐步调试甚至不能在命令式语言中工作;并且它在Prolog中根本没有扩展。
答案 1 :(得分:2)
让我们追踪:
?- trace, del(5, [3,4,5,6,7], X).
Call: (7) del(5, [3, 4, 5, 6, 7], _G218) ? creep
Call: (8) 5\=3 ? creep
Exit: (8) 5\=3 ? creep
Call: (8) del(5, [4, 5, 6, 7], [_G344]) ? creep
Call: (9) 5\=4 ? creep
Exit: (9) 5\=4 ? creep
Call: (9) del(5, [5, 6, 7], [[]]) ? creep
Fail: (9) del(5, [5, 6, 7], [[]]) ? creep
Fail: (8) del(5, [4, 5, 6, 7], [_G344]) ? creep
Fail: (7) del(5, [3, 4, 5, 6, 7], _G218) ? creep
false.
所以你可以看到你的代码在到达5时实际上是失败的,因为5 \= 5
是假的。您的第一条规则永远不会匹配,因为该列表中包含多个项目。在找到5 \= 3
和5 \= 4
之后,第二条规则“正确”重复,但由于您的任何规则中都没有5 = 5
个案例,因此会发生失败。
顺便提一下,让我们看看当列表中没有出现5时会发生什么:
?- trace, del(5, [3,4,6,7], X).
Call: (7) del(5, [3, 4, 6, 7], _G350) ? creep
Call: (8) 5\=3 ? creep
Exit: (8) 5\=3 ? creep
Call: (8) del(5, [4, 6, 7], [_G473]) ? creep
Call: (9) 5\=4 ? creep
Exit: (9) 5\=4 ? creep
Call: (9) del(5, [6, 7], [[]]) ? creep
Fail: (9) del(5, [6, 7], [[]]) ? creep
Fail: (8) del(5, [4, 6, 7], [_G473]) ? creep
Fail: (7) del(5, [3, 4, 6, 7], _G350) ? creep
false.
这就是为什么我之前说“正确”:你的归纳案也不对。首先,你有del(Ele, T, [New])
,但是你已经del(Ele, [H|T], [H|New])
,所以你需要在右边打开一个额外的时间(这就是我们的跟踪中有[[]]
的原因)。但是@false遇到了一个更大的问题,那就是你根本没有考虑到你实际找到你要删除的内容的情况。 :)你也不处理列表为空的情况。
生活中不幸的是,遍历数据结构并查看每个项目将是O(N)。另一个不幸的事实是,在函数和声明性语言(缺少"assignables"的语言)中修改列表意味着复制至少部分列表。在Prolog中有更有效的方法(例如,您可以使用差异列表),但它们将共享相同的基本问题。 Prolog效率是一个相当大的话题。我告诉你不要在前面担心太多,但它有一种很快成为你关注的方法。但在这种情况下,不,实际上并没有使用破坏性更新的更有效的方法。