prolog programming (volume 5) dr w.f. clocksin. list cells + unification = great idea normally we...

30
Prolog Programming (Volume 5) Dr W.F. Clocksin

Upload: kaleb-crown

Post on 14-Dec-2015

217 views

Category:

Documents


0 download

TRANSCRIPT

Prolog Programming(Volume 5)

Dr W.F. Clocksin

List cells + Unification = Great Idea

Normally we use ‘proper lists’, which are defined according to the theory of lists:A list of length 0 is []A list of length N is [H|T] where T is a list of length N-1

This is good when we only want to cons elements onto the from of the list. But, Prolog variables make it possible to do something else:

List cells + Unification = Great Idea

zap([a, b, c, Z], Z, R)

zap(X, d, X)

so, R = [a, b, c, d]More generally,

zap([a, b, c | Z], Z, R)

zap(X, [d, e, f], X)

Difference Structures

Probably one of the most ingenious programming techniques ever invented (by Sten-Åke Tärnlund, 1975) yet neglected by mainstream computer science.

A way of using variables as ‘holes’ in data structures that are filled in by unification as the computation proceeds.Most examples here will be with difference lists.

Will introduce by showing the usual (non difference list) algorithm for concatenating lists.

Concatenating two lists (WS 25)

?- append([a, b, c], [d, e, f], X).X = [a, b, c, d, e, f]

append([], L, L).append([X|Y], T, [X|Z]) :- append(Y, T, Z).

Notice this is tail recursive. This definition is possible because of logical variables. Definitions in other languages cannot be tail recursive because the last call is cons, not append.

Concatenating two lists (WS 25)

What do these goals do??- append([a, b, c], X, [a, b, c, d, e, f]).

?- append([a, b, c], [d, e, f], X).

?- append(X, Y, [a, b, c, d, e, f]).

X = [] Y = [a, b, c, d, e, f];

X = [a] Y = [b, c, d, e, f];

X = [a, b] Y = [c, d, e, f];

. .

. .

X = [a, b, c, d, e, f] Y = []

Linearising (flattening) a list (WS 25)

Flattening a list is a way of listing the leaf nodes (finding the fringe) of tree-structured data. For example,[ a, b], [c, d, [e, f], g], h, [i, [k] ] ]

flattens to [a, b, c, d, e, f, g, h, i, k]

Here is the usual program:flatten([], []).

flatten([H|T], L3) :-

flatten(H, L1), flatten(T, L2), append(L1, L2, L3).

flatten(X, [X]).

This is very inefficient: how many calls to append?

Instead, Difference Lists

The idea is to represent a list segment as a pair of terms, the front and the back. For example, the list segment L1-L2 has the elements a,b:

L1 refers to the front of the list, and L2 refers to the back, often a variable. This variable can be used as a ‘hole’ into which another term may be instantiated. If the other term has a hole too, this can be useful.

L1 L2

a

b

L1 [a, b | L2]

Example: Append L1-L2 to L3-L4

L1 L2

a

b

L3 L4

d

ec

Call the result X-Y. The following are true about X-Y:

X should co-refer with L1 (to be the front of the list)

Y should co-refer with L4 (to be the back of the list)

L2 should co-refer with L3 (to join the lists together)

Make it so:

L1 L2

a

b

L3 L4

d

ec

X Y

In Prolog we can use this idea to implement constant-time appending of two lists. As suggested above, doing this is simply a matter of re-arranging variables.

Definition of app

The goal app(L1,L2,L3,L4,X,Y) succeeds when the difference lists made from L1 and L2 and concatenated with the difference lists made from L3 and L4 to give the resulting difference list made from X and Y.

app(L1, L2, L2, L4, L1, L4).

Example execution:?- app([a, b, c | Z1], Z1, [d, e, | Z2), Z2, X, Y).

X = [a, b, c, d, e | Y].

A better way to see the arrangement of variables is like this:

app(A, B, B, C, A, C).

Usual notation of difference lists

Denote difference list with front L1 and back L2 as L1-L2, where - is an infix binary operator. This represents difference lists as a single term, so cuts down on the arity of procedures. Programs are clearer, as in this definition of app:.

app(A-B, B-C, A-C).

Example execution:?- app([a, b, c | Z1]-Z1, [d, e, | Z2)-Z2, X-Y).

X-Y = [a, b, c, d, e | Y]-Y.

The extra space taken by the ‘-’ functor is not a serious problem in practice. But then, why use app when you can do the rearrangement of variables in-place where it is needed in a program!

Summary

L-L is the null difference list

[a | Z]-Z is the difference list containing ‘a’. Similarly, [a, b, c | Z]-Z is the difference list containing a, b, c.

Unifying the difference list X with Y-[] will rectify the list, that is, turn it into a proper list. For example. unifying [a, b, c | Z]-Z with Y-[] instantiates Y to [a, b, c].

Linearising (flattening) a list (WS 28)

flatten(X, Y) :- flatpair(X, Y-[]).

flatpair([], L-L).

flatpair([H|T], L1-L3) :-

flatpair(H, L1-L2), flatpair(T, L2-L3).

flatpair(X, [X|Z]-Z).

Notice the pattern of ‘stitching’ elements together.

Linearising (flattening) Trees (WS 29)

First, how to represent a binary tree.

Leaves are any terms.

