• No results found

P ROLOG as a Programming Language

In document 8 Extended Database Concepts (Page 36-55)

In the following, we will present PROLOG implementations of well–known

algorithms for searching in graphs and binary search trees. The benefits of PROLOG are

• the elegant handling of data structures (lists, trees, XML), • (implicit) backtracking, and

• the compact representation of case distinctions in different rules.

The algorithms are typically recursive. Recursion can be formulated nicely due to the compact list access.

Graph Search

The graph is given by PROLOG facts for the predicates graph_arc/2 and graph_sink/1. Labyrinth: 6 - a b c d e f g h i graph_arc(i, f). graph_arc(i, h). graph_arc(h, g). graph_arc(g, d). graph_arc(d, e). graph_arc(d, a). graph_arc(a, b). graph_arc(b, c). graph_sink(c).

Search for Simple Paths

The predicate graph_search/2 searches for a simple path from a given node to a sink of a graph:

% graph_search(+Node, ?Path) <- graph_search(X, Path) :-

graph_search(X, [X], Path).

Another predicate graph_search/3 with the same predicate symbol but a

different arity is called.

Notation for arguments in the comment line:

Visited

Path =

[Y1=Y, . . . ,Yn=Z]

X Y Z

- - -

• A call graph_search(X, Visited, Nodes) with a bound

argument X, which is not a sink, and a list Visited of already visitied nodes

– uses an edge from X to not yet vistited successor node Y, and then – calculates a path Path from Y to a sink Z, which does not visit Y and

the nodes in Visited.

If no path from Y to a sink can be found, then another successor node of

X must be used (Backtracking).

• The result Nodes = [X|Path] is a simple path from X to a sink of the graph.

The predicate graph_search/3 is recursive, because of its second rule:

% graph_search(+Node, +Visited, ?Path) <- graph_search(X, _, [X]) :-

graph_sink(X).

graph_search(X, Visited, [X|Path]) :- graph_edge(X, Y),

not(member(Y, Visited)),

write(user, ’->’), write(user, Y), graph_search(Y, [Y|Visited], Path).

Visited

Path =

[Y1=Y, . . . ,Yn=Z]

X Y Z

- - -

Termination is ensured by the fact that already visited nodes cannot be visited

The following rule symmetrisises the predicate graph_arc/2:

graph_edge(X, Y) :- ( graph_arc(X, Y)

; graph_arc(Y, X) ).

Thus, it is not necessary to explicitely list the inverse edges. E.g., from

graph_arc(i, f).

we obtain

graph_edge(i, f). graph_edge(f, i).

• The initial call graph_search(X, [X], Path) calculates a simple path from X to a sink of the graph.

– If X is a sink, then the first rule for graph_search/3 computes

Path as an empty list.

– Otherwise, the recursive, second rule choses a successor node Y using

graph_edge(X, Y), and then it continues searching from there.

• Further paths can be searched for by backtracking.

– Alternative successor nodes Y can be used in the second rule.

– In the implementation above, we can continue searching beyond a sink by using the second rule instead of the fist one.

Implicit and Explicit Backtracking

In PROLOG, backtracking is used automatically (implicitly).

In a procedural language, backtracking has to be implemented explicitly. In a direct translation of the code above to a procedural environment, a call

graph_edge(X, Y) can only produce a single successor node Y of X

if there is no path from Y to a sink, then the computation fails. Moreover, at most one solution could be computed.

If we implement the graph search procedurally using explicit backtracking, then we get more lines of more complex code than in PROLOG.

Computation

• The predicate graph_search/2 use depth first search, and it calculates simple paths (without duplicate nodes).

• With the call graph_search(+Node, -Path), we can calculate all simple paths from Node to a sink (graph_sink) by backtracking:

?- graph_search(i, Path). ->f->h->g->d->e->a->b->c Path = [i, h, g, d, a, b, c] ?- graph_search(e, Path). ->d->a->b->c Path = [e, d, a, b, c] ; ->g->h->i->f No

• If we add another edge graph_arc(e, b) to the graph (i.e., we tear down the wall between e and b), then there appears another simple path

[e, b, c] from e to the sink c.

• All results can be calculated by backtracking and findall/3:

graph_arc(e, b). ?- findall( Path,

graph_search(e, Path), Paths ).

Paths = [[e, d, a, b, c], [e, b, c]] Yes

The Meta–Predicate findall/3

Finding of all solutions for a goal:

findall( X, goal(X), Xs )

The DDK allows for the following equivalent set notation:

Xs <= { X | goal(X) }

Further important meta–predicates are checklist/2 and maplist/3 for

lists, as well as the predicates for loops (control structures) from the library

Binary Search Trees

% search_in_tree(+Key, +Tree) <- search_in_tree(Key, Tree) :-

Tree = tree(Root, Lson, Rson), ( Key = Root

; Key < Root ->

search_in_tree(Key, Lson) ; Key > Root ->

search_in_tree(Key, Rson) ).

Term Representation of a Search Tree

tree(5,

tree(4, nil, nil), tree(9,

tree(6, nil, nil),

tree(10, nil, nil) ) )

5

10 6

9 4

% insert_into_tree(+Key, +Tree, ?New_Tree) <- insert_into_tree(Key, Tree, New_Tree) :-

Tree = tree(Root, Lson, Rson), ( Key = Root ->

New_Tree = Tree ; Key < Root ->

insert_into_tree(Key, Lson, L), New_Tree = tree(Root, L, Rson) ) ; K > Root ->

insert_into_tree(Key, Rson, R), New_Tree = tree(Root, Lson, R) ). insert_into_tree(Key, nil New_Tree) :-

Important Concepts

• Terms (for Data and Control Structures) and Unification • Backtracking

• SLDNF–Resolution

PROLOG allows for

• declarative programming, • compact programs, and

Data Structures, Operations, and Control Structures

• The restriction to a few basic data types and a single complex data type,

namely the terms, which is generic and subsumes all the other types, standardizes the data structures.

• There are no explicit type declarations.

• There exists a large collection of generic operations that are applicable to

terms – and thus to all data types.

• Frequently, meta–predicates are used.

• In addition to standard control structures, such as branching

(if–then–else), loops (for, while), and recursion, user–defined control structures can be built as meta–predicates.

Software Engineering Aspects

PROLOG supports abstraction and compact code, and thus stimulates

refactoring:

• The generic type of terms with generic operations supports abstraction

and code reuse.

• User–defined control structures allow for further abstraction.

• Unification, implicit backtracking, and abstaining from explicit type

declarations, result in very compact code and support rapid prototyping.

• Declarativity makes the code much more readable and thus extensible.

Switching from conventional programming languages to the logic

programming paradigm is difficult and usually requires a lot of training and effort.

Course on Deductive Databases

Topics:

• foundations and applications of PROLOG and DATALOG,

data modelling and programming;

• the deductive database system DDBASE; • efficient evaluation of DATALOG programs;

• further language constructs in the DDK (DISLOG Developers’ Kit):

– complex data structures,

– default negation and disjunction;

In document 8 Extended Database Concepts (Page 36-55)

Related documents