Несколько конструкторов в Prolog

Я пытался реализовать различные формы запросов в Hailstone Sequence.

Последовательности градин — это последовательности положительных целых чисел со следующими свойствами:

  • 1 считается конечным значением для последовательности.
  • Для любого четного положительного числа i значение, следующее за i в последовательности, равно i/2.
  • Для любого нечетного положительного целого числа j > 1 значение, следующее за j в последовательности, равно 3j+1.

Запросы могут быть

  • Последовательность града(Seed,Sequence): где Sequence — это последовательность градин, сгенерированная из данного Seed.

  • градины(M,N): где N — число, следующее за M в последовательности градин. Например. если M равно 5, то N должно быть 16, если M равно 20, то N должно быть 10 и т. д.

  • hailStorm(Seed,Depth,HailTree): где HailTree — это дерево значений, которое может предшествовать Seed в последовательности заданной глубины.

Пример:

| ?- hailStorm(1,4,H).  
H = hs(1,hs(2,hs(4,hs(8)))) ?  
yes 

| ?- hailStorm(5,3,H).  
H = hs(5,hs(10,hs(3),hs(20))) ?  
yes

Графическое изображение

Теперь я реализовал первые два предиката:

hailSequence(1,[1]) :- !.  
hailSequence(N,[N|S]) :- 0 is N mod 2, N1 is round(N / 2), hailSequence(N1,S).  
hailSequence(N,[N|S]) :- 1 is N mod 2, N1 is (3 * N) + 1, hailSequence(N1, S).  

hailStone(X,Y) :- nonvar(X), 0 is X mod 2, Y is round(X / 2).  
hailStone(X,Y) :- nonvar(X), 1 is X mod 2, Y is (3 * X) + 1.  
hailStone(X,Y) :- nonvar(Y), 1 is Y mod 3, T is round( (Y - 1) / 3), 1 is T mod 2, X is T.  

Для предиката hailStorm/2 я написал следующий код, но он не работает должным образом:

make_hs1(S,hs(S)).  
make_hs2(S,R,hs(S,make_hs1(R,_))).  
make_hs3(S,L,R,hs(S,make_hs1(L,_),make_hs1(R,_))).  

hailStorm(S,1,hs(S)) :- !.  
hailStorm(S,D,H) :- nonvar(S), nonvar(D), 4 is S mod 6, S=\= 4, make_hs3(S,hailStorm(round((S-1)/3),D-1,_X),hailStorm(2*S,D-1,_Y),H).  

hailStorm(S,D,H) :- nonvar(S), nonvar(D), make_hs2(S,hailStorm(2*S,D-1,_X),H).  

Выход:

| ?- hailStorm(5,2,H).  
H = hs(5,make_hs1(hailStorm(2*5,2-1,_),_))  
yes

что не является желаемым результатом, т. е.

H = hs(5,hs(10)) ?

person sarvagya    schedule 28.02.2015    source источник
comment
градПоследовательность(1,[1]) :- !. было отредактировано/исправлено? я прочитал градПоследовательность(1,1) :- !. ранее, которые привели к необходимости этого образца града Sequence (5, [5,16,8,4,2|1]).   -  person philippe lhardy    schedule 28.02.2015
comment
@philippelhardy В сообщении всегда было hailSequence(1,[1]), но поскольку оно было отформатировано как цитата, а не как код, StackOverflow подумал, что [1] означает ссылку на сноску, и отобразил ее как 1 (обратите внимание, что она отображается синим цветом и кликабельна, если вы посмотрите на предыдущий версия).   -  person Philipp Wendler    schedule 28.02.2015
comment
@PhilippWendler спасибо за ваше редактирование, это помогает сосредоточиться на реальной проблеме.   -  person philippe lhardy    schedule 28.02.2015
comment
0 is N mod 2, N1 is round(N / 2), будет работать, но лучше писать 0 =:= N mod 2, N1 is N // 2 или N /\ 1 =:= 0, N1 is N // 2. X is T следует писать X = T. is/2 предназначен для вычисления и присвоения арифметических выражений, а =/2 используется для унификации.   -  person lurker    schedule 28.02.2015
comment
Должен ли также быть еще один предикат hailStone: hailStone(X, Y) :- nonvar(Y), Y is X * 2.?   -  person lurker    schedule 28.02.2015
comment
Вы ссылаетесь на предикаты Пролога как на функции, что не только является неправильной терминологией, но и вводит в заблуждение, поскольку Пролог не вызывает функторы внутри других функторов, как если бы они были функции, как и в других языках. Например, в make_hs2(S, hailStorm(2*S, D-1, _X), H). 2*S будет просто термином *(2,S), а hailStorm не будет вызываться, а просто будет передан как функтор hailStorm(2*S, D-1, _X).   -  person lurker    schedule 28.02.2015


