在PROLOG中,是否有一种方法可以仅选择列表的第一个和最后一个元素?

3

我正在尝试编写一个谓词,如果X是Y的子列表,则为真,而不考虑Y的第一个和最后一个项。例如,查询listWithinList([b,c,d],[a,b,c,d,e])将返回True,但查询listWithinList([b,c,d,e],[a,b,c,d,e])将产生False,因为Y的最后一个元素e不应该是X的一部分。

目前我的代码是listWithinList(X,Y):-append(_,Y2,Y), append(X,_,Y2). 但我不确定如何更改代码,以便在不考虑Y的第一个和最后一个项的情况下实现相同的效果。


1
SWI Prolog 中,您可以定义 pred(A,B):- append([[_],A,[_]],B) - Will Ness
4个回答

5
当你在调用append方法时,在参数中使用下划线_表示该参数是一个任意列表,「任意」的含义也包括长度任意。
例如:
?- append(_, Suffix, [a, b, c]).
Suffix = [a, b, c] ;
Suffix = [b, c] ;
Suffix = [c] ;
Suffix = [] ;
false.

这里的_可以代表任何一个列表[][a][a, b][a, b, c]。但我不需要告诉你这一点,如果你给匿名变量_指定一个适当的名称,Prolog就能告诉你这一点:

?- append(Prefix, Suffix, [a, b, c]).
Prefix = [],
Suffix = [a, b, c] ;
Prefix = [a],
Suffix = [b, c] ;
Prefix = [a, b],
Suffix = [c] ;
Prefix = [a, b, c],
Suffix = [] ;
false.

与之相反,[_] 这个术语不代表任意列表,而是代表确切只有一个元素的列表。这个元素用 _ 表示,其本身是任意的。

例如:

?- append([_], Suffix, [a, b, c]).
Suffix = [b, c].

或者,再次使用适当的变量名称,以便我们可以看到绑定关系:
?- append([X], Suffix, [a, b, c]).
X = a,
Suffix = [b, c].

这里想要表达的是,从问题中提出的定义是:
listWithinList(X,Y):-append(_,Y2,Y), append(X,_,Y2).

这段话基本上是正确的。但是两个_使用并不是每次"移除"一个元素,而是每次"移除" 任意数量的元素。因此你并不仅仅获得列表的中间部分:

?- listWithinList(Middle, [a, b, c, d, e]).
Middle = [] ;
Middle = [a] ;
Middle = [a, b] ;
Middle = [a, b, c] ;
Middle = [a, b, c, d] ;
Middle = [a, b, c, d, e] ;
Middle = [] ;
Middle = [b] ;
Middle = [b, c] ;
Middle = [b, c, d] ;
Middle = [b, c, d, e] ;
Middle = [] ;
Middle = [c] ;
Middle = [c, d] ;
Middle = [c, d, e] ;
Middle = [] ;
Middle = [d] ;
Middle = [d, e] ;
Middle = [] ;
Middle = [e] ;
Middle = [] ;
false.

如果我们想要从列表的开头和结尾“删除”仅有一个元素的列表,那么我们必须写成[_]
listWithinList(X, Y) :-
    append([_], Y2, Y),
    append(X, [_], Y2).

现在它的行为是这样的:

?- listWithinList(Middle, [a, b, c, d, e]).
Middle = [b, c, d] ;
false.

此外,请注意[_][_|_]的区别。前者代表仅有一个元素的列表。而后者表示拥有一个或多个元素的列表。在这种情况下,您不希望“删除”超过一个元素,因此像其他答案建议的那样使用 [_|_] 是绝对荒谬的。
最后,Prolog可以为我们提供进一步简化:
?- append([X], Xs, Ys).
Ys = [X|Xs].

将一个只含有一个元素 [X] 和一个任意列表 Xs 连接在一起可以得到一个新的列表,我们也可以使用 [X | Xs] 来表示这个列表,而不需要使用 append。因此,其中一个 append 调用是不必要的。我可能会这样写这个谓词:

list_middle(List, Middle) :-
    append([_First | Middle], [_Last], List).

并像这样使用:

?- list_middle([a, b, c, d, e], Middle).
Middle = [b, c, d] ;
false.

或者像这样:

?- list_middle(List, [1, 2, 3]).
List = [_2658, 1, 2, 3, _2664].

非常周到的答案和清晰的解释,谢谢!我从你的帖子中学到了很多。 - Pervy Sage

2

对于这样的任务,语法非常直观。只需描述我们拥有什么:

list_within(Xs, Ys) :-
   phrase(( [_First], seq(Ys), [_Last] ), Xs).

seq([]) --> [].
seq([E|Es]) --> [E], seq(Es).

也就是说,首先是元素_First,然后是序列Ys,最后是元素_Last

0
由于在Prolog中列表的表示方式, 你可以通过将其拆分为头和尾并将结果与尾部统一来轻松地删除第一个元素, 如下所示:
tail([_|L], L).

成功时,谓词将第二个参数与第一个参数的尾部统一。

要删除最后一个元素,可以说你的输入列表是将前缀附加到一个元素列表(其值不重要)的结果:

butlast(List, Prefix) :-
  append(Prefix, [_LastValue], List).

你可以将它们结合起来以去除两个极端:
chop(List, Middle):
  tail(List, Tail),
  butlast(Tail, Middle).

-2
这是我的方法:
首先:创建所有可接受的列表组合,当第一个字母和最后一个字母被移除时。
listWithinList(M,L):-
    append([_|_],L2,L),
    append(S,[_|_],L2),

第一个append从列表中删除第一个元素,第二个append从列表中删除最后一个元素。List的组合存储在S中。

第二:我们使用相同的谓词来检查M是否与S中的任何组合相同。

same(L1,L2):-
    L1==L2.

将代码组合在一起:

listWithinList(M,L):-
        append([_|_],L2,L),
        append(S,[_|_],L2),
        ( same(M,S)->write(S),
        write('This combination is correct.') ).   
    same(L1,L2):-
        L1==L2.

例子:

    ?-listWithinList([b,c,d],[a,b,c,d,e]).
    [b, c, d]This combination is correct.
    1true
    false

    ?-listWithinList([b,c,d,e],[a,b,c,d,e]).
    false
    
    ?-listWithinList([a,b,c,d,e],[a,b,c,d,e]).
    false

我喜欢这种方法,但是我希望它不是用if-then-else语句,而是在不匹配时直接返回“False”。 - Pervy Sage

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接