Пролог лаконичен foreach - PullRequest
       12

Пролог лаконичен foreach

1 голос
/ 29 марта 2019

Я пытаюсь использовать Пролог foreach в сжатой форме. Например, предположим, у меня есть список, и я хочу посмотреть, симметричен ли он. Рассмотрим следующий код:

isSymmetricList1(Size, List) :-
  foreach(between(1, Size, I1), (
    I2 is Size - I1 + 1,
    nth1(I1, List, Elem),
    nth1(I2, List, Elem)
  )).

isSymmetricList2Sub1(Size, List, I1) :-
  I2 is Size - I1 + 1,
  nth1(I1, List, Elem),
  nth1(I2, List, Elem).

isSymmetricList2(Size, List) :-
  foreach(between(1, Size, I1), (
    isSymmetricList2Sub1(Size, List, I1)
  )).

Рассмотрим несколько тестовых случаев:

?- isSymmetricList1(4,[0,0,0,0]).
false.

?- isSymmetricList1(4,[0,0,0,1]).
false.

?- isSymmetricList1(4,[1,2,2,1]).
false.

?- isSymmetricList1(4,List).
false.

?- isSymmetricList2(4,[0,0,0,0]).
true.

?- isSymmetricList2(4,[0,0,0,1]).
false.

?- isSymmetricList2(4,[1,2,2,1]).
true.

isSymmetricList1 завершается ошибкой, возвращая только false даже для симметричных списков. Я понимаю, почему это так, что I2 фактически входит в область действия isSymmetricList1 и поэтому может иметь только одно значение на всех 4 итерациях, что не соответствует его цели. В isSymmetricList2 я связываю I2 в isSymmetricList2Sub1, вынимая его из области действия isSymmetricList2, фактически позволяя привязать его к нескольким значениям (потому что это разные экземпляры или, как говорится).

Это работает. Тем не менее, это влечет за собой беспорядок. Я действительно не хочу, чтобы куча подправил плавала в моем пространстве имен. Я знаю, что есть лямбда-модуль , и, возможно, это просто то, что я должен использовать, но мне интересно знать, строго ли это необходимо. (Кроме того, его синтаксис немного сложен.) Можно ли использовать foreach для выполнения действия более сложного, чем одна цель, без добавления дополнительных правил в пространство имен и без импорта дополнительных модулей? Например, если вы можете объявить локальные правила или ограничить область привязки.

Ответы [ 2 ]

2 голосов
/ 29 марта 2019

Просто примечание, не учитывающее ваши опасения по поводу использования foreach/2.

Разве ваше определение симметричного списка не доказывает, что список и его реверс совпадают?

symmetric_list(List) :-
    reverse(List, List).

Примеры вызовов:

|?- symmetric_list([0,0,0,0]).
yes

| ?- symmetric_list([0,0,0,1]).
no

| ?- symmetric_list([1,2,2,1]).
yes

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

| ?- symmetric_list(List).
List = [] ;
List = [_16114] ;
List = [_16114, _16114] ;
List = [_16114, _16126, _16114] ;
List = [_16114, _16126, _16126, _16114]
...

Относительно предикатов foreach/2 против forall/2, как указано в комментариях и в одном издругие ответы, они используются в различных сценариях.Предикат forall/2 является де-факто стандартным предикатом, который реализует цикл генерации и тестирования .Цель forall/2 является истинной, когда для всех решений первого аргумента второй аргумент является истинным.Это неявно требует возврата к аргументу генератора и, следовательно, стандартному определению предиката с использованием отрицания:

forall(Generate, Test) :- \+ (Generate, \+ Test).

Использование отрицания приводит к forall/2, не возвращающему привязки.Когда эти привязки необходимы, предикат foreach/2 является возможной альтернативой.Не желая перефразировать здесь документацию по этому предикату, я предлагаю вам ознакомиться с документацией систем Prolog, таких как SWI-Prolog и SICStus Prolog, предоставив ее.

1 голос
/ 29 марта 2019

Самая вопиющая проблема в том, что foreach/2 - это не то, что вы, похоже, хотите.Во-первых, давайте сделаем минимальное исправление для вашего isSymmetricList1/2, которое сделает его исправным на ваших примерах:

isSymmetricList1(Size, List) :-
  forall(between(1, Size, I1), (
    I2 is Size - I1 + 1,
    nth1(I1, List, Elem),
    nth1(I2, List, Elem)
  )).

Можете ли вы заметить разницу?

С этим определением я получаю:

?- isSymmetricList1(4,[0,0,0,0]).
true.

?- isSymmetricList1(4,[0,0,0,1]).
false.

?- isSymmetricList1(4,[1,2,2,1]).
true.

Кажется, вы точно понимаете, почему foreach/2 делает то, что он делает, и почему он терпит неудачу.Я подозреваю, что вы также знаете, что использование forall/2 решило бы вашу "проблему".Сложный вопрос: как мы узнаем, когда нам нужно одно, а когда другое?Я не думаю, что действительно знаю ответ, поэтому нам придется подождать, пока эксперты придут и объяснят его нам обоим.

Вместо этого я придираюсь к более доступным темам, которые поднимает ваш вопрос..

Вы понимаете, что это действительно обходной способ делать то, что вы делаете, верно?Сначала вы просматриваете весь список, сравнивая каждую пару элементов дважды .Затем nth1/3 должен обходить список с самого начала каждый раз, когда вы его вызываете.

Про "подправилы, плавающие в вашем пространстве имен", в конце концов, это не так уж плохо.Предикаты-помощники помогут вам думать об отношениях.Они также дают вам прекрасную возможность документировать ваше намерение с помощью имен предикатов.

Что заставляет меня хотеть начать говорить об именах и, в частности, о CamelCase и о том, как вы не должны этого делать, но это слишком далекотема.

...