Prolog 从列表中删除元素

Prolog 从列表中删除元素,prolog,prolog-dif,Prolog,Prolog Dif,我知道已经有人问过这个问题,但我只想问一下我的具体实施情况。我写这个函数只是为了练习Prolog和更好地理解Prolog。以下是我所拥有的: del(Ele, [H], [H]) :- Ele \= H. del(Ele, [H|T], [H|New]) :- Ele \= H, del(Ele, T, [New]). 如果要删除的元素不等于H,我将向名为new的新列表中添加一个元素。据我所知,我的代码无法工作,因为当我到达Ele\=H的元素时,代码停止。有没有办法解决这个问题

我知道已经有人问过这个问题,但我只想问一下我的具体实施情况。我写这个函数只是为了练习Prolog和更好地理解Prolog。以下是我所拥有的:

del(Ele, [H], [H]) :- Ele \= H.
del(Ele, [H|T], [H|New]) :-
    Ele \= H,
    del(Ele, T, [New]).
如果要删除的元素不等于
H
,我将向名为
new
的新列表中添加一个元素。据我所知,我的代码无法工作,因为当我到达
Ele\=H
的元素时,代码停止。有没有办法解决这个问题

例如,
del(5[3,4,5,6,7],X)
将返回false


还有,有没有更好的解决方案?继续将列表中的每个元素添加到新列表中似乎是一个糟糕的解决方案,因为这对于一个大的列表来说会很慢。我宁愿将元素当前保留在列表中,找到与
Ele
匹配的元素,删除该元素,然后返回列表。

您已经描述了
del/3
应该保持的一些情况。但只有在这些情况下,
Ele
与列表中的元素不相等/不一致。缺少几件事:

空名单呢

如果
Ele
等于该元素,会发生什么

所以你需要添加更多的条款。(出于冗余的原因,稍后您可能还会删除一些)

如果使用SWI、B、SICSTUS或YAP,考虑使用<代码> DIF/2 代替<代码>(\=)/2 < /COD>。p> 这就是为什么在这种情况下,

dif/2
非常有用的原因。使用
dif/2
您将拥有一个纯单调的程序。您可以直接试用:

?- del(5, [3,4,5,6,7], X). false. 我觉得很完美!也许另一个答案是:

?- 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,Xs,Ys)。 Xs=Ys,Ys=[[u G1076], dif(E,_G1076); Xs=[[u G1133,[u G1136], Ys=[[U G1133 | U G1136], dif(E,_G1136), dif(E,_G1133)。。。 现在我们得到了一个错误的答案。我将对其进行一些实例化,以使其更具可读性:

?- del(e, [a,b], Ys). Ys = [a|b] ; false. ?-del(e,[a,b],Ys)。 Ys=[a | b]; 错。 这个答案显然不正确,因为
[a | b]
不是一个列表。而且,这可能是最小的错误答案

我想通过这个向大家展示的是,您通常可以找到问题,而无需一步一步地进行。一旦获得更复杂的控制流(如并发),逐步调试甚至不能在命令式语言中工作;在Prolog中它根本不可缩放。

让我们跟踪:

?- 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
为false。您的第一条规则从未匹配,因为列表中有多个项。第二条规则在找到
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)。另一个不幸的事实是,在函数式语言和声明式语言(缺少)中,修改列表意味着至少复制列表的一部分。在Prolog中有更有效的方法来实现这一点(例如,您可以使用差异列表),但它们将共享相同的基本问题。Prolog的效率是一个相当大的话题。我想告诉你不要预先太担心它,但它会很快成为你的担忧。但是在这种情况下,没有比使用破坏性更新更有效的方法了。

Wow!我不敢相信这就是问题所在!我想我只是太习惯OOP了,我想如果我只是递归地浏览列表,它
Ele
不等于
H
,那么它会跳过这个元素,然后转到下一个。@user1831442:你太执着于Prolog的实际执行了。当您来自面向命令的语言时,这是正常的。@false:但不幸的是,我发现编写正确的Prolog程序需要考虑执行模型。不可否认,这与C语言的编程不同,但至少我不能在不积极思考程序的过程意义的情况下编写一个简单的谓词(如OP的示例)。我猜它最终会被很好地内化,人们不必再有意识地思考它了。@Boris:你需要从纯单调的程序开始。单一的
(\=)/2
会破坏单调性。另一点是更多地依赖顶级查询,而不是跟踪。您可以查看tag以获取示例,了解如何以这种方式对非终止等程序性属性进行推理。谢谢!我刚刚解决了我的问题。但是,我有一个单例变量,因为我的基本情况是
del(Ele,[],[])。
我觉得单例变量不好。我如何删除此警告?它们很糟糕。通过将单例变量替换为
,始终可以消除警告。这也很有帮助,因为如果这看起来像是改变了代码的含义(在本例中不是这样),这很好地表明您对代码的直觉是错误的。谢谢!大牛,你是普罗格的救世主!最后一个问题。。我马上就要考试了,这将是一个关于如何为Prolog、Haskell和Perl编写函数的测试。我还必须了解这些语言的差异(它们的类型系统、动态/静态、推理机等)。有吗
?- 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.