Aplatir une liste dans Prolog

Je ne travaille avec Prolog que depuis quelques jours. Je comprends certaines choses, mais c'est vraiment déroutant moi.

je suis supposé écrire une fonction qui prend une liste et l'aplatit.

?- flatten([a,[b,c],[[d],[],[e]]],Xs).  
Xs = [a,b,c,d,e].                           % expected result

La fonction prend les structures internes de la liste.

c'est Ce que j'ai jusqu'à présent:

flatten2([],[]).
flatten2([Atom|ListTail],[Atom|RetList]) :-
      atom(Atom), flatten2(ListTail,RetList).
flatten2([List|ListTail],RetList) :-
      flatten2(List,RetList).

maintenant, ça marche quand j'appelle:

?- flatten2([a,[b,c],[[d],[],[e]]], R).
R = [a,b,c,d,e].                         % works as expected!

mais quand J'appelle pour voir si une liste que j'entre est déjà aplatie, retourne false au lieu de true :

?- flatten2([a,[b,c],[[d],[],[e]]], [a,b,c,d,e]).
false.                                   % BAD result!

pourquoi cela fonctionne - t-il d'un côté, mais pas de l'autre? J'ai l'impression de rater quelque chose de très simple.

21
demandé sur mat 2012-01-30 09:02:56

7 réponses

la définition de flatten2/2 que vous avez donnée est grillée; elle se comporte en fait comme ceci:

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

donc , étant donné le cas où vous avez déjà lié R à [a,b,c,d,e] , l'échec n'est pas surprenant.

votre définition est de jeter la queue des listes ( ListTail ) dans la troisième clause - cela doit être rangé et connecté de nouveau dans la liste pour retourner via RetList . Voici une suggestion:

flatten2([], []) :- !.
flatten2([L|Ls], FlatL) :-
    !,
    flatten2(L, NewL),
    flatten2(Ls, NewLs),
    append(NewL, NewLs, FlatL).
flatten2(L, [L]).

celui-ci réduit récursivement toutes les listes de listes en une seule liste d'articles [x] , ou des listes vides [] qu'il jette. Puis, il accumule et les ajoute tous dans une liste à nouveau sur la sortie.

notez que, dans la plupart des implémentations Prolog, la liste vide [] est un atome et une liste, donc l'appel à atom([]) et is_list([]) tous les deux évaluer à vrai; cela ne vous aidera pas jeter vide listes contrairement à caractère atomes.

18
répondu sharky 2012-01-30 05:39:41

vous pouvez maintenir vos listes ouvertes, avec à la fois un pointeur à son début, et un pointeur de fin de trou libre" (c.-à-d. logvar) à son extrémité, que vous pouvez ensuite instancier lorsque la fin est atteinte:

flatten2( [], Z, Z):- !.                                        % ---> X
flatten2( [Atom|ListTail], [Atom|X], Z) :-                      %      .
    \+is_list(Atom), !,                                         %      .
    flatten2( ListTail, X, Z).                                  %      Y
flatten2( [List|ListTail], X, Z) :-                             %      .
    flatten2( List,     X, Y),       % from X to Y, and then    %      .
    flatten2( ListTail, Y, Z).       % from Y to Z              %      Z --->

vous l'appelez alors

flatten2( A, B):- flatten2( A, B, []).

comme ça il n'y a pas besoin d'utiliser reverse n'importe où. Cette technique est connue sous le nom de "listes de différences", mais il est beaucoup plus facile d'y penser comme "listes ouvertes" à la place.


mise à jour: c'est beaucoup plus facile codé en utilisant la syntaxe . Puisqu'il est unidirectionnel (le premier argument doit être entièrement fondé), pourquoi ne pas utiliser les coupes après tout:

flattn([]) --> [], !.
flattn([A|T]) --> {\+is_list(A)}, [A], !, flattn(T).
flattn([A|T]) --> flattn(A), flattn(T).

Test:

16 ?- phrase(flattn([a,[b,c],[[d],[],[e]]]), [a, b, c, d, e]).
true.

17 ?- phrase(flattn([a,[b,c],[[d],[],[e]]]), R).
R = [a, b, c, d, e].

18 ?- phrase(flattn([a,[b,X],[[d],[],[e]]]), [a, b, c, d, e]).
X = c.

si la définition était entièrement déclarative, la dernière aurait dû réussir aussi avec X=[c] ; X=[[],c] ; ... ; X=[[c]] ; ... ; hélas, il Non.

( edit2 : a simplifié les deux versions, grâce aux commentaires de @ mat !)

7
répondu Will Ness 2017-05-23 12:09:38

la notation de la liste de Prolog est syntactic sugar en plus de termes prolog très simples. Les listes Prolog sont indiquées ainsi:

  1. La liste vide est représentée par l'atome [] . Pourquoi? Parce que cela ressemble à la notation mathématique pour une liste vide. Ils auraient pu utiliser un atome comme nil pour dénoter la liste vide mais ils ne l'ont pas fait.

  2. Un non-vide list est représenté par le terme . , où le premier argument (le plus à gauche) est le head de la liste et le second argument (le plus à droite) est le tail de la liste, qui est, récursivement, elle-même une liste.

quelques exemples:

  • Une liste vide: [] est représenté comme l'atome, c'est:

    []
    
  • Une liste d'un élément, le [a] est stockée en interne comme

    .(a,[])
    
  • Une liste de deux éléments [a,b] est stockée en interne comme

    .(a,.(b,[]))
    
  • Une liste de trois éléments, [a,b,c] est stockée en interne comme

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

