Бесконечный цикл в прологе? Или просто очень медленно?

Я пытаюсь выяснить, есть ли у меня бесконечный цикл в моей программе на Прологе, или я просто плохо написал его, поэтому он медленный. Я пытаюсь решить проблему цепочек квадратных сумм от dailyprogrammer сабреддит. Для заданного числа N найдите такой порядок чисел от 1 до N (включительно), чтобы сумма каждой пары соседних чисел в этом порядке была полным квадратом. Наименьшее N, для которого это верно, равно 15 с порядком [8, 1, 15, 10, 6, 3, 13, 12, 4, 5, 11, 14, 2, 7, 9]. Это код, который я пытаюсь использовать для решения проблемы:

is_square(Num):- is_square_help(Num, 0).

is_square_help(Num, S):- Num =:= S * S.
is_square_help(Num, S):- 
    Num > S * S,
    T is S+1,
    is_square_help(Num, T).
is_square_help(Num, S):- Num < S * S, fail.

contains(_, []):- fail.
contains(Needle, [Needle|_]).
contains(Needle, [_|Tail]):- contains(Needle, Tail).

nums(0, []).
nums(Num, List) :- length(List, Num), nums_help(Num, List).

nums_help(0, _).
nums_help(Num, List) :- 
    contains(Num, List),
    X is Num - 1,
    nums_help(X, List).

square_sum(Num, List) :- 
    nums(Num, List),
    square_sum_help(List).

square_sum_help([X, Y|T]) :- 
    Z is X + Y,
    is_square(Z),
    square_sum_help(T).

В настоящее время, когда я запускаю square_sum(15, List)., программа не завершается. Я оставил его в покое примерно на 10 минут, и он просто продолжает работать. Я знаю, что есть проблемы, на решение которых уходит много времени, но другие, как сообщается, генерируют ответы порядка миллисекунд. Что я здесь делаю неправильно?


person Lily Mara    schedule 30.01.2018    source источник


Ответы (4)


SWI-Prolog позволяет эту компактную реализацию

square_sum(N,L) :-
    numlist(1,N,T),
    select(D,T,R),
    adj_squares(R,[D],L).

adj_squares([],L,R) :- reverse(L,R).
adj_squares(T,[S|Ss],L) :-
    select(D,T,R),
    float_fractional_part(sqrt(S+D))=:=0,
    adj_squares(R,[D,S|Ss],L).

который завершается очень быстро для N = 15

редактируйте в соответствии с рекомендациями, построение списка по порядку дает лучший код:

square_sum(N,L) :-
    numlist(1,N,T),
    select(D,T,R),
    adj_squares(R,D,L).

adj_squares([],L,[L]).
adj_squares(T,S,[S|L]) :-
    select(D,T,R),
    float_fractional_part(sqrt(S+D))=:=0,
    adj_squares(R,D,L).

изменить

приведенный выше код становится слишком медленным, когда N растет. Я изменил стратегию и теперь пытаюсь найти гамильтонов путь в графе, индуцированный бинарным отношением. Для N=15 это выглядит так

введите здесь описание изображения

(вот код для создания скрипта Graphviz:

square_pairs(N,I,J) :-
    between(1,N,I),
    I1 is I+1,
    between(I1,N,J),
    float_fractional_part(sqrt(I+J))=:=0.
square_pairs_graph(N) :-
    format('graph square_pairs_N_~d {~n', [N]),
    forall(square_pairs(N,I,J), format(' ~d -- ~d;~n', [I,J])),
    writeln('}').

)

и здесь код для поиска пути

hamiltonian_path(N,P) :-
    square_pairs_struct(N,G),
    between(1,N,S),
    extend_front(1,N,G,[S],P).

extend_front(N,N,_,P,P) :- !.
extend_front(Len,Tot,G,[Node|Ins],P) :-
    arg(Node,G,Arcs),
    member(T,Arcs),
    \+memberchk(T,Ins),
    Len1 is Len+1,
    extend_front(Len1,Tot,G,[T,Node|Ins],P).


struct_N_of_E(N,E,S) :-
    findall(E,between(1,N,_),As),
    S=..[graph|As].

square_pairs_struct(N,G) :-
    struct_N_of_E(N,[],G),
    forall(square_pairs(N,I,J), (edge(G,I,J),edge(G,J,I))).
edge(G,I,J) :-
    arg(I,G,A), B=[J|A], nb_setarg(I,G,B).
person CapelliC    schedule 31.01.2018
comment
Почему бы не избавиться от уродливого reverse/2 и сразу построить список в правильном порядке? - person jschimpf; 01.02.2018
comment
@jschimpf: потому что это не хвостовая рекурсия. Вы должны сделать минусы после рекурсивного вызова. - person Tomas By; 02.02.2018
comment
@ Томас, ты ошибаешься, оба шаблона являются хвостовыми рекурсивными. Ячейка списка всегда строится перед вызовом хвоста (во втором случае она просто имеет не конкретизированный хвост). - person jschimpf; 02.02.2018
comment
Хорошо, возможно, современные Прологи умнее, чем когда я узнал об этом. - person Tomas By; 02.02.2018

