我想在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).
问题:这部分是正确的还是我完全走错了路?
答案 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/2
和append/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,_,_,_,_] ?
...