Nodes are constructed from the term n(t1, t2) where terms t1 and t2 are trees called the left branch and the right branch. For example, the term n(n(a,b),n(c,n(d,e))) names the tree:

n

n n

a b c n

d e

Linearising (flattening) Trees (WS 29)

Linearising a tree. This example will add interest by doing a partial map: only the integers will be linearised.

The naive method (using append):

lintree(n(A,B), L1) :-lintree(A, LA),lintree(B, LB),append(LA, LB, L1).

lintree(I, [I]) :- integer(I).

lintree(X, []).

Linearising (flattening) Trees (WS 29)

The difference list method:

lintree(n(A,B), L1) :-lintree(A, LA),lintree(B, LB),append(LA, LB, L1).

lintree(I, [I]) :- integer(I).

lintree(X, []).

Worked exercise for WS 29

/* picktree(tree, list of apples, list of pears) */

picktree(apple, [apple | L]-L, P-P).

picktree(pear, A-A, [pear | L]-L).

picktree(n(L,R), A1-A3, P1-P3) :-

picktree(L, A1-A2, P1-P2),

picktree(R, A2-A3, P2-P3).

picktree(Other, A-A, P-P).

Normalising Sum Expressions (WS 30)

The sums (a+b)+(c+d) and (a+(b+(c+d))) may be normalised into a standard form that associates on the left: a+b+c+d, or equivalently, ((a+b)+c)+d. This is only possible because they are sums.

+

+

+a

b

c d

+

+ +

a b c d

+

+

+

a b

cd

Define a predicate normsum

The goal normsum(X,Y) succeeds when sum expression X normalises to Y. One method is to first flatten the sum, and then build up the normalised version in another pass. Here is flat(A,B,C), which flattens the sum into the difference list B-C:

flat(X+Y), R1-R3) :-

!, flat(X, R1-R2), flat(Y, R2-R3).

flat(X, [X|Z] - Z).

The difference ‘thread’ is R1R2R3, and note the ‘!’ to commit to the first clause when a sum node is encountered.

Building up the normalised sum

This is not obvious. One way is to accumulate the ‘tree so far’, giving it a new parent node each time an element is found. This ‘stacks up’ nodes instead of inserting them. A ‘base case’ for nil is not needed because the flattened list will have at least two elements.

build([X], T, T+X) :- !.

build([H | L], T, Z) :- build(L, T+H, Z).

Putting them together

Now normalise by flattening then building. The ‘tree so far’ in the second argument of build needs to be initialised with the first element of the flattened list:

normalise(A, C) :-

flat(A, B-[]),

B = [T | L],

build(L, T, C).

Do it in one pass instead!

Here is a better program:

normalise(X, Y) :- norm(X, []-Y).

norm(X+Y, A-C) :- !,

norm(X, A-B), norm(Y, B-C).

norm(X, []-X) :- !.

norm(X, A-(A+X)).

Here the accumulator is used not only for differencing the elements, but also for building the tree so far. The constant [] is used to represent the null accumulator, but there is no list processing as such.

Find maximum element of a tree

A valued (weighted, coloured) binary tree can be defined using compound terms. A node is represented by n(V,L,R), where V is the value (say a number), and L and R are the left and right branches of the tree. A terminal node will have L and R instantiated to []. For example the term:

n(3, n(1, n(4, [], []), n(1, [], [])), n(5, n(9, n(2, [], []), []), n(6, [], n(5, [], []))))

represents the following tree:

3

1

4 1

5

9

2

6

5

Notice no terminals coming from here and here.

Find maximum element of a tree

Just like finding the maximum element of a list, use an accumulator to represent the ‘biggest value so far’.

/* findmax(Tree, Value so far, Final value) */

findmax([], A, A).findmax(n(V, L, R), A, Z) :-

V >= A, !,findmax(L, V, A1), findmax(R, A1, Z).

findmax(n(V, L, R), A, Z) :-V < A, !,findmax(L, A, A1), findmax(R, A1, Z).

Copy a tree

Make a tree of the same shape as the input.

/* copyTree(InputTree, OutputTree) */

copyTree([], []).copyTree(n(V, L, R), n(V, L1, R1) :-

copyTree(L, L1),copyTree(R, R1).

Worksheet 32: Max Tree

Given an input tree T, write a program that constructs a tree of the same shape as T, but in which the value of each node has been set to the value of the greatest node in T.

This can be by finding the greatest element, and then making a copy inserting the greatest element as the value of each node in the copy. However, it can be done in one pass (!) as follows:

Worked Exercise

/* mt(InTree, OutTree, Hole, AccumHighest, Highest) */

maxtree(A, B) :- mt(A, B, H, 0, H).

mt(n(W, A, B), n(H, A1, B1), H, AC, N) :-W < AC,mt(A, A1, H, AC, ACA),mt(B, B1, H, ACA, N).

mt(n(W, A, B), n(H, A1, B1), H, AC, N) :-W < AC,mt(A, A1, H, AC, ACA),mt(B, B1, H, ACA, N).

mt([], [], H, A, A).

Difference Lists are...

• Very efficient.• Popular -- in widespread use in Prolog.• Expressive of new methods of computation.• Sometimes misleading: difference-list append is

not free of side-effects (but is backtrackable)• Often hidden. Programmers encapsulate the

front and back in a structure p(L1, L2). Most common for this purpose is the infix ‘-’.