K-99:
Ninety-Nine K Problems
encrypted@nsl.com
This problem collection originated as set of Prolog
exercises. Versions exist for Haskell
and Lisp
as well. In this document, the problems have been adapted for K. For the most part, I've left the
original text intact, only modifying the examples and
accompanying explanations for a K audience.
The problems were originally assigned different levels of
difficulty. (*) for easy, (**) for intermediate, (***) for
difficult. I've retained the original estimates, although
opinions may vary about how accurately they measure the
difficulty of finding K solutions.
In general 'f x' is a function of one argument x, and
'f[x;..;z]' is a function of arguments x .. z. Each problem in
the set has a corresponding K script; e.g. problem K12 has k12.k.
Each script defines a function with the same name as the script;
e.g. k12.k defines k12. In some cases, subfunctions kn_, kn__,
&c. are defined; e.g. k26 uses k26_. A problem which asks for
solutions to subproblems a, b, ... has functions kna, knb, ...;
e.g. k28 has k28a and k28b. A script that uses functions defined
in other scripts explicitly loads those scripts. A script k.k is
supplied which loads all scripts k01.k through k99.k.
Working with K lists
For the most part, the problems
in this section are trivial applications of commonly-used K
primitives.
- K01
(*) Find the last element of a list.
k01 10 20 30 -> 30
- K02
(*) Find the last but one element of a list.
k02 10 20 30 40 -> 30
- K03
(*) Find the K'th element of a list.
k03[10 20 30 40;1] -> 20
- K04
(*) Find the number of elements of a list.
k04 10 20 30 40 -> 4
- K05
(*) Reverse a list.
k05 10 20 30 40 -> 40 30 20 10
- K06
(*) Find out whether a list is a palindrome.
k06 10 20 30 20 10 -> 1
- K07
(**) Flatten a nested list structure.
k07 (10;(20;(30 40;50))) -> 10 20 30 40 50
- K08
(**) Eliminate consecutive duplicates of list elements.
k08 `a`a`b`b`b`b`b`a`a`c`c`b`b`b -> `a`b`a`c`b
- K09
(**) Pack consecutive duplicates of list elements into
sublists.
k09 `a`a`b`b`b`b`b`a`a`c`c`b`b`b -> (`a`a;`b`b`b`b`b;`a`a;`c`c;`b`b`b)
- K10
(*) Using the result of K09, construct the run-length
encoding of a list.
k10 `a`a`a`a`b`c`c`a`a`d`e`e`e`e -> ((4;`a);(1;`b);(2;`c);(2;`a);(1;`d);(4;`e))
- K11
(*) Using the result of K10, construct the modified
run-length encoding.
k11 `a`a`a`a`b`c`c`a`a`d`e`e`e`e -> ((4;`a);`b;(2;`c);(2;`a);`d;(4;`e))
- K12
(**) Using the result of K11, decode a run-length encoded
list.
-
- Given a run-length code list generated as specified in
problem K11. Construct its uncompressed version.
-
- K13
(**) Run-length encoding of a list (direct solution).
-
- Implement the so-called modified run-length encoding data
compression method directly. I.e. don't explicitly create
the sublists containing the duplicates, as in problem
K09, but only count them. As in problem K11, simplify the
result list by replacing the singleton terms (1;x) by x.
-
- K14
(*) Duplicate the elements of a list.
k14 10 20 30 -> 10 10 20 20 30 30
- K15
(**) Duplicate the elements of a list a given number of
times.
k15[10 20 30;3] -> 10 10 10 20 20 20 30 30 30
- K16
(**) Drop every N'th element from a list.
k16[10 20 30 40 50 60 70 80 90 100;3] -> 10 20 40 50 70 80 100
- K17
(*) Split a list into two parts; the length of the first
part is given.
k17[10 20 30 40 50 60 70 80;3] -> (10 20 30;40 50 60 70 80)
- K18
(**) Extract a slice from a list.
k18[10 20 30 40 50 60 70;2;5] -> 30 40 50 60
- K19
(**) Rotate a list N places to the left.
k19[10 20 30 40 50 60;2] -> 30 40 50 60 10 20
- K20
(*) Remove the K'th element from a list.
k20[10 20 30 40 50 60;2] -> 10 20 40 50 60
- K21
(*) Insert an element at a given position into a list.
k21[10 20 30 40 50 60;2;99] -> 10 20 99 30 40 50 60
- K22
(*) Create a list containing all integers within a given
range.
k22[4;9] -> 4 5 6 7 8 9
- K23
(**) Extract a given number of randomly selected elements
from a list.
k23[`a`b`c`d`e`f`g`h;3] -> `a`d`h
- K24
(*) Lotto: Draw N different random numbers from the set
1..M.
k24[6;49] -> 23 1 17 33 32 37
- K25
(*) Generate a random permutation of the elements of a
list.
k25 `a`b`c`d`e`f -> `b`a`d`c`e`f
- K26
(**) Generate the combinations of K distinct objects
chosen from the N elements of a list.
k26[3;`a`b`c`d`e]
(`c `d `e
`b `d `e
`b `c `e
`b `c `d
`a `d `e
`a `c `e
`a `c `d
`a `b `e
`a `b `d
`a `b `c)
- K27
(**) Group the elements of a set into disjoint subsets.
k27[2 3 4;`a`b`c`d`e]
((`d `e
`c `e
`c `d
`b `e
`b `d
`b `c
`a `e
`a `d
`a `c
`a `b)
(`c `d `e
`b `d `e
`b `c `e
`b `c `d
`a `d `e
`a `c `e
`a `c `d
`a `b `e
`a `b `d
`a `b `c)
(`b `c `d `e
`a `c `d `e
`a `b `d `e
`a `b `c `e
`a `b `c `d))
- K28
(**) Sort a list of lists according to length of sublists
(a) and frequency of length of sublists (b):
k28a(`a`b`c;`d`e;`f`g`h;`d`e;`i`j`k`l;`m`n;`o)
(`o
`d `e
`d `e
`m `n
`a `b `c
`f `g `h
`i `j `k `l)
k28b(`a`b`c;`d`e;`f`g`h;`d`e;`i`j`k`l;`m`n;`o)
(`i `j `k `l
`o
`a `b `c
`f `g `h
`d `e
`d `e
`m `n)
Arithmetic
- K31
(**) Determine whether a given integer number is prime.
- Example:
?- is_prime(7).
Yes
- K32
(**) Determine the greatest common divisor of two
positive integer numbers.
- Use Euclid's algorithm.
Example:
?- gcd(36, 63, G).
G = 9 Define gcd as an arithmetic function; so you can
use it like this:
?- G is gcd(36,63).
G = 9
- K33
(*) Determine whether two positive integer numbers are
coprime.
- Two numbers are coprime if their greatest common divisor
equals 1.
Example:
?- coprime(35, 64).
Yes
- K34
(**) Calculate Euler's totient function phi(m).
- Euler's so-called totient function phi(m) is defined as
the number of positive integers r (1 <= r < m) that
are coprime to m.
Example: m = 10: r = 1,3,7,9; thus
phi(m) = 4. Note the special case: phi(1) = 1.
?- Phi is totient_phi(10).
Phi = 4
Find out what the value of phi(m) is if m is a prime
number. Euler's totient function plays an important role
in one of the most widely used public key cryptography
methods (RSA). In this exercise you should use the most
primitive method to calculate this function (there are
smarter ways that we shall discuss later).
- K35
(**) Determine the prime factors of a given positive
integer.
- Construct a flat list containing the prime factors in
ascending order.
Example:
?- prime_factors(315, L).
L = [3,3,5,7]
- K36
(**) Determine the prime factors of a given positive
integer (2).
- Construct a list containing the prime factors and their
multiplicity.
Example:
?- prime_factors_mult(315, L).
L = [[3,2],[5,1],[7,1]] Hint: The problem is similar
to problem K13.
- K37
(**) Calculate Euler's totient function phi(m)
(improved).
- See problem P34 for the definition of Euler's totient
function. If the list of the prime factors of a number m
is known in the form of problem P36 then the function
phi(m) can be efficiently calculated as follows: Let
[[p1,m1],[p2,m2],[p3,m3],...] be the list of prime
factors (and their multiplicities) of a given number m.
Then phi(m) can be calculated with the following formula:
phi(m) = (p1 - 1) * p1 ** (m1 - 1) + (p2 - 1) * p2 **
(m2 - 1) + (p3 - 1) * p3 ** (m3 - 1) + ...
Note that a ** b stands for the b'th power of a.
- K38
(*) Compare the two methods of calculating Euler's
totient function.
- Use the solutions of problems P34 and P37 to compare the
algorithms. Take the number of logical inferences as a
measure for efficiency. Try to calculate phi(10090) as an
example.
- K39
(*) A list of prime numbers.
- Given a range of integers by its lower and upper limit,
construct a list of all prime numbers in that range.
- K40
(**) Goldbach's conjecture.
- Goldbach's conjecture says that every positive even
number greater than 2 is the sum of two prime numbers.
Example: 28 = 5 + 23. It is one of the most famous facts
in number theory that has not been proved to be correct
in the general case. It has been numerically
confirmed up to very large numbers (much larger than we
can go with our Prolog system). Write a predicate to find
the two prime numbers that sum up to a given even
integer.
Example:
?- goldbach(28, L).
L = [5,23]
- K41
(**) A list of Goldbach compositions.
- Given a range of integers by its lower and upper limit,
print a list of all even numbers and their Goldbach
composition.
Example:
?- goldbach_list(9,20).
10 = 3 + 7
12 = 5 + 7
14 = 3 + 11
16 = 3 + 13
18 = 5 + 13
20 = 3 + 17
In most cases, if an even number is written as the sum
of two prime numbers, one of them is very small. Very
rarely, the primes are both bigger than say 50. Try to
find out how many such cases there are in the range
2..3000.
Example (for a print limit of 50):
?- goldbach_list(1,2000,50).
992 = 73 + 919
1382 = 61 + 1321
1856 = 67 + 1789
1928 = 61 + 1867
Logic and Codes
- K46 (**) Truth
tables for logical expressions containing two variables.
k46[`p`q;"and[p;or[p;q]]"]
(`false `false `false
`false `true `false
`true `false `true
`true `true `true)
- K47 (*) Truth
tables for logical expressions in infix notation
containing two variables.
k46[`p`q;"p and p or q"]
(`false `false `false
`false `true `false
`true `false `true
`true `true `true)
- K48 (**) Truth
tables for logical expressions in infix notation
containing n variables.
-
- K48 is just K47, which is already generalized for n
variables.
-
- K49 (**) Gray
code.
-
- K50 (***) Huffman
code.
-
Binary Trees
A binary tree is either empty or
it is composed of a root element and two successors, which are
binary trees themselves.
In Prolog we represent the empty tree by the atom 'nil' and the
non-empty tree by the term t(X,L,R), where X denotes the root
node and L and R denote the left and right subtree, respectively.
The example tree depicted opposite is therefore represented by
the following Prolog term:
T1 =
t(a,t(b,t(d,nil,nil),t(e,nil,nil)),t(c,nil,t(f,t(g,nil,nil),nil)))
Other examples are a binary tree that consists of a root node
only:
T2 = t(a,nil,nil) or an empty binary tree: T3 = nil
You can check your predicates using these example trees. They
are given as test cases in p54.k.
- K54A (*) Check
whether a given term represents a binary tree
- Write a predicate istree/1 which succeeds if and only if
its argument is a Prolog term representing a binary tree.
Example:
?- istree(t(a,t(b,nil,nil),nil)).
Yes
?- istree(t(a,t(b,nil,nil))).
No
- K55 (**) Construct
completely balanced binary trees
- In a completely balanced binary tree, the following
property holds for every node: The number of nodes in its
left subtree and the number of nodes in its right subtree
are almost equal, which means their difference is not
greater than one.
Write a predicate cbal_tree/2 to construct completely
balanced binary trees for a given number of nodes. The
predicate should generate all solutions via backtracking.
Put the letter 'x' as information into all nodes of the
tree.
Example:
?- cbal_tree(4,T).
T = t(x, t(x, nil, nil), t(x, nil, t(x, nil, nil))) ;
T = t(x, t(x, nil, nil), t(x, t(x, nil, nil), nil)) ;
etc......No
- K56 (**) Symmetric
binary trees
- Let us call a binary tree symmetric if you can draw a
vertical line through the root node and then the right
subtree is the mirror image of the left subtree. Write a
predicate symmetric/1 to check whether a given binary
tree is symmetric. Hint: Write a
predicate mirror/2 first to check whether one tree is the
mirror image of another. We are only interested in the
structure, not in the contents of the nodes.
- K57 (**) Binary
search trees (dictionaries)
- Use the predicate add/3, developed in chapter 4 of the
course, to write a predicate to construct a binary search
tree from a list of integer numbers.
Example:
?- construct([3,2,5,7,1],T).
T = t(3, t(2, t(1, nil, nil), nil), t(5, nil, t(7, nil,
nil)))
Then use this predicate to test the solution of the
problem P56.
Example:
?- test_symmetric([5,3,18,1,4,12,21]).
Yes
?- test_symmetric([3,2,5,7,1]).
No
- K58 (**)
Generate-and-test paradigm
- Apply the generate-and-test paradigm to construct all
symmetric, completely balanced binary trees with a given
number of nodes. Example:
?- sym_cbal_trees(5,Ts).
Ts = [t(x, t(x, nil, t(x, nil, nil)), t(x, t(x, nil,
nil), nil)), t(x, t(x, t(x, nil, nil), nil), t(x, nil,
t(x, nil, nil)))]
How many such trees are there with 57 nodes? Investigate
about how many solutions there are for a given number of
nodes? What if the number is even? Write an appropriate
predicate.
- K59 (**) Construct
height-balanced binary trees
- In a height-balanced binary tree, the following property
holds for every node: The height of its left subtree and
the height of its right subtree are almost equal, which
means their difference is not greater than one.
Write a predicate hbal_tree/2 to construct
height-balanced binary trees for a given height. The
predicate should generate all solutions via backtracking.
Put the letter 'x' as information into all nodes of the
tree.
Example:
?- hbal_tree(3,T).
T = t(x, t(x, t(x, nil, nil), t(x, nil, nil)), t(x, t(x,
nil, nil), t(x, nil, nil))) ;
T = t(x, t(x, t(x, nil, nil), t(x, nil, nil)), t(x, t(x,
nil, nil), nil)) ;
etc......No
- K60 (**) Construct
height-balanced binary trees with a given number of nodes
- Consider a height-balanced binary tree of height H. What
is the maximum number of nodes it can contain?
Clearly, MaxN = 2**H - 1. However, what is the minimum
number MinN? This question is more difficult. Try to find
a recursive statement and turn it into a predicate
minNodes/2 defined as follwos:
% minNodes(H,N) :- N is the minimum number of nodes in a
height-balanced binary tree of height H.
(integer,integer), (+,?)
On the other hand, we might ask: what is the maximum
height H a height-balanced binary tree with N nodes can
have?
% maxHeight(N,H) :- H is the maximum height of a
height-balanced binary tree with N nodes
(integer,integer), (+,?)
Now, we can attack the main problem: construct all the
height-balanced binary trees with a given nuber of nodes.
% hbal_tree_nodes(N,T) :- T is a height-balanced binary
tree with N nodes.
Find out how many height-balanced trees exist for N = 15.
- K61 (*) Count the
leaves of a binary tree
- A leaf is a node with no successors. Write a predicate
count_leaves/2 to count them.
% count_leaves(T,N) :- the binary tree T has N leaves
- K61A (*) Collect
the leaves of a binary tree in a list
- A leaf is a node with no successors. Write a predicate
leaves/2 to collect them in a list.
% leaves(T,S) :- S is the list of all leaves of the
binary tree T
- K62 (*) Collect
the internal nodes of a binary tree in a list
- An internal node of a binary tree has either one or two
non-empty successors. Write a predicate internals/2 to
collect them in a list.
% internals(T,S) :- S is the list of internal nodes of
the binary tree T.
- K62B (*) Collect
the nodes at a given level in a list
- A node of a binary tree is at level N if the path from
the root to the node has length N-1. The root node is at
level 1. Write a predicate atlevel/3 to collect all nodes
at a given level in a list.
% atlevel(T,L,S) :- S is the list of nodes of the binary
tree T at level L
Using atlevel/3 it is easy to construct a predicate
levelorder/2 which creates the level-order sequence of
the nodes. However, there are more efficient ways to do
that.
- K63 (**) Construct
a complete binary tree
- A complete binary tree with height H is defined
as follows: The levels 1,2,3,...,H-1 contain the maximum
number of nodes (i.e 2**(i-1) at the level i, note that
we start counting the levels from 1 at the root). In
level H, which may contain less than the maximum possible
number of nodes, all the nodes are
"left-adjusted". This means that in a
levelorder tree traversal all internal nodes come first,
the leaves come second, and empty successors (the nil's
which are not really nodes!) come last.
Particularly, complete binary trees are used as data
structures (or addressing schemes) for heaps.
We can assign an address number to each node in a
complete binary tree by enumerating the nodes in
levelorder, starting at the root with number 1. In doing
so, we realize that for every node X with address A the
following property holds: The address of X's left and
right successors are 2*A and 2*A+1, respectively,
supposed the successors do exist. This fact can be used
to elegantly construct a complete binary tree structure.
Write a predicate complete_binary_tree/2 with the
following specification:
% complete_binary_tree(N,T) :- T is a complete binary
tree with N nodes. (+,?)
Test your predicate in an appropriate way.
- K64 (**) Layout a
binary tree (1)
- Given a binary tree as the usual Prolog term t(X,L,R) (or
nil). As a preparation for drawing the tree, a layout
algorithm is required to determine the position of each
node in a rectangular grid. Several layout methods are
conceivable, one of them is shown in the illustration
below.
In this layout strategy, the
position of a node v is obtained by the following
two rules:
- x(v) is equal to the position of the node v
in the inorder sequence
- y(v) is equal to the depth of the node v
in the tree
In order to store the position of the nodes, we extend
the Prolog term representing a node (and its successors)
as follows:
% nil represents the empty tree (as usual)
% t(W,X,Y,L,R) represents a (non-empty) binary tree with
root W "positioned" at (X,Y), and subtrees L
and R
Write a predicate layout_binary_tree/2 with the following
specification:
% layout_binary_tree(T,PT) :- PT is the
"positioned" binary tree obtained from the
binary tree T. (+,?)
Test your predicate in an appropriate way.
- K65 (**) Layout a
binary tree (2)
An alternative layout method is
depicted in the illustration opposite. Find out the rules
and write the corresponding Prolog predicate. Hint: On a
given level, the horizontal distance between neighboring
nodes is constant.
Use the same conventions as in problem P64 and test your
predicate in an appropriate way.
- K66 (***) Layout a
binary tree (3)
Yet another layout strategy is
shown in the illustration opposite. The method yields a
very compact layout while maintaining a certain symmetry
in every node. Find out the rules and write the
corresponding Prolog predicate. Hint: Consider the
horizontal distance between a node and its successor
nodes. How tight can you pack together two subtrees to
construct the combined binary tree?
Use the same conventions as in problem P64 and P65 and
test your predicate in an appropriate way. Note: This is
a difficult problem. Don't give up too early!
Which layout do you like most?
- K67 (**) A string
representation of binary trees
Somebody represents binary trees as strings of the
following type (see example opposite):
a(b(d,e),c(,f(g,)))
a) Write a Prolog
predicate which generates this string representation, if
the tree is given as usual (as nil or t(X,L,R) term).
Then write a predicate which does this inverse; i.e.
given the string representation, construct the tree in
the usual form. Finally, combine the two predicates in a
single predicate tree_string/2 which can be used in both
directions.
b) Write the same
predicate tree_string/2 using difference lists and a
single predicate tree_dlist/2 which does the conversion
between a tree and a difference list in both directions.
For simplicity, suppose the information in the nodes is a
single letter and there are no spaces in the string.
- K68 (**) Preorder
and inorder sequences of binary trees
- We consider binary trees with nodes that are identified
by single lower-case letters, as in the example of
problem P67.
a) Write predicates
preorder/2 and inorder/2 that construct the preorder and
inorder sequence of a given binary tree, respectively.
The results should be atoms, e.g. 'abdecfg' for the
preorder sequence of the example in problem P67.
b) Can you use
preorder/2 from problem part a) in the reverse direction;
i.e. given a preorder sequence, construct a corresponding
tree? If not, make the necessary arrangements.
c) If both the
preorder sequence and the inorder sequence of the nodes
of a binary tree are given, then the tree is determined
unambiguously. Write a predicate pre_in_tree/3 that does
the job.
d) Solve problems a)
to c) using difference lists. Cool! Use the predefined
predicate time/1 to compare the solutions.
What happens if the same character appears in more than
one node. Try for instance pre_in_tree(aba,baa,T).
- K69 (**) Dotstring
representation of binary trees
- We consider again binary trees with nodes that are
identified by single lower-case letters, as in the
example of problem P67. Such a tree can be represented by
the preorder sequence of its nodes in which dots (.) are
inserted where an empty subtree (nil) is encountered
during the tree traversal. For example, the tree shown in
problem P67 is represented as 'abd..e..c.fg...'.
First, try to establish a syntax (BNF or syntax diagrams)
and then write a predicate tree_dotstring/2 which does
the conversion in both directions. Use difference lists.
Multiway Trees
A multiway tree is composed of a
root element and a (possibly empty) set of successors which are
multiway trees themselves. A multiway tree is never empty. The
set of successor trees is sometimes called a forest.
In Prolog we represent a multiway tree by a term t(X,F), where X
denotes the root node and F denotes the forest of successor trees
(a Prolog list). The example tree depicted opposite is therefore
represented by the following Prolog term:
T = t(a,[t(f,[t(g,[])]),t(c,[]),t(b,[t(d,[]),t(e,[])])])
- K70B (*) Check
whether a given term represents a multiway tree
- Write a predicate istree/1 which succeeds if and only if
its argument is a Prolog term representing a multiway
tree.
Example:
?-
istree(t(a,[t(f,[t(g,[])]),t(c,[]),t(b,[t(d,[]),t(e,[])])])).
Yes
- K70C (*) Count
the nodes of a multiway tree
- Write a predicate nnodes/1 which counts the nodes of a
given multiway tree.
Example:
?- nnodes(t(a,[t(f,[])]),N).
N = 2
Write another version of the predicate that allows for a
flow pattern (o,i).
- K70 (**) Tree
construction from a node string
- We suppose that the nodes of a multiway tree contain
single characters. In the depth-first order sequence of
its nodes, a special character ^ has been inserted
whenever, during the tree traversal, the move is a
backtrack to the previous level.
By this rule, the tree in the figure opposite is
represented as: afg^^c^bd^e^^^
Define the syntax of the string and write a predicate
tree(String,Tree) to construct the Tree when the String
is given. Work with atoms (instead of strings). Make your
predicate work in both directions.
- K71 (*) Determine
the internal path length of a tree
- We define the internal path length of a multiway tree as
the total sum of the path lengths from the root to all
nodes of the tree. By this definition, the tree in the
figure of problem P70 has an internal path length of 9.
Write a predicate ipl(Tree,IPL) for the flow pattern
(+,-).
- K72 (*) Construct
the bottom-up order sequence of the tree nodes
- Write a predicate bottom_up(Tree,Seq) which constructs
the bottom-up sequence of the nodes of the multiway tree
Tree. Seq should be a Prolog list. What happens if you
run your predicate backwords?
- K73 (**) Lisp-like
tree representation
- There is a particular notation for multiway trees in Lisp.
Lisp is a prominent functional programming language,
which is used primarily for artificial intelligence
problems. As such it is one of the main competitors of
Prolog. In Lisp almost everything is a list, just as in
Prolog everything is a term.
The following pictures show how multiway tree structures
are represented in Lisp.
Note that in the "lispy" notation a node with
successors (children) in the tree is always the first
element in a list, followed by its children. The
"lispy" representation of a multiway tree is a
sequence of atoms and parentheses '(' and ')', which we
shall collectively call "tokens". We can
represent this sequence of tokens as a Prolog list; e.g.
the lispy expression (a (b c)) could be represented as
the Prolog list ['(', a, '(', b, c, ')', ')']. Write a
predicate tree_ltl(T,LTL) which constructs the
"lispy token list" LTL if the tree is given as
term T in the usual Prolog notation.
Example:
?- tree_ltl(t(a,[t(b,[]),t(c,[])]),LTL).
LTL = ['(', a, '(', b, c, ')', ')'] As a second, even
more interesting exercise try to rewrite tree_ltl/2 in a
way that the inverse conversion is also possible: Given
the list LTL, construct the Prolog tree T. Use difference
lists.
Graphs
A graph is defined as a set of nodes
and a set of edges,
where each edge is a pair of nodes.
There are several ways to represent graphs in Prolog. One
method is to represent each edge separately as one clause (fact).
In this form, the graph depicted below is represented as the
following predicate:
edge(h,g).
edge(k,f).
edge(f,b).
...
We call this edge-clause form. Obviously, isolated
nodes cannot be represented. Another method is to represent the
whole graph as one data object. According to the definition of
the graph as a pair of two sets (nodes and edges), we may use the
following Prolog term to represent the example graph:
graph([b,c,d,f,g,h,k],[e(b,c),e(b,f),e(c,f),e(f,k),e(g,h)])
We call this graph-term form. Note, that the lists are
kept sorted, they are really sets, without duplicated
elements. Each edge appears only once in the edge list; i.e. an
edge from a node x to another node y is represented as e(x,y),
the term e(y,x) is not present. The graph-term form is our
default representation. In SWI-Prolog there are predefined
predicates to work with sets.
A third representation method is to associate with each node
the set of nodes that are adjacent to that node. We call this the
adjacency-list form. In our example:
[n(b,[c,f]), n(c,[b,f]), n(d,[]), n(f,[b,c,k]), ...]
The representations we introduced so far are Prolog terms and
therefore well suited for automated processing, but their syntax
is not very user-friendly. Typing the terms by hand is cumbersome
and error-prone. We can define a more compact and
"human-friendly" notation as follows: A graph is
represented by a list of atoms and terms of the type X-Y (i.e.
functor '-' and arity 2). The atoms stand for isolated nodes, the
X-Y terms describe edges. If an X appears as an endpoint of an
edge, it is automatically defined as a node. Our example could be
written as:
[b-c, f-c, g-h, d, f-b, k-f, h-g]
We call this the human-friendly form. As the example
shows, the list does not have to be sorted and may even contain
the same edge multiple times. Notice the isolated node d.
(Actually, isolated nodes do not even have to be atoms in the
Prolog sense, they can be compound terms, as in d(3.75,blue)
instead of d in the example).
When the edges are directed we
call them arcs. These are represented by ordered
pairs. Such a graph is called directed graph. To represent
a directed graph, the forms discussed above are slightly
modified. The example graph opposite is represented as follows:
- Arc-clause form
- arc(s,u).
arc(u,r).
...
- Graph-term form
- digraph([r,s,t,u,v],[a(s,r),a(s,u),a(u,r),a(u,s),a(v,u)])
- Adjacency-list form
- [n(r,[]),n(s,[r,u]),n(t,[]),n(u,[r]),n(v,[u])]
Note that the adjacency-list does not have the
information on whether it is a graph or a digraph.
- Human-friendly form
- [s > r, t, u > r, s > u, u > s, v > u]
Finally, graphs and digraphs may have additional information
attached to nodes and edges (arcs). For the nodes, this is no
problem, as we can easily replace the single character
identifiers with arbitrary compound terms, such as city('London',4711).
On the other hand, for edges we have to extend our notation.
Graphs with additional information attached to edges are called labelled
graphs.
- Arc-clause form
- arc(m,q,7).
arc(p,q,9).
arc(p,m,5).
- Graph-term form
- digraph([k,m,p,q],[a(m,p,7),a(p,m,5),a(p,q,9)])
- Adjacency-list form
- [n(k,[]),n(m,[q/7]),n(p,[m/5,q/9]),n(q,[])]
Notice how the edge information has been packed into a
term with functor '/' and arity 2, together with the
corresponding node.
- Human-friendly form
- [p>q/9, m>q/7, k, p>m/5]
The notation for labelled graphs can also be used for so-called multi-graphs,
where more than one edge (or arc) are allowed between two given
nodes.
- K80 (***)
Conversions
- Write predicates to convert between the different graph
representations. With these predicates, all
representations are equivalent; i.e. for the following
problems you can always pick freely the most convenient
form. The reason this problem is rated (***) is not
because it's particularly difficult, but because it's a
lot of work to deal with all the special cases.
- K81 (**) Path from
one node to another one
- Write a predicate path(G,A,B,P) to find an acyclic path P
from node A to node b in the graph G. The predicate
should return all paths via backtracking.
- K82 (*) Cycle from
a given node
- Write a predicate cycle(G,A,P) to find a closed path
(cycle) P starting at a given node A in the graph G. The
predicate should return all cycles via backtracking.
- K83 (**) Construct
all spanning trees
- Write a predicate s_tree(Graph,Tree) to construct (by
backtracking) all spanning trees of a given graph. With
this predicate, find out how many spanning trees there
are for the graph depicted to the left. The data of this
example graph can be found in the file p83.dat. When you
have a correct solution for the s_tree/2 predicate, use
it to define two other useful predicates: is_tree(Graph)
and is_connected(Graph). Both are five-minutes tasks!
- K84 (**) Construct
the minimal spanning tree
- Write a predicate ms_tree(Graph,Tree,Sum) to construct
the minimal spanning tree of a given labelled graph.
Hint: Use the algorithm of Prim. A small modification of
the solution of P83 does the trick. The data of the
example graph to the right can be found in the file
p84.dat.
- K85 (**) Graph
isomorphism
- Two graphs G1(N1,E1) and G2(N2,E2) are isomorphic if
there is a bijection f: N1 -> N2 such that for any
nodes X,Y of N1, X and Y are adjacent if and only if f(X)
and f(Y) are adjacent.
Write a predicate that
determines whether two graphs are isomorphic. Hint: Use
an open-ended list to represent the function f.
- K86 (**) Node
degree and graph coloration
- a) Write a predicate degree(Graph,Node,Deg) that
determines the degree of a given node.
b) Write
a predicate that generates a list of all nodes of a graph
sorted according to decreasing degree.
c) Use Welch-Powell's algorithm to paint the
nodes of a graph in such a way that adjacent nodes have
different colors.
- K87 (**)
Depth-first order graph traversal (alternative solution)
- Write a predicate that generates a depth-first order
graph traversal sequence. The starting point should be
specified, and the output should be a list of nodes that
are reachable from this starting point (in depth-first
order).
- K88 (**) Connected
components (alternative
solution)
- Write a predicate that splits a graph into its connected
components.
- K89 (**) Bipartite
graphs
- Write a predicate that finds out whether a given graph is
bipartite.
Miscellaneous Problems
- K90 (**) Eight
queens problem
- This is a classical problem in computer science. The
objective is to place eight queens on a chessboard so
that no two queens are attacking each other; i.e., no two
queens are in the same row, the same column, or on the
same diagonal.
Hint: Represent the positions of the queens as a list of
numbers 1..N. Example: [4,2,7,3,6,8,5,1] means that the
queen in the first column is in row 4, the queen in the
second column is in row 2, etc. Use the generate-and-test
paradigm.
- K91 (**) Knight's
tour
- Another famous problem is this one: How can a knight jump
on an NxN chessboard in such a way that it visits every
square exactly once?
Hints: Represent the squares by pairs of their
coordinates of the form X/Y, where both X and Y are
integers between 1 and N. (Note that '/' is just a
convenient functor, not division!) Define the relation
jump(N,X/Y,U/V) to express the fact that a knight can
jump from X/Y to U/V on a NxN chessboard. And finally,
represent the solution of our problem as a list of N*N
knight positions (the knight's tour).
- K92 (***) Von
Koch's conjecture
- Several years ago I met a mathematician who was intrigued
by a problem for which he didn't know a solution. His
name was Von Koch, and I don't know whether the problem
has been solved since.
Anyway the puzzle goes like this: Given a tree with N
nodes (and hence N-1 edges). Find a way to enumerate the
nodes from 1 to N and, accordingly, the edges from 1 to
N-1 in such a way, that for each edge K the difference of
its node numbers equals to K. The conjecture is that this
is always possible.
For small trees the problem is easy to solve by hand.
However, for larger trees, and 14 is already very large,
it is extremely difficult to find a solution. And
remember, we don't know for sure whether there is always
a solution!
Write a predicate that calculates a numbering scheme
for a given tree. What is the solution for the larger
tree pictured above?
- K93 (***) An
arithmetic puzzle
- Given a list of integer numbers, find a correct way of
inserting arithmetic signs (operators) such that the
result is a correct equation. Example: With the list of
numbers [2,3,5,7,11] we can form the equations 2-3+5+7 =
11 or 2 = (3*5+7)/11 (and ten others!).
- K94 (***) Generate
K-regular simple graphs with N nodes
- In a K-regular graph all nodes have a degree of K; i.e.
the number of edges incident in each node is K. How many
(non-isomorphic!) 3-regular graphs with 6 nodes are
there? See also a table of
results and a Java
applet that can represent graphs geometrically.
- K95 (**) English
number words
- On financial documents, like cheques, numbers must
sometimes be written in full words. Example: 175 must be
written as one-seven-five. Write a predicate full_words/1
to print (non-negative) integer numbers in full words.
- K96 (**) Syntax
checker (alternative
solution with difference lists)
In a certain
programming language (Ada) identifiers are defined by the
syntax diagram (railroad chart) opposite. Transform the
syntax diagram into a system of syntax diagrams which do
not contain loops; i.e. which are purely recursive. Using
these modified diagrams, write a predicate identifier/1
that can check whether or not a given string is a legal
identifier.
% identifier(Str) :- Str is a legal identifier
- K97 (**) Sudoku
- Sudoku puzzles go like this:
Problem statement Solution
. . 4 | 8 . . | . 1 7 9 3 4 | 8 2 5 | 6 1 7
| | | |
6 7 . | 9 . . | . . . 6 7 2 | 9 1 4 | 8 5 3
| | | |
5 . 8 | . 3 . | . . 4 5 1 8 | 6 3 7 | 9 2 4
--------+---------+-------- --------+---------+--------
3 . . | 7 4 . | 1 . . 3 2 5 | 7 4 8 | 1 6 9
| | | |
. 6 9 | . . . | 7 8 . 4 6 9 | 1 5 3 | 7 8 2
| | | |
. . 1 | . 6 9 | . . 5 7 8 1 | 2 6 9 | 4 3 5
--------+---------+-------- --------+---------+--------
1 . . | . 8 . | 3 . 6 1 9 7 | 5 8 2 | 3 4 6
| | | |
. . . | . . 6 | . 9 1 8 5 3 | 4 7 6 | 2 9 1
| | | |
2 4 . | . . 1 | 5 . . 2 4 6 | 3 9 1 | 5 7 8
Every spot in the puzzle belongs to a (horizontal) row
and a (vertical) column, as well as to one single 3x3
square (which we call "square" for short). At
the beginning, some of the spots carry a single-digit
number between 1 and 9. The problem is to fill the
missing spots with digits in such a way that every number
between 1 and 9 appears exactly once in each row, in each
column, and in each square.
- K98 (***)
Nonograms
- Around 1994, a certain kind of puzzles was very popular
in England. The "Sunday Telegraph" newspaper
wrote: "Nonograms are puzzles from Japan and are
currently published each week only in The Sunday
Telegraph. Simply use your logic and skill to complete
the grid and reveal a picture or diagram." As a
Prolog programmer, you are in a better situation: you can
have your computer do the work! Just write a little
program ;-).
The puzzle goes like this: Essentially,
each row and column of a rectangular bitmap is annotated
with the respective lengths of its distinct strings of
occupied cells. The person who solves the puzzle must
complete the bitmap given only these lengths.
Problem statement: Solution:
|_|_|_|_|_|_|_|_| 3 |_|X|X|X|_|_|_|_| 3
|_|_|_|_|_|_|_|_| 2 1 |X|X|_|X|_|_|_|_| 2 1
|_|_|_|_|_|_|_|_| 3 2 |_|X|X|X|_|_|X|X| 3 2
|_|_|_|_|_|_|_|_| 2 2 |_|_|X|X|_|_|X|X| 2 2
|_|_|_|_|_|_|_|_| 6 |_|_|X|X|X|X|X|X| 6
|_|_|_|_|_|_|_|_| 1 5 |X|_|X|X|X|X|X|_| 1 5
|_|_|_|_|_|_|_|_| 6 |X|X|X|X|X|X|_|_| 6
|_|_|_|_|_|_|_|_| 1 |_|_|_|_|X|_|_|_| 1
|_|_|_|_|_|_|_|_| 2 |_|_|_|X|X|_|_|_| 2
1 3 1 7 5 3 4 3 1 3 1 7 5 3 4 3
2 1 5 1 2 1 5 1
For the example above, the problem can be stated as
the two lists
[[3],[2,1],[3,2],[2,2],[6],[1,5],[6],[1],[2]] and
[[1,2],[3,1],[1,5],[7,1],[5],[3],[4],[3]] which give the
"solid" lengths of the rows and columns,
top-to-bottom and left-to-right, respectively. Published
puzzles are larger than this example, e.g. 25 x 20, and
apparently always have unique solutions.
- K99 (***)
Crossword puzzle
- Given an empty (or almost empty) framework of a crossword
puzzle and a set of words. The problem is to place the
words into the framework.
The
particular crossword puzzle is specified in a text file
which first lists the words (one word per line) in an
arbitrary order. Then, after an empty line, the crossword
framework is defined. In this framework specification, an
empty character location is represented by a dot (.). In
order to make the solution easier, character locations
can also contain predefined character values. The puzzle
opposite is defined in the file p99a.dat, other examples are
p99b.dat and p99d.dat. There is also an
example of a puzzle (p99c.dat)
which does not have a solution.
Words are strings (character lists) of at least
two characters. A horizontal or vertical sequence of
character places in the crossword puzzle framework is
called a site. Our problem is to find a compatible
way of placing words onto sites.
Hints: (1) The problem is not easy. You will
need some time to thoroughly understand it. So, don't
give up too early! And remember that the objective is a
clean solution, not just a quick-and-dirty hack!
(2) Reading the data file is a tricky problem for which a
solution is provided in the file p99-readfile.k. Use
the predicate read_lines/2.
(3) For efficiency reasons it is important, at least for
larger puzzles, to sort the words and the sites in a
particular order. For this part of the problem, the
solution of K28 may be very
helpful.
Last modified: Sun Dec 11 18:57:38 W. Europe Standard Time
2005