Арифметические вычисления в Прологе по спискам

в настоящее время мы работаем над реализацией предиката reduce, который должен взять список L, бинарную функцию F, которая работает с элементами L и имеет нейтральный элемент N, и отображает их в число E, которое получается путем применения F к элементы L рекурсивно.

Вот наш Кодекс:

reduce([],F,NEUTRAL,EE).

reduce([H|T],F,NEUTRAL,EE) :-
    Goal =.. [F, H, NEUTRAL, EE],
    call(Goal),
    reduce(T,F,NEUTRAL,EE).

add(N1,NEUTRAL, EE) :- EE is EE + N1 + NEUTRAL.

mult(N1,NEUTRAL, EE) :- EE is N1 * EE * NEUTRAL.

Проблема в том, что для тех же примеров запросов, таких как ?- reduce([], add, 0, R)., мы просто получаем логическое значение (в данном случае истина), для других возвращается ошибка, которая сообщает нам, что наши аргументы не созданы должным образом. На самом деле мы стремимся к ответу типа R = 0. (для примера, приведенного выше).

Надеюсь, вы можете нам помочь, пожалуйста, не усложняйтесь, так как мы новички в Prolog;)


person Anna    schedule 24.05.2017    source источник
comment
Вы можете заменить Goal =.. [F, H, NEUTRAL, EE], call(Goal) на call(F, H, NEUTRAL, EE)   -  person false    schedule 24.05.2017
comment
В вашем базовом случае reduce([], F, NEUTRAL, EE) слишком много переменных (вы, вероятно, видели предупреждения синглтона). Этот базовый вариант должен ответить на вопрос Что должно быть EE, если список пуст? (И в этом случае вам, вероятно, все равно, что такое F и NEUTRAL.) reduce([], _, _, ??) .. что такое ??? Это 0? Неправильно созданный экземпляр ошибки возникает, когда вы пытаетесь выполнить вычисление, подобное EE is N1 * EE * NEUTRAL, и одна из переменных справа не имеет значения.   -  person lurker    schedule 24.05.2017
comment
Наконец, вы не можете повторно создать экземпляр переменной после ее создания в предложении предиката. Таким образом, EE is N1 * EE * NEUTRAL может потерпеть неудачу, если EE слева не будет того же значения, что и EE справа (они такие же EE).   -  person lurker    schedule 24.05.2017
comment
как мы можем выполнить артметическую операцию с переменной? Можем ли мы присвоить значение EE? Мы пробовали это с equals и is, но это тоже не работает.   -  person Anna    schedule 25.05.2017
comment
Вы можете использовать CLP (FD), если хотите выразить арифметические действия с целыми числами, когда значения еще не известны: EE1 #= N1 * EE * NEUTRAL. И, да, EE может быть привязан к значению, его просто нельзя связать снова без возврата. Помните: Prolog не похож на C, C ++ или Java, только с другим синтаксисом. Это язык для определения фактов и правил. Когда переменная связана, Prolog пытается использовать эту привязку, поскольку предикат продолжает выполнение. Чтобы выполнить повторную привязку, должен произойти возврат с возвратом из-за сбоя последующих выражений.   -  person lurker    schedule 25.05.2017
comment
... мы - новички в Prolog, это королевские Мы или вы на самом деле группа людей?   -  person    schedule 25.05.2017
comment
@Boris: следующий   -  person false    schedule 26.05.2017


Ответы (4)


Чтобы завершить рекурсию, вам понадобится нейтральный элемент. Вам это не нужно на каждом этапе. Я бы почти сказал, что нейтральный элемент принадлежит оператору, а не сокращению. Что-то вроде этого:

operation_neutral(add, 0).
operation_neutral(mult, 1).

Тогда ваш reduce может выглядеть так:

reduce([], Op, Result) :- operation_neutral(Op, Result).
reduce([X|Xs], Op, Result) :-
    reduce(Xs, Op, R0),
    call(Op, X, R0, Result).

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

reduce([], _, N, N).

В противном случае перейдите в рекурсию, а затем примените операцию к результату (и вы можете использовать call напрямую):

reduce([X|Xs], Op, N, R) :-
    reduce(Xs, Op, N, R0),
    call(Op, X, R0, R).

Вы можете узнать здесь, как использовать reduce/4, например, с append/3.

Вы видите N внутри call? Я этого не вижу, и ему здесь не место.

Для полноты картины два оператора:

add(X, Y, R) :- R is X+Y.
mult(X, Y, R) :- R is X*Y.

Нет упоминания о нейтральном элементе.


Между прочим: то, что вы назвали «сокращение», также известно как «сворачивание» по списку. Вы можете определить свой reduce/3 с точки зрения foldl следующим образом:

reduce(List, Op, Result) :-
    operation_neutral(Op, N),
    foldl(Op, List, N, Result).

И рекурсия, и call происходят внутри foldl, reduce/4 вам вообще не нужен.


Как бы вы решили это процедурным языком? Используя псевдокод с синтаксисом, подобным C, и предполагая, что у нас есть некоторая поддержка дженериков, это могло бы выглядеть так:

template <typename A, typename Op>
A reduce(A[] operands, Op op) {
    return reduce(operands, op, 0);
}

template <typename A, typename Op>
A reduce(A[] operands, Op op, int n) {
    if (n == operands.length) return neutral<Op>();
    return op(operands[n], reduce(operands, op, n+1));
}

(очевидно, если бы это был действительно C ++, мы бы использовали итераторы, а не индекс ...)

Она ничем не отличается от версии на Прологе. И если бы вы сделали это так же, как foldl выше, это, вероятно, выглядело бы так:

template <typename A, typename Op>
A reduce(A[] operands, Op op) {
    A x = neutral<Op>();
    for (int n = 0; n < operands.length; ++n)
        x = op(x, operands[n]);
    return x;
}

Опять же, ни о нейтральном элементе внутри цикла, ни о вызове op().

person Community    schedule 25.05.2017

Основываясь на комментариях выше и на том факте, что вы все еще новичок в Prolog, я попытаюсь объяснить некоторые вещи, которые могут быть не очень ясными:

What are variables in Prolog and what is instantiation ?

В Прологе переменные - это все, что начинается с заглавных букв и анонимные переменные _ (просто подчеркивание определяет анонимную переменную, которая является переменной без имени). Переменные в Prolog означает, что они могут представлять что угодно: список, строку, атом (... что угодно), когда вы используете новую переменную, она не создается, что означает, что она может представлять что угодно, но прямо сейчас она представляет - она ​​не имеет не быть bind со списком, атом ... Если вы хотите создать экземпляр переменной, вы можете сделать это:

  • используя унификацию =/2, например X = 2 унифицирует-создает экземпляр переменной X с номером 2.
  • или используя is/2 только для выражений типа X is 2, делает то же, что и выше.

Очень важно то, что когда создается переменная в Прологе, мы знаем, что она представляет, и мы не можем изменить, например X =2, X = 3 will fail!!, поскольку знаем, что X связывается с 2, и мы пытаемся восстановить с 3, что не удается.

What is =/2, is/2, and what's the difference between these two prediactes ??

  • is/2: возвращает результат арифметического выражения в неустановленную переменную. Это означает, что вы можете использовать его как: X is 2, or X is mod(Z,Y), но здесь важно отметить, что все, что находится в expression MUST be instantiated, если это ранее были переменные, такие как Y и Z. Если выражение не создано, вы получите instantiation error, как в вашем случае. Даже простые вещи, такие как 2 is X+1, выйдут из строя с ошибкой создания экземпляра, если экземпляр X не создан, поэтому не ожидайте, что с is / 2 вернет что-то вроде X=1 в предыдущем случае, что является очевидным ответом (но не уникальным, поэтому он будет непоследовательным!). Также арифметическое выражение означает, что вы не можете использовать что-то вроде X is [], чтобы объединить X с пустым списком, это, конечно, не удастся, поэтому для этого используйте =/2, описанный ниже.

  • =/2: используется для объединения переменной (или в целом термина) с другим термином. Например, вы можете использовать это как: X = 2 для объединения X с номером 2 или X = [1, 2] для объединения X со списком или X = 1 + 2 , который не даст 3, а только термин 1 + 2, последние два примера Думаю наглядно показывает разницу с is/2.

But then how could we use arithmetic expressions like is/2 but more relational ?

Ответ здесь library CLPFD (как рекомендовано @lurker в комментариях). Все, что вам нужно сделать, это поместить :- use_module(library(clpfd)). в свой файл и использовать оператор #= вместо is/2. Теперь вы можете попробовать X #= 2, который создаст экземпляр переменной X с номером 2 или даже 2 #= 1 + X, и теперь вы не получите ошибки создания экземпляра, но получите ответ X = 1, так что вы можете видеть, что теперь он стал намного более реляционным.

Теперь к вопросу ...

Я согласен с тем, что нейтральный @Boris не следует переносить через рекурсию, а объявлять с функцией. Хотя я постараюсь не отставать от вашей попытки ответа:

Прежде всего, когда я пытаюсь проконсультироваться с файлом с вашим кодом в прологе swi, я получаю:

Warning: /Users/Desktop/reduce.pl:1:
    Singleton variables: [F,NEUTRAL,EE]

Это говорит о том, что переменные F, Neutral, EE являются одноэлементными в этом предложении:

reduce([],F,NEUTRAL,EE). 

Как видите, в этом предложении вы не используете, например, переменную F.

