Prolog - 获取List的中间元素

时间:2017-03-13 19:55:40

标签: list prolog

我想在Prolog中获得列表的中间元素。

谓词middle([1,2,3],M)middle([1,2,3,4],M)都应该返回2。 我被允许使用谓词deleteLast

我知道有类似的posts解决了这个问题,但我找不到只使用deleteLast的问题。

即使语法不正确 - 但到目前为止这是我的解决方案:

middle([], _).
middle([X|XTail|Y], E) :-
   1 is mod(list_length([X|XTail|Y], 2)),
   middle([XTail], E).
middle([X|XTail|Y], E) :-
   0 is mod(list_length([X|XTail|Y], 2)),
   middle([X|XTail], E).
middle([X], X).

问题:这部分是正确的还是我完全走错了路?

4 个答案:

答案 0 :(得分:3)

很抱歉,你所尝试的解决方案完全走错了路。

  • 根据您的要求
  • ,它不会使用deleteLast/2
  • 您正在使用list_length/2,就好像它是一个算术函数,它不是。它是一个谓词。
  • 您的术语语法无效,语义未知,[X|XTail|Y]

在Prolog中,你只需要根据规则来考虑它。这是使用deleteLast/2的方法:

middle([X], X).     % `X` is the middle of the single element list `[X]`
middle([X,_], X).   % `X` is the middle of the two-element list `[X,_]`

% X is the middle of the list `[H|T]` if X is the middle of the list TWithoutLast
%   where TWithoutLast is T with its last element removed
%
middle([H|T], X) :-
    deleteLast(T, TWithoutLast),
    middle(TWithoutLast, X).

我认为deleteLast/2表现良好,如果T为空,则会失败。

您也可以使用same_length/2append/3执行此操作,但是,唉,不使用deleteLast/2

middle(L, M) :-
    same_length(L1, L2),
    append(L1, [M|L2], L).
middle(L, M) :-
    same_length(L1, L2),
    append(L1, [M,_|L2], L).

答案 1 :(得分:1)

这么多不必要的工作,以及不必要的代码。 length/2非常有效,而且是真正的关系。它的第二个参数保证是一个非负整数。所以:

middle(List, Middle) :-
    List = [_|_],           % at least one element
    length(List, Len),
    divmod(Len, 2, Q, R),   % if not available do in two separate steps
    N is Q + R,
    nth1(N, List, Middle).

你准备好了:

?- middle(L, M), numbervars(L).
L = [A],
M = A ;
L = [A, B],
M = A ;
L = [A, B, C],
M = B ;
L = [A, B, C, D],
M = B ;
L = [A, B, C, D, E],
M = C ;
L = [A, B, C, D, E, F],
M = C .

