从列表中删除元素

时间:2013-03-19 21:01:36

标签: prolog prolog-dif

我知道这个问题已被提出,但我只是想问一下我的具体实施情况。我正在编写这个函数只是为了练习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匹配的元素,删除该元素,然后返回列表。

2 个答案:

答案 0 :(得分:3)

您已经描述了del/3应该保留的一些个案。但这些只是Ele与列表中的元素不相等/不可统一的情况。缺少一些东西:

空列表怎么样?

如果Ele等于元素,该怎么办?

所以你需要添加更多的子句。 (稍后您可能会因冗余原因删除一些内容。)

如果您使用的是SWI,B,SICStus或YAP,请考虑使用dif/2代替(\=)/2

以下是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 \= 35 \= 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效率是一个相当大的话题。我告诉你不要在前面担心太多,但它有一种很快成为你关注的方法。但在这种情况下,不,实际上并没有使用破坏性更新的更有效的方法。