Prolog:有效地实现Luhn算法

时间:2015-08-01 06:05:22

标签: algorithm prolog failure-slice luhn

我尝试在SWI-Prolog中应用Luhn算法。但是我在跑步时遇到了一些问题。当我用123之类的简单数字进行测试时,它会快速给出结果。如果我测试更长的数字,如5379173895860200,它会运行很长时间,我只能中止此执行。需要帮助才能找到问题。 代码:

UIStackView

2 个答案:

答案 0 :(得分:2)

没有必要用大号来查看代码的问题。只需考虑luhn(1)哪个循环以及“有效”luhn(0)

就足够了

你遇到的问题不是效率问题,而是终止问题。在Prolog中终止是一个非常微妙的概念。如果您通过顶层循环使用Prolog,许多系统只显示第一个答案。如果查询不包含变量,有些甚至不会显示任何进一步的答案。通过这种方式,您可以轻松获得程序正常工作的错误印象,并在实际情况下不会终止。

有一种非常简单的方法,如何在任何系统中测试终止。只需将目标 false 添加到您的查询中即可。现在甚至luhn(0), false 循环。

您可以继续前进,并将 false 目标添加到您的程序 1 中,从而减少您必须理解的文本的大小。在您的情况下,可以考虑改为:

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].

程序的这一小部分(称为)足以理解非终止。实际上,任何整数N都会导致循环。您需要的是直接添加N > 0作为spliter/2的第一个目标。

有关详情,请参阅

精细打印
1实际上,这只适用于程序等纯粹的单调程序。

答案 1 :(得分:1)

以下是我对Wikipedia

的Python翻译
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.
除了更详细,从上面的页面看测试用例似乎是正确的。但是:

?- is_luhn_valid(123).
false.

代码:

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

,当然还有

?- luhn(124).
....

不会终止。所以,你坚持一个失败循环,每次都要求Prolog试图证明一个无法解决的目标......

跟踪片段:

?- 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)
...

问题似乎是spliter / 2在序列前面不断添加0,而它应该失败。

关于效率:我的代码段可以改写为

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.

利用图书馆(aggregate

修改

我认为spliter / 2应检查N> 0,否则它将永远递归... 尝试

spliter(N,L):- N>0,
    N1 is floor(N/10),
    ...