Пролог: получить максимальное / минимальное значение с помощью возврата?
Я хотел бы знать, если это хорошая идея, чтобы получить максимальное значение (самый старый человек) некоторых фактов с помощью обратного отслеживания, как это:
data(MaxID, MaxName, MaxAge),
\+ (data(ID, Name, Age), ID \= MaxID, MaxAge < Age).
Или наоборот для минимального значения (самый молодой человек):
data(MinID, MinName, MinAge),
\+ (data(ID, Name, Age), ID \= MinID, MinAge > Age).
Эффективно ли это с точки зрения пространственной или временной сложности?
Является ли стиль реализации простым / понятным? Существуют ли "более хорошие" реализации?
2 ответа
Эффективно ли это с точки зрения пространственной или временной сложности?
Ваша реализация - это O(N^2) относительно временной сложности, поскольку для каждого предиката-кандидата все остальные "вызываются" для сравнения. Пространственная сложность O(1).
Существуют ли "более хорошие" реализации?
Да, более хорошие и эффективные реализации существуют. Уже давно SWI-Prolog предлагает библиотеку ( агрегат), прямое улучшение классических встроенных функций setof/bagof/findall:
?- aggregate(max(Age,data(ID,Name,Age)), ID^Name^data(ID,Name,Age), max(_,X)).
Обратите внимание на стиль количественного контроля, выраженный ID^Name^...
, Это то же самое, что требуется для setof/3 и bagof/3. Реализация является более эффективной, основанной на невыбираемом назначении.
Последним дополнением можно считать библиотеку ( solution_sequence). Прежде чем копаться в этом последнем, подумайте об использовании библиотеки (агрегата).
редактировать
Основываясь на @false комментарии к вопросу и ответе @boris, я бы попытался обеспечить более "хорошую" (конечно, субъективную оценку) реализацию:
min(P,A) :-
copy_term(P,Q),
arg(A,P,V), arg(A,Q,U),
call(P), \+ ( call(Q), U@<V ).
Теперь вы можете передать свой предикат в качестве первого параметра и указать аргумент P
использоваться для сравнения со вторым параметром.
Короткий ответ: нет, на самом деле ничего более "хорошего" не существует, с точки зрения того, чтобы быть всегда правильным, простым в реализации и эффективным одновременно.
Небольшая поправка: достаточно написать
data(MaxID, MaxName, MaxAge),
\+ (data(ID, Name, Age), MaxAge < Age)
(как предложено @false в комментарии к вашему вопросу)
Вы можете увидеть также этот вопрос и ответы. Этот ответ в деталях, когда и почему с помощью setof/3
может быть проблематичным. Это может быть важно для вашего случая использования.
Другой способ сделать это (не упомянутый в очень полезном ответе @CapelliC) - собрать все решения из вашего предиката и отсортировать их, используя ключ. Если вы используете SWI-Prolog или другой Prolog, имеющий версию сортировки с 4 аргументами, которая позволяет вам выбирать сравнение и ключ в термине, вы можете сделать, например:
bagof(data(ID, Name, Age), data(ID, Name, Age), All),
sort(3, @>=, All, [data(Max_ID, Max_Name, Max_Age)|_])
Пока ваш data/3
это просто таблица фактов, это безопасно для использования.
Это, конечно, ломается, если у вас есть эквивалентные, но не равные элементы в списке, как data(10, john, 34)
а также data(101, jane, 34)
, В вопросе и ответе, который я связал, есть примеры, как с этим справиться, но опять же, я действительно не думаю, что это становится "лучше". Это может быть более эффективным. Я настоятельно рекомендую тщательно рассмотреть ваш вариант использования и измерить производительность, если вы считаете, что это может быть "горлышком бутылки".
Рассматривая реализацию библиотеки (агрегата), предложенную @CapelliC, становится очевидным, что она предназначена именно для этого варианта использования: она может найти минимум, максимум, сумму и т. Д. В постоянной памяти и затрагивать каждый факт только один раз и отступает построить весь список, если это необходимо.