Пролог: как сделать «проверить (a ++ b ++ c ++ d равно d ++ a ++ c ++ b) -> да» - PullRequest
4 голосов
/ 29 августа 2011

Давайте определим пользовательские операторы - пусть это будет ++, equals

:- op(900, yfx, equals).
:- op(800, xfy, ++).

А факт:

check(A equals A).

Я пытаюсь создать предикат, пусть он будет check/1, который вернет true во всех следующих ситуациях:

check( a ++ b ++ c ++ d equals c ++ d ++ b ++ a ),
check( a ++ b ++ c ++ d equals d ++ a ++ c ++ b),
check( a ++ b ++ c ++ d equals d ++ b ++ c ++ a ),
% and all permutations... of any amount of atoms
check( a ++ ( b ++ c ) equals (c ++ a) ++ b),
% be resistant to any type of parentheses

Возвращение

yes

Как реализовать это в Прологе? (Фрагмент кода, пожалуйста. Возможно ли это? Я что-то упустил?)

Гну-Пролог предпочтителен, но SWI-Пролог также приемлем.

P.S. Пожалуйста, рассматривайте код как черновик «псевдокод» и не обращайте внимания на небольшие проблемы с синтаксисом.

P.P.S '++' только начинается. Я хотел бы добавить больше операторов. Вот почему я боюсь, что помещение материала в список может быть не очень хорошим решением.

Дополнительно

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

check( a ++ (b ++ X) equals (c ++ Y) ++ b) )

один из возможных результатов (спасибо @mat за показ других)

X=c, Y=a

В основном я ищу решение для первой части вопроса - проверка "да / нет".

Вторая часть с X, Y была бы хорошим дополнением. В нем X, Y должны быть простыми атомами. Для приведенных выше примеров доменов для X, Y указаны: domain(X,[a,b,c]),domain(Y,[a,b,c]).

Ответы [ 2 ]

6 голосов
/ 29 августа 2011

Ваше представление называется «дефолтным»: для обработки выражений этой формы вам необходим регистр «по умолчанию» или явная проверка для atom / 1 (который не является монотонным) - вы не можете использовать сопоставление с образцом напрямую для обработки все дела. Как следствие, рассмотрим еще раз ваш случай:

check( a ++ (b ++ X) equals (c ++ Y) ++ b) )

Вы говорите, что это должно ответить X=c, Y=a. Тем не менее, он также может ответить X = (c ++ d), Y = (a ++ d). Должно ли это решение также произойти? Если нет, это не будет монотонным и, таким образом, значительно усложнит декларативную отладку и рассуждения о вашей программе. В вашем случае, имеет ли смысл представлять такие выражения в виде списков? Например, [a, b, c, d] равно [c, d, b, a]? Затем вы можете просто использовать библиотеку предикатов permutation / 2, чтобы проверить равенство таких «выражений».

Конечно, также возможно работать с дефолтными представлениями, и во многих случаях они могут быть более удобными для пользователей (подумайте о самом исходном коде Prolog с его дефолтной нотацией для целей или арифметическими выражениями Prolog). Вы можете использовать немонотонные предикаты, такие как var / 1 и atom / 1, а также термины предикаты проверки, такие как functor / 3 и (= ..) / 2, чтобы систематически обрабатывать все случаи, но они обычно предотвращают или, по крайней мере, мешают хорошим декларативным решениям которые могут использоваться во всех направлениях для проверки, а также для генерации всех случаев.

0 голосов
/ 30 ноября 2013

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

Я многое узнал о DCG и списках различий. Боюсь, я не придумала решение, которое не использует списки. Как и предложенный mat, он преобразует термины в плоские списки, чтобы справиться с круглыми скобками, и использует permutation/2 для сопоставления списков, учитывая коммутативный характер оператора ++ ...

Вот что я придумал:

:- op(900, yfx, equ).
:- op(800, xfy, ++).

check(A equ B) :- A equ B.

equ(A,B) :- sum_pp(A,AL,Len), sum_pp(B,BL,Len), !, permutation(AL, BL).

sum_pp(Term, List, Len) :- sum_pp_(Term, List,[], 0,Len).

sum_pp_(A, [A|X],X, N0,N) :- nonvar(A), A\=(_++_), N is N0+1.
sum_pp_(A, [A|X],X, N0,N) :- var(A), N is N0+1.
sum_pp_(A1++A2, L1,L3, N0,N) :-
    sum_pp_(A1, L1,L2, N0,N1), (nonvar(N), N1>=N -> !,fail; true),
    sum_pp_(A2, L2,L3, N1,N).

