next up previous contents
Next: Recursive Aggregation Up: Aggregation Previous: Min, Max, Sum, Count,

BagReduce and BagPO

Actually, XSB provides two basic aggregation operators, bagReduce/4 and bagPO/3, which are used to define all those predicates described in the previous section. We can also use the basic operators to define our own aggregation operators to do specialized things.

The bagReduce/4 predicate takes a bag name, an operator and its identity, and composes the elements of the bag using the operator. For example, we can use bagReduce/4 to define bagSum/2. First we define the sum operator, which must be a 3-ary HiLog operator:

    :- hilog sum.
    sum(X,Y,Z) :- Z is X + Y.
Then we can use this operator in bagReduce/4 to define bagSum/2:
    bagSum(Bag,Res) :- bagReduce(Bag,Res,sum,0).

The bagReduce/4 predicate intuitively works as follows: It finds the first element of the bag, applies the operator to the identity and that element and stores the result in the table. It then finds the second element in the bag, applies the operator to the element in the table and that second element, and replaces the current element in the table by this new result. It continues this way: for each new element the operator is applied to the current value and the new element and the result replaces the current value. The final value in the table is returned as the result.

As another simple example, we can define bagMin/2 by:

    :- hilog minimum.
    minimum(X,Y,Min) :- X =< Y -> Min = X ; Min = Y.

    bagMin(Bag,Res) :- bagReduce(Bag,Res,minimum,1.0e+38).
(assuming that 1.0e+38 is the maximum representable number.)

A slightly more complicated example is the definition of bagAvg/2, which requires a more complex operator that must both sum and count the elements of the bag. It can be defined as follows:

    :- hilog sumcount.
    sumcount([S|C],X,[S1|C1]) :- S1 is S+X, C1 is C+1.

    bagAvg(Bag,Avg) :- 
        bagReduce(Bag,[Sum|Count],sumcount,[0|0]),
        Avg is Sum/Count.

The bagPO/3 operator is also a metapredicate in that it takes a predicate as a parameter. It takes a HiLog binary predicate that defines a partial order. Given a bag, it returns nondeterministically all the maximal elements in the bag under the given partial order.

Consider the following example in which we try to find nice ways to explore a park while going from one point to another in it. Say the park has various interesting things to see and paths between them. We'll represent the paths in the park as a directed acyclic graph, with the points of interest as the nodes. (Both the acyclicity and the directedness of the graph might be somewhat unrealistic, but they can both be relaxed.) The goal now is, given a source and a destination node, find all ``maximal'' paths from the source to the destination. The idea is that we want to take a more-or-less direct route to our target, but we'd like to see as many points of interest as is reasonable along the way.

The following program will compute the set of such maximal paths.

    :- import bagPO/3 from aggregs.
    :- import member/2,append/3 from basics.

% stroll(X,Y,Path) if Path is a way to go from X to Y seeing many things.
    stroll(X,Y,Path) :- bagPO(walk(X,Y),BPath,subset), reverse(BPath,Path).

% subset(L1,L2) if L1 is a subset of L2.
    :- hilog subset.
    subset([],_L).
    subset([X|L1],L2) :- member(X,L2), subset(L1,L2).

% L is in walk(X,Y) if L is a (reversed) path from X to Y.
% (must be tabled because of left-recursion.)
    :- table walk(_,_)(_).
    walk(X,Y)([Y,X]) :- edge(X,Y).
    walk(X,Y)([Y|P]) :- walk(X,Z)(P), edge(Z,Y).

Here walk(X,Y) is a parameterized predicate name which represents the set of paths that go from node X to node Y. Each path is represented as a list of nodes (in reverse order of traversal.) The bagPO aggregation takes just the maximal paths, since we want the alternatives that allow us to see as many points of interest as possible. Here is the execution of the program on the data shown.

    edge(a,b).
    edge(b,c).
    edge(b,d).
    edge(c,d).
    edge(d,e).
    edge(a,f).
    edge(f,g).
    edge(g,e).

| ?- [aggreg].
[aggreg loaded]

yes
| ?- stroll(a,e,P).

P = [a,b,c,d,e];

P = [a,f,g,e];

no
| ?-

Some aggregate operations can be implemented using either bagReduce/4 or bagPO/3. bagMax/2 is a good example. Both of the following definitions are correct:

    :- hilog maximum.
    maximum(X,Y,Z) :- X >= Y -> Z = X ; Z = Y.
    bagMax(Bag,Max) :- bagReduce(Bag,Max,maximum,-1.0e38).

    :- hilog lt.
    lt(X,Y) :- X < Y.
    bagMax(Bag,Max) :- bagPO(Bag,Max,lt).

In such cases it is more efficient to use BagReduce/4 because it can take advantage of the fact that at any point in time, there will be at most one value in the table.


next up previous contents
Next: Recursive Aggregation Up: Aggregation Previous: Min, Max, Sum, Count,
David S. Warren
1999-07-31