На мой взгляд, предикат сокращения должен завершиться ошибкой с пустым списком, поскольку вы не можете выполнять никаких операций с нейтральным элементом и функцией, которая принимает два параметра. Итак, в качестве базового случая я бы определил использование списка только с одним элементом:

reduce([H],F,NEUTRAL,EE):-
        Goal =.. [F, H, NEUTRAL, EE], 
        call(Goal).

Затем, как описано выше, действуют такие правила, как:

add(N1,NEUTRAL, EE) :- EE is EE + N1 + NEUTRAL.

вызовет ошибку создания экземпляра, поскольку в EE есть var, и вы не можете использовать его в is / 2, но даже если он был создан, вы также не можете его использовать, потому что, как объяснялось, EE is EE + whatever не будет работать, потому что you try to reinstantiate a variable..!!

Чтобы исправить это, я бы попытался использовать другую переменную для следующей рекурсии, а затем вычислить результат, например:

 reduce([H],F,NEUTRAL,EE):-
     Goal =.. [F, H, NEUTRAL, EE], 
     call(Goal).
  reduce([H|T],F,NEUTRAL,EE) :-
     reduce(T,F,NEUTRAL,EE1),
     Goal =.. [F, H, EE1, EE],
     call(Goal).

(Здесь добавлена ​​новая переменная EE1, выполняет рекурсивный вызов, а затем вычисляет результат на основе результата EE1, который будет возвращен из рекурсивного вызова.)

Теперь проверим:

?- reduce([1,2,3,4],add,0,L).
L = 10 ;
false.

?- reduce([1,2,3,4],mult,1,L).
L = 24 ;
false.

Еще одна вещь, когда он выдал результат L=10 нажатием ";" мы запрашиваем больше ответов и возвращает false, поскольку нет возможных ответов. Ложь или истина - это всего лишь способ, которым верхний уровень Пролога информирует вас об успешном или неудачном выполнении предиката, например:

?- reduce([1,2,3,4],mult,1,24).
true ;
false.

Это говорит о том, что приведенное выше верно, и когда мы спрашиваем, есть ли какие-либо другие способы, чтобы это могло быть правдой, он возвращает false ... Так что все, в конце концов, Prolog не кажется таким уж плохим :) (согласно вашему описанию ).
В качестве упражнения вы можете попытаться написать то же самое, используя CLPFD, чтобы быть более реляционным и иметь возможность отвечать на такие вопросы, как: reduce(L,mult,1,24). и, возможно, идея @Bority о том, чтобы не переносить нейтраль на всем протяжении рекурсии.


ИЗМЕНИТЬ

В соответствии с предложением @false и полезными комментариями я напишу второй способ, используя call / N, который явно лучше (см. Комментарии ...):

reduce([H],F,NEUTRAL,EE):-
        call(F, H, NEUTRAL, EE).
reduce([H|T],F,NEUTRAL,EE) :-
        reduce(T,F,NEUTRAL,EE1),
        call(F, H, EE1, EE).
person coder    schedule 25.05.2017
comment
О, пожалуйста, используйте call/N. - person false; 25.05.2017
comment
@false, точно такой же разговор был раньше с lurker :) !! Я согласен с вашим предложением, но @Anna сказала (в комментариях к ответу @Boris), что нужно использовать =.. (я думаю, домашнее задание ...), так что причина ... - person coder; 25.05.2017
comment
@coder: Тогда будь так мил с ней и скажи ей, что упражнения надо обновлять! - person false; 25.05.2017
comment
@coder: Просто повторите то, что нужно сказать! Послушайте, call / N существует с 1984 года. Но он не вошел в стандарт 1995 года, потому что многие (включая разработчиков системы) не понимали его в то время. Только в Кор.2: 2012 эта ошибка была исправлена. Когда вместо этого можно использовать call/N, (=..)/2 просто зло! - person false; 26.05.2017
comment
@coder: И есть гораздо более глубокие причины, например, call/N печатает, а (=..)/2 - нет ... - person false; 26.05.2017
comment
@false, что вы имеете в виду, call/N печатает? что на самом деле это значит? - person coder; 26.05.2017
comment
@coder: Он вписывается в оригинальную систему типов Майкрофта О'Киф. Фактически, объявления meta_predicate - это очень простая форма объявления типа. - person false; 26.05.2017
comment
@false, отредактировал ответ и добавил ваше предложение, все еще не эффективное решение, но call/N обязательно следует упомянуть, спасибо за полезные комментарии !! - person coder; 26.05.2017
comment
Еще одно: написание вместо F скорее F_3 внутри call(F, H, EE1, EE) помогает отделить подобные мета-аргументы от обычных аргументов. - person false; 26.05.2017
comment
Да Вы используете reduce([H], ...) и reduce([H|T], ...), а не reduce([], ...) и reduce([H|T], ...), я пытаюсь понять, в чем ваша причина, но я могу найти только то, что вы хотите использовать =.. и call не один, а два раза или, может быть, у вас есть более веские причины? - person ; 26.05.2017
comment
Я не могу дать вам ответ в комментарии, и я не хочу писать ответ на этот вопрос, потому что он ответит на неправильный вопрос, но я совершенно уверен, что reduce([], ...) имеет четко определенное значение, и это значение является нейтральным элементом, но вы, вероятно, разбираюсь в математике и программировании лучше меня, и, может быть, я ошибаюсь? Вам следует задать вопрос, если вы действительно хотите получить ответы. - person ; 26.05.2017
comment
@ User9213, вы правы насчет картинки, что сделанное возлагает вину на того, кто это сделал, так что забудьте об этом ... Что касается пустого списка, который я видел (как в переполнении стека, так и в университете), множество примеров с двумя переменными функции нет ничего определенного в том, чтобы просто дать нейтральный элемент. Эти примеры широко используются в функциональном программировании Haskell-SML-OCAML и других, где функции foldr, foldl и другие функции высокого порядка широко используются, но никогда не определяли операцию с предоставлением только нейтрального элемента, поэтому, на мой взгляд, reduce ([], ...) с пустым списком обязательно должен потерпеть неудачу ... - person coder; 27.05.2017
comment
reduce примерно столько же лет, сколько Lisp, и вы можете просто сократить его в Google и решить для себя, как его лучше реализовать! - person ; 27.05.2017