Предикат sum_pp/3 - это рабочая лошадка, которая берет термин и преобразует его в плоский список слагаемых, возвращая (или проверяя) длину, в то же время не подвергаясь скобкам. Это очень похоже на правило DCG (с использованием списков различий), но оно написано как обычный предикат, потому что оно использует длину, чтобы помочь разбить левую рекурсию, которая в противном случае привела бы к бесконечной рекурсии (мне потребовалось довольно много времени, чтобы победить ее) .

Может проверять наземные условия:

?- sum_pp(((a++b)++x++y)++c++d, L, N).
L = [a,b,x,y,c,d],
N = 6 ;
false.

Он также генерирует решения:

?- sum_pp((b++X++y)++c, L, 5).
X = (_G908++_G909),
L = [b,_G908,_G909,y,c] ;
false.

?- sum_pp((a++X++b)++Y, L, 5).
Y = (_G935++_G936),
L = [a,X,b,_G935,_G936] ;
X = (_G920++_G921),
L = [a,_G920,_G921,b,Y] ;
false.

?- sum_pp(Y, L, N).
L = [Y],
N = 1 ;
Y = (_G827++_G828),
L = [_G827,_G828],
N = 2 ;
Y = (_G827++_G836++_G837),
L = [_G827,_G836,_G837],
N = 3 .

Оператор equ/2 "объединяет" два термина и может также предоставить решения, если есть переменные:

?- a++b++c++d equ c++d++b++a.
true ;
false.

?- a++(b++c) equ (c++a)++b.
true ;
false.

?- a++(b++X) equ (c++Y)++b.
X = c,
Y = a ;
false.

?- (a++b)++X equ c++Y.
X = c,
Y = (a++b) ;
X = c,
Y = (b++a) ;
false.

В правиле equ / 2

equ(A,B) :- sum_pp(A,AL,Len), sum_pp(B,BL,Len), !, permutation(AL, BL).

первый вызов sum_pp генерирует длину, а второй вызов принимает длину в качестве ограничения. Сокращение необходимо, потому что первый вызов может продолжать генерировать постоянно растущие списки, которые никогда больше не будут совпадать со вторым списком, что приведет к бесконечной рекурсии. Я еще не нашел лучшего решения ...

Спасибо за публикацию такой интересной проблемы!

/ Peter

edit: sum_pp_ записано как правила DCG:

sum_pp(Term, List, Len) :- sum_pp_(Term, 0,Len, List, []).
sum_pp_(A, N0,N) --> { nonvar(A), A\=(_++_), N is N0+1 }, [A].
sum_pp_(A, N0,N) --> { var(A), N is N0+1 }, [A].
sum_pp_(A1++A2, N0,N) -->
    sum_pp_(A1, N0,N1), { nonvar(N), N1>=N -> !,fail; true },
    sum_pp_(A2, N1,N).

Обновление:

sum_pp(Term, List, Len) :-
    (    ground(Term)
    ->   sum_pp_chk(Term, 0,Len, List, []), !  % deterministic
    ;    length(List, Len), Len>0,
         sum_pp_gen(Term, 0,Len, List, [])
    ).

sum_pp_chk(A, N0,N) --> { A\=(_++_), N is N0+1 }, [A].
sum_pp_chk(A1++A2, N0,N) --> sum_pp_chk(A1, N0,N1), sum_pp_chk(A2, N1,N).

sum_pp_gen(A, N0,N) --> { nonvar(A), A\=(_++_), N is N0+1 }, [A].
sum_pp_gen(A, N0,N) --> { var(A), N is N0+1 }, [A].
sum_pp_gen(A1++A2, N0,N) -->
    { nonvar(N), N0+2>N -> !,fail; true }, sum_pp_gen(A1, N0,N1),
    { nonvar(N), N1+1>N -> !,fail; true }, sum_pp_gen(A2, N1,N).

Я разделил sum_pp на два варианта. Первая - это тонкая версия, которая проверяет основные термины и является детерминированной. Второй вариант вызывает length/2 для выполнения некоторого итеративного углубления, чтобы не допустить того, чтобы левая рекурсия убежала до того, как правый рекурсон получит возможность что-то произвести. Вместе с проверками длины перед каждым рекурсивным вызовом это теперь бесконечное доказательство рекурсии для гораздо большего числа случаев, чем раньше.

В частности, теперь работают следующие запросы:

?- sum_pp(Y, L, N).
L = [Y],
N = 1 ;
Y = (_G1515++_G1518),
L = [_G1515,_G1518],
N = 2 .

?- sum_pp(Y, [a,b,c], N).
Y = (a++b++c),
N = 3 ;
Y = ((a++b)++c),
N = 3 ;
false.
...