Prolog: predicate for maximum without accumulator

爱⌒轻易说出口 提交于 2019-12-01 16:42:20
Willem Van Onsem

Yes, you can calculate the maximum after the recursive step. Like:

max([M],M).          % the maximum of a list with one element is that element.
max([H|T],M) :-
    max(T,M1),       % first calculate the maximum of the tail.
    M is max(H,M1).  % then calculate the real maximum as the max of
                     % head an the maximum of the tail.

This predicate will work on floating points for instance. Nevertheless it is better to use an accumulator since most Prolog interpreters use tail call optimization (TCO) and predicates with accumulators tend to work with tail calls. As a result predicates with TCO will usually not get a stack overflow exception if you want to process huge lists.

As @Lurker says, is only works in case that the list is fully grounded: it is a finite list and all elements grounded. You can however use Prolog's constraint logic programming package clp(fd):

:- use_module(library(clpfd)).

max([M],M).          % the maximum of a list with one element is that element.
max([H|T],M) :-
    max(T,M1),       % first calculate the maximum of the tail.
    M #= max(H,M1).  % then calculate the real maximum as the max of
                     % head an the maximum of the tail.

You can then for instance call:

?- max([A,B,C],M),A=2,B=3,C=1.
A = 2,
B = M, M = 3,
C = 1 ;
false.

So after the max/2 call, by grounding A, B and C, we obtain M=3.

Neither the standard predicate is/2 nor a CLP(FD) predicate (#=)/2 can do mathematics here. So that in the end, for certain applications such as exact geometry, they might not be suitable.

To make a point lets consider an example and the alternative of a computer algebra system (CAS). I am doing the demonstration with the new Jekejeke Minlog 0.9.2 prototype which provides CAS from within Prolog.

As a preliminary we have two predicates eval_keys/2 and min_key/2, their code is found at the appendix in this post. Lets illustrate what this predicates do, first with integer. The first predicate just makes the keys of a pair list evaluated:

Welcome to SWI-Prolog (Multi-threaded, 64 bits, Version 7.3.25-3-gc3a87c2)
Copyright (c) 1990-2016 University of Amsterdam, VU Amsterdam

?- eval_keys([(1+3)-foo,2-bar],L).
L = [4-foo,2-bar]

The second predicate picks this first value where the key is minimum:

?- min_key([4-foo,2-bar],X).
X = bar

Now lets look at other key values, we will use square roots, which belong to the domain of algebraic numbers. Algebraic numbers are irrational and thus never fit into a float. We therefore get for the new example the outcome foo:

?- eval_keys([(sqrt(98428513)+sqrt(101596577))-foo,sqrt(400025090)-bar],L).
L = [20000.62724016424-foo, 20000.627240164245-bar].

?- min_key([20000.62724016424-foo, 20000.627240164245-bar], X).
X = foo.

CLP(FD) has only integers and there is no direct way to represent algebraic numbers. On the other hand many CAS systems support radicals. Our Prototype even supports comparison of them so that we can obtain the exact result bar:

Jekejeke Prolog 2, Runtime Library 1.2.2
(c) 1985-2017, XLOG Technologies GmbH, Switzerland

?- eval_keys([(sqrt(98428513)+sqrt(101596577))-foo,sqrt(400025090)-bar],L).
L = [radical(0,[98428513-1,101596577-1])-foo,
   radical(0,[400025090-1])-bar]

?- min_key([radical(0,[98428513-1,101596577-1])-foo,
   radical(0,[400025090-1])-bar],X).
X = bar

That bar is the exact result can be seen for example by using a multi-precision calculator. If we double the precision we indeed see that the last square root is the smaler one and not the sum of the square roots:

?- use_module(library(decimal/multi)).
% 7 consults and 0 unloads in 319 ms.
Yes

?- X is mp(sqrt(98428513)+sqrt(101596577), 32).
X = 0d20000.627240164244658958331341095

?- X is mp(sqrt(400025090), 32).
X = 0d20000.627240164244408966171597261

But a CAS need not to proceed this way. For example our Prolog implementation uses a Swinnerton-Dyer polynomial inspired method to compare radical expressions, which works purely symbolic.

Appendix Test Code:

% :- use_module(library(groebner/generic)). /* to enable CAS */

eval_keys([X-A|L], [Y-A|R]) :- Y is X, eval_keys(L, R).
eval_keys([], []).

min_key([X-A|L], B) :- min_key(L, X, A, B).

min_key([X-A|L], Y, _, B) :- X < Y, !, min_key(L, X, A, B).
min_key([_|L], X, A, B) :- min_key(L, X, A, B).
min_key([], _, A, A).
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!