Я надеюсь, что этот частичный ответ не слишком не соответствует теме, что исключает его включение в это обсуждение.

Что касается оператора OP «к тем же типовым запросам, как? - reduce ([], add, 0, R). Мы просто получаем логическое значение (в данном случае истина)», важно иметь хорошую интерпретацию того, что вы имеете в виду под "истинный" . На мой взгляд, НЕуместно считать, что «возвращаемое значение», которое вы определили (т.е. (неявное) возвращаемое значение), имеет «логический» тип. Это связано с тем, что с логическим типом >> "ложь" означает противоположное истинному ‹----------------. В прологе в этом конкретном контексте (. Т.е. в отношении (неявного) возвращаемого значения) применение ожидания логического типа не применяется, потому что в случае этого типа и не похожего на логический тип --- >> "false" означает что-то другое, чем «истина» ‹<.

person Kintalken    schedule 27.05.2017
comment
Пожалуйста, не подписывайте свои сообщения. Подписи и слоганы запрещены в ваших сообщениях, поскольку они уже подписаны вашей карточкой пользователя, и мы стремимся сохранить содержание, если ваши вопросы и ответы не содержат несущественного текста. - person meagar; 31.05.2017

Как упоминалось в предыдущих ответах, вы действительно хотите избавиться от зависимости от call/99.

Вот что вам нужно для начала. Это ваш исходный полностью дисфункциональный код с другим синтаксисом. Я имею в виду, что я надеюсь представить альтернативу синтазу, которая может быть вам полезна, но сломанные семантики оригинала остаются без изменений.

  % [ reduce , VECTOR , FUNCTION , NEUTRAL , EE ] 

  :- [library(clpfd)] .

  [reduce , [] , F , NEUTRAL , EE] :- ( true ) .

  [reduce , [N|NN] , F , NEUTRAL , EE]
  :-
  (
      [F , N , NEUTRAL , EE] ,
      [reduce , NN , Z , NEUTRAL , EE] 
  )
  .

  [add , N , NEUTRAL , EE] :- ( EE #= EE + N + NEUTRAL ) .

  [mult , N , NEUTRAL , EE] :- ( EE #= N * EE * NEUTRAL ) .

Вот оригинал для удобного сравнения ...

  %

  reduce([],F,NEUTRAL,EE).

  reduce([H|T],F,NEUTRAL,EE) :-
      Goal =.. [F, H, NEUTRAL, EE],
      call(Goal),
      reduce(T,F,NEUTRAL,EE).

  add(N1,NEUTRAL, EE) :- EE is EE + N1 + NEUTRAL.

  mult(N1,NEUTRAL, EE) :- EE is N1 * EE * NEUTRAL.
person Kintalken    schedule 28.05.2017
comment
Новое определение [_ | _], как вы, не работает. Вы сами используете его в своем коде для консультации, в операторе: - [library (clpfd)]. Это не сработает, поскольку [_ | _] - это системный предикат, вы не должны его отменять. Ваш код - это только искусство ASCII. См. Также groups.google.com/forum/#!topic/swi- пролог / 3xs7xNwqLOo - person Mostowski Collapse; 01.06.2017
comment
Комментарии предназначены для запроса разъяснений, а не для открытого обсуждения. Пожалуйста, перестаньте оставлять длинные строки комментариев под вашими сообщениями, они будут и дальше удаляться. - person meagar; 24.07.2017