Выровнять список в прологе
Я работаю с 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: упрощенные обе версии, спасибо @матС комментариями!)
список пролога обозначение синтаксический сахар поверх очень простых терминов пролога. Списки прологов обозначаются так:
пустой список представлен атомом
[]
. Почему? Потому что это похоже на математическую нотацию для пустого списка. Они могли бы использовать такой атом, какnil
чтобы обозначить пустой список, но они этого не сделали.непустой список представлен термином
.
, где первый (самый левый) аргумент -глава списка и второй (самый правый) аргумент -хвост списка, который рекурсивно сам является списком.
примеры:
-
пустой список:
[]
представляется как атом это:[]
-
список из одного элемента,
[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([], []).