Почему SWI-Prolog предлагает только одно решение?

Честно говоря, я новичок в Prolog, так что прошу прощения за мое незнание.

У меня есть простой предикат для подсчета вхождений атома в список, а именно:

count(L, B, C) :-
    L = [], C = 0, !;
    L = [H|T], H \= B, count(T, B, C), !;
    L = [H|T], H = B, count(T, B, C1), C is C1 + 1.

Следующие запросы возвращают правильные результаты:

?- count([a, c, g, t], a, C).
C = 1.

?- count([a, c, g, t], c, C).
C = 1.

?- count([a, c, g, t], g, C).
C = 1.

?- count([a, c, g, t], t, C).
C = 1.

Однако если я попытаюсь найти все возможные решения, это даст только одно.

?- count([a, c, g, t], X, C).
X = a,
C = 1.

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


person luksan    schedule 14.09.2013    source источник
comment
Почему бы вам не записать это как несколько предикатов?   -  person Carl Norum    schedule 15.09.2013
comment
Я мог бы иметь. Думаю, это был просто стилистический выбор.   -  person luksan    schedule 15.09.2013
comment
+1 для обучения путем реализации общих функций списков   -  person Cephalopod    schedule 17.09.2013
comment
@Arian на самом деле мне это просто нужно для чего-то - я не думаю, что в SWI Prolog есть что-то похожее на count/3.   -  person luksan    schedule 21.09.2013


Ответы (4)


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

?- count(Ls, L, C).

и увидим, что это дает только одно решение, хотя очевидно, что их должно быть бесконечно много, потому что первым аргументом может быть список произвольной длины. Итак, сначала удалите все порезы. Другая проблема - это (\=)/2, что не является истинным отношением: оно имеет смысл только в том случае, если его аргументы являются обоснованными. Вместо (\=)/2 используйте более общий предикат dif/2, который доступен в SWI-Prolog, а также в других системах и ограничивает его аргументы разными терминами. Тогда ваш предикат будет работать во всех направлениях.

РЕДАКТИРОВАТЬ: я расширяю пункт «можно использовать во всех направлениях». Рассмотрим следующую версию list_term_count/3, которая связывает список с количеством вхождений термина в этом списке, используя clpfd в дополнение к dif/2:

list_term_count([], _, 0).
list_term_count([L|Ls], L, N) :-
        list_term_count(Ls, L, N0),
        N #= N0 + 1.
list_term_count([L|Ls], E, N) :-
        dif(L, E),
        list_term_count(Ls, E, N).

Мы можем использовать его самым общим вообразимым образом, оставляя все аргументы неопределенными, и получать правильные ответы:

?- list_term_count(Ls, E, N).
Ls = [],
N = 0 ;
Ls = [E],
N = 1 .

Чтобы точно перечислить все решения, мы можем использовать length/2:

?- length(Ls, _), list_term_count(Ls, E, N).
Ls = [],
N = 0 ;
Ls = [E],
N = 1 ;
Ls = [_G167],
N = 0,
dif(_G167, E) .

Обратите внимание на ограничение dif/2, которое возникает как остаточная цель и которое заставляет элемент списка отличаться от E, когда N равно 0. Таким образом мы можем выразить бесконечный набор терминов, который не ограничен никакими другими способами, кроме отличия от E.

Также допустим любой другой образец создания экземпляра. Например:

?- list_term_count([a], E, N).
E = a,
N = 1 ;
N = 0,
dif(E, a).

Или например:

?- list_term_count([X], a, N).
X = a,
N = 1 ;
N = 0,
dif(X, a).

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

person mat    schedule 15.09.2013

Думаю, вы изучаете «темную сторону» Пролога: агрегацию.

Действительно, ваш предикат count / 3 может быть - любезно предоставлен библиотекой (aggregate ) -

count(L, B, C) :-
    aggregate(count, member(B, L), C).

Будучи языком, основанным на relational модели данных, мы сродни ожидаем обычных преимуществ, доступных из SQL, начиная с более простых:

select count( distinct B ) from L

Если вы изучите реализацию библиотеки (например, с помощью ?- edit(aggregate/3).), вы оцените сложность, необходимую для обобщения модели Prolog выполнения, ориентированной на «один кортеж за раз», до модели «ориентированной на набор».

person CapelliC    schedule 15.09.2013
comment
Это гораздо менее общее решение, чем решение, которое OP может легко получить, используя чистые предикаты (dif/2) в данном примере кода. Попробуйте, например, ?- count(Ls, L, C) с вашей версией (это не работает) или ?- count([X], a, C), что дает только одно решение, хотя их должно быть два, или ?- count([a,b,c], x, C), что дает false вместо C=0. - person mat; 15.09.2013
comment
Темная сторона? Что ж, многие формы агрегирования немонотонны. - person false; 17.09.2013
comment
@false: выполнение агрегации в Prolog довольно сложно, особенно если (внешне) по сравнению с SQL. По крайней мере, таков был мой опыт. - person CapelliC; 17.09.2013
comment
Основная трудность заключается в том, что он не монотонен. Сравнение с SQL непросто: вам потребуется много рук, чтобы сформулировать монотонное подмножество SQL. - person false; 17.09.2013

Неожиданное поведение исходит от Объединения Пролога, которое всегда пытается добиться успеха.

Читать H \= B как not(H = B). Когда B не связан, H = B всегда будет успешным, потому что объединение возможно, поэтому not(...) всегда будет терпеть неудачу. В результате рекурсия будет происходить только в третьей ветви после того, как B был привязан к H.

Представим, что H \= B преуспеет для несвязанного B, и произойдет рекурсия. Теперь, если тот же элемент H встречается снова, на этот раз он может быть привязан к B, что даст вам несколько результатов для каждого значения. Например, count([a,a],X,C) вернет X = a, C = 1. X = a, C = 2.. Уж точно не то, что вы хотели.

Однако если я попытаюсь найти все возможные решения, это даст только одно.

?- count([a, c, g, t], X, C). X = a, C = 1.

Что бы вы сказали «все возможные решения»? Это бесконечные значения для X, для которых C равно нулю (совет: попробуйте ?- X.). Или вы имеете в виду все значения в списке?

Есть очень простое решение вашей проблемы: просто убедитесь, что B привязан, когда \= будет достигнут. Самый простой способ добиться этого - просто изменить порядок предикатов, то есть переместить неравенство в конец.

% count(?List, ?Element, ?Count)
count([], _, 0).
count([E|R], E, C) :-
     count(R, E, C0),
     C is C0+1.
count([F|R], E, C) :-
     count(R, E, C),
     F \= E.

Оптимизация оставлена ​​читателю в качестве упражнения.

Последнее замечание: не используйте сокращения (!), пока вы не поймете их на самом деле, в большинстве случаев они вам все равно не понадобятся.

person Cephalopod    schedule 17.09.2013
comment
+1 за хорошее объяснение того, почему \=/2 и dif/2 (ba dum bum) разные. Эти предикаты сложно гуглить. - person luksan; 21.09.2013
comment
Между этим и ответом Мэтта была подбрасывание. Хотел бы я принять их обоих. - person luksan; 22.09.2013
comment
Это нормально. объяснение мата более детально. Но положительные отзывы приветствуются ;-) - person Cephalopod; 22.09.2013
comment
Эта конкретная реализация count/3 очень неэффективна! - person repeat; 12.05.2016

Ну, похоже, это сработало.

base(a).
base(c).
base(g).
base(t).

count(L, B, C) :-
    base(B),
    (
        L = [], C = 0;
        L = [H|T], H = B, count(T, B, C1), C is C1 + 1;
        L = [H|T], H \= B, count(T, B, C)
    ).

Это имеет смысл, поскольку у Пролога нет другого способа узнать, что такое домен B, я полагаю. Я просто удивлен, что это не дало мне эту ошибку «аргумент недостаточно конкретизирован» в исходном виде. Кроме того, если я включу операторы вырезания, это не сработает, что я пока еще не могу учесть.

person luksan    schedule 14.09.2013
comment
Это гораздо менее общий подход, чем мог бы быть: например, ?- count([a,c,g], x, C) дает false вместо C=0. Используйте ограничение dif/2, чтобы ограничить домен B, при этом разрешив любой другой термин Пролога. И не используйте никаких !/0, они делают эту программу менее общей: в данном случае это потому, что добавление !/0 в последние несколько строк также отсекает решения цели base(B), что совсем не то, что вы планировали. Стремитесь к общности, ваши отношения должны использоваться во многих направлениях. Название count/3 не предполагает этого, а как насчет того, чтобы назвать его list_element_count/3. - person mat; 15.09.2013