Davvero una bella domanda, +1!
coda di chiamata (e, come un caso speciale, la coda ricorsione) l'ottimizzazione si applica solo se il predicato è deterministico! Questo non è il caso, quindi il tuo predicato richiederà sempre lo spazio locale dello stack, indipendentemente dall'ordine in cui punti gli obiettivi. La versione ricorsiva non-tail è più efficiente in termini di tempo quando si generano tutte le soluzioni perché è necessario eseguire un numero inferiore di unioni durante il backtracking.
EDIT: Mi sto espandendo su questo punto poiché vale la pena studiare la differenza di prestazioni in modo più dettagliato.
In primo luogo, per chiarezza, a rinominare le due versioni differenti per chiarire quale versione di cui sto parlando:
Variante 1: Non-coda ricorsiva:
permute1([], []).
permute1([X|Rest], L) :-
permute1(Rest, L1),
select(X, L, L1).
Variante 2 : Ricorsivo di coda:
permute2([], []).
permute2(L, [P|P1]) :-
select(P, L, L1),
permute2(L1, P1).
Notare che, sebbene la seconda versione n è chiaramente coda ricorsiva, coda chiamata (e quindi anche ricorsione della coda) ottimizzazione aiuta solo se il predicato è deterministico, e quindi non può aiutare quando generiamo tutte le permutazioni, perché i punti di scelta sono ancora lasciati in quel caso.
Nota anche che sto deliberatamente mantenendo il nome della variabile originale e il nome del predicato principale per evitare di introdurre più varianti. Personalmente, preferisco una convenzione di denominazione che chiarisca quali variabili denotano gli elenchi aggiungendo uno s ai loro nomi, in analogia al normale plurale inglese. Inoltre, preferisco i nomi dei predicati che mostrano più chiaramente la natura dichiarativa e relazionale (almeno intenzionale e intenzionale) del codice e raccomandano di evitare nomi imperativi per questo motivo.
consideri ora dispiegarsi la prima variante e parzialmente valutazione per un elenco di 3 elementi. Si comincia con un obiettivo semplice:
?- Xs = [A,B,C], permute1(Xs, L).
e poi gradualmente aprirlo inserendo nella definizione di permute1/2
, rendendo tutte le unificazioni testa esplicito. Nella prima iterazione, otteniamo:
?- Xs = [A,B,C], Xs1 = [B,C], permute1(Xs1, L1), select(A, L, L1).
sto segnando l'unificazione testa in grassetto.
Ora, rimane ancora un obiettivo di permute1/2
. Quindi ripetiamo il processo, ancora una volta di collegare unico organo norma applicabile del predicato nel luogo della sua testa:
?- Xs = [A,B,C], Xs1 = [B,C], Xs2 = [C], permute1(Xs2, L2), select(B, L1, L2), select(A, L, L1).
Un altro passo di questo, e otteniamo:
?- Xs = [A,B,C], Xs1 = [B,C], Xs2 = [C], select(C, L2, []), select(B, L1, L2), select(A, L, L1).
Questo è ciò che l'originale l'obiettivo sembra se abbiamo appena spiegato la definizione di permute1/2
ripetutamente.
Ora, che dire della seconda variante?Anche in questo caso, si parte con un obiettivo semplice:
?- Xs = [A,B,C], permute2(Xs, Ys).
Un'iterazione del dispiegarsi permute2/2
cede la versione equivalente:
?- Xs = [A,B,C], Ys = [P|P1], select(P, Xs, L1), permute2(L1, P1).
e una seconda rendimenti di iterazione:
?- Xs = [A,B,C], Ys = [P|P1], select(P, Xs, L1), Ys1 = [P1|P2], select(P1, L1, L2), permute2(L2, P2).
lascio la terza e ultima iterazione come semplice esercizio che ti consiglio vivamente di fare.
E da questo è chiaro quello che inizialmente probabilmente non avevamo previsto: Una grande differenza sta nelle unificazioni testa, che la prima versione esegue in modo deterministico proprio all'inizio, e la seconda versione esegue più e più volte su backtracking.
Questo famoso esempio mostra che, contrariamente alle aspettative comuni, la ricorsione della coda può essere piuttosto lenta se il codice non è deterministico.
Non riesco davvero a ricordare di usare mai (tutte) le permutazioni senza bisogno e fare affidamento sull'ordine lessicografico. Le permutazioni sono enumerate tradizionalmente in ordine lessicografico e tutte le librerie che forniscono permutazioni le creano in questo ordine. In questo senso sarebbe un po 'strano e molto fuorviante cambiare l'ordine. –
@Boris: hai ragione, [C++] (http://en.cppreference.com/w/cpp/algorithm/next_permutation) per esempio chiama la funzione next_permutation e si limita a consigliare l'ordine lessicografico nella documentazione. Ma un fattore 3 (o più) di accelerazione potrebbe essere significativo. – CapelliC
Guardando il link che hai fornito, sembra che non sia "solo un consiglio", ma nelle specifiche. Ad ogni modo, presumo che l'ordine sia importante solo perché tutte le librerie che ho controllato lo rispettano (e così anche i libri di testo di matematica). C'è un uso per l'ordine o è solo una convenzione? –