To count down from a number using Prolog

802 views Asked by At

Trivial question but I want the program to return a list of the numbers less than or equal to a given number. For example, CountD(4,L). should give [4,3,2,1]. This is what I have so far:

CountD(1,[]).
CountD(Num,List):- [Num|List], CountD(M,List), Num is M+1.
3

There are 3 answers

1
AudioBubble On

This is also a solution:

countd(N, [N|List0]) :-
    succ(N0, N),
    countd(N0, List0).
countd(0, []).

Here, the important line is succ(N0, N). This will only succeed for N > 0, and will fail when the second argument is 0 (it will also raise an error if N is not a non-negative integer).

When succ(N0, N) fails, the second clause will be evaluated. It will only succeed when its arguments are 0 and the empty list.

0
false On

@Boris' answer is perfect. I would just like to explain, why your original program, and that of another answer does not terminate.

At first sight, it seems to work, after all we get an answer:

?- countD(4,L).
   L = [4, 3, 2, 1]

But note that Prolog showed us only the first answer, there are more, waiting ...

?- countD(4,L).
   L = [4, 3, 2, 1]
;  loops.

The best way to understand termination in Prolog is to concentrate on the relevant parts of your program. And the nice thing in Prolog is that while its actual control flow is quite complex, it often suffices to look at a very tiny part of your program to determine certain properties. So there is no need to "understand everything at once".

Instead of looking at everything, I will take the following :

countD(0,[]) :- false.
countD(Num,List) :-
   List = [Num|L],
   countD(M,L), false,
   Num is M+1.

In the first line, by inserting false at the fact, the fact is effectively removed. So no matter what it describes exactly, the 0 or 1 does not have any influence on the termination of this predicate. In general, non-termination of any failure-slice implies non-termination of the original program.

Of course we have to find the right slice. That's a bit of experience. But once it is found, its termination properties are easy to check.

To fix this problem, we can say immediately that adding a further clause will not improve termination (provided the program is a pure, monotonic one). We need to change something in the visible part. Boris did this by adding some check for Num before the actual recursion.

4
Grzegorz Adam Kowalski On
countD(0,[]).
countD(Num,List) :- List = [Num|L], countD(M,L), Num is M+1.

UPDATED:

Fixes problems found by @false and @boris in comments:

countD(Num,[Num|L]) :- Num > 0, M is Num-1, countD(M,L).
countD(0,[]).