I'm trying to sort a 10k element list in prolog with bubblesort and I get the out of local stack error. Mergesort seems to be the best option since I don't get any errors for the same input. However I'd really like to get some running times for bubblesort with large input data but I can't. Any ideas?
Here's the code:
%% NOTE: SWI-PROLOG USED
%% generate_list(Limit, N, L): - insert upper limit and length of list N
%% to get a random list with N numbers from 0 to limit
generate_list(_, 0, []).
generate_list(Limit, N, [Y|L]):-
N =\= 0,
random(0, Limit, Y),
N1 is N-1,
generate_list(Limit, N1, L).
%% bubble(L, Ls, max):- insert list L and get max member of list by
%% swapping members from the start of L.
bubble([Z], [], Z).
bubble([X,Y|L], [X|Ls], Z):- X =< Y, bubble([Y|L], Ls, Z).
bubble([X,Y|L], [Y|Ls], Z):- X > Y, bubble([X|L], Ls, Z).
%% bubble_sort(List, Accumulator, Sorted_List)
bubblesort([X], Ls, [X|Ls]).
bubblesort(L, Accumulate, Result):- bubble(L, Ls, Max),
bubblesort(Ls, [Max|Accumulate], Result).
bubble_sort(L, Sorted):- bubblesort(L, [], Sorted).
As you can I see I'm using tail recursion. I've also tried enlarging the stacks by using:
set_prolog_stack(global, limit(100 000 000 000)).
set_prolog_stack(trail, limit(20 000 000 000)).
set_prolog_stack(local, limit(2 000 000 000)).
but it just runs for a bit longer. Eventually I get out of local stack again.
Should I use another language like C and malloc
the list or not use recursion?
Since there are two answers, and no one pointed out explicitly enough the reason why you get into "out of local stack" trouble (Mat says in the comment to your question that your predicates are not deterministic, but does not explain exactly why).
Two of the predicates you have defined, namely,
bubblesort/3
andbubble/3
, have mutually exclusive clauses. But Prolog (at least SWI-Prolog) does not recognize that these are mutually exclusive. So, choice points are created, you don't get tail recursion optimization, and probably no garbage collection (you need to measure using your implementation of choice if you want to know how much goes where and when).You have two different problems.
Problem 1: lists with exactly one element
This problem pops up in both predicates. In the most simple predicate possible:
And then:
This is not surprising; consider:
You can solve this by using a technique called lagging:
Then:
In the definition of
bar_1/2
, the first argument to the first clause is the empty list; the first argument to the second clause is a non-empty list (a list with at least one element, and a tail). Prolog does not create choice points when all clauses are obviously exclusive. What obvious means will depend on the implementation, but usually, when the first arguments to all clauses are all terms with different functors, then no choice points are created.Try the following (you might get different results, but the message is the same):
See this question and the answer by Mat to see how you can use this to make your program deterministic.
Mat, in his answer, uses this approach, if I see correctly.
Problem 2: constraints (conditions) in the body of the clauses
This is the problem with the second and third clause of
bubble/3
. In the textbook "correct" example of choosing the minimum of two elements:Then:
but:
You can solve this in two ways: either by doing what Mat is doing, which is, using
compare/3
, which succeeds deterministically; or, by doing what CapelliC is doing, which is, using an if-then-else.Mat:
And Carlo:
I know there will always be at least as many opinions as heads, but both are fine, depending on what you are doing.
PS
You could use the built in
length/2
to generate a list, and re-write yourgenerate_list/3
like this:The helper
random_pos_ints/2
is a trivial predicate that can be expressed in terms ofmaplist
:Here is a version of
bubble/3
that is deterministic if the first argument is instantiated, so that tail call optimisation (and, more specifically, tail recursion optimisation) applies:Example usage, with the rest of the program unchanged (running times depend on the random list, which is bad if you want to reproduce these results - hint: introduce the random seed as argument to fix this):
For testing different versions, please use a version of the query that you can use to reliably reproduce the same initial list, such as:
The nice thing is: If you just use
zcompare/3
fromlibrary(clpfd)
instead ofcompare/3
, you obtain a version that can be used in all directions:This describes the relation in general terms between integers.
Disclaimer: following the hint by @mat could be more rewarding...
I've played a bit with your code, in my experiment the local stack overflow was thrown with a list length near 2500. Then I've placed some cut:
and I get
so, it's working, but very slowly, showing the quadratic complexity...