【问题标题】:How to remove even numbers in List using Prolog如何使用 Prolog 删除 List 中的偶数
【发布时间】:2013-12-18 20:48:09
【问题描述】:

我需要删除第一个列表中的所有偶数并将其余的保存到第二个列表中。 我的第一个非工作方法是:

remove_even([],[]).
remove_even([H1|T1],[H2|T2]):- 
    H1 mod 2 =:= 0,
    remove_even(T1,_).
remove_even([H1|T1],[H2|T2]):-
    remove_even(T1,T2).

示例查询,使用 SWI-Prolog 7.1.37 运行:

?- remove_even([1,2,3,4],NewList).
NewList = [_G252, _G255|_G256].     % BAD! expected: NewList = [1,3]

我的代码有什么问题?

更新:下一次尝试,它不起作用,因为 remove_even 曾经检查它是否有效,然后返回 false 并转到另一个发生递归的规则...

remove_even([],[]).
remove_even([El|T],[T]):- El mod 2 =:= 0.
remove_even([H1|T1],[H2|T2]):-
    remove_even(T1,T1).

UPDATE2:迷失了逻辑:

remove_even([],[]).
remove_even([El|T],T):-El mod 2 =:= 0. % removing the head if head is even
remove_even([H|T1], [H|T2]) :-         % case where head is odd
    H mod 2 =\= 0,                     % rules that ensure head is odd
    remove_even(T1, T1). 

    % just copying T1 from source list to destination, 
    % will look up T1 values in next recursive iteration when it becomes a H. 

UPDATE3:遵循了建议,但仍然不起作用。

remove_even([],[]).
remove_even([El|T], NewT)   :- El mod 2 =:= 0, remove_even(T, NewT).
remove_even([H|T1], [H|T2]) :- H  mod 2 =\= 0, remove_even(T1, T1).

UPDATE4:没有单例错误

remove_even([],[]).
remove_even([El|T], NewT)  :- El mod 2 =:= 0, remove_even(T, NewT).
remove_even([El|T1], NewT) :- El mod 2 =\= 0, remove_even(T1,[NewT|T1]). 

UPDATE5: 让它工作了,但几乎不知道如何(一些魔法;)也许)。 gtrace 很好,但让 Prolog 绘制某种决策树或一些易于理解的步骤图形表示会很有用。

remove_even([],[]).
remove_even([El|T], NewT)   :- El mod 2 =:= 0, remove_even(T, NewT).
remove_even([H|T1], [H|T2]) :- H  mod 2 =\= 0, remove_even(T1, T2).

如何在考虑递归调用的情况下从逻辑上解释最后一个子句? 我的尝试:[H|T2] 是一个列表,其中从 [H|T1] 中删除了偶数元素.对吗?

【问题讨论】:

  • 在您的第二个子句中,H2T2 是单例的,这意味着您没有在规则中指出它们的来源。在第三个子句中,H1H2 都是单例的,所以在这种情况下你也没有完成规则中的逻辑。
  • 在您的更新中,您的第二个子句T(尾部)已经是一个列表。所以你不想要[T],而只想要T。在您的第三个子句中,H1H2 仍然是单例,现在 T2 也是。
  • 在您的第二次更新中,您的第二个子句将允许 T 包含偶数元素。换句话说,它表示如果E1 是偶数,则删除偶数元素的列表[E1|T]T 换句话说,如果它是偶数,它会删除第一个元素,然后宣布胜利.除了将其包含在结果中之外,它并未指示如何处理 T。我认为您的第三个子句看起来不错。
  • 这就是 prolog 的工作原理。如果在提出解决方案时序言逻辑中有选择点,您按;,它将寻求其他解决方案。当它找不到更多时,它会显示falseno(没有更多解决方案)。关于不知道您的程序如何工作,如果您按照我在回答中描述的方式考虑它应该是合乎逻辑的。例如,您的最后一个子句说,如果H 是奇数,则删除偶数元素的[H|T1][H|T2],如果T2 是从T1 中删除偶数元素时得到的列表。这不合逻辑吗? :)
  • 在您对第 3 条的最后解释中,您是说,...两个列表的 IFF 头很奇怪... 但这并不完全正确。该子句对两个头都使用H,因此头不仅奇数而且相同(它是相同的值),这很重要,因为我们希望第二个列表具有与第一个相同的值列表,而不是偶数。

标签: prolog


【解决方案1】:

您有单例变量警告这一事实表明有些事情是不正确的。您的子句标题暗示您关心特定变量,但子句逻辑不会实例化它或以其他方式使用它。

分析您提供的规则(条款)

remove_even([],[]).

好规则。 删除了偶数的空列表是空列表。

remove_even([H1|T1],[H2|T2]):- 
    H1 mod 2 =:= 0,
    remove_even(T1,_).

