Пролог: получение максимального/минимального значения с помощью возврата?

Я хотел бы знать, стоит ли получать максимальное значение (самый старый человек) некоторых фактов с помощью обратного отслеживания следующим образом:

data(MaxID, MaxName, MaxAge),
\+ (data(ID, Name, Age), ID \= MaxID, MaxAge < Age).

Или наоборот для минимального значения (самый молодой человек):

data(MinID, MinName, MinAge),
\+ (data(ID, Name, Age), ID \= MinID, MinAge > Age).

Является ли это эффективным с точки зрения пространственной или временной сложности?

Является ли стиль реализации простым/прямым? Существуют ли «более приятные» реализации?


person daniel451    schedule 04.03.2017    source источник
comment
Удаление ID \= MinID делает его немного быстрее.   -  person false    schedule 04.03.2017


Ответы (2)


Является ли это эффективным с точки зрения пространственной или временной сложности?

В вашей реализации это O (N ^ 2) по временной сложности, поскольку для каждого предиката-кандидата все остальные «вызываются» для сравнения. Пространственная сложность O (1).

Существуют ли «более приятные» реализации?

Да, существуют более приятные и эффективные реализации. Уже давно SWI-Prolog предлагает библиотеку (aggregate), прямое расширение классических встроенных функций 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_sequences). Прежде чем копаться в этом последнем, рассмотрите возможность использования библиотеки (агрегата).

изменить

Основываясь на комментарии @false к вопросу и ответе @boris, я бы попытался предоставить «более приятную» (конечно, субъективную оценку) реализацию:

min(P,A) :-
    copy_term(P,Q),
    arg(A,P,V), arg(A,Q,U),
    call(P), \+ ( call(Q), U@<V ).

теперь вы можете передать свой предикат в качестве первого параметра и указать аргумент P, который будет использоваться для сравнения со вторым параметром.

person CapelliC    schedule 04.03.2017
comment
Недостаток библиотеки (совокупность): нарушает ограничения. А как насчет другого? - person false; 04.03.2017
comment
Говоря только о вашем редактировании: еще одна приятная вещь (очень субъективная) была бы, если бы библиотека (агрегат) могла сделать эквивалент foldl без создания списка. На данный момент он делает это только для жестко запрограммированного набора агрегатных функций: count, min, max. По крайней мере, это то, что я получаю, глядя на код. - person ; 05.03.2017

Короткий ответ: нет, ничего действительно «лучшего» не существует с точки зрения того, чтобы быть всегда правильным, простым в реализации и в то же время эффективным.

Небольшая поправка: достаточно написать

data(MaxID, MaxName, MaxAge),
\+ (data(ID, Name, Age), MaxAge < Age)

(как было предложено @false в комментарии к вашему вопросу)

Вы также можете увидеть этот вопрос и ответы. В этом ответе подробно рассказывается, когда и почему использование setof/3 может быть проблематичным. Это может быть важно для вашего варианта использования.

Другой способ сделать это (не упомянутый в очень полезном ответе @CapelliC) - собрать все решения из вашего предиката и отсортировать их с помощью ключа. Если вы используете SWI-Prolog или другой Prolog с 4- версия аргумента sort, позволяющая выбрать сравнение и ключ внутри термина, например:

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, очевидно, что она предназначена именно для этого варианта использования: она может найти минимум, максимум, сумму и т. д. в постоянной памяти и касаясь каждого факта только один раз, и возвращается для построения всего списка, если это необходимо.

person Community    schedule 05.03.2017
comment
Конечно, библиотека (агрегат) показывает свою полезность, когда вам нужно агрегировать. Сумма не может быть решена в том же стиле ОП. Тогда не лучше иметь единый синтаксис для изучения, охватывающий все (ну, более или менее) функции агрегирования, которые предлагает SQL? - person CapelliC; 05.03.2017
comment
@CapelliC Безусловно, я пытался сравнить выполнение этого вручную с помощью bagof или findall с использованием библиотеки (агрегата). - person ; 05.03.2017
comment
Я не совсем понимаю, что вы имеете в виду ... по всем показателям необходимость писать программу, даже такую ​​глупую, как та, которую мы обсуждаем, кажется менее хорошим выбором, WRT учится использовать библиотеку. Разве это не основное свойство всего ПО сегодня? - person CapelliC; 05.03.2017
comment
@CapelliC Теперь я не уверен, понимаю ли я, что вы имеете в виду. Если вы имеете в виду, что научиться пользоваться библиотеками полезнее, чем делать что-то самостоятельно, что ж, да, но выбор библиотек требует понимания, которое вы получаете, делая это самостоятельно. Или я пропустил вашу мысль? - person ; 05.03.2017
comment
Если бы это (from doing it yourself) было правдой, то не могло бы существовать хорошей библиотеки :) - person CapelliC; 05.03.2017
comment
@CapelliC Ха, конечно, может, пытаешься сделать это сам и терпишь неудачу? Но если серьезно, если вы научитесь программировать, даже не пытаясь заново реализовать функциональность библиотеки, вы получите очень специфический и несколько ограниченный набор навыков. - person ; 05.03.2017