Graph Walks
We can think of a function g of type 'a -> 'a -> bool as a graph
in which there is an edge between i and j exactly when g i j
is true.
A function p of type 'a -> 'a generates a list of values by
repeated application from a given starting point. We will call this
a potential path and we will call p a path generator. For
example, the list [x; p x; p (p x)] is a potential path of length
2 starting at x.
A potential path is valid for a given graph g if every pair of
adjacent elements forms an edge in g. Continuing the above
example, the potential path [x; p x; p (p x)] is valid for g if
g x (p x) && g (p x) (p (p x)) is true.
Implement the function
val walks : ('a -> 'a -> bool) -> int -> (('a -> 'a) * 'a) list -> 'a list
so that walks g len paths_starts is the list of endpoints of all
valid paths which have length len generated by each path generator
on its associated starting point. The order of endpoints in the
output should be consistent with the order of the starting points,
even if some potential paths are dropped because they are not valid in
g. The behavior of the function is undefined if len is negative.
You should use the functions List.map and List.filter. It may
also be useful to try to use the pipelining operator |>.
Examples
let g1 (i : int) (j : int) = i < j && i <= 10 && j <= 10
let g2 (i : int) (j : int) = i <= 10 && j <= 10
let p1 i = i + 1
let p2 i = i - 1
let p3 i = i + 2
let _ = assert (walks g1 0 [(p1, 0); (p2, 0); (p3, 0)] = [0;0;0])
let _ = assert (walks g1 1 [(p1, 0); (p2, 0); (p3, 0)] = [1;2])
let _ = assert (walks g1 3 [(p1, 0); (p2, 0); (p3, 0)] = [3;6])
let _ = assert (walks g1 6 [(p1, 0); (p2, 0); (p3, 0)] = [6])
let _ = assert (walks g2 2 [(p1, 3); (p2, 5); (p3, 3)] = [5; 3; 7])
let _ = assert (walks g2 4 [(p1, -10); (p2, -20); (p3, 8)] = [-6; -24])
let _ = assert (walks g2 6 [(p1, 5); (p2, 11); (p3, -10)] = [2])