Ответы (1)


В постановке задачи выражено несколько вопросов:

  • В Прологе есть предикаты и термы, но нет функций. Думая о них как о функциях, можно подумать, что вы можете написать такие термины, как foo(bar(3), X*2)), и ожидать, что Пролог вызовет bar(3) и оценит X*2, а затем передаст эти результаты в качестве двух аргументов foo. Но что делает Пролог, так это передает их так же, как термы, как вы их видите (на самом деле, X*2 внутри является термом, *(X,2)). И если была вызвана bar(3), она не возвращает значение, а либо завершается успешно, либо терпит неудачу.

  • is/2 — это не оператор присваивания переменной, а скорее оценщик арифметического выражения. Он оценивает выражение во втором аргументе и объединяет его с переменной или атомом слева. Он преуспевает, если может объединиться, и терпит неудачу в противном случае.

  • Хотя такие выражения, как 0 is N mod 2, N1 is round(N / 2), будут работать, вы можете получить больше преимуществ от целочисленной арифметики в Прологе и записать ее более подходящим образом, как 0 =:= N mod 2, N1 is N // 2, где =:= — арифметический оператор сравнения. Вы также можете использовать битовые операции: N /\ 1 =:= 0, N1 is N // 2.

  • Вы не определили последовательное определение того, как выглядит дерево от града. Например, иногда ваш термин hs имеет один аргумент, а иногда — три. Это приведет к ошибкам унификации, если вы явно не разберетесь в своем предикате hailStorm.

Таким образом, ваш hailSequence в остальном правильный, но вам не нужен разрез. Я бы немного реорганизовал его так:

hail_sequence(1, [1]).
hail_sequence(Seed, [Seed|Seq]) :-
    Seed > 1,
    Seed /\ 1 =:= 0,
    S is Seed // 2,
    hail_sequence(S, Seq).
hail_sequence(Seed, [Seed|Seq]) :-
    Seed > 1,
    Seed /\ 1 =:= 1,
    S is Seed * 3 + 1,
    hail_sequence(S, Seq).

Или, более компактно, используя шаблон Пролога if-else:

hail_sequence(1, [1]).
hail_sequence(Seed, [Seed|Seq]) :-
    Seed > 1,
    (   Seed /\ 1 =:= 0
    ->  S is Seed // 2
    ;   S is Seed * 3 + 1
    ),
    hail_sequence(S, Seq).

В вашем описании для hailStone не говорится, что он должен быть "двунаправленным", но ваша реализация подразумевает, что вы этого и хотели. Таким образом, он кажется неполным, поскольку отсутствует корпус:

hailStone(X, Y) :- nonvar(Y), Y is X * 2.

Я бы реорганизовал это, используя немного CLPFD, так как это даст «двунаправленность» без проверки var и nonvar. Я также собираюсь различать hail_stone1 и hail_stone2 по причинам, которые вы увидите позже. Они представляют собой два способа образования градин.

hail_stone(S, P) :-
    hail_stone1(S, P) ; hail_stone2(S, P).
hail_stone1(S, P) :-
    S #> 1,
    0 #= S rem 2,
    P #= S // 2.
