// file: acyclic.rex // author: R. Keller // purpose: high-level functional program for testing whether a directed // graph is acyclic // description: The function 'acyclic' tests whether its argument is // acyclic (has no cycles). The argument is a graph represented as // a list of node pairs. // This program has not been optimized to avoid recomputation of the // list of nodes, etc. It tries to present the method in simplest terms. // Method: If a graph is empty, it is acyclic. // Otherwise, if the graph has no leaf, it is cyclic. // Otherwise, the answer is obtained by removing a leaf from the graph // and testing that reduced graph is acyclic. acyclic(Graph) = Graph == [ ] ? 1 // empty graph is acyclic : no_leaf(Graph) ? 0 // graph with no leaf is cyclic : acyclic(remove_leaf(Graph)); // try reduced graph // Function 'is_leaf' tells whether a given node is a leaf. It does // this by seeing if there is a pair having the node as a first element. // If there is such a pair, the node is connected to another node, so it // can't be a leaf. The higher-order function 'no' (the opposite of 'some') // is used: no(P, L) returns 1 if there is no element X of L such that P(X). // Examples: // // is_leaf(2, [ [1, 2], [2, 3], [2, 4]]) ==> 0 // // is_leaf(3, [ [1, 2], [2, 3], [2, 4]]) ==> 1 is_leaf(Node, Graph) = no((Pair) => first(Pair) == Node, Graph); // Function 'find_leaf' finds a given node in the given graph which is a leaf, // if there is one. Function 'is_leaf' is used to construct a function argument // to the built-in function 'find'. 'find' takes a predicate and a list // and returns the list beginning with an element satisfying that predicate. // If there is no such element, the empty list is returned. // // Because 'is_leaf' needs to take the graph as its second argument, we need // to use an anonymous function to get a single-argument function to give to // 'find'. // Examples: // // find_leaf([ [1, 2], [2, 3], [2, 4]]) ==> [3, 4] // // indicating 3 is a leaf. // // find_leaf([ [1, 2], [2, 3], [3, 1]]) ==> [] // // indicating there is no leaf. find_leaf(Graph) = find((Node) => is_leaf(Node, Graph), nodes(Graph)); // Function 'no_leaf' simply tells whether there is a leaf or not, by using // the fact that if there is no leaf, the result of 'find' is [] // Examples: // // no_leaf([ [1, 2], [2, 3], [2, 4]]) ==> 0; // // // no_leaf([ [1, 2], [2, 3], [3, 1]]) ==> 1; no_leaf(Graph) = find_leaf(Graph) == [ ]; // Function 'nodes' returns the list of nodes of a graph represented // as a list of pairs. nodes(Graph) = remove_duplicates(leaves(Graph)); // Function remove_leaf removes a designated leaf from the graph. // by using dropping arcs containing that node. // Example: // // remove_leaf(3, [ [1, 2], [2, 3], [2, 4]]) ==> [[1, 2], [2, 4]] remove_leaf(Leaf, Graph) = drop((Pair) => second(Pair) == Leaf, Graph); // The one argument version of 'remove' leaf finds a leaf, then removes it. // Example: // // remove_leaf([ [1, 2], [2, 3], [2, 4]]) ==> [[1, 2], [2, 4]] remove_leaf(Graph) = remove_leaf(first(find_leaf(Graph)), Graph); // Some sample graphs for testing graph1 = [ [1, 2], [2, 3], [2, 4], [4, 5], [6, 3], [4, 6], [5, 6] ]; // acyclic graph2 = [ [1, 2], [2, 3], [2, 4], [4, 5], [6, 3], [6, 4], [5, 6] ]; // cyclic // Here is how the rex test function may be used. The correct answer is the // second argument. If the test fails, it will complain. If it is ok, it // will tell you. test(acyclic(graph1), 1); test(acyclic(graph2), 0);