我知道这并不能解决您的问题(answer by @lurker does),但它会回答您的问题。 : - (

答案 2 :(得分:0)

这是我的尝试:

middle(L,M):- append(L1,L2,L),length(L1,N),length(L2,N), reverse(L1,[M|_]).
middle(L,M):- append(L1,L2,L),length(L1,N),length(L2,N1), N is N1+1 ,
              reverse(L1,[M|_]). 

实施例: ? - 中([1,2,3],M)。

M = 2 ;
false.

?- middle([1,2,3,4],M).
M = 2 ;
false.

在您的实现中,问题是通过编写例如:

list_length([X|XTail|Y], 2) 

上面没有给你X作为第一个元素,Y作为最后一个元素,所以我认为它有一些重大问题......

潜伏者指出,你可以在一个子句中编写上述解决方案而不使用reverse/2

middle(L, M) :- append(L1, [M|T], L), length(L1, N), length([M|T], N1), 
                (N1 is N + 1 ; N1 is N + 2).

另外,为了使解决方案更具关系性(另请参见下面的评论),您可以使用CLPFD库并将is/2替换为#=,如:

middle(L, M) :- append(L1, [M|T], L), length(L1, N), length([M|T], N1), 
                (N1 #= N + 1 ; N1 #= N + 2).

答案 3 :(得分:0)

另一个有趣的解决方案是考虑将这个谓词分成两半:

half(List, Left, Right) :-
    half(List, List, Left, Right).
half(L, [], [], L).
half(L, [_], [], L).
half([H|T], [_,_|T2], [H|Left], Right) :-
    half(T, T2, Left, Right).

这个谓词将偶数列表分成两个相等的一半,或者将奇数列表分成两个部分,其中右半部分比左部分多一个元素。它通过第二个参数减少原始列表,通过两个元素(每个递归调用)减少原始列表,同时通过第一个参数将每个递归调用的原始列表减少一个元素。当它递归到第二个参数为零或一个元素的长度时,第一个参数代表左边的一半,这是右边的列表。

half/3的示例结果是:

| ?- half([a,b,c], L, R).

L = [a]
R = [b,c] ? a

(1 ms) no
| ?- half([a,b,c,d], L, R).

L = [a,b]
R = [c,d] ? a

no
| ?-

我们不能很好地使用它来轻松找到中间元素,因为在偶数情况下,我们想要左手列表的最后一个元素。如果我们可以通过一个额外的元素来偏置右侧列表,那么我们可以选择右侧的一半作为“中间”元素。我们可以使用deleteLast/2此处完成此操作:

middle([X], X).
middle(List, Middle) :-
    deleteLast(List, ListWithoutLast),
    half(ListWithoutLast, _, [Middle|_]).

原始列表的右半部分列表的头部,删除了最后一个元素,是“中间”元素。我们也可以简单half/3并将其与middle/2合并,因为我们并不真正需要half/3所做的一切(例如,我们不需要左边 - 手牌清单,或右手清单的尾部):

middle([X], X).
middle(List, Middle) :-
    deleteLast(List, ListWithoutLast),
    middle(ListWithoutLast, ListWithoutLast, Middle).
middle([M|_], [], M).
middle([M|_], [_], M).
middle([_|T], [_,_|T2], Right) :-
    middle(T, T2, Right).

<小时/> 另一种方法是修改half/3以将原始列表的分割偏向右半部分,这样就无需使用deleteLast/2

modified_half(List, Left, Right) :-
    modified_half(List, List, Left, Right).
modified_half(L, [_], [], L).
modified_half(L, [_,_], [], L).
modified_half([H|T], [_,_,X|T2], [H|Left], Right) :-
    modified_half(T, [X|T2], Left, Right).

这会使右手列表偏向于在左边的“费用”处有一个额外的元素:

| ?- modified_half([a,b,c,d,e], L, R).

L = [a,b]
R = [c,d,e] ? a

no
| ?- modified_half([a,b,c,d,e,f], L, R).

L = [a,b]
R = [c,d,e,f] ? a

no
| ?-

现在我们可以看到,根据原始定义,中间元素只是右侧列表的头部。我们可以使用上面的内容为middle/2创建一个新定义。正如我们之前使用half/3所做的那样,我们可以忽略除了右半部分之外的所有内容,我们可以消除左半部分,因为我们不需要它,并创建一个统一的middle/2谓词:< / p>

middle(List, Middle) :-
    middle(List, List, Middle).
middle([M|_], [_,_], M).
middle([M|_], [_], M).
middle([_|T], [_,_,X|T2], Middle) :-
    middle(T, [X|T2], Middle).

这会将原始列表一次减少一个元素(第一个参数),一次减少两个元素(第二个参数),直到第二个参数减少为一个或两个元素。然后它将头部第一个参数视为中间元素:

这给出了:

| ?- middle([a,b,c], M).

M = b ? ;

no
| ?- middle([a,b,c,d], M).

M = b ? ;

no
| ?- middle(L, M).

L = [M,_] ? ;

L = [M] ? ;

L = [_,M,_,_] ? ;

L = [_,M,_] ? ;

L = [_,_,M,_,_,_] ? ;

L = [_,_,M,_,_] ? ;

L = [_,_,_,M,_,_,_,_] ?
...