这条规则说,[H2|T2] 是列表 [H1|T1],如果 H1 是偶数,则删除偶数,如果我从 T1 中删除偶数并丢弃它们。这听起来不对。它也没有说明如何获得H2。注意:如果逻辑没有规定,您可能不想将此子句中的结果列表拆分为头部和尾部。

remove_even([H1|T1],[H2|T2]):-
    remove_even(T1,T2).

这条规则说 [H2|T2][H1|T1],如果 T2T1 并删除偶数。这听起来部分正确,但该规则并未说明如何处理H1H2

UPDATE:在您的更新中,新的第二个子句:

remove_even([El|T],[T]):- El mod 2 =:= 0.

这更近了。一个问题是T 已经是一个列表,所以你不需要[T] 而只需要T。然后就变成了:

remove_even([E1|T], T) :- E1 mod 2 =:= 0.

其中说:如果E1 是偶数,则删除偶数元素的列表[E1|T] 是列表T。这是一个正确的陈述,但不是完整的逻辑。它没有对T做出任何规定。如果列表T 有偶数元素怎么办?有关此特定条款的更正版本,请参阅 @Sergey 的答案。

您更新后的第三条有一些新问题:

remove_even([H1|T1],[H2|T2]):-
    remove_even(T1,T1).

共有三个单例变量。规则说,如果T1 本身是删除了偶数元素(即T1 没有偶数元素),则[H1|T1] 删除偶数元素会产生[H2|T2]。这听起来根本不合逻辑。因此,您需要重新考虑该规则。我假设您打算使用奇数头的情况(因为第 2 条涉及偶数头)。在这种情况下,您只是将头部复制到结果列表中。您的子句标题应如下所示:

remove_even([H|T1], [H|T2]) :-   % case where head is odd
     % put rules here that ensure head is odd, and define how T1 and T2 are related

所以你最终将有 3 个子句:(1) 从空列表中删除偶数元素,(2) 从头部为偶数的列表中删除偶数元素,以及 (3) 从头部为偶数的列表中删除偶数元素很奇怪。这一切听起来都很完整。顺其自然就好。

UPDATE4 响应:

新的第 3 条通过引入一些问题消除了单例:

remove_even([El|T1], NewT):-         
    El mod 2 =\= 0,                     
    remove_even(T1, [NewT|T1]). 

通读:如果E1 是奇数且[NewT|T1] 是从T1 中删除偶数元素的列表,则删除偶数元素的[E1|T1]NewT。这里的一个大问题是您使用NewT(一个列表)作为另一个列表[NewT|T1] 的头部,所以它现在是一个列表列表,它不会匹配任何内容。请参阅上面第 3 条的先前提示。此外,规则的一部分不再表明E1NewT 的一部分。如果E1 是奇数,则在删除偶数元素时它应该是另一个列表的一部分。

UPDATE5 响应(为什么现在有效?):

所以最终的工作版本如下所示:

remove_even([],[]).

和以前一样:如果删除空列表的偶数元素,则会得到一个空列表。

remove_even([El|T], NewT):- 
    El mod 2 =:= 0,
    remove_even(T, NewT).

NewT 是列表[E1|T],如果E1 是偶数且NewTT(原始列表的“尾列表”),则删除偶数元素,并从T 中删除偶数元素.换句话说,我们删除了E1(一个偶数元素,第一个列表的头 - 我们不再想要它,因为它是偶数)并留下了T,我们想要的列表的其余部分“ process" 并找到类似于T 但删除了偶数元素的列表。

remove_even([H|T1], [H|T2]):-         
    H mod 2 =\= 0,                     
    remove_even(T1, T2).

我们之前已经介绍过这个,但为了完整起见:如果H 不是偶数,则删除偶数元素的[H|T1][H|T2],而删除偶数元素的T1T2。您对此子句的描述为,[H|T2] 是从 [H|T1] 中删除了偶数元素的列表. 这不太准确。你是说,“......两个列表的IFF头是奇数......”,而在子句中,我们说的是头是相同和奇数(它是相同的数字),而不是两者都很奇怪。

正如我之前在回答中进一步描述的那样,所有案例都已涵盖。如果你从逻辑上考虑,这是有道理的。 :)

【讨论】:

  • (3) 为什么我们在结果列表的头部remove_even([H|T1], [H|T2]):-H mod 2 =\= 0, ....... 中寻找奇数(也)?
  • @RCola 从逻辑上考虑。如果我只是说remove_even([H|T1], [H|T2]) :- % some logic showing how T1 and T2 are related,那么即使H 是偶数,这也将允许H 成为奇数列表的一部分。你显然不想那样。奇数是偶数的反义词,第三条规则是处理正面为奇数的情况。您的第二条规则处理头部是偶数的情况。您不希望第三条规则允许头部的所有情况。
