Я разместил код ниже как ответ на этот вопрос и пользователь "повтор" ответил и прокомментировал, что он логически не чист и "если вы заинтересованы в минимальных изменениях в вашем коде, которые сохранят его логическую чистоту, я предлагаю опубликовать новый вопрос об этом. Буду рад ответить :)".
% minset_one(1 in D1, 1 in D2, D1, D2, D1Len, D2Len, T).
minset_one_(true, false, D1, _, _, _, D1).
minset_one_(false, true, _, D2, _, _, D2).
minset_one_(true, true, _, D2, D1Len, D2Len, D2) :- D1Len >= D2Len.
minset_one_(true, true, D1, _, D1Len, D2Len, D1) :- D1Len < D2Len.
minset_one(D1, D2, T) :-
(member(1, D1) -> D1check = true ; D1check = false),
(member(1, D2) -> D2check = true ; D2check = false),
length(D1, D1Len),
length(D2, D2Len),
minset_one_(D1check, D2check, D1, D2, D1Len, D2Len, T).
например
?- D1 = [X,Y,Z], D2 = [U,V], minset_one(D1,D2,T).
D1 = [1, Y, Z],
D2 = T, T = [1, V],
U = X, X = 1 ;
false
возможны и другие решения. member(1, D1)
не возвращается через [1, Y, Z]
, затем [X, 1, Z]
, затем [X, Y, 1]
.
Я думаю, это было бы:
добавлять:
:- use_module(library(reif)).
... и заменить:
%(member(1, D1) -> D1check = true ; D1check = false),
%(member(1, D2) -> D2check = true ; D2check = false),
memberd_t(1, D1, D1check),
memberd_t(1, D2, D2check),
Пример разницы между member и memberd_t:
?- member(X, [A, B, C]).
X = A ;
X = B ;
X = C.
?- memberd_t(X, [A, B, C], IsMember).
X = A,
IsMember = true ;
X = B,
IsMember = true,
dif (A,B) ;
X = C,
IsMember = true,
dif (A,C),
dif (B,C) ;
IsMember = false,
dif (A,X),
dif (B,X),
dif (C,X).
?- memberd_t(X, [A, B, C], IsMember), X = 5, A = 5, C = 5.
X = A, A = C, C = 5,
IsMember = true ;
false.
Таким образом, memberd_t сам добавляет ограничения dif/2. Чтобы немного повысить производительность, он проходит по списку только один раз.
Определение memberd_t находится, например, в. https://github.com/meditans/reif/blob/master/prolog/reif.pl#L194 и https://www.swi-prolog.org/pack/file_details/reif/prolog/reif.pl?show=src
Спасибо; это ответ, он работает и отвечает на то, что я на самом деле спрашивал, в том, что это минимальное изменение, которое делает его логически чистым, так что голосуйте. Но если бы это был вопрос «Как исправить мой код для рисования эллипса, чтобы он мог рисовать идеальные круги?» Я думаю, что «Использовать библиотеку рисования с функцией круга» не будет тем ответом, которого я желал. Я действительно хотел знать, что не так с моей попыткой овеществления с помощью ->
и как я могу написать немного лучше.
Я добавил еще немного пояснений. Объяснение @repeat, конечно, лучше моего :-)
(->)/2
(и друзьями)Рассмотрим следующую цель:
(member(1,D1) -> D1check = true ; D1check = false)
(->)/2
фиксирует первый ответ member(1,D1)
— остальные ответы не учитываются.
Могут ли нам здесь помочь альтернативы (->)/2
, такие как (*->)/2
(SWI, GNU) или if/3
(SICStus)?
Нет. Эти делать не игнорируют альтернативные ответы, чтобы сделать member(1,D1)
успешным, но они не считают, что логическое отрицание member(1,D1)
могло быть успешным также.
Итак, давайте перепишем (If -> Then ; Else)
как (If, Then ; Not_If, Else)
:
(member(1,D1), D1check = true ; non_member(1,D1), D1check = false)
Как мы должны реализовать non_member(X,Xs)
— можем ли мы просто написать \+ member(X,Xs)
?
Нет! Чтобы сохранить логическую чистоту, нам лучше не основываться на «отрицании как конечной неудаче».
К счастью, сочетание maplist/2
и dif/2
работает здесь:
non_member(X,Xs) :- maplist(dif (X),Xs).
Итак, вот минимальное изменение, которое я предлагаю:
minset_one_(true, false, D1, _, _, _, D1). minset_one_(false, true, _, D2, _, _, D2). minset_one_(true, true, _, D2, D1Len, D2Len, D2) :- D1Len >= D2Len. minset_one_(true, true, D1, _, D1Len, D2Len, D1) :- D1Len < D2Len. non_member(X,Xs) :- maplist(dif (X),Xs). minset_one(D1, D2, T) :- (member(1,D1), D1check = true ; non_member(1,D1), D1check = false), (member(1,D2), D2check = true ; non_member(1,D2), D2check = false), length(D1, D1Len), length(D2, D2Len), minset_one_(D1check, D2check, D1, D2, D1Len, D2Len, T).
Выполнив пример запроса, мы теперь получаем:
?- D1 = [X,Y,Z], D2 = [U,V], minset_one(D1,D2,T). D1 = [1,Y,Z], X = U, U = 1, D2 = T, T = [1,V] ; D1 = [1,Y,Z], X = V, V = 1, D2 = T, T = [U,1] ; D1 = T, T = [1,Y,Z], X = 1, D2 = [U,V], dif (U,1), dif (V,1) ; D1 = [X,1,Z], Y = U, U = 1, D2 = T, T = [1,V] ; D1 = [X,1,Z], Y = V, V = 1, D2 = T, T = [U,1] ; D1 = T, T = [X,1,Z], Y = 1, D2 = [U,V], dif (U,1), dif (V,1) ; D1 = [X,Y,1], Z = U, U = 1, D2 = T, T = [1,V] ; D1 = [X,Y,1], Z = V, V = 1, D2 = T, T = [U,1] ; D1 = T, T = [X,Y,1], Z = 1, D2 = [U,V], dif (U,1), dif (V,1) ; D1 = [X,Y,Z], D2 = T, T = [1,V], U = 1, dif (X,1), dif (Y,1), dif (Z,1) ; D1 = [X,Y,Z], D2 = T, T = [U,1], V = 1, dif (X,1), dif (Y,1), dif (Z,1) ; false.
Лучше. Конечно, мне кажется, что ничего не пропало.
Я понимаю; и я кажется понял, спасибо :). (Он должен пройти по списку дважды, один раз, чтобы member/2 завершился неудачно, и еще раз, чтобы non_member завершился успешно?)
Да, при таком подходе необходимо дважды просмотреть список; memberd_t/3
снижает эту стоимость, но делает больше: if помогает сохранить небольшое количество точек выбора, покрывая при этом максимально общие случаи.
Я, конечно, чувствую, что здесь скрываются интересные вопросы: 1 как показать, что одна чистая реализация дает избыточные ответы на конкретный запрос, а другая нет, 2 как избавиться от этих избыточных ответов.
Действительно очень умный :)