2

I try to apply Luhn algorithm in SWI-Prolog. But I get some problem in running. When I test with some simple number like 123, it gives out result fast. If I test with longer number like 5379173895860200, it runs so long time that I can only abort this execution. Need help to find problem. code:

luhn(N):-
    spliter(N,Y),
    reverse(Y, Z),
    check(Z,X),
    sum_all(X, Res),
    T is Res mod 10,
    T is 0.

spliter(0,[]).
spliter(N,L):-
    N1 is floor(N/10),
    X is N mod 10,
    spliter(N1, L2),
    L = [X|L2].

check(A,B):-
    double(A,B,_).

double([],[],0).
double([H|T], [H1|T1], C):-
    double(T,T1, C1),
    C is C1 +1,
    H1 is H*(1+ C mod 2).

sum_all([],0).
sum_all([H|T],Sum):-
    sum_all(T,Subsum),
    X is floor(H/10),
    Y is H mod 10,
    Sum is (Subsum + X + Y).
false
  • 10,264
  • 13
  • 101
  • 209
G_cy
  • 994
  • 3
  • 13
  • 28

2 Answers2

2

There is no need to take a big number to see the problem with your code. It suffices to consider luhn(1) which loops as well and the "efficient" luhn(0).

The problem you have is not one of efficiency but rather termination. Termination is a very subtle notion in Prolog. If you use Prolog via a toplevel loop, many systems show you only the first answer. And some even do not show any further answer should the query contain no variables. In this manner you can easily get the false impression that your program works and terminates when in fact it does not.

There is a very simple way, how you can test for termination in any system. Simply add the goal false to your query. Now even luhn(0),false loops.

You can proceed even further and add such false goals into your program1, thereby reducing the size of the text you have to understand. In your case it is sufficient to consider instead:

luhn(N):-
    spliter(N,Y), false,
    reverse(Y, Z),
    check(Z,X),
    sum_all(X, Res),
    T is Res mod 10,
    T is 0.

spliter(0,[]) :- false.
spliter(N,L):-
    N1 is floor(N/10),
    X is N mod 10,
    spliter(N1, L2), false,
    L = [X|L2].

This tiny part of your program, (called a ) is sufficient to understand non-termination. In fact, any integer N will lead to a loop. What you need is to add N > 0 directly as the first goal of spliter/2.

For more see

Fine Print
1 Actually, this will only work in pure, monotonic programs like your program.

false
  • 10,264
  • 13
  • 101
  • 209
  • 1
    I am a little confused now. I always consider the base condition as the sign of termination, which is similar to the `return null` in JAVA recursion. As you mean they are not the same thing? function will not end when it reach the base condition prolog? – G_cy Aug 02 '15 at 19:56
  • 1
    @G_cy: Good reason to be confused! But the "base case" is not `return null;` it is rather `yield value;` so to speak. That is: Here is one answer, but maybe I have more than that! – false Aug 02 '15 at 20:16
  • 1
    Ahha! I get it. In this situation, it not like something in a list that can terminate at when it is empty. It will goes on doing mod calculation. What a mistake! So helpful, dude! – G_cy Aug 02 '15 at 20:26
  • 1
    @G_cy. This is the reason why looking at more than the first answer to Prolog queries is good practice. – repeat Aug 02 '15 at 21:20
1

Here is my translation of Python from Wikipedia

is_luhn_valid(Card_number):-
    luhn_checksum(Card_number, 0).

luhn_checksum(Card_number, Checksum) :-
    digits_of(Card_number, Digits),
    findall(D, (nth0(I, Digits, D), I mod 2 =:= 0), Odd_digits),
    findall(D, (nth0(I, Digits, D), I mod 2 =:= 1), Even_digits),
    sum_list(Odd_digits, Checksum_t),
    findall(S, (
        member(T, Even_digits),
        T1 is T * 2,
        digits_of(T1, D1),
        sum_list(D1, S)
    ), St),
    sum_list(St, St1),
    Checksum is (Checksum_t + St1) mod 10.

digits_of(Number, Digits) :-
    number_codes(Number, Cs),
    maplist(code_digit, Cs, Digits).
code_digit(C, D) :- D is C - 0'0.

apart being more verbose, it seems to be correct wrt the test case from the above page. But:

?- is_luhn_valid(123).
false.

while your code:

?- luhn(123).
true ;
true ;
...

and, of course

?- luhn(124).
....

doesn't terminate. So, you're stick in a failure loop, where Prolog is asked every time to try to prove an unsolvable goal...

A fragment of trace:

?- leash(-all),trace.
true.

[trace] ?- luhn(124).
   Call: (7) so:luhn(124)
   Call: (8) so:spliter(124, _G9437)
...
   Exit: (8) 2 is 12 mod 10
   Call: (8) 2 is 0
   Fail: (8) 2 is 0
   Redo: (11) so:spliter(0, _G9461)
   Call: (12) _G9465 is floor(0/10)
...

The problem seems to be that spliter/2 keeps adding 0s in front of the sequence, while it should fail instead.

About efficiency: my snippet can be rewritten as

luhn_checksum(Card_number, Checksum) :-
    digits_of(Card_number, Digits),
    aggregate_all(sum(V), (
        nth0(I, Digits, D),
        (   I mod 2 =:= 0
        ->  V = D           % Odd_digits
        ;   Dt is D * 2,    % Even_digits
            digits_of(Dt, Ds),
            sum_list(Ds, V)
        )),
        Checksum_t),
    Checksum is Checksum_t mod 10.

making use of library(aggregate)

edit

I think spliter/2 should check if N>0, otherwise it will recurse forever... try

spliter(N,L):- N>0,
    N1 is floor(N/10),
    ...
CapelliC
  • 59,646
  • 5
  • 47
  • 90
  • it seems that in 124 example, when the final sum T= 12 fail the test of even divided by 10, my program call splinter/2 again. I don't know why it does that. need help. – G_cy Aug 01 '15 at 19:48