Tuesday, September 27, 2011

List Manipulation, artificial intelligence


Chapter 2
List Manipulation


This chapter introduces a special notation for lists, one of the most important data structures in Prolog, and provides some examples for how to work with them.

2.1     Notation
Lists are contained in square brackets with the elements being separated by commas. Heres an example:
[elephant, horse, donkey, dog]
This is the list of the four atoms elephant, horse, donkey, and dog. Elements of lists could be any valid Prolog terms, i.e. atoms, numbers, variables, or compound terms. This includes also other lists. The empty list is written as []. The following is another example for a (slightly more complex) list:
[elephant, [], X, parent(X, tom), [a, b, c], f(22)]

Internal representation.  Internally, lists are represented as compound terms using the functor . (dot). The empty list [] is an atom and elements are added one by one. The list [a,b,c], for example, corresponds to the following term:
.(a, .(b, .(c, [])))

2.2     Head and Tail
The first element of a list is called its head and the remaining list is called the tail. An empty list doesnt have a head. A list just containing a single element has a head (namely that particular single element) and its tail is the empty list.
A variant of the list notation allows for convenient addressing of both head and tail
of a list.  This is done by using the separator | (bar).   If it is put just before the last
term inside a list, it means that that last term denotes another list.  The entire list is

15








16                             Chapter 2.  List Manipulation

then constructed by appending this sub-list to the list represented by the sequence of elements before the bar. If there is exactly one element before the bar, it is the head and the term after the bar is the lists tail. In the next example 1 is the head of the list and [2,3,4,5] is the tail, which has been computed by Prolog simply by matching the list of numbers with the head/tail-pattern.
?- [1, 2, 3, 4, 5] = [Head | Tail]. Head = 1
Tail = [2, 3, 4, 5]
Yes
Note that Head and Tail are just names for variables. We could have used X and Y or whatever instead with the same result. Note also that the tail of a list (more generally speaking: the thing after |) is always a list itself. Possibly the empty list, but definitely a list.  The head, however, is an element of a list.  It could be a list as well, but not necessarily (as you can see from the previous example — 1 is not a list).  The same applies to all other elements listed before the bar in a list.
This notation also allows us to retrieve the, say, second element of a given list.  In the following example we use the anonymous variable for the head and also for the list after the bar, because we are only interested in the second element.
?- [quod, licet, jovi, non, licet, bovi] = [_, X | _]. X = licet
Yes
The head/tail-pattern can be used to implement predicates over lists in a very compact
and elegant way. We exemplify this by presenting an implementation of a predicate that
can be used to concatenate two lists.1  We call it concat_lists/3. When called with the
first two elements being instantiated to lists, the third argument should be matched with
the concatenation of those two lists, in other words we would like to get the following
behaviour:
?- concat_lists([1, 2, 3], [d, e, f, g], X). X = [1, 2, 3, d, e, f, g]
Yes
The general approach to such a problem is a recursive one.  We start with a base case
and then write a clause to reduce a complex problem to a simpler one until the base case
is reached.  For our particular problem, a suitable base case would be when one of the
two input-lists (for example the first one) is the empty list. In that case the result (the
third argument) is simply identical with the second list. This can be expressed through
the following fact:
1Note that most Prolog systems already provide such a predicate, usually called append/3 (see Section 2.3). So you do not actually have to implement this yourself.








Ulle Endriss.  And Introduction to Prolog Programming                                                  17


concat_lists([], List, List).
In all other cases (i.e. in all cases where a query with concat_lists as the main functor
doesn
t match with this fact) the first list has at least one element.  Hence, it can be
written as a head/tail-pattern:
[Elem | List1].  If the second list is associated with
the variable List2, then we know that the head of the result should be Elem and the tail
should be the concatenation of List1 and List2.  Note how this simplifies our initial
problem:  We take away the head of the first list and try to concatenate it with the
(unchanged) second list.  If we repeat this process recursively, we will eventually end
up with an empty first list, which is exactly the base case that can be handled by the
previously implemented fact. Turning this simplification algorithm into a Prolog rule is
straightforward:
concat_lists([Elem | List1], List2, [Elem | List3]) :-
   
concat_lists(List1, List2, List3).
And thats it! The concat_lists/3 can now be used for concatenating two given lists as specified. But it is actually much more flexible than that. If we call it with variables in the first two arguments and instantiate the third one with a list, concat_lists/3 can be used to decompose that list.  If you use the semicolon key to get all alternative solutions to your query, Prolog will print out all possibilities how the given list could be obtained from concatenating two lists.

?- concat_lists(X, Y, [a, b, c, d]).

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

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

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

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

No

Recall that the No at the end means that there are no further alternative solutions.








18                                        Chapter 2.  List Manipulation

2.3    Some Built-in Predicates for List Manipulation
Prolog comes with a range of predefined predicates for manipulating lists. Some of the most important ones are presented here. Note that they could all easily be implemented by exploiting the head/tail-pattern.
length/2: The second argument is matched with the length of the list in the first argu-
         ment. Example:
?- length([elephant, [], [1, 2, 3, 4]], Length). Length = 3
Yes
It is also possible to use length/2 with an uninstantiated first argument. This will generate a list of free variables of the specified length:
?- length(List, 3).
List = [_G248, _G251, _G254]
Yes
The names of those variables may well be different every time you call this query, because they are generated by Prolog during execution time.
member/2: The goal member(Elem, List) will succeed, if the term Elem can be matched
        
with one of the members of the list List. Example:
?- member(dog, [elephant, horse, donkey, dog, monkey]).
Yes
append/3: Concatenate  two  lists.   This  built-in  works  exactly  like  the  predicate
         concat_lists/3 presented in Section
2.2.
last/2: This predicate succeeds, if its second argument matches the last element of the
         list given as the first argument of last/2.
reverse/2: This predicate can be used to reverse the order of elements in a list. The first
        
argument has to be a (fully instantiated) list and the second one will be matched
         with the reversed list. Example:
?- reverse([1, 2, 3, 4, 5], X). X = [5, 4, 3, 2, 1]
Yes
select/3: Given a list in the second argument and an element of that list in the first, this
         predicate will match the third argument with the remainder of that list. Example:
?- select(bird, [mouse, bird, jellyfish, zebra], X). X = [mouse, jellyfish, zebra]
Yes








Ulle Endriss.  And Introduction to Prolog Programming                                                  19

2.4    Exercises
Exercise 2.1.  Write a Prolog predicate analyse_list/1 that takes a list as its argument and prints out the lists head and tail on the screen. If the given list is empty, the predicate should put out an accordant message. If the argument term isnt a list at all, the predicate should just fail. Examples:
?- analyse_list([dog, cat, horse, cow]). This is the head of your list: dog
This is the tail of your list: [cat, horse, cow]
Yes

?- analyse_list([]).
This is an empty list.
Yes
?- analyse_list(sigmund_freud).
No
Exercise 2.2.  Write a Prolog predicate membership/2 that works like the built-in predicate member/2 (without using member/2).
Hint: This exercise, like many others, can and should be solved using a recursive approach and the head/tail-pattern for lists.

Exercise 2.3.  Implement a Prolog predicate remove_duplicates/2 that removes all duplicate elements from a list given in the first argument and returns the result in the second argument position. Example:
?- remove_duplicates([a, b, a, c, d, d], List). List = [b, a, c, d]
Yes

Exercise 2.4.  Write a Prolog predicate reverse_list/2 that works like the built-in predicate reverse/2 (without using reverse/2). Example:
?- reverse_list([tiger, lion, elephant, monkey], List). List = [monkey, elephant, lion, tiger]
Yes
Exercise 2.5.  Consider the following Prolog program:
        
whoami([]).








20                             Chapter 2.  List Manipulation

whoami([_, _ | Rest]) :-
   
whoami(Rest).
Under what circumstances will a goal of the form whoami(X) succeed?

Exercise 2.6.  The objective of this exercise is to implement a predicate for returning the last element of a list in two different ways.
(a) Write a predicate last1/2 that works like the built-in predicate last/2 using a
      
recursion and the head/tail-pattern of lists.
(b) Define a similar predicate last2/2 solely in terms of append/3, without using a
      
recursion.
Exercise 2.7.  Write a predicate replace/4 to replace all occurrences of a given element (second argument) by another given element (third argument) in a given list (first argument). Example:
?- replace([1, 2, 3, 4, 3, 5, 6, 3], 3, x, List). List = [1, 2, x, 4, x, 5, 6, x]
Yes

Exercise  2.8.  Prolog lists without duplicates can be interpreted as sets.  Write a
program that given such a list computes the corresponding power set.  Recall that the
power set of a set S is the set of all subsets of S. This includes the empty set as well as
the set S itself.
Define a predicate power/2 such that, if the first argument is instantiated with a
list, the corresponding power set (i.e. a list of lists) is returned in the second position.
Example:
?-   power([a, b, c], P).
P = [[a, b, c], [a, b], [a, c], [a], [b, c], [b], [c], []]
Yes
Note: The order of the sub-lists in your result doesnt matter.

0 comments: