Вы описываете списки, так что вы можете использовать DCG для задачи, так как они обычно дают легко читаемый код.Кроме того, вы можете использовать дополнительный аргумент для подсчета элементов в списке по мере его обхода.Рассмотрим следующий код:
list_filtered_length(L,F,Len) :- % the filtered list F is described
phrase(filtered_len(L,Len,0),F). % by the DCG filtered_len//3
filtered_len([],N,N) --> % if the list is empty, the counter is the length
[]. % and the filtered list is empty
filtered_len([(A,2)|Ps],N,C0) --> % if the head of the list is (A,2)
{C1 is C0+1}, % the counter is increased
[(A,2)], % (A,2) is in the filtered list
filtered_len(Ps,N,C1). % the same for the tail
filtered_len([(_,B)|Ps],N,C) --> % if the head of the list is (_,B)
{dif(B,2)}, % with B not being 2, it's not in the list
filtered_len(Ps,N,C). % the same for the tail
Запрос этого предиката в вашем примере дает желаемый результат:
?- list_filtered_length([(1,2),(3,4),(5,2),(4,2),(8,0)],F,Len).
F = [ (1, 2), (5, 2), (4, 2)],
Len = 3 ;
false.
Очевидно, что если вы хотите применить другой фильтр, вы должны переписатьдва рекурсивных правила DCG.Было бы лучше иметь фильтр, определенный как отдельный предикат, и передавать его в качестве аргумента, что делает предикат более универсальным.Также было бы неплохо, чтобы предикат успешно выполнялся, если существует только одно решение.Это можно реализовать с помощью if_ / 3 и (=) / 3 .Чтобы использовать его в качестве первого аргумента if_/3
, предикату фильтра необходимо подтвердить значение истинности в качестве дополнительного аргумента:
filter_t((_,X),T) :-
if_(X=2,T=true,T=false).
Как видите, последний аргумент - true
, еслиусловие фильтра выполняется и false
в противном случае:
?- filter_t((1,1),T).
T = false.
?- filter_t((1,2),T).
T = true.
Теперь предикат можно переопределить с дополнительным аргументом для фильтра, например:
list_filtered_by_length(L,LF,F_2,Len) :- % F_2 is the filter argument
phrase(filtered_by_len(L,F_2,Len,0),LF).
filtered_by_len([],_F_2,N,N) -->
[].
filtered_by_len([P|Ps],F_2,N,C0) -->
{if_(call(F_2,P),(X=[P], C1 is C0+1),
(X=[], C1 = C0))},
X, % X is in the filtered list
filtered_by_len(Ps,F_2,N,C1).
Если заголовок спискаудовлетворяет условию фильтра (call(F_2,P)
), он находится в фильтрованном списке (X=[P]
) и счетчик увеличивается (C1 is C0+1
), в противном случае его нет в списке (X=[]
) и счетчик не увеличивается(C1 = C0
).
Теперь пример запроса выполняется детерминистически:
?- list_filtered_by_length([(1,2),(3,4),(5,2),(4,2),(8,0)],F,filter_t,Len).
F = [ (1, 2), (5, 2), (4, 2)],
Len = 3.
Если вы хотите фильтровать что-то еще, просто определите другой предикат фильтра.Например, если вы хотите отфильтровать все пары одинаковых элементов из списка пар, вы можете определить ...
filter2_t(X-Y,T) :-
if_(X=Y,T=true,T=false).
... и затем запрос:
?- list_filtered_by_length([a-a,b-c,d-d,e-f],F,filter2_t,Len).
F = [a-a, d-d],
Len = 2.
РЕДАКТИРОВАТЬ
В качестве альтернативы, вы можете выразить это отношение довольно компактно, используя tfilter / 3 , как предложено @false в комментариях.Как и в версии DCG, вы передаете предикат фильтра reifying в качестве третьего аргумента, который затем используется в качестве первого аргумента tfilter/3
.Впоследствии длина отфильтрованного списка описывается встроенным length/2
.
list_filtered_by_length(L,FL,F_2,Len) :-
tfilter(F_2,L,FL),
length(FL,Len).
. Вышеуказанные запросы дают те же ответы, что и в версии DCG.