Вот решение, использующее программирование логики ограничений:

squares_chain(N, Cs) :-
        numlist(1, N, Ns),
        phrase(nums_partners(Ns, []), NPs),
        group_pairs_by_key(NPs, Pairs),
        same_length(Ns, Pairs),
        pairs_values(Pairs, Partners),
        maplist(domain, Is0, Partners),
        circuit([D|Is0]),
        labeling([ff], Is0),
        phrase(chain_(D, [_|Is0]), Cs).

chain_(1, _) --> [].
chain_(Pos0, Ls0) --> [Pos],
        { Pos0 #> 1, Pos #= Pos0 - 1,
          element(Pos0, Ls0, E) },
        chain_(E, Ls0).

plus_one(A, B) :- B #= A + 1.

domain(V, Ls0) :-
        maplist(plus_one, Ls0, Ls),
        foldl(union_, Ls, 1, Domain),
        V in Domain.

union_(N, Dom0, Dom0\/N).

nums_partners([], _) --> [].
nums_partners([N|Rs], Ls) -->
        partners(Ls, N), partners(Rs, N),
        nums_partners(Rs, [N|Ls]).

partners([], _) --> [].
partners([L|Ls], N) -->
        (   { L + N #= _^2 } -> [N-L]
        ;   []
        ),
        partners(Ls, N).

Пример запроса и ответов:

?- squares_chain(15, Cs).
Cs = [9, 7, 2, 14, 11, 5, 4, 12, 13|...] ;
Cs = [8, 1, 15, 10, 6, 3, 13, 12, 4|...] ;
false.

Более длинная последовательность:

?- time(squares_chain(100, Cs)).
15,050,570 inferences, 1.576 CPU in 1.584 seconds (99% CPU, 9549812 Lips)
Cs = [82, 87, 57, 24, 97, 72, 28, 21, 60|...] .
person mat    schedule 03.02.2018
comment
circuit/1 ВСЁ! - person repeat; 04.02.2018
comment
Спасибо за поучительный и элегантный ответ. Я пытался сам, но не смог найти подходящего шаблона, чтобы воспользоваться преимуществами схемы/1. - person CapelliC; 04.02.2018
comment
Еще пара вариантов и мыслей по этому поводу на eclipseclp.org/wiki/Examples/SquareSumChain - person jschimpf; 06.02.2018

Что вы делаете неправильно, так это то, что вы создаете весь список перед началом тестирования.

Два предложения, вызывающие fail, бессмысленны. Их удаление не изменит программу. Единственная причина для этого - если вы делаете что-то побочное, например, вывод на печать.

Ваш код для создания списка и всех перестановок, кажется, работает, но это можно сделать намного проще, используя select/3.

Кажется, у вас нет базового случая в square_sum_help/1, и вы также, похоже, проверяете только каждую другую пару, что привело бы к проблемам через несколько лет или что-то еще, когда ваша программа удосужилась проверить правильный порядок.

Итак, чередуя генерацию и тестирование, вот так

square_sum(Num,List) :-
  upto(Num,[],List0),
  select(X,List0,List1),
  square_sum_helper(X,List1,[],List).

square_sum_helper(X1,Rest0,List0,List) :-
  select(X2,Rest0,Rest),
  Z is X1 + X2,
  is_square(Z,0),
  square_sum_helper(X2,Rest,[X1|List0],List).
square_sum_helper(_,[],List0,List) :- reverse(List0,List).

is_square(Num,S) :-
  Sqr is S * S,
  ( Num =:= Sqr ->
    true
  ; Num > Sqr,
    T is S + 1,
    is_square(Num,T) ).

upto(N,List0,List) :-
  ( N > 0 ->
    M is N - 1,
    upto(M,[N|List0],List)
  ; List = List0 ).

правильный результат получается примерно через 9 мс (SWI Prolog).

?- ( square_sum(15,List), write(List), nl, fail ; true ).
[8,1,15,10,6,3,13,12,4,5,11,14,2,7,9]
[9,7,2,14,11,5,4,12,13,3,6,10,15,1,8]

?- time(square_sum(15,_)).
% 37,449 inferences, 0.009 CPU in 0.009 seconds (100% CPU, 4276412 Lips)

Изменить: исправлены некоторые опечатки.

person Tomas By    schedule 31.01.2018
comment
Я новичок в прологе, поэтому позвольте мне посмотреть, правильно ли я это понимаю. Похоже, что вы выбираете каждую пару из двух чисел, затем проверяете, являются ли эти два числа в сумме простыми, и только если это так, вы добавляете их в выходной список. Это правильно? - person Lily Mara; 31.01.2018
comment
Что ж, я выбираю одну пару, проверяю ее и добавляю, если она верна; если нет, код возвращается и пытается выполнить другое сопряжение. - person Tomas By; 31.01.2018

contains/2: пункт contains(_, []):- fail. содержит ошибки и в лучшем случае избыточен.

вы должны ввести тело !, fail.

Но в этом нет необходимости, потому что нельзя упоминать недоказуемое (предположение о закрытом мире).

кстати contains/2 на самом деле member/2 (встроенный)

person Anton Danilov    schedule 02.02.2018