【解决方案2】:

如果您的 Prolog 系统提供 ,那么这个答案适合您。 (如果没有,请继续阅读!)

通过使用 tfilter/3reified predicatezodd_t/2,直接写:

?- tfilter(zodd_t,[1,2,3,4],Zs)。 % 删除偶数 = 保留奇数 Zs = [1,3]。

由于我们在这里只使用 monotone 代码,因此对于更一般的查询我们也能得到正确的答案:

?- tfilter(zodd_t,[A,B,C],Zs)。 Zs = [ ], A mod 2 #= 0, B mod 2 #= 0, C mod 2 #= 0 ; Zs = [ C], A mod 2 #= 0, B mod 2 #= 0, C mod 2 #= 1 ; Zs = [ B ], A mod 2 #= 0, B mod 2 #= 1, C mod 2 #= 0 ; Zs = [ B,C], A mod 2 #= 0, B mod 2 #= 1, C mod 2 #= 1 ; Zs = [A ], A mod 2 #= 1, B mod 2 #= 0, C mod 2 #= 0 ; Zs = [A, C], A mod 2 #= 1, B mod 2 #= 0, C mod 2 #= 1 ; Zs = [A,B ], A mod 2 #= 1, B mod 2 #= 1, C mod 2 #= 0 ; Zs = [A,B,C], A mod 2 #= 1, B mod 2 #= 1,C mod 2 #= 1。

即使没有,我们也可以使用上面的代码,只需使用不同的reified predicateseveninteger_t/2zeven_t/2,和 oddinteger_t/2zodd_t/2.

示例查询:

?- tfilter(oddinteger_t,[1,2,3,4,5,6,7], Xs)。 Xs = [1,3,5,7]。

编辑 2015-06-06

让我们试试下面的变化!

不要直接使用zodd_t/2,而是使用zeven_t/2not_t/3

truth_negated(true,false).
truth_negated(false,true).

not_t(Goal,Param,Truth1) :-       % meta-predicate, negate truth value
   call(Goal,Param,Truth0),
   truth_negated(Truth0,Truth1).

让我们看看它是否适用于正面比较:

?- tfilter(zodd_t,[1,2,3,4],Zs)。 Zs = [1,3]。 ?- tfilter(not_t(zeven_t),[1,2,3,4],Zs)。 Zs = [1,3]。

【讨论】:

  • _t 代表真值。 tf 在 Prolog 中不是常见的值,它们在 Scheme 中称为 #t。而是truefalse_t 对应于 t 作为前缀。
【解决方案3】:

让我们看看你的一个子句:

remove_even([El|T],[T]) :- El mod 2 =:= 0.

首先,在符号[El|T]中,El是一个单项,T是一个列表。然后 [T] 将成为列表中的列表,这可能不是您想要的。它应该只是“remove_even([El|T],T)”。

接下来,您的规则变体只是将 T 复制到答案中,而不是从尾部删除任何偶数。只有第一个数字(如果是偶数)将被删除。 remove_even 也应该应用于 T 。

最后我们应该有这样的东西:

remove_even([El|T], NewT) :- 
    El mod 2 =:= 0,
    remove_even(T, NewT).

【讨论】:

  • 您对remove_even 规则的最后建议无法自行解决。 False 被返回。
  • 这只是条款之一。必须添加空列表的子句和第一个元素不是偶数的子句。
  • @RCola,谢尔盖已经告诉你第二个子句应该是什么样子了。根据我的回答,您仍然需要您的第一个子句和一个修改后的第三个子句。
【解决方案4】:

一个方便的内置库是exclude

1 ?- [user].
|: even(N) :- N mod 2 =:= 0.
% user://1 compiled 0.02 sec, 2 clauses
true.

2 ?- exclude(even, [1,2,3,4], L).
L = [1, 3].

【讨论】:

    【解决方案5】:

    我的意思是也许我读错了,但你不认为你对这个问题想得太多了吗?因此,取出第一个列表中的所有偶数元素并将它们存储在第二个列表中,我可以通过跳过每个偶数元素来做到这一点,如下所示:

    remove_evens([], Ys).
    remove_evens([X], [X|Ys]).
    remove_evens([X,_|Xs], [X|Ys]):-
      remove_evens(Xs, Ys), !.
    

    这反过来会给出这个结果:

    | ?- remove_evens([1,2,3,4,5,6,7], List2).
    List2 = [1,3,5,7|_]
    

    【讨论】:

    • 这个剪切加上不正确的第一个和第二个子句可能会导致奇怪的错误,例如:remove_evens([a,_,b,_,c],[a,b,c]). 正确成功,但 remove_evens([],[a,b,c]). 也成功。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-31
    • 1970-01-01
    • 2017-02-06
    相关资源
    最近更新 更多