计算列表中的元素而忽略相邻重复项

时间:2019-03-04 10:08:07

标签: list prolog

count([], 0).

count(L, N) :- countDistinct(L, 0).

countDistinct([H,H1|T], N) :-
                      (H == H1,
                      countDistinct([H1|T], N));

                      (H =\= H1, N1 is N+1,
                      countDistinct([H1|T], N1)).   

我的方法显然是具有平凡的基本情况,然后以初始N为0的方式调用新的谓词countDistinct。然后,仅当相邻元素不同时,N才递增。

我这样称呼countDistinct的想法错了吗?我应该如何适应它。

1 个答案:

答案 0 :(得分:1)

由于您尝试通过递归解决此问题,因此此答案将采用该方法。同样,此答案将仅涵盖列表绑定和计数未绑定的模式,并且不会使用剪切来删除选择点。您可以  根据需要增强代码。

在为列表创建递归谓词时,我通常从以下模板开始:

process_list([H|T],R) :-
    process_item(H,R),
    process_list(T,R).
process_list([],R).

具有递归的情况:

process_list([H|T],R) :-
    process_item(H,R),
    process_list(T,R).

和基本情况:

process_list([],R).

使用[H|T]解构列表,其中H用于列表的开头,T用于列表的结尾。 R是为了结果。

使用以下方法处理主条目

process_item(H,R)

,列表的尾部使用:

process_list(T,R)

由于这需要处理列表中的两个相邻项,因此需要进行修改:

process_list([H1,H2|T],R) :-
    process_item(H1,H2,R),
    process_list([H2|T],R).
process_list([],0).
process_list([_],1).

NB现在有 2 个基本案例,而不是一个。仅仅因为递归谓词通常是一个递归子句和一个基本案例子句并不意味着它们始终是一个递归子句和一个基本案例子句。

下一步更新process_item

process_item(I1,I1,N,N).
process_item(I1,I2,N0,N) :-
    I1 \== I2,
    N is N0 + 1.

由于使用is/2来增加计数,因此需要传入,更新和传递计数状态,因此变量N0N会出现。

使用状态变量或线程变量时,命名约定是将0附加到输入值上,在输出值上不附加数字,并在与线程进行相同的子句中增加附加的数字。

当项目相同时,不增加计数,方法是:

process_item(I1,I1,N,N).

当项目不同时,使用以下方法递增计数:

process_item(I1,I2,N0,N) :-
    I1 \== I2,
    N is N0 + 1.

在更改process_item的过程中,R变成了N0N,因此这需要更改为process_list

process_list([H1,H2|T],N0,N) :-
    process_item(H1,H2,N0,N1),
    process_list([H2|T],N1,N).

并为此使用辅助谓词,以便原始谓词的签名可以保持不变。

count(L,N) :-
    process_list(L,0,N).

完整代码

count(L,N) :-
    process_list(L,0,N).

process_list([H1,H2|T],N0,N) :-
    process_item(H1,H2,N0,N1),
    process_list([H2|T],N1,N).
process_list([],N,N).
process_list([_],N0,N) :-
    N is N0 + 1.

process_item(I1,I1,N,N).
process_item(I1,I2,N0,N) :-
    I1 \== I2,
    N is N0 + 1.

测试用例

:- begin_tests(count).

test(1,[nondet]) :-
    count([],N),
    assertion( N == 0 ).

test(2,[nondet]) :-
    count([a],N),
    assertion( N == 1 ).

test(3,[nondet]) :-
    count([a,a],N),
    assertion( N == 1 ).

test(4,[nondet]) :-
    count([a,b],N),
    assertion( N == 2 ).

test(5,[nondet]) :-
    count([b,a],N),
    assertion( N == 2 ).

test(6,[nondet]) :-
    count([a,a,b],N),
    assertion( N == 2 ).

test(7,[nondet]) :-
    count([a,b,a],N),
    assertion( N == 3 ).

test(8,[nondet]) :-
    count([b,a,a],N),
    assertion( N == 2 ).

:- end_tests(count).

示例运行

?- run_tests.
% PL-Unit: count ........ done
% All 8 tests passed
true.

使用DCG的解决方案

% Uses DCG Semicontext 
lookahead(C),[C] -->
    [C].

% empty list
% No lookahead needed because last item in list.
count_dcg(N,N) --> [].

% single item in list
% No lookahead needed because only one in list.
count_dcg(N0,N) -->
    [_],
    \+ [_],
    { N is N0 + 1 }.

% Lookahead needed because two items in list and
% only want to remove first item.
count_dcg(N0,N) -->
    [C1],
    lookahead(C2),
    { C1 == C2 },
    count_dcg(N0,N).

% Lookahead needed because two items in list and
% only want to remove first item.
count_dcg(N0,N) -->
    [C1],
    lookahead(C2),
    {
        C1 \== C2,
        N1 is N0 + 1
    },
    count_dcg(N1,N).

count(L,N) :-
    DCG = count_dcg(0,N),
    phrase(DCG,L).

示例运行:

?- run_tests.
% PL-Unit: count ........ done
% All 8 tests passed
true.

使用DCG更好地solution


旁注

在您的示例代码中是使用;/2

;/2取消代码时的典型约定是这样格式化

(
;
)

以使;脱颖而出。

您的代码重新格式化

countDistinct([H,H1|T], N) :-
  (
    (
      H == H1,
      countDistinct([H1|T], N)
    )
  ;
    (
      H =\= H1, 
      N1 is N+1,
      countDistinct([H1|T], N1)
    )
  ).