hail_stone2(S, P) :-
    S #> 1,
    1 #= S rem 2,
    P #= S * 3 + 1.

Обратите внимание, что S должно быть ограничено > 1, так как после 1 градина нет. Если вы хотите, чтобы они использовали var и nonvar, я оставлю это в качестве упражнения для обратного преобразования. :)

Теперь к последовательности. Во-первых, я бы дал четкое определение тому, как выглядит дерево. Поскольку это бинарное дерево, общее представление будет таким:

hs(N, Left, Right)

Где Left и Right - это ветви (поддеревья), которые могут иметь значение nul, n, nil или любой другой атом, который вы хотите представить пустым деревом. Теперь у нас есть согласованный терм с тремя аргументами для представления дерева.

Тогда предикат может быть более легко определен для получения града:

hail_storm(S, 1, hs(S, nil, nil)).  % Depth of 1

hail_storm(S, N, hs(S, HSL, HSR)) :-
    N > 1,
    N1 is N - 1,

    % Left branch will be the first hail stone sequence method
    (   hail_stone1(S1, S)       % there may not be a sequence match
    ->  hail_storm(S1, N1, HSL)
    ;   HSL = nil
    ),

    % Right branch will be the second hail stone sequence method
    (   hail_stone2(S2, S)       % there may not be a sequence match
    ->  hail_storm(S2, N1, HSR)
    ;   HSR = nil
    ).

Из чего получаем, например:

| ?- hail_storm(10, 4, Storm).

Storm = hs(10,hs(20,hs(40,hs(80,nil,nil),hs(13,nil,nil)),nil),hs(3,hs(6,hs(12,nil,nil),nil),nil)) ? ;

(1 ms) no


Если вы хотите использовать менее симметричное и, возможно, менее каноническое определение бинарного дерева:

hs(N)        % leaf node
hs(N, S)     % one sub tree
hs(N, L, R)  % two sub trees

Затем предикат hail_storm/3 становится немного более сложным, но управляемым:

hail_storm(S, 1, hs(S)).
hail_storm(S, N, HS) :-
    N > 1,
    N1 is N - 1,
    (   hail_stone1(S1, S)
    ->  hail_storm(S1, N1, HSL),
        (   hail_stone2(S2, S)
        ->  hail_storm(S2, N1, HSR),
            HS = hs(S, HSL, HSR)
        ;   HS = hs(S, HSL)
        )
    ;   (   hail_stone2(S2, S)
        ->  hail_storm(S2, N1, HSR),
            HS = hs(S, HSR)
        ;   HS = hs(S)
        )
    ).

Из чего получаем:

| ?- hail_storm1(10, 4, Storm).

Storm = hs(10,hs(20,hs(40,hs(80),hs(13))),hs(3,hs(6,hs(12)))) ? ;

no
person lurker    schedule 28.02.2015
comment
hail_storm с seed = 4 не должен иметь левого поддерева (т.е. 1), как показано в графическом представлении в вопросе. - person sarvagya; 01.03.2015
comment
еще 1 вещь.. если я хочу преобразовать возвращенный град в список, например | ?- град_шторм (1 ,3 ,шторм), град_лист(шторм ,список). storm = hs(1,hs(2,hs(4))) list = [1,[2,[4]]] ? да - person sarvagya; 01.03.2015
comment
@sarvagya да, ты прав. Я исправил проблему, заключавшуюся в том, что в hail_stone требуется ограничение, которое заставляет S быть больше, чем 1. - person lurker; 01.03.2015
comment
Как вы отобразите его в виде списка, зависит от вас. Это может выглядеть так: [N,L,R] для двух ветвей (L и R являются поддеревьями), [N,S] (S является единственным поддеревом) , or [N]` (значение узла без поддерева). Вы можете легко изменить ответ, который я предоставил, чтобы получить список вместо нотации hs, если вы немного изучите его. Также обратите внимание, что переменные ДОЛЖНЫ начинаться с верхнего регистра: hail(Storm, Llist), а не hail(storm, list). - person lurker; 01.03.2015