Выровнять список в прологе

Я работаю с Prolog всего пару дней. Я понимаю некоторые вещи, но это действительно сбивает с толку меня.

Я должен написать функцию, которая берет список и выравнивает его.

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

функция извлекает внутренние структуры списка.

это то, что у меня есть до сих пор:

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

теперь это работает, когда я звоню:

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

но когда я звоню, чтобы узнать, сплющен ли список, который я ввожу, это возвращает false вместо true:

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

почему это работает с одной стороны, но не другой? Мне кажется, я упускаю что-то очень простое.

7 ответов


определение flatten2/2 вы дали сломан; он на самом деле ведет себя так:

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

Итак, учитывая случай, когда вы уже связали R to [a,b,c,d,e] неудача не удивительно.

ваше определение отбрасывает хвост списков (ListTail) в 3-м предложении-это должно быть убрано и подключено обратно в список, чтобы вернуться через RetList. Вот предложение:

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

этот рекурсивно уменьшает все списки списки в списки одного элемента [x], или пустые списки [], который он выбрасывает. Тогда он накапливается и добавляет их в список на выход.

обратите внимание, что в большинстве реализаций Prolog пустой список [] - это атом и список, поэтому вызов atom([]) и is_list([]) как оценить значение true; это не поможет вам выбросить пустые списки в отличие от атомов характер.


вы можете поддерживать свои списки открытыми, как с указателем на его начало, так и с "окончание отверстия ⁄ свободный указатель" (т. е. logvar) в конце, который вы можете создать, когда конец будет достигнут:

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 --->

вы тогда называете это как

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

таким образом, нет необходимости использовать reverse в любом месте. Этот метод известен как" списки различий", но гораздо проще просто думать об этом как "открытые списки" вместо.


обновление: это гораздо проще закодировать с помощью dcg синтаксис. Поскольку он однонаправлен (первый аргумент должен быть полностью заземлен), почему бы не использовать сокращения в конце концов:

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

тестирование:

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.

если определение было полностью декларативным, последний должен был также преуспеть с X=[c] ; X=[[],c] ; ... ; X=[[c]] ; ...; увы, это не так.

(edit2: упрощенные обе версии, спасибо @матС комментариями!)


список пролога обозначение синтаксический сахар поверх очень простых терминов пролога. Списки прологов обозначаются так:

  1. пустой список представлен атомом []. Почему? Потому что это похоже на математическую нотацию для пустого списка. Они могли бы использовать такой атом, как nil чтобы обозначить пустой список, но они этого не сделали.

  2. непустой список представлен термином ., где первый (самый левый) аргумент -глава списка и второй (самый правый) аргумент -хвост списка, который рекурсивно сам является списком.

примеры:

  • пустой список: [] представляется как атом это:

    []
    
  • список из одного элемента, [a] хранится как

    .(a,[])
    
  • список из двух элементы [a,b] хранится как

    .(a,.(b,[]))
    
  • список из трех элементов, [a,b,c] хранится как

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

рассмотрение главы списка аналогично синтаксическому сахару над той же нотацией:

  • [X|Xs] идентичен .(X,Xs)

  • [A,B|Xs] идентичен .(A,.(B,Xs))

  • [A,B] is (см. выше) идентично .(A,.(B,[]))

Я бы сам написал flatten/2 такой:

%------------------------
% 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( [_|_] ) .

дом на if_//3 и list_truth/2, мы можем реализовать myflatten/2 следующим образом:

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)
   ).

примеры запросов (взяты из других ответов и комментариев к ответам):

?- 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 и not(nil_or_cons_t) describe имеют одинаковые решения, но порядок решения отличается. Подумайте:

?- 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

вот аккумуляторная версия для полноты:

% 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.

Я не нашел решение, используя findall, поэтому я добавлю его. (он будет работать, если список заземлен)

во-первых, мы определяем, как проверить список:

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

и транзитивное замыкание of member, мы называем это member*:

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

сплющенный список-это все решение member*, которые не перечислены:

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

пример:

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

без какого-либо другого предиката, только с хвостовой рекурсией.

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([], []).