在 prolog 中解压一个列表

Decompression of a list in prolog

我需要在 prolog 中解压一个列表,如下例所示:

decode([[a,1],[b,2],[c,1],[d,3]],L). 
L = [a, b, b, c, d, d, d] ; 

我编写了这段代码:

divide(L,X,Y):-length(X,1),append(X,Y,L).

divide2(L,X,Y):-divide(L,[X|_],[Y|_]).


makelist(_,N,[]):- N =< 0 .
makelist(X,Y,[X|Result]):-Y1 is Y-1,makelist(X,Y1,Result).

makelist2(L,L2):-divide2(L,X,Y),makelist(X,Y,L2).

decode([],[]).
decode([H|T],L):-makelist2(H,H2),append(H2,L,L2),decode(T,L2).

当我打电话给

makelist2([a,3],L2).
L2 = [a,a,a].

但是当我打电话时

decode([[a,3],[b,1],[c,4]],L)

连续运行。我做错了什么?

问题出在decode的最后一个子句中appenddecode的顺序。尝试追踪它,或者更好的是,追踪它 "by hand" 看看会发生什么。

另一种方法:参见 this answer。因此,repeat/3 定义为:

% True when L is a list with N repeats of X
repeat(X, N, L) :-
    length(L, N),
    maplist(=(X), L).

您可以将 decode/2 写为:

decode([], []).
decode([[X,N]|XNs], Decoded) :-
    decode(XNs, Decoded_rest),
    repeat(X, N, L),
    append(L, Decoded_rest, Decoded).

但这是一种稍微迂回的方式。您可以定义 repeat/3 的差异列表版本,称为 repeat/4:

repeat(X, N, Reps, Reps_back) :-
    (   succ(N0, N)
    ->  Reps = [X|Reps0],
        repeat(X, N0, Reps0, Reps_back)
    ;   Reps = Reps_back
    ).

然后你可以使用decode/2decode_1/3

的差异列表版本
decode(Encoded, Decoded) :-
    decode_1(Encoded, Decoded, []).

decode_1([], Decoded, Decoded).
decode_1([[X,N]|XNs], Decoded, Decoded_back) :-
    repeat(X, N, Decoded, Decoded_rest),
    decode_1(XNs, Decoded_rest, Decoded_back).


?- decode([[a,1],[b,2],[c,1],[d,3]],L).
L = [a, b, b, c, d, d, d].

?- decode([[a,3],[b,1],[c,0],[d,3]],L).
L = [a, a, a, b, d, d, d].

?- decode([[a,3]],L).
L = [a, a, a].

?- decode([],L).
L = [].

一种紧凑的方式:

decode(L,D) :- foldl(expand,L,[],D).
expand([S,N],L,E) :- findall(S,between(1,N,_),T), append(L,T,E).

findall/3 这是 'old fashioned' Prolog 列表理解工具

主题的另一个变体,使用鲍里斯 repeat/3 谓词的略微修改版本:

% True when L is a list with N repeats of X
repeat([X, N], L) :-
    length(L, N),
    maplist(=(X), L).

decode(Encoded, Decoded) :-
    maplist(repeat, Encoded, Expanded),
    flatten(Expanded, Decoded).

如果Encode = [[a,1],[b,2],[c,1],[d,3]],那么在上面的decode/2中,maplist/3调用会产生Expanded = [[a],[b,b],[c],[d,d,d]],然后flatten/2调用会产生Decoded = [a,b,b,c,d,d,d].

在 SWI Prolog 中,您可以使用 append/2 而不是 flatten/2,因为您只需要一个级别的 "flattening"。


编辑:添加一个 "bidirectional" 版本,使用一点 CLPFD:

rle([], []).
rle([X], [[1,X]]).
rle([X,Y|T], [[1,X]|R]) :-
    X \== Y,     % use dif(X, Y) here, if available
    rle([Y|T], R).
rle([X,X|T], [[N,X]|R]) :-
    N #= N1 + 1,
    rle([X|T], [[N1,X]|R]).

这将产生:

| ?- rle([a,a,a,b,b], L).

L = [[3,a],[2,b]] ? ;

(1 ms) no
| ?- rle(L, [[3,a],[2,b]]).

L = [a,a,a,b,b] ? ;

no
| ?- rle([a,a,a,Y,Y,Z], [X, [N,b],[M,c]]).

M = 1
N = 2
X = [3,a]
Y = b
Z = c ? a

no
| ?- rle([A,B,C], D).

D = [[1,A],[1,B],[1,C]] ? ;

C = B
D = [[1,A],[2,B]] ? ;

B = A
D = [[2,A],[1,C]] ? ;

B = A
C = A
D = [[3,A]] ? ;

(2 ms) no
| ?- rle(A, [B,C]).

A = [D,E]
B = [1,D]
C = [1,E] ? ;

A = [D,E,E]
B = [1,D]
C = [2,E] ? ;

A = [D,E,E,E]
B = [1,D]
C = [3,E] ? ;
...

| ?- rle(A, B).

A = []
B = [] ? ;

A = [C]
B = [[1,C]] ? ;

A = [C,D]
B = [[1,C],[1,D]] ? ;
...

正如@mat 在他的评论中建议的那样,在具有 dif/2 的 Prolog 实现中,dif(X,Y) 优于上面的 X \== Y

您可以使用此代码处理两个方向:

:- use_module(library(lambda)).
% code from Pascal Bourguignon
packRuns([],[]).

packRuns([X],[[X]]).

packRuns([X|Rest],[XRun|Packed]):-
    run(X,Rest,XRun,RRest),
    packRuns(RRest,Packed).


run(Var,[],[Var],[]).

run(Var,[Var|LRest],[Var|VRest],RRest):-
    run(Var,LRest,VRest,RRest).

run(Var,[Other|RRest],[Var],[Other|RRest]):-
    dif(Var,Other).
%end code

pack_1(In, Out) :-
    maplist(\X^Y^(X = [V|_],
              Y = [V, N],
              length(X, N),
              maplist(=(V), X)),
        In, Out).

decode(In, Out) :-
    when((ground(In); ground(Out1)),pack_1(Out1, In)),
    packRuns(Out, Out1).

输出:

 ?- decode([[a,1],[b,2],[c,1],[d,3]],L).

L = [a, b, b, c, d, d, d] .

 ?- decode(L, [a,b,b,c,d,d,d]).

L = [[a, 1], [b, 2], [c, 1], [d, 3]] .

decode 是你的谓词的一个糟糕的名字:正确地完成,你的谓词应该是双向的——如果你说

decode( [[a,1],[b,2],[c,3]] , L )

你应该得到

L = [a,b,b,c,c,c].

如果你说

decode( L , [a,b,b,c,c,c] ) .

你应该得到

L = [[a,1],[b,2],[c,3]].

所以我会使用不同的名称,例如 run_length_encoding/2。我也可能不会使用列表来表示单个 运行 长度,因为 [a,1] 是这个序言术语:.(a,.(1,[])。只需使用一个带有 arity 2 的简单术语——我自己,我喜欢使用 :/2 因为它被定义为一个中缀运算符,所以你可以简单地说 a:1.

试穿尺码:

run_length_encoding( []     , []     ) .  % the run-length encoding of the empty list is the empty list.
run_length_encoding( [X|Xs] , [R|Rs] ) :- % the run-length encoding of a non-empty list is computed by
  rle( Xs , X:1 , T , R ) ,               % - run-length encoding the prefix of the list
  run_length_encoding( T , Rs )           % - and recursively run-length encoding the remainder
  .                                       % Easy!

rle( []     , C:N , []     , C:N ) .  % - the run is complete when the list is exhausted.
rle( [X|Xs] , C:N , [X|Xs] , C:N ) :- % - the run is complete,
  X \= C                              % - when we encounter a break
  .                                   %
rle( [X|Xs] , X:N , T      , R   ) :- % - the run continues if we haven't seen a break, so....
  N1 is N+1 ,                         % - increment the run length,
  rle( Xs, X:N1, T, R )               % - and recurse down.
  .                                   % Easy!

直接回答原始问题,我做错了什么?...

当我运行原始代码时,任何预期的用例"ran indefinitely"都没有产生结果。

阅读主谓词:

decode([],[]).

这表示[]是解码[]的结果。听起来不错。

decode([H|T],L) :- makelist2(H,H2), append(H2,L,L2), decode(T,L2).

这表示 L 是解码 [H|T] 的结果,如果 H2H 的扩展(这就是 makelist2 所做的...... . 也许 - 我们将在下面讨论),并且附加到此结果的 H2 给出另一个列表 L2,这是原始尾部 T 的解码形式。这听起来不正确。如果我解码 [H|T],我应该 (1) 扩展 H,(2) 解码 T 给出 L2,然后 (3) 将 H 附加到 L2 给出 L.

所以更正后的第二个子句是:

decode([H|T], L) :- makelist2(H, H2), decode(T, L2), append(H2, L2, L).

注意 append/3 的参数顺序,调用发生在尾部的 decode 之后。正如 Boris 先前指出的那样,append 和递归 decode 的错误顺序会导致连续的 运行 没有任何输出,因为 append 具有更多未实例化的参数会生成大量decode 成功之前不需要的可能性。

但现在的结果是:

| ?- decode([[a,3]], L).

L = [a,a,a] ? ;

L = [a,a,a,a] ? ;
...

如果您在 Prolog 解释器中手动尝试我们的其他谓词,您会发现 makelist2/2 有一个问题:

它产生了正确的结果,但也产生了一堆不正确的结果。我们来看看makelist2/2。我们可以自己试试这个谓词,看看会发生什么:

| ?- makelist2([a,3], L).

L = [a,a,a] ? ;

L = [a,a,a,a] ? ;
...

存在一个问题:makelist2/2 应该只给出第一个解决方案,但它一直在继续,给出了错误的解决方案。让我们仔细看看 makelist/2:

makelist2(L,L2) :- divide2(L,X,Y), makelist(X,Y,L2).

它采用 [A,N] 形式的列表 L,将其(通过 divide2/3)分为 X = AY = N,然后调用辅助, makelist(X, Y, L2).

makelist(_,N,[]):- N =< 0 .
makelist(X,Y,[X|Result]):-Y1 is Y-1,makelist(X,Y1,Result).

makelist/3 应该通过将第一个参数复制第二个参数中给定的次数来生成列表(第三个参数)。第二个递归子句看起来没问题,但有一个重要缺陷:即使 Y 的值小于或等于 0,它也会成功。因此,即使找到了正确的解决方案,它也会在不正确的解决方案上继续取得成功,因为基本情况允许计数为 =< 0:

| ?- makelist(a,2,L).

L = [a,a] ? ;

L = [a,a,a] ? ;

我们可以修复makelist/2如下:

makelist(_,N,[]):- N =< 0 .
makelist(X,Y,[X|Result]):- Y > 0, Y1 is Y-1, makelist(X,Y1,Result).

现在代码将生成正确的结果。我们只需要修复 decode/2 的第二个子句和 makelist/3.

的第二个子句
| ?- decode([[a,3],[b,4]], L).

L = [a,a,a,b,b,b,b]

yes

完整的原始代码,仅进行了这几处更正,如下所示:

divide(L, X, Y) :- length(X, 1), append(X, Y, L).

divide2(L, X, Y) :- divide(L, [X|_], [Y|_]).

makelist(_, N, []) :- N =< 0 .
makelist(X, Y, [X|Result]) :- Y > 0, Y1 is Y-1, makelist(X,Y1,Result).

makelist2(L, L2) :- divide2(L, X, Y), makelist(X, Y, L2).

decode([], []).
decode([H|T], L) :- makelist2(H,H2), decode(T,L2), append(H2,L2,L).

注意一些简单、直接的改进。谓词 divide2(L, X, Y) 获取包含两个元素的列表 L 并生成每个单独的元素 XY。这个谓词是不必要的,因为在 Prolog 中,您可以通过简单的统一获得这些元素:L = [X, Y]。您可以在 Prolog 解释器中尝试这样做:

| ?- L = [a,3], L = [X,Y].

L = [a,3]
X = a
Y = 3

yes

然后我们可以完全删除 divide/3divide2/3 谓词,并将对 divide2(L, X, Y) 的调用替换为 L = [X,Y] 并将 makelist2/2 减少为:

makelist2(L, L2) :- L = [X, Y], makelist(X, Y, L2).

或者更简单(因为我们可以在子句的头部进行统一):

makelist2([X,Y], L2) :- makelist(X, Y, L2).

您可以删除 makelist2/2 并直接从 decode/2 调用 makelist/2,方法是将 H 直接与其两个元素 [X, N] 统一起来。所以原代码简化为:

makelist(_, N, []) :- N =< 0 .
makelist(X, Y, [X|Result]) :- Y > 0, Y1 is Y-1, makelist(X,Y1,Result).

decode([], []).
decode([[X,N]|T], L) :- makelist(X, N, H2), decode(T, L2), append(H2, L2, L).

并且 makelist/3 可以使用其他答案中提供的方法之一更清楚地执行(例如,请参阅鲍里斯的 repeat/3 谓词).