L'examen de la tête de la liste est également syntaxique sugar over la même notation:

  • [X|Xs] est identique à .(X,Xs)

  • [A,B|Xs] est identique à .(A,.(B,Xs))

  • [A,B] est (voir ci-dessus) identique à .(A,.(B,[]))

moi-même, j'écrirais flatten/2 comme ceci:

%------------------------
% public : flatten a list
%------------------------
flatten( X , R ) :-
  flatten( X , [] , T ) ,
  reverse( T , R )
  .

%--------------------------------------------
% private : flatten a list into reverse order
%--------------------------------------------
flatten( [] , R , R ) .        % the empty list signals the end of recursion
flatten( [X|Xs] , T , R ) :-   % anything else is flattened by
  flatten_head( X , T , T1 ) , % - flattening the head, and
  flatten( Xs , T1 , R )       % - flattening the tail
  .                            %

%-------------------------------------
% private : flatten the head of a list
%-------------------------------------
flatten_head( X , T , [X|T] ) :- % if the head is a not a list
  \+ list(X) ,                   % - simply prepend it to the accumulator.
  ! .                            %
flatten_head( X , T , R     ) :- % if the head is a list
  flatten( X , T , R )           % - recurse down and flatten it.
  .

%-----------------------
% what's a list, anyway?
%-----------------------
list( X ) :- var(X) , ! , fail .
list( []    ) .
list( [_|_] ) .
1
répondu Nicholas Carey 2015-04-17 17:13:59

en se basant sur if_//3 et list_truth/2 , nous pouvons implémenter myflatten/2 comme suit:

myflatten(Xs,Ys) :-
   phrase(myflatten_aux(Xs),Ys).

myflatten_aux([]) --> [].
myflatten_aux([T|Ts]) --> 
   if_(neither_nil_nor_cons_t(T), [T], myflatten_aux(T)),
   myflatten_aux(Ts).

:- use_module(library(dialect/sicstus/block)).

:- block neither_nil_nor_cons(-).
neither_nil_nor_cons(X) :-
   \+nil_or_cons(X).

nil_or_cons([]).
nil_or_cons([_|_]).

neither_nil_nor_cons_t(X,Truth) :-
   (  nonvar(X)
   -> (  neither_nil_nor_cons(X) -> Truth = true
      ;                             Truth = false
      )
   ;  nonvar(Truth) 
   -> (  Truth == true -> neither_nil_nor_cons(X)
      ;  Truth == false,  nil_or_cons(X)
      )
   ;  Truth = true,  neither_nil_nor_cons(X)
   ;  Truth = false, nil_or_cons(X)
   ).

exemples de questions (tirées d'autres réponses et commentaires à des réponses):

?- myflatten([[4],[[5,6],[7,[8],[9,[10,11]]]]], Xs).
Xs = [4, 5, 6, 7, 8, 9, 10, 11].

?- myflatten([1,[8,3],[3,[5,6],2],8], Xs).
Xs = [1, 8, 3, 3, 5, 6, 2, 8].

?- myflatten([a,[b,c],[],[[[d]]]], Xs).
Xs = [a, b, c, d].

?- myflatten([a,[b,c],[[d],[],[e]]], Xs).
Xs = [a, b, c, d, e].

neither_nil_nor_cons_t et not(nil_or_cons_t) décrivent les mêmes solutions, mais l'ordre de la solution diffère. Considérons:

?- myflatten([A,B,C],Xs), A=a,B=b,C=c.
A = a,
B = b,
C = c,
Xs = [a, b, c] ;                       % does not terminate universally
1
répondu repeat 2017-05-23 11:54:31

Voici une version à base d'accumulateurs pour être complète:

% flatten/2
flatten(List, Result) :- flatten(List, [], Result).

% auxiliary predicate flatten/3
flatten([], Result, Result).
flatten([Head| Tail], Part, Result) :- 
    is_list(Head),
    !, 
    flatten(Head, HR),
    append(Part, HR, PR),
    flatten(Tail, PR, Result).
flatten([Head| Tail], Part, Result) :- 
    append(Part, [Head], PR),
    flatten(Tail, PR, Result).
flatten(X, Part, Result) :-
    fail.
1
répondu FK82 2015-06-15 14:53:09

Je n'ai pas trouvé de solution en utilisant findall , donc je vais l'ajouter. (cela ne fonctionne pas si la liste est au sol)

tout d'Abord, nous définissons comment tester une liste:

list(X) :- var(X), !, fail.
list([]).
list([_|_]).

et la fermeture transitoire de member , nous l'appelons member* :

'member*'(X, Y) :- member(X, Y).
'member*'(X, Y) :- member(Z, Y), 'member*'(X, Z).

la liste aplatie est toute la solution de member* qui ne sont pas des listes:

flatten(X, Y) :- findall(Z, ('member*'(Z, X), \+ list(Z)), Y).

exemple:

?- flatten([[4],[[5,6],[7,[8],[9,[10,11]]]]],Y).
Y = [4, 5, 6, 7, 8, 9, 10, 11].
0
répondu fferri 2015-06-13 20:45:31

sans autre prédicat, avec recursion de la queue seulement.

flatten([[X|S]|T], F) :- flatten([X|[S|T]], F).
flatten([[]|S], F) :- flatten(S, F).
flatten([X|S], [X|T]) :- \+(X = []), \+(X = [_|_]), flatten(S, T).
flatten([], []).
0
répondu Loic 2017-01-17 11:02:27