Similar Posts

Subscribe
Notify of
1 Answer
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
regex9
9 months ago

List are always written in square brackets. Their elements are each separated by a comma.

Example:

:- initialization(main).
main :- Numbers = [1, 2, 3].

Within main-Predicates becomes a variable Numbers created a list of numbers.

The values of a list do not necessarily have to be of the same type. For example, you can insert sublists:

Numbers = [1, [2, 3]]

This list now has two elements: a number (1) and a list consisting of numbers 2 and 3.

Lists are also recursive. They always consist of a value (the first value/head) and a reference to another list (all values except the head). That means this list:

Numbers = [1, 2, 3]

has an element 1 and a sublist which contains the remaining values (2, 3). If these sublists were explicitly extracted, they were again stored from a value (2) and a reference to a list that carries the 3 (and an empty list).

This structure will also be good with the help of display display-Predicates clearly:

display(Numbers) % output: '.'(1,'.'(2,'.'(3,[])))

Prolog now allows the pipe operator to disassemble the two parts:

:- initialization(main).
main :- 
  Numbers = [1, 2, 3],
  [Head | Tail] = Numbers,
  write(Head), % output: 1
  nl,
  write(Tail). % output: [2, 3]

You can also specify such a breakdown when the list is initial:

[Head | Tail] = [1, 2, 3]

Furthermore, the pipe operator serves to hang new elements in front of a list. In the following case, a list of 5 is created at the beginning:

:- initialization(main).
main :- 
  Numbers = [1, 2, 3],
  Numbers2 = [5 | Numbers],
  write(Numbers2). % output: [5, 1, 2, 3]

Knowing how lists can work and be broken down can now be used to allow each individual list element to be recursively output:

:- initialization(main).
main :-
  Numbers = [1, 2, 3],
  print_elements(Numbers).
        
print_elements([]) .
print_elements([Head | Tail]) :-
  write(Head),
  nl,
  print_elements(Tail).

First the list is created and a predicate print_element which already specifies a division during the value recording. The head value is output on the console, the rest to a new call of the print_element– Predicates passed on. This part separates this part again and moves as before. As soon as there is nothing to disassemble (so there is an empty list), the first print_element– Fact too and nothing is done. It therefore serves as a kind of Breakdown condition for recursion.

The following section clearly shows the recursive call stack (each indent illustrates the context of a call from print_element:

print_elements([1, 2, 3]):
  write(1)
  nl
  print_elements([2, 3]):
    write(2)
    nl
    print_elements([3]):
      write(3)
      nl
      print_elements([]):
        .

Another example (in which this time the number of elements in the list is determined):

:- initialization(main).
main :-
  Numbers = [1, 2, 3],
  number_of_elements(Numbers, Counter),
  write(Counter).
  
number_of_elements([], 0) .
number_of_elements([_ | Tail], Counter) :-
  number_of_elements(Tail, Subcounter),
  Counter is Subcounter + 1.

Again, a fact serves as a termination condition. When an empty list is passed, its length is 0. The principle is, by the way, similar to the previous example: The passed list is further divided up and the subsequence is passed on every call of the predicate. A counter variable is incremented more and more. Since the head element is not explicitly used, I chose an underscore as a variable name.

Callstack of the recursive call (each indent highlights the context of a call from number_of_element:

number_of_elements([1, 2, 3], Counter):
  number_of_elements([2, 3], Counter):
    number_of_elements([3], Counter):
      number_of_elements([], 0):
        .
      Counter is 0 + 1
    Counter is 1 + 1
  Counter is 2 + 1

Too good last one could still refer to a few helpful system predictions that can be manipulated or tested. For example Member or appendix.

?- member(1, Numbers). % check if Numbers contains 1

?- append([1, 2, 3], [4, 5, 6], Numbers). % Numbers = [1, 2, 3, 4, 5, 6]

A complete overview of existing system predictions can be found in the reference of a prologue implementation (e.g. GNU-Prolog or SWI-Prolog).

To Recursion Again, it is important to know how to create predicates (facts and rules) and how recursion generally works.

Predicates should basically make logical statements. This is how this fact meets:

likes(Marie, Anna).

a true statement. By means of a rule, one can formulate logical relationships between facts:

friends(personA, personB) :-
  likes(personA, personB),
  likes(personB, personA).

enemies(personA, personB) :-
  not(likes(personA, personB)),
  not(likes(personB, personA)).

The facts after the prefix operator (:-) make logical statements, the left side can be understood as a conclusion. She’s true when the facts happen.

In this example:

:- initialization(main).
main :- is_ancestor(klaus, richard).

is_parent(klaus, gert).
is_parent(gert, richard).
is_parent(richard, bernhard).

is_ancestor(Child, Ancestor) :-
  is_parent(Child, Ancestor),
  write('is ancestor').

is_ancestor(Child, Ancestor) :-
  is_parent(Child, X),
  is_ancestor(X, Ancestor).

we have already had a recursion that tries to cliché to find. First, the program tries the first is_ancestor– Claw. Since it does not find a fact which richard as a parent cliché she goes to the second is_ancestor– Claw. This lists a new fact which states that X a ancestor of cliché can be. Then the predicate calls itself again, it goes back to the first clause. This time they are looking for a fact that is_parent(X, richard) I would like to say that this is a very important point. The fact is_parent(gert, judgeard) can be assigned.

In order to be able to understand and apply recourse, it is necessary to exercise. In my opinion, it is also helpful to sketch out the call stack, but you should be careful to work with small specific values/chains to save time and effort.

If you want to develop your own recursive processes, it is helpful to first consider which basic operation must be repeated and how to stop them again. Furthermore, it may be necessary to carry on a calculated state. A variable should be defined for this purpose.

Renews a simple example to calculate the sum of all numbers of a list:

  1. Basic operation: The head of the list is added with the current sum.
  2. The rest of the list and the current sum are each data that must be passed on with each recursion step.
  3. At some point, the list is empty. This can be specified as a fact which immediately characterizes the termination of the recursion.
sum_of_numbers([], 0).
sum_of_numbers([Head | Tail], Sum) :-
  sum_of_numbers(Tail, TailSum),
  Sum is Head + TailSum.

Callstack on a list [4, 5, 6]:

sum_of_numbers([4 | [5, 6]], Sum):
  sum_of_numbers([5 | [6]], Sum):
    sum_of_numbers([6 | []], Sum):
      sum_of_numbers([], 0)
      Sum is 6 + 0
    Sum is 5 + 6
  Sum is 4 + 11

Try your own exercises. For example:

  • Turn around the order of the elements of a list.
  • Remove duplicates in the list [1, 2, 3, 4, 1].
  • Check that this list [e, b, b, e] corresponds to a Palindrom.
  • Exchange all occurrences of an item in a list with a different value.
  • Search in this list [0, 1, 2, 3, 5, 8, 13] by numbers 7 and 8. At Fund, the number is output in the console, if not the value -1.
  • Calculate the first nine digits of the Fibonacci order.

Furthermore, it would not be bad to look for some additional learning sources. A few examples: