Почему мы используем '!' в прологе
Это код, который я пытаюсь понять.
co(X) :- co(X,[],L).
co([],A,A):- write(A).
co([X|Xs], A, L) :- p(X-Z,A,R), !, Z1 is Z+1, co(Xs, [X-Z1|R], L).
co([X|Xs], A, L) :- co(Xs, [X-1|A], L).
p(X-Y,[X-Y|R],R):- !.
p(X,[H|Y], [H|Z]) :- p(X,Y,Z).
Какая польза от "!" и предикат p(,,) в приведенном выше коде. ИЛИ Кто-нибудь может просто добавить комментарии на каждом этапе кода выше, чтобы я мог понять. Благодарю.
2 ответа
В вашей программе есть много вещей, к которым можно обратиться. Сокращения - даже не главная проблема. Пожалуйста, принесите мне метлу.
Очистить интерфейс
Какой именно интерфейс вы ищете? Цель co(Xs)
В настоящее время это вызывает побочный эффект. В противном случае он может быть успешным или неуспешным для данного списка. Но не более того. Тем не менее, этот побочный эффект вовсе не нужен - и для большинства ситуаций не является полезным подходом, поскольку такая программа практически не используется и не поддается никаким логическим рассуждениям. Вы должны оставить дыру, чтобы позволить некоторому результату вырваться из отношения. Добавьте еще один аргумент и удалите цель write/1
в co/3
,
co(Xs, D) :-
co(Xs, [], D).
Теперь вы можете протестировать программу только с помощью оболочки верхнего уровня. Вам не нужны никакие жгуты или песочница для проверки "выхода". Это там, легко в отдельный аргумент.
Очистить структуру программы
Далее это co/3
сам. Здесь лучше всего прояснить намерение, немного разделив проблемы и сделав эти дополнительные аргументы более откровенными. D
обозначает словарь. Другое хорошее имя будет KVs
список значений (множественное число s
) пар ключ-значение. Обратите внимание, как пронумерованы разные состояния: они начинаются с D0
, D1
... и в конце есть D
, Таким образом, если вы начнете писать правило, вы можете поставить D0,D
уже в голове, не зная, сколько состояний вам понадобится в этом правиле.
co([], D,D).
co([X|Xs], D0,D) :-
nn(X, D0,D1),
co(Xs, D1,D).
nn(K, D0,D) :-
p(K-V0,D0,D1), !,
V is V0+1,
D = [X-V|D1].
nn(K, D0,D) :-
D = [K-1|D0].
p(X-Y,[X-Y|R],R):- !.
p(X,[H|Y], [H|Z]) :- p(X,Y,Z).
co/3
Теперь более четко раскрывает свое намерение. Он каким-то образом связывает элементы списка с некоторым состоянием, которое "обновляется" для каждого элемента. Есть слово для этого: это левый сгиб. И есть даже предикат для этого: foldl/4
, Таким образом, мы могли бы одинаково определить co/3
как:
co(Xs, D0,D) :-
foldl(nn, Xs, D0,D).
или лучше избавиться от co/3
в целом:
co(Xs, D) :-
foldl(nn, Xs, [], D).
foldl(_C_3, [], S,S).
foldl(C_3, [X|Xs], S0,S) :-
call(C_3, X, S0,S1),
foldl(C_3, Xs, S1,S).
Обратите внимание, что до сих пор я даже не коснулся ваших порезов, это их последние минуты...
Удаляет лишние порезы
Разрез в p/3
не служит какой-либо цели. Сразу после ворот p/3
тем не мение. Затем, X-Y
не нужен в p/3
Вы можете смело заменить его другой переменной. Короче, p/3
теперь предикат select/3
из пролога пролог.
select(E, [E|Xs], Xs).
select(E, [X|Xs], [X|Ys]) :-
select(E, Xs, Ys).
nn(K, D0,D) :-
select(K-V0, D0,D1), !,
V is V0+1,
D = [K-V|D1].
nn(K, D0,D) :-
D = [K-1|D0].
Этот оставшийся вырез не может быть удален так легко: он защищает альтернативный пункт от использования, если K-V
не встречаются в D
, Тем не менее, есть еще лучшие способы выразить это.
Заменить порезы на (\+)/1
nn(K, D0,D) :-
select(K-V0, D0,D1),
V is V0+1,
D = [K-V|D1].
nn(K, D0,D) :-
\+select(K-_, D0,_),
D = [K-1|D0].
Теперь каждое правило утверждает, что оно хочет для себя. Это означает, что теперь мы можем свободно изменять порядок этих правил. Назовите это суеверием, но я предпочитаю:
nn(K, D0,D) :-
\+select(K-_, D0,_),
D = [K-1|D0].
nn(K, D0,D) :-
select(K-V0, D0,D1),
V is V0+1,
D = [K-V|D1].
Очистить с dif/2
Чтобы сделать это истинным отношением, нам нужно избавиться от этого отрицания. Вместо того, чтобы сказать, что решения не существует, мы можем вместо этого потребовать, чтобы все ключи (ключ является первым аргументом в Key-Value
) отличаются от K
,
nokey(_K, []).
nokey(K, [Kx-|KVs]) :-
dif(K, Kx),
nokey(K, KVs).
nn(K, D,[K-1|D]) :-
nokey(K, D).
nn(K, D0,[K-V|D]) :-
select(K-V0, D0,D),
V is V0+1.
С помощью лямбд, nokey(K, D)
становится maplist(K+\(Kx-_)^dif(Kx,K), D)
Подводя итог, мы имеем теперь:
co(Xs, D) :-
foldl(nn, Xs, [], D).
nn(K, D,[K-1|D]) :-
maplist(K+\(Kx-_)^dif(Kx,K), D).
nn(K, D0,[K-V|D]) :-
select(K-V0, D0,D),
V is V0+1.
Итак, что же это за отношение: первый аргумент - это список, а второй аргумент - список "ключ-значение" с каждым элементом и количеством вхождений в списке.
Начинающие склонны использовать !/0
потому что они не знают о его негативных последствиях.
Это потому, что большинство учебников по Прологу, которые популярны среди начинающих, довольно плохие и часто содержат неверную и вводящую в заблуждение информацию о !/0
,
@False дает отличный ответ о том, когда использовать !/0
, В итоге: нет.
Вместо этого сосредоточьтесь на декларативном описании того, что имеет место, и попытайтесь сделать описание элегантным и общим, используя чистые и монотонные методы, такие как ограничения, чистые представления,...