Prolog Does the order of recursion matter?

lefunction

I have a question about the difference between two solutions to a problem. The problem asks to transform a list to a truncated list like so:

?- reduce([a,a,a,b,b,c,c,b,b,d,d],Z).
Z = [a,b,c,b,d].

This first solution needs an extra step that reverses the list:

reduce([X|Xs],Z) :-
   reduce(X,Xs,Y,[X]),
   reverse(Y,Z).

reduce(X,[L|Ls],Y,List) :-
    (  X=L
    -> reduce(X,Ls,Y,List)
    ;  reduce(L,Ls,Y,[L|List])
    ).
reduce(_,[],Y,Y).

The second solution does not require reverse/2:

reduced([X|Xs],Result) :- 
    reduced(Xs,List),
    List=[A|_],
    (  A=X
    -> Result=List
    ;  Result=[X|List]
    ),
    !.
reduced(Result,Result).

What are the optimization considerations when performing recursion before or after a series of statements? Does the order of the conditions matters? My inclination is to think that doing all the recursion upfront is the way to go, especially because building the list backwards is necessary here.

user1812457

When you optimize anything, make sure to measure first! (most of us tend to forget this....)

When you optimize Prolog, look out for the following:

  • Tail recursion tends to do better (so there goes your "before or after series of statements" question);
  • Avoid creating choice points you don't need (this depends on the Prolog implementation)
  • Use an optimal algorithm (as in, don't traverse a list twice if you don't have to).

A solution that is "optimized" for a more or less standard Prolog implementation will look maybe a bit different. I will name it list_uniq (in analogy to the command-line uniq tool):

list_uniq([], []). % Base case
list_uniq([H|T], U) :-
    list_uniq_1(T, H, U). % Helper predicate

list_uniq_1([], X, [X]).
list_uniq_1([H|T], X, U) :-
    (   H == X
    ->  list_uniq_1(T, X, U)
    ;   [X|U1] = U,
        list_uniq_1(T, H, U1)
    ).

It is different from the reduce0/2 by @CapelliC because it uses lagging to avoid the inherent non-determinism of [X|Xs] and [X,X|Xs] in the first argument.

Now to the claim that it is "optimized":

  • It traverses the list exactly once (no need for reversing)
  • It it tail-recursive
  • It does not create and discard choice points

You will get the same 12 inferences as @CapelliC, and if you then use a somewhat longer list, you will start to see differences:

?- length(As, 100000), maplist(=(a), As),
   length(Bs, 100000), maplist(=(b), Bs),
   length(Cs, 100000), maplist(=(c), Cs),
   append([As, Bs, Cs, As, Cs, Bs], L),
   time(list_uniq(L, U)).
% 600,006 inferences, 0.057 CPU in 0.057 seconds (100% CPU, 10499893 Lips)
As = [a, a, a, a, a, a, a, a, a|...],
Bs = [b, b, b, b, b, b, b, b, b|...],
Cs = [c, c, c, c, c, c, c, c, c|...],
L = [a, a, a, a, a, a, a, a, a|...],
U = [a, b, c, a, c, b].

The same query with reduce0, reduce1, reduce2 from @CapelliC's answer:

% reduce0(L, U)
% 600,001 inferences, 0.125 CPU in 0.125 seconds (100% CPU, 4813955 Lips)
% reduce1(L, U)
% 1,200,012 inferences, 0.393 CPU in 0.394 seconds (100% CPU, 3050034 Lips)
% reduce2(L, U)
% 2,400,004 inferences, 0.859 CPU in 0.861 seconds (100% CPU, 2792792 Lips)

So, creating and discarding choice points with cuts (!) has a price, too.

However, list_uniq/2, as it stands, can be wrong for queries where the first argument is not ground:

?- list_uniq([a,B], [a,b]).
B = b. % OK

?- list_uniq([a,A], [a]).
false. % WRONG!

reduce0/2 and reduce1/2 can be wrong, too:

?- reduce0([a,B], [a,b]).
false.

?- reduce1([a,B], [a,b]).
false.

As for reduce2/2, I am not sure about this one:

?- reduce2([a,A], [a,a]).
A = a.

Instead, using the definition of if_/3 from this answer:

list_uniq_d([], []). % Base case
list_uniq_d([H|T], U) :-
    list_uniq_d_1(T, H, U). % Helper predicate

list_uniq_d_1([], X, [X]).
list_uniq_d_1([H|T], X, U) :-
    if_(H = X,
        list_uniq_d_1(T, H, U),
        (   [X|U1] = U,
            list_uniq_d_1(T, H, U1)
        )
    ).

With it:

?- list_uniq_d([a,a,a,b], U).
U = [a, b].

?- list_uniq_d([a,a,a,b,b], U).
U = [a, b].

?- list_uniq_d([a,A], U).
A = a,
U = [a] ;
U = [a, A],
dif(A, a).

?- list_uniq_d([a,A], [a]).
A = a ;
false. % Dangling choice point

?- list_uniq_d([a,A], [a,a]).
false.

?- list_uniq_d([a,B], [a,b]).
B = b.

?- list_uniq_d([a,A], [a,a]).
false.

It takes longer, but the predicate seems to be correct. With the same query as used for the other timings:

% 3,000,007 inferences, 1.140 CPU in 1.141 seconds (100% CPU, 2631644 Lips)

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

From Dev

Prolog Does the order of recursion matter?

From Dev

PROLOG: Determining if elements in list are equal if order does not matter

From Java

Does annotations order matter?

From Dev

Does order of conditions in $and matter?

From Dev

Prolog - Confusion With Order of Printing in Recursion

From Dev

SQL - does order of OR conditions matter?

From Java

Does the order of members in a struct matter?

From Dev

Does the order of natural joins matter

From Dev

Does the order of subscribeOn and observeOn matter?

From Dev

Does Python import order matter

From Dev

Does variable order matter for sscanf?

From Dev

Does the order of a Java class matter?

From Dev

Does parameter order matter with tar?

From Dev

xargs: Does order of options matter?

From Dev

Does the order of rules in ABNF matter?

From Dev

.zshrc configuration, does order matter?

From Dev

Does the order of header properties matter?

From Dev

Does the order of constraints in a pred matter?

From Dev

Does order of html meta tags matter?

From Dev

Does the order of partitioned columns in WHERE clause matter

From Dev

Does the order of Babel 6 presets matter?

From Dev

Does class/function order matter in C++?

From Dev

Why does the order of css selectors matter?

From Dev

Boolean equal: 0 == a, does operand order matter?

From Dev

Does order of inheritance between class and interface matter?

From Dev

Does the order in which dynamic libraries are loaded matter?

From Java

Why does order of mutable borrows matter in Rust?

From Dev

Why does declaration order matter for generic members?

From Dev

Does the order of implicit parameters matter in Scala?

Related Related

HotTag

Archive