diff --git a/README.md b/README.md index 88ed955..6121732 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ A collection of functionality that extend the official Flix library. ## Modules +- `Extras.Graph` — functions on directed graphs represented as collections of edges. - `Extras.Queue` — an immutable first-in, first-out queue. ## Usage diff --git a/src/Extras.flix b/src/Extras.flix index d16fd49..77bf825 100644 --- a/src/Extras.flix +++ b/src/Extras.flix @@ -18,6 +18,8 @@ /// The `Extras` module is a collection of submodules that extend the official /// Flix library, each offering a self-contained piece of functionality: /// +/// - `Extras.Graph` is a library of functions on directed graphs represented +/// as collections of edges. /// - `Extras.Queue` is an immutable first-in, first-out queue. /// pub mod Extras { diff --git a/src/Extras/Graph.flix b/src/Extras/Graph.flix new file mode 100644 index 0000000..610d2ee --- /dev/null +++ b/src/Extras/Graph.flix @@ -0,0 +1,561 @@ +/* + * Copyright 2022 Nina Andrup Pedersen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/// +/// A library of functions that operates on graphs, represented as foldable +/// collections of either pairs or triples, for unweighted and weighted graphs +/// respectively. These pairs, or triples, represent directed edges while +/// nodes(vertices) are given implicitly. +/// +pub mod Extras.Graph { + + /// + /// Returns the pairs `(a, b)` where `a` can reach `b` through a number of + /// edges in the directed graph `g`, including zero. + /// + pub def closure(g: m[(t, t)]): Set[(t, t)] \ Foldable.Aef[m] with Foldable[m], Order[t] = { + let edges = inject g into Edge/2; + let res = query edges, nodes(), reachability() + select (src, dst) + from Reachable(src, dst); + Vector.toSet(res) + } + + /// + /// Returns the nodes that are reachable from `src` in the directed + /// graph `g`. + /// + pub def reachableFrom(src: t, g: m[(t, t)]): Set[t] \ Foldable.Aef[m] with Foldable[m], Order[t] = { + let edges = inject g into Edge/2; + let res = query edges, reachabilityFromSrc(src) + select dst + from Reachable(dst); + Vector.toSet(res) + } + + /// + /// Returns the nodes that are unreachable from `src` in the directed + /// graph `g`. + /// + pub def unreachableFrom(src: t, g: m[(t, t)]): Set[t] \ Foldable.Aef[m] with Foldable[m], Order[t] = { + let edges = inject g into Edge/2; + let unreachablility = #{ + // If there is a node that is not reachable from `src` then it is + // unreachable. + UnReachable(x) :- Node(x), not Reachable(x). + }; + let res = query edges, nodes(), reachabilityFromSrc(src), unreachablility + select dst + from UnReachable(dst); + Vector.toSet(res) + } + + /// + /// Returns `true` if there is a path from `src` to `dst` in the directed + /// graph `g`. + /// + pub def reachable(src: {src = t}, dst: {dst = t}, g: m[(t, t)]): Bool \ Foldable.Aef[m] with Foldable[m], Order[t] = + reachableFrom(src#src, g) |> Set.exists(x -> dst#dst == x) + + /// + /// Returns the strongly connected components of the directed graph `g`. + /// Two nodes are in the same component if and only if they can both + /// reach each other. + /// + pub def stronglyConnectedComponents(g: m[(t, t)]): Set[Set[t]] \ Foldable.Aef[m] with Foldable[m], Order[t] = { + let edges = inject g into Edge/2; + let connected = #{ + // If `n1` can reach `n2` and `n2` can reach `n1` then they are part + // of the same strongly connected component. + Connected(n1; Set#{n2}) :- Reachable(n1, n2), Reachable(n2, n1). + }; + let components = #{ + // After the full computation of `Connected`, duplicates are removed + // by checking that `n` is the minimum in the strongly connected + // component. + Components(s) :- fix Connected(n; s), if (Some(n) == Set.minimum(s)). + }; + let res = query edges, nodes(), reachability(), connected, components + select x + from Components(x); + Vector.toSet(res) + } + + /// + /// Returns the graph where all edges in the directed graph `g` have their + /// nodes flipped. + /// + pub def flipEdges(g: m[(t, t)]): Set[(t, t)] \ Foldable.Aef[m] with Foldable[m], Order[t] = { + let edges = inject g into Edge/2; + let rev = #{ + RevEdge(y, x) :- Edge(x, y). + }; + let res = query edges, rev + select (x, y) + from RevEdge(x, y); + Vector.toSet(res) + } + + /// + /// Returns the inverse graph of the directed graph `g`. For all nodes in + /// `g`. The new graph contains exactly those edges that are not in `g`. + /// + /// OBS: No self-edges are returned no matter the input. + /// + pub def invert(g: m[(t, t)]): Set[(t, t)] \ Foldable.Aef[m] with Foldable[m], Order[t] = { + let edges = inject g into Edge/2; + let inverse = #{ + InvEdge(x, y) :- Node(x), Node(y), not Edge(x, y), if (x != y). + }; + let res = query nodes(), edges, inverse + select (x, y) + from InvEdge(x, y); + Vector.toSet(res) + } + + /// + /// Returns `true` if the directed graph `g` contains at least one cycle. + /// + pub def isCyclic(g: m[(t, t)]): Bool \ Foldable.Aef[m] with Foldable[m], Order[t] = { + let edges = inject g into Edge/2; + let reachability = #{ + // Reachability given the edges. + Reachable(n1, n2) :- Edge(n1, n2). + // If `n1` can reach `m` and there is an edge from `m` to `n2` then + // `n1` can also reach `n2`. + Reachable(n1, n2) :- Reachable(n1, m), Edge(m, n2). + }; + let res = query edges, reachability + select () + from Reachable(x, y) + where x == y; + Vector.length(res) > 0 + } + + /// + /// Returns the shortest distance between all pairs of nodes in the + /// weighted directed graph `g`. Returns `None` if `g` contains a + /// negative cycle. + /// + pub def distances(g: m[(t, Int32, t)]): Option[Map[(t, t), Int32]] \ Foldable.Aef[m] with Foldable[m], Order[t] = { + // Algorithm based on floyd-warshall. + /// Returns the number of items in `s` less than `v`. + def rank(v, s) = s |> Set.partition(v0 -> v0 < v) |> fst |> Set.size; + let edges = inject g into Edge/3; + let negCycle = #{ + Node(x) :- Edge(x, _, _). + Node(x) :- Edge(_, _, x). + // Collect all nodes. + Nodes(; Set#{x}) :- Node(x). + // Compute the number of nodes. + NodeCount(Set.size(ns)) :- fix Nodes(; ns). + // Compute the rank (the index in a sorted order) each node has. + NodeRank(x, rank(x, ns)) :- Node(x), fix Nodes(; ns). + // `Dist(x, y, k; w)` says that `x` can reach `y` with a path of + // length `w` using only intermediate nodes of rank less than + // `k`. The `k` parameter is to control the number of iterations. + // Direct paths use no intermediate vertices. + Dist(x, x, 0; Down.Down(0)) :- Node(x). + Dist(x, y, 0; Down.Down(w)) :- Edge(x, w, y). + // If there exists a path of length `w` without using node `k+1` + // then that path is also valid using node `k+1`. + Dist(x, y, k + 1; w) :- Dist(x, y, k; w), NodeCount(c), if (k < c). + // Combine paths if they use intermediate nodes of the correct + // rank (also making sure `k` doesn't increase forever). + Dist(x, y, k + 1; w1 + w2) :- Dist(x, kth, k; w1), + Dist(kth, y, k; w2), + NodeRank(kth, k), + NodeCount(c), + if (k < c). + // If `x` can go to `x` with a negative distances, then it can be + // repeated. + NegCycle() :- fix Dist(x, x, _; w), if (destructDown(w) < 0). + }; + let mapping = #{ + Mapping((x, y), destructDown(d)) :- fix Dist(x, y, c; d), NodeCount(c). + }; + let sol = solve edges, negCycle, mapping; + let cycle = query sol + select () + from NegCycle(); + if (Vector.isEmpty(cycle)) { + // No negative cycle. + let res = query sol + select (p, d) + from Mapping(p, d); + Some(Vector.toMap(res)) + } else { + // Negative cycle. + None + } + } + + /// + /// Returns the shortest distance between all pairs of nodes in the + /// weighted directed graph `g`. + /// + /// OBS: No negative cycles must be present. + /// + pub def boundedDistances(g: m[(t, Int32, t)]): Map[(t, t), Int32] \ Foldable.Aef[m] with Foldable[m], Order[t] = { + let edges = inject g into Edge/3; + let dists = #{ + // Initialize all node distances to zero. + Dist(x, x; Down.Down(0)) :- Edge(x, _, _). + Dist(x, x; Down.Down(0)) :- Edge(_, _, x). + // Add distances from the edges. + Dist(x, y; Down.Down(d)) :- Edge(x, d, y). + // Add transitive distances. + Dist(x, y; d1 + Down.Down(d2)) :- Dist(x, z; d1), Edge(z, d2, y). + }; + let mapping = #{ + Mapping((x, y), destructDown(d)) :- fix Dist(x, y; d). + }; + let res = query edges, dists, mapping + select (p, d) + from Mapping(p, d); + res |> Vector.toMap + } + + /// + /// Returns the shortest distance from `src` to every other reachable vertex in the + /// weighted directed graph `g`. + /// + pub def distancesFrom(src: t, g: m[(t, Int32, t)]): Map[t, Int32] \ Foldable.Aef[m] with Foldable[m], Order[t] = { + let edges = inject g into Edge/3; + let dists = #{ + Dist(src; Down.Down(0)). + Dist(y; d + Down.Down(w)) :- Dist(x; d), Edge(x, w, y). + }; + let res = query edges, dists select (x, coerce(d)) from Dist(x; d); + res |> Vector.toMap + } + + /// + /// Returns the shortest distance from `src` to `dst` in the weighted + /// directed graph `g`. + /// + pub def distance(src: {src = t}, dst: {dst = t}, g: m[(t, Int32, t)]): Option[Int32] \ Foldable.Aef[m] with Foldable[m], Order[t] = + distancesFrom(src#src, g) |> Map.get(dst#dst) + + /// + /// Returns a copy of the directed graph `g` where all flipped edges are + /// added. An undirected graph in directed representation. + /// + pub def toUndirected(g: m[(t, t)]): Set[(t, t)] \ Foldable.Aef[m] with Foldable[m], Order[t] = { + g |> Foldable.toSet + |> Set.flatMap(match (a, b) -> Set#{(a, b), (b, a)}) + } + + /// + /// Returns a copy of the weighted directed graph `g` where all flipped + /// edges are added. An undirected graph in directed representation. + /// + pub def toUndirectedLabeled(g: m[(t, Int32, t)]): Set[(t, Int32, t)] \ Foldable.Aef[m] with Foldable[m], Order[t] = { + g |> Foldable.toSet + |> Set.flatMap(match (a, w, b) -> Set#{(a, w, b), (b, w, a)}) + } + + /// + /// Returns the nodes that are at most `limit` (inclusive) distance away + /// from `src` in the weighted directed graph `g`. + /// + /// OBS: No negative cycles must be present. + /// + pub def withinDistanceOf(src: t, limit: Int32, g: m[(t, Int32, t)]): Set[t] \ Foldable.Aef[m] with Foldable[m], Order[t] = { + let edges = inject g into Edge/3; + let nodes = #{ + Dist(src; Down.Down(0)) :- if (limit >= 0). + Dist(y; d + Down.Down(w)) :- Dist(x; d), + Edge(x, w, y), + if (destructDown(d) + w <= limit). + Node(x) :- fix Dist(x; _). + }; + let res = query edges, nodes + select x + from Node(x); + Vector.toSet(res) + } + + /// + /// Returns the nodes that are at most `limit` (inclusive) edges away + /// from `src` in the directed graph `g`. + /// + pub def withinEdgesOf(src: t, limit: Int32, g: m[(t, t)]): Set[t] \ Foldable.Aef[m] with Foldable[m], Order[t] = { + let edges = inject g into Edge/2; + let nodes = #{ + Dist(src; Down.Down(0)) :- if (limit >= 0). + Dist(y; d + Down.Down(1)) :- Dist(x; d), + Edge(x, y), + if (destructDown(d) + 1 <= limit). + Node(x) :- fix Dist(x; _). + }; + let res = query edges, nodes + select x + from Node(x); + Vector.toSet(res) + } + + /// + /// Returns the topologically sorted nodes (all edges go from lower indices + /// to higher indices of the list) in the directed graph `g`. + /// Unordered nodes are consistently (although not intuitively) ordered. + /// + /// OBS: No cycles must be present. + /// + pub def topologicalSort(g: m[(t, t)]): List[t] \ Foldable.Aef[m] with Foldable[m], Order[t] = region rc { + // https://github.com/souffle-lang/benchmarks/tree/master/benchmarks/topological_ordering + let edges = inject g into Edge/2; + let topSort = #{ + EdgeSecond(x) :- Edge(_, x). + EdgeFirst(x) :- Edge(x, _). + + Before(x, y) :- Edge(x, y). + Before(x, y) :- Before(x, z), Edge(z, y). + + After(x, y) :- Edge(y, x). + After(x, y) :- After(z, y), Edge(z, x). + + // Find a consistent index for each node, not necessarily unique. + Index(x; 0) :- Node(x), not EdgeSecond(x), EdgeFirst(x). + Index(x; i + 1) :- Before(y, x), + not Before(x, y), + After(x, y), + Index(y; i). + }; + let res = query edges, nodes(), topSort select (x, i) from Index(x; i); + let ml = MutList.empty(rc); + // Extract nodes in fixed order. + res + |> Vector.sortBy(match (x, i) -> (i, x)) + |> Vector.forEach(match (x, _) -> MutList.push(x, ml)); + MutList.toList(ml) + } + + /// + /// Returns the degree of each node in the directed graph `g` (the number of + /// times a node exists as an endpoint of an edge). + /// + pub def degrees(g: m[(t, t)]): Map[t, Int32] \ Foldable.Aef[m] with Foldable[m], Order[t] = { + use Option.{getWithDefault, map}; + let edges = inject g into Edge/2; + let in = #{ + // Careful use of option type here to make sure the empty set is not + // the bottom lattice element. That would exclude degrees of zero. + TouchSet(n; Some(Set#{})) :- Node(n). + TouchSet(n; Some(Set#{(other, n)})) :- Edge(other, n). + TouchSet(n; Some(Set#{(n, other)})) :- Edge(n, other). + Degree(n, s |> map(Set.size) |> getWithDefault(0)) :- + fix TouchSet(n; s). + }; + let res = query edges, in, nodes() + select (n, d) + from Degree(n, d); + Vector.toMap(res) + } + + /// + /// Returns a mapping from distances to the set of nodes for which the + /// shortest path from `src` in the directed graph `g` is of a given length. + /// + pub def frontiersFrom(src: t, g: m[(t, t)]): Map[Int32, Set[t]] \ Foldable.Aef[m] with Foldable[m], Order[t] = { + let edges = inject g into Edge/2; + let frontiers = #{ + Dist(src; Down.Down(0)). + Dist(y; n + Down.Down(1)) :- Dist(x; n), Edge(x, y). + + // Find the max frontier. + Frontiers(; Set#{destructDown(n)}) :- fix Dist(_; n). + MaxFrontier(Set.maximum(s) |> Option.getWithDefault(0)) :- fix Frontiers(; s). + + // Initialize all frontiers (with non-bot element). + Frontier(0; Some(Set#{})) :- MaxFrontier(m), if (m > 0). + Frontier(n + 1; Some(Set#{})) :- Frontier(n; _), MaxFrontier(m), if (n < m). + + // Collect the frontiers. + Frontier(destructDown(n); Some(Set#{x})) :- fix Dist(x; n). + }; + let res = query edges, frontiers + select (n, s) + from Frontier(n; s); + let unwrapOption = match (n, s) -> match s { + case Some(v) => Some((n, v)) + case None => None + }; + res + |> Vector.filterMap(unwrapOption) + |> Vector.toMap + } + + /// + /// Returns triples `(x, cut, y)` such that `x` cannot reach `y` without + /// using `cut` (where `x`, `cut` and `y` are all distinct) in the directed + /// graph `g`. + /// + /// There will at most be one triple for each pair of nodes (which will + /// be the maximum `cut` of the possible choices). + /// + pub def cutPoints(g: m[(t, t)]): Set[(t, t, t)] \ Foldable.Aef[m] with Foldable[m], Order[t] = { + let edges = inject g into Edge/2; + def unpack(opt) = match opt { + case Some(v) => v + case None => unreachable!() + }; + let cuts = #{ + CutPoint(x, Set.maximum(cut) |> unpack, y) :- + fix CutPointSet(x, y; cut), + if (not Set.isEmpty(cut)). + CutPointSet(x, y; Set#{cut}) :- Reachable(x, y), + Node(cut), + not Circumvent(x, cut, y), + if (x != cut), + if (y != cut), + if (x != y). + Circumvent(x, cut, y) :- Node(cut), + Edge(x, y), + if (x != cut), + if (y != cut). + Circumvent(x, cut, y) :- Circumvent(x, cut, z), + Edge(z, y), + Node(cut), + if (y != cut). + }; + let res = query edges, nodes(), reachability(), cuts + select (x, cut, y) + from CutPoint(x, cut, y); + Vector.toSet(res) + } + + /// + /// Returns the in-degree (how many edges end in a given node) + /// of each node in the directed graph `g`. + /// + pub def inDegrees(g: m[(t, t)]): Map[t, Int32] \ Foldable.Aef[m] with Foldable[m], Order[t] = { + use Option.{getWithDefault, map}; + let edges = inject g into Edge/2; + let in = #{ + // Careful use of option type here to make sure the empty set is not + // the bottom lattice element. That would exclude degrees of zero. + InSet(n; Some(Set#{})) :- Node(n). + InSet(n; Some(Set#{other})) :- Edge(other, n). + InDegree(n, s |> map(Set.size) |> getWithDefault(0)) :- + fix InSet(n; s). + }; + let res = query edges, in, nodes() + select (n, d) + from InDegree(n, d); + Vector.toMap(res) + } + + /// + /// Returns the out-degree (how many edges start in a given node) + /// of each node in the directed graph `g`. + /// + pub def outDegrees(g: m[(t, t)]): Map[t, Int32] \ Foldable.Aef[m] with Foldable[m], Order[t] = { + use Option.{getWithDefault, map}; + let edges = inject g into Edge/2; + let out = #{ + // Careful use of option type here to make sure the empty set is not + // the bottom lattice element. That would exclude degrees of zero. + OutSet(n; Some(Set#{})) :- Node(n). + OutSet(n; Some(Set#{other})) :- Edge(n, other). + OutDegree(n, s |> map(Set.size) |> getWithDefault(0)) :- + fix OutSet(n; s). + }; + let res = query edges, out, nodes() + select (n, d) + from OutDegree(n, d); + Vector.toMap(res) + } + + /// + /// Returns a Graphviz (DOT) string of the directed graph `g`. + /// The strings of nodes are put in quotes but DOT identifier validity is + /// up to the caller. + /// + pub def toGraphviz(g: m[(t, t)]): String \ Foldable.Aef[m] with Foldable[m], Order[t], ToString[t] = + "digraph {" + String.lineSeparator() + + Foldable.joinWith(match (x, y) -> " ${graphvizId(x)} -> ${graphvizId(y)}" + String.lineSeparator(), "", g) + + "}" + + String.lineSeparator() + + /// + /// Returns a Graphviz (DOT) string of the directed graph `g`. + /// The strings of nodes are put in quotes and existing quotes are escaped. + /// Other than that, DOT identifier validity is up to the caller. + /// + pub def toGraphvizLabeled(g: m[(t, l, t)]): String \ Foldable.Aef[m] with Foldable[m], ToString[t], ToString[l] = + "digraph {" + String.lineSeparator() + + Foldable.joinWith(match (x, w, y) -> " ${graphvizId(x)} -> ${graphvizId(y)} [label = ${w}]" + String.lineSeparator(), "", g) + + "}" + + String.lineSeparator() + + // ------------------------------------------------------------------------- + // Private Functions ------------------------------------------------------- + // ------------------------------------------------------------------------- + + /// + /// Wraps `toString(id)` with double quotes and escapes any existing quotes + /// with backslash. + /// + def graphvizId(id: t): String with ToString[t] = { + "\"${String.replace(src = "\"", dst = "\\\"", "${id}")}\"" + } + + /// + /// Returns a Datalog program which computes the reachable nodes when + /// given a set of `Edge` and `Node` facts. Nodes can always reach + /// themselves. + /// + def reachability(): #{ Edge(t, t), Reachable(t, t), Node(t) | r } with Order[t] = #{ + // All nodes can reach themselves. + Reachable(n, n) :- Node(n). + // If `n1` can reach `m` and there is an edge from `m` to `n2` then `n1` + // can also reach `n2`. This adds all node pairs to the relational that + // are reachable using any number of nodes. + Reachable(n1, n2) :- Reachable(n1, m), Edge(m, n2). + } + + /// + /// Returns a Datalog program that, when given a set of `Edge` facts, + /// computes the nodes. + /// + def nodes(): #{ Edge(t, t), Node(t) | r } with Order[t] = #{ + Node(x) :- Edge(x, _). + Node(x) :- Edge(_, x). + } + + /// + /// Returns a Datalog program which computes the reachable nodes from `src` + /// when given a set of `Edge` facts. + /// + def reachabilityFromSrc(src: t): #{ Edge(t, t), Reachable(t) | r } with Order[t] = #{ + // A node can reach itself. + Reachable(src). + // If `src` can reach `m` and there is an edge from `m` to `n` then + // `src` can also reach `n`. This adds all node pairs to the relational + // that are reachable using any number of nodes. + Reachable(n) :- Reachable(m), Edge(m, n). + } + + /// + /// Returns the value inside the `d` value. + /// + def destructDown(d: Down[a]): a = { + let Down.Down(a) = d; + a + } + +} diff --git a/test/TestGraph.flix b/test/TestGraph.flix new file mode 100644 index 0000000..e33c900 --- /dev/null +++ b/test/TestGraph.flix @@ -0,0 +1,1475 @@ +mod TestGraph { + + use Assert.{assertEq, assertTrue, assertFalse, assertSome, assertNone}; + use Extras.Graph + + //////////////////////////////////////////////////////////////////////////// + // directed graphs // + //////////////////////////////////////////////////////////////////////////// + + def graph01(): Set[(Int32, Int32)] = + Set#{} + + def graph02(): Set[(Int32, Int32)] = + let _graphString = + " ┌─┐ ┌─┐ " :: + " │1├─→─┤2│ " :: + " └─┘ └─┘ " :: + Nil; + Set#{(1, 2)} + + def graph03(): Set[(Int32, Int32)] = + let _graphString = + " ┌─┐ ┌─┐ " :: + " │1├─→─┤2│ " :: + " └─┘ └─┘ " :: + " ┌─┐ ┌─┐ " :: + " │3├─→─┤4│ " :: + " └─┘ └─┘ " :: + Nil; + Set#{(1, 2), (3, 4)} + + def graph04(): Set[(Int32, Int32)] = + let _graphString = + " ┌─┐ ┌─┐ " :: + " │1├─→─┤2│ " :: + " └─┘ └┬┘ " :: + " ↓ " :: + " ┌┴┐ " :: + " │3│ " :: + " └─┘ " :: + Nil; + Set#{(1, 2), (2, 3)} + + def graph05(): Set[(Int32, Int32)] = + let _graphString = + " ┌─┐ ┌─┐ " :: + " │1├─→─┤2│ " :: + " └─┘ └┬┘ " :: + " ↓ " :: + " ┌─┐ ┌┴┐ " :: + " │4├─←─┤3│ " :: + " └─┘ └─┘ " :: + Nil; + Set#{(1, 2), (2, 3), (3, 4)} + + def graph06(): Set[(Int32, Int32)] = + let _graphString = + " ┌─┐ ┌─┐ " :: + " │1├─→─┤2│ " :: + " └┬┘ └┬┘ " :: + " ↑ ↓ " :: + " ┌┴┐ ┌┴┐ " :: + " │4├─←─┤3│ " :: + " └─┘ └─┘ " :: + Nil; + Set#{(1, 2), (2, 3), (3, 4), (4, 1)} + + def graph07(): Set[(Int32, Int32)] = + let _graphString = + " ┌─┐ ┌─┐ ┌─┐ " :: + " │1├─→─┤2├─→─┤5│ " :: + " └┬┘ └┬┘ └┬┘ " :: + " ↑ ↓ ↕ " :: + " ┌┴┐ ┌┴┐ ┌┴┐ " :: + " │4├─←─┤3│ │6│ " :: + " └─┘ └─┘ └─┘ " :: + Nil; + Set#{(1, 2), (2, 3), (2, 5), (3, 4), (4, 1), (5, 6), (6, 5)} + + + //////////////////////////////////////////////////////////////////////////// + // weighted directed graph // + //////////////////////////////////////////////////////////////////////////// + + def graphWithDist01(): Set[(Int32, Int32, Int32)] = + Set#{} + + def graphWithDist02(): Set[(Int32, Int32, Int32)] = + let _graphString = + " ┌─┐ ┌─┐ " :: + " │1├─4→─┤2│ " :: + " └─┘ └─┘ " :: + Nil; + Set#{(1, 4, 2)} + + def graphWithDist03(): Set[(Int32, Int32, Int32)] = + let _graphString = + " ┌─┐ ┌─┐ " :: + " │1├─4→─┤2│ " :: + " └─┘ └─┘ " :: + " ┌─┐ ┌─┐ " :: + " │3├─7→─┤4│ " :: + " └─┘ └─┘ " :: + Nil; + Set#{(1, 4, 2), (3, 7, 4)} + + def graphWithDist04(): Set[(Int32, Int32, Int32)] = + let _graphString = + " ┌─┐ ┌─┐ " :: + " │1├─4→─┤2│ " :: + " └─┘ └┬┘ " :: + " 5 " :: + " ↓ " :: + " ┌─┐ ┌┴┐ " :: + " │4├─←7─┤3│ " :: + " └─┘ └─┘ " :: + Nil; + Set#{(1, 4, 2), (2, 5, 3), (3, 7, 4)} + + def graphWithDist05(): Set[(Int32, Int32, Int32)] = + let _graphString = + " ┌─┐ ┌─┐ " :: + " │1├─4→─┤2│ " :: + " └┬┘ └┬┘ " :: + " ↑ 2 " :: + " 5 ↓ " :: + " ┌┴┐ ┌┴┐ " :: + " │4├─←1─┤3│ " :: + " └─┘ └─┘ " :: + Nil; + Set#{(1, 4, 2), (2, 2, 3), (3, 1, 4), (4, 5, 1)} + + def graphWithDist06(): Set[(Int32, Int32, Int32)] = + let _graphString = + " ┌─┐ " :: + " │1├─5→──┐ " :: + " └┬┘ │ " :: + " 2 │ " :: + " ↓ ↓ " :: + " ┌┴┐ ┌┴┐ ┌─┐ " :: + " │2├─←7─┤4├─2→─┤5│ " :: + " └┬┘ └┬┘ └┬┘ " :: + " 10 4 1 " :: + " ↓ ↓ ↓ " :: + " ┌┴┐ ┌┴┐ │ " :: + " │3│ │6├─←───┘ " :: + " └─┘ └─┘ " :: + Nil; + Set#{ + (1, 2, 2), (1, 5, 4), + (2, 10, 3), (4, 7, 2), + (4, 2, 5), (4, 4, 6), + (5, 1, 6) + } + + + //////////////////////////////////////////////////////////////////////////// + // closure // + //////////////////////////////////////////////////////////////////////////// + + @Test + def closure01(): Unit \ Assert = { + let g = graph01(); + let s = Set#{}; + assertEq(expected = s, Graph.closure(g)) + } + + @Test + def closure02(): Unit \ Assert = { + let g = graph02(); + let s = Set#{(1, 1), (1, 2), (2, 2)}; + assertEq(expected = s, Graph.closure(g)) + } + + @Test + def closure03(): Unit \ Assert = { + let g = graph03(); + let s = Set#{(1, 1), (1, 2), (2, 2), (3, 3), (3, 4), (4, 4)}; + assertEq(expected = s, Graph.closure(g)) + } + + @Test + def closure04(): Unit \ Assert = { + let g = graph04(); + let s = Set#{(1, 1), (1, 2), (1, 3), (2, 2), (2, 3), (3, 3)}; + assertEq(expected = s, Graph.closure(g)) + } + + @Test + def closure05(): Unit \ Assert = { + let g = graph05(); + let s = Set#{(1, 1), (1, 2), (1, 3), (1, 4), + (2, 2), (2, 3), (2, 4), + (3, 3), (3, 4), + (4, 4)}; + assertEq(expected = s, Graph.closure(g)) + } + + + //////////////////////////////////////////////////////////////////////////// + // reachableFrom // + //////////////////////////////////////////////////////////////////////////// + + @Test + def reachable01(): Unit \ Assert = { + let g = graph01(); + assertEq(expected = Set#{1}, Graph.reachableFrom(1, g)) + } + + @Test + def reachable02(): Unit \ Assert = { + let g = graph02(); + assertEq(expected = Set#{1, 2}, Graph.reachableFrom(1, g)) + } + + @Test + def reachable03(): Unit \ Assert = { + let g = graph03(); + assertEq(expected = Set#{1, 2}, Graph.reachableFrom(1, g)) + } + + @Test + def reachable04(): Unit \ Assert = { + let g = graph04(); + assertEq(expected = Set#{1, 2, 3}, Graph.reachableFrom(1, g)) + } + + @Test + def reachable05(): Unit \ Assert = { + let g = graph05(); + assertEq(expected = Set#{1, 2, 3, 4}, Graph.reachableFrom(1, g)) + } + + @Test + def reachable06(): Unit \ Assert = { + let g = graph05(); + assertEq(expected = Set#{3, 4}, Graph.reachableFrom(3, g)) + } + + @Test + def reachable07(): Unit \ Assert = { + let g = graph05(); + assertEq(expected = Set#{5}, Graph.reachableFrom(5, g)) + } + + + //////////////////////////////////////////////////////////////////////////// + // unreachableFrom // + //////////////////////////////////////////////////////////////////////////// + + @Test + def unreachable01(): Unit \ Assert = { + let g = graph01(); + assertEq(expected = Set#{}, Graph.unreachableFrom(1, g)) + } + + @Test + def unreachable02(): Unit \ Assert = { + let g = graph02(); + assertEq(expected = Set#{}, Graph.unreachableFrom(1, g)) + } + + @Test + def unreachable03(): Unit \ Assert = { + let g = graph02(); + assertEq(expected = Set#{1}, Graph.unreachableFrom(2, g)) + } + + @Test + def unreachable04(): Unit \ Assert = { + let g = graph03(); + assertEq(expected = Set#{3, 4}, Graph.unreachableFrom(1, g)) + } + + @Test + def unreachable05(): Unit \ Assert = { + let g = graph04(); + assertEq(expected = Set#{1}, Graph.unreachableFrom(2, g)) + } + + @Test + def unreachable06(): Unit \ Assert = { + let g = graph05(); + assertEq(expected = Set#{1, 2}, Graph.unreachableFrom(3, g)) + } + + @Test + def unreachable07(): Unit \ Assert = { + let g = graph05(); + assertEq(expected = Set#{1, 2, 3, 4}, Graph.unreachableFrom(5, g)) + } + + + //////////////////////////////////////////////////////////////////////////// + // reachable // + //////////////////////////////////////////////////////////////////////////// + + @Test + def isConnected01(): Unit \ Assert = { + let g = graph01(); + assertFalse(Graph.reachable(src = 1, dst = 2, g)) + } + + @Test + def isConnected02(): Unit \ Assert = { + let g = graph02(); + assertTrue(Graph.reachable(src = 1, dst = 2, g)) + } + + @Test + def isConnected03(): Unit \ Assert = { + let g = graph02(); + assertFalse(Graph.reachable(src = 2, dst = 1, g)) + } + + @Test + def isConnected04(): Unit \ Assert = { + let g = graph03(); + assertFalse(Graph.reachable(src = 1, dst = 3, g)) + } + + @Test + def isConnected05(): Unit \ Assert = { + let g = graph04(); + assertTrue(Graph.reachable(src = 1, dst = 3, g)) + } + + @Test + def isConnected06(): Unit \ Assert = { + let g = graph05(); + assertTrue(Graph.reachable(src = 2, dst = 4, g)) + } + + @Test + def isConnected07(): Unit \ Assert = { + let g = graph05(); + assertFalse(Graph.reachable(src = 4, dst = 1, g)) + } + + @Test + def isConnected08(): Unit \ Assert = { + let g = graph05(); + assertFalse(Graph.reachable(src = 5, dst = 6, g)) + } + + + //////////////////////////////////////////////////////////////////////////// + // stronglyConnectedComponents // + //////////////////////////////////////////////////////////////////////////// + + @Test + def stronglyConnectedComponents01(): Unit \ Assert = { + let g = graph01(); + let s = Set#{}; + assertEq(expected = s, Graph.stronglyConnectedComponents(g)) + } + + @Test + def stronglyConnectedComponents02(): Unit \ Assert = { + let g = graph02(); + let s = Set#{Set#{1}, Set#{2}}; + assertEq(expected = s, Graph.stronglyConnectedComponents(g)) + } + + @Test + def stronglyConnectedComponents03(): Unit \ Assert = { + let g = graph03(); + let s = Set#{Set#{1}, Set#{2}, Set#{3}, Set#{4}}; + assertEq(expected = s, Graph.stronglyConnectedComponents(g)) + } + + @Test + def stronglyConnectedComponents04(): Unit \ Assert = { + let g = graph04(); + let s = Set#{Set#{1}, Set#{2}, Set#{3}}; + assertEq(expected = s, Graph.stronglyConnectedComponents(g)) + } + + @Test + def stronglyConnectedComponents05(): Unit \ Assert = { + let g = graph05(); + let s = Set#{Set#{1}, Set#{2}, Set#{3}, Set#{4}}; + assertEq(expected = s, Graph.stronglyConnectedComponents(g)) + } + + @Test + def stronglyConnectedComponents06(): Unit \ Assert = { + let g = graph06(); + let s = Set#{Set#{1, 2, 3, 4}}; + assertEq(expected = s, Graph.stronglyConnectedComponents(g)) + } + + @Test + def stronglyConnectedComponents07(): Unit \ Assert = { + let g = graph07(); + let s = Set#{Set#{1, 2, 3, 4}, Set#{5, 6}}; + assertEq(expected = s, Graph.stronglyConnectedComponents(g)) + } + + //////////////////////////////////////////////////////////////////////////// + // isCyclic // + //////////////////////////////////////////////////////////////////////////// + + @Test + def isCyclic01(): Unit \ Assert = { + let g = graph01(); + assertFalse(Graph.isCyclic(g)) + } + + @Test + def isCyclic02(): Unit \ Assert = { + let g = graph02(); + assertFalse(Graph.isCyclic(g)) + } + + @Test + def isCyclic03(): Unit \ Assert = { + let g = graph03(); + assertFalse(Graph.isCyclic(g)) + } + + @Test + def isCyclic04(): Unit \ Assert = { + let g = graph04(); + assertFalse(Graph.isCyclic(g)) + } + + @Test + def isCyclic05(): Unit \ Assert = { + let g = graph05(); + assertFalse(Graph.isCyclic(g)) + } + + @Test + def isCyclic06(): Unit \ Assert = { + let g = graph06(); + assertTrue(Graph.isCyclic(g)) + } + + @Test + def isCyclic07(): Unit \ Assert = { + let g = graph07(); + assertTrue(Graph.isCyclic(g)) + } + + //////////////////////////////////////////////////////////////////////////// + // boundedDistances // + //////////////////////////////////////////////////////////////////////////// + + @Test + def distances01(): Unit \ Assert = { + let g = graphWithDist01(); + assertEq(expected = Map.empty(), Graph.boundedDistances(g)) + } + + @Test + def distances02(): Unit \ Assert = { + let g = graphWithDist02(); + assertEq(expected = Map#{ + (1, 1) => 0, + (2, 2) => 0, + + (1, 2) => 4 + }, Graph.boundedDistances(g)) + } + + @Test + def distances03(): Unit \ Assert = { + let g = graphWithDist03(); + assertEq(expected = Map#{ + (1, 1) => 0, + (2, 2) => 0, + (3, 3) => 0, + (4, 4) => 0, + + (1, 2) => 4, + (3, 4) => 7 + }, Graph.boundedDistances(g)) + } + + @Test + def distances04(): Unit \ Assert = { + let g = graphWithDist04(); + assertEq(expected = Map#{ + (1, 1) => 0, + (2, 2) => 0, + (3, 3) => 0, + (4, 4) => 0, + + (1, 2) => 4, + (1, 3) => 4+5, + (1, 4) => 4+5+7, + (2, 3) => 5, + (2, 4) => 5+7, + (3, 4) => 7 + }, Graph.boundedDistances(g)) + } + + @Test + def distances05(): Unit \ Assert = { + let g = graphWithDist05(); + assertEq(expected = Map#{ + (1, 1) => 0, + (2, 2) => 0, + (3, 3) => 0, + (4, 4) => 0, + + (1, 2) => 4, + (1, 3) => 4+2, + (1, 4) => 4+2+1, + (2, 1) => 2+1+5, + (2, 3) => 2, + (2, 4) => 2+1, + (3, 1) => 1+5, + (3, 2) => 1+5+4, + (3, 4) => 1, + (4, 1) => 5, + (4, 2) => 5+4, + (4, 3) => 5+4+2 + }, Graph.boundedDistances(g)) + } + + @Test + def distances06(): Unit \ Assert = { + let g = graphWithDist06(); + assertEq(expected = Map#{ + (1, 1) => 0, + (2, 2) => 0, + (3, 3) => 0, + (4, 4) => 0, + (5, 5) => 0, + (6, 6) => 0, + + (1, 2) => 2, + (1, 3) => 2+10, + (1, 4) => 5, + (1, 5) => 5+2, + (1, 6) => 5+2+1, + (2, 3) => 10, + (4, 2) => 7, + (4, 3) => 7+10, + (4, 5) => 2, + (4, 6) => 3, + (5, 6) => 1 + }, Graph.boundedDistances(g)) + } + + //////////////////////////////////////////////////////////////////////////// + // distancesFrom // + //////////////////////////////////////////////////////////////////////////// + + @Test + def distancesFrom01(): Unit \ Assert = { + let g = graphWithDist01(); + assertEq(expected = Map#{1 => 0}, Graph.distancesFrom(1, g)) + } + + @Test + def distancesFrom02(): Unit \ Assert = { + let g = graphWithDist02(); + assertEq(expected = Map#{1 => 0, 2 => 4}, Graph.distancesFrom(1, g)) + } + + @Test + def distancesFrom03(): Unit \ Assert = { + let g = graphWithDist02(); + assertEq(expected = Map#{2 => 0}, Graph.distancesFrom(2, g)) + } + + @Test + def distancesFrom04(): Unit \ Assert = { + let g = graphWithDist03(); + assertEq(expected = Map#{3 => 0, 4 => 7}, Graph.distancesFrom(3, g)) + } + + @Test + def distancesFrom05(): Unit \ Assert = { + let g = graphWithDist04(); + assertEq(expected = Map#{2 => 0, 3 => 5, 4 => (5 + 7)}, Graph.distancesFrom(2, g)) + } + + @Test + def distancesFrom06(): Unit \ Assert = { + let g = graphWithDist05(); + assertEq(expected = Map#{ + 1 => 5, + 2 => (5 + 4), + 3 => (5 + 4 + 2), + 4 => 0 + }, Graph.distancesFrom(4, g)) + } + + @Test + def distancesFrom07(): Unit \ Assert = { + let g = graphWithDist06(); + assertEq(expected = Map#{ + 2 => 7, + 3 => (7 + 10), + 4 => 0, + 5 => 2, + 6 => 3 + }, Graph.distancesFrom(4, g)) + } + + + //////////////////////////////////////////////////////////////////////////// + // distance // + //////////////////////////////////////////////////////////////////////////// + + @Test + def distance01(): Unit \ Assert = { + let g = graphWithDist01(); + assertEq(expected = None, Graph.distance(src = 1, dst = 2, g)) + } + + @Test + def distance02(): Unit \ Assert = { + let g = graphWithDist02(); + assertEq(expected = Some(4), Graph.distance(src = 1, dst = 2, g)) + } + + @Test + def distance03(): Unit \ Assert = { + let g = graphWithDist02(); + assertEq(expected = None, Graph.distance(src = 2, dst = 1, g)) + } + + @Test + def distance04(): Unit \ Assert = { + let g = graphWithDist03(); + assertEq(expected = Some(4), Graph.distance(src = 1, dst = 2, g)) + } + + @Test + def distance05(): Unit \ Assert = { + let g = graphWithDist03(); + assertEq(expected = Some(7), Graph.distance(src = 3, dst = 4, g)) + } + + @Test + def distance06(): Unit \ Assert = { + let g = graphWithDist03(); + assertEq(expected = None, Graph.distance(src = 1, dst = 4, g)) + } + + @Test + def distance07(): Unit \ Assert = { + let g = graphWithDist04(); + assertEq(expected = Some(4 + 5 + 7), Graph.distance(src = 1, dst = 4, g)) + } + + @Test + def distance08(): Unit \ Assert = { + let g = graphWithDist05(); + assertEq(expected = Some(5 + 4), Graph.distance(src = 4, dst = 2, g)) + } + + @Test + def distance09(): Unit \ Assert = { + let g = graphWithDist06(); + assertEq(expected = Some(5 + 2 + 1), Graph.distance(src = 1, dst = 6, g)) + } + + //////////////////////////////////////////////////////////////////////////// + // toUndirected // + //////////////////////////////////////////////////////////////////////////// + + @Test + def toUndirected01(): Unit \ Assert = { + let g = graph01() |> Graph.toUndirected; + assertEq(expected = Set#{}, g) + } + + @Test + def toUndirected02(): Unit \ Assert = { + let g = graph02() |> Graph.toUndirected; + assertEq(expected = Set#{(1, 2), (2, 1)}, g) + } + + @Test + def toUndirected03(): Unit \ Assert = { + let g = graph03() |> Graph.toUndirected; + assertEq(expected = Set#{(1, 2), (2, 1), (3, 4), (4, 3)}, g) + } + + @Test + def toUndirected04(): Unit \ Assert = { + let g = graph04() |> Graph.toUndirected; + assertEq(expected = Set#{(1, 2), (2, 1), (2, 3), (3, 2)}, g) + } + + @Test + def toUndirected05(): Unit \ Assert = { + let g = graph05() |> Graph.toUndirected; + assertEq(expected = Set#{(1, 2), (2, 1), (2, 3), (3, 2), (3, 4), (4, 3)}, g) + } + + @Test + def toUndirected06(): Unit \ Assert = { + let g = graph06() |> Graph.toUndirected; + assertEq(expected = Set#{ + (1, 2), (2, 1), + (2, 3), (3, 2), + (3, 4), (4, 3), + (4, 1), (1, 4) + }, g) + } + + @Test + def toUndirected07(): Unit \ Assert = { + let g = graph07() |> Graph.toUndirected; + assertEq(expected = Set#{ + (1, 2), (2, 1), + (1, 4), (4, 1), + (2, 3), (3, 2), + (2, 5), (5, 2), + (3, 4), (4, 3), + (5, 6), (6, 5) + }, g) + } + + //////////////////////////////////////////////////////////////////////////// + // toUndirectedLabeled // + //////////////////////////////////////////////////////////////////////////// + + @Test + def toUndirectedLabeled01(): Unit \ Assert = { + let g = graphWithDist01() |> Graph.toUndirectedLabeled; + assertEq(expected = Set#{}, g) + } + + @Test + def toUndirectedLabeled02(): Unit \ Assert = { + let g = graphWithDist02() |> Graph.toUndirectedLabeled; + assertEq(expected = Set#{(1, 4, 2), (2, 4, 1)}, g) + } + + @Test + def toUndirectedLabeled03(): Unit \ Assert = { + let g = graphWithDist03() |> Graph.toUndirectedLabeled; + assertEq(expected = Set#{ + (1, 4, 2), (2, 4, 1), + (3, 7, 4), (4, 7, 3) + }, g) + } + + @Test + def toUndirectedLabeled04(): Unit \ Assert = { + let g = graphWithDist04() |> Graph.toUndirectedLabeled; + assertEq(expected = Set#{ + (1, 4, 2), (2, 4, 1), + (2, 5, 3), (3, 5, 2), + (3, 7, 4), (4, 7, 3) + }, g) + } + + @Test + def toUndirectedLabeled05(): Unit \ Assert = { + let g = graphWithDist05() |> Graph.toUndirectedLabeled; + assertEq(expected = Set#{ + (1, 4, 2), (2, 4, 1), + (1, 5, 4), (4, 5, 1), + (2, 2, 3), (3, 2, 2), + (3, 1, 4), (4, 1, 3) + }, g) + } + + @Test + def toUndirectedLabeled06(): Unit \ Assert = { + let g = graphWithDist06() |> Graph.toUndirectedLabeled; + assertEq(expected = Set#{ + (1, 2, 2), (2, 2, 1), + (1, 5, 4), (4, 5, 1), + (2, 10, 3), (3, 10, 2), + (2, 7, 4), (4, 7, 2), + (4, 2, 5), (5, 2, 4), + (4, 4, 6), (6, 4, 4), + (5, 1, 6), (6, 1, 5) + }, g) + } + + @Test + def toUndirectedLabeled07(): Unit \ Assert = { + let g0 = Set#{(7, 9, 13), (13, 8, 7)}; + let g = g0 |> Graph.toUndirectedLabeled; + assertEq(expected = Set#{ + (7, 9, 13), (13, 9, 7), + (7, 8, 13), (13, 8, 7) + }, g) + } + + //////////////////////////////////////////////////////////////////////////// + // inDegrees // + //////////////////////////////////////////////////////////////////////////// + + @Test + def inDegrees01(): Unit \ Assert = { + assertEq(expected = Map#{}, Graph.inDegrees(graph01())) + } + + @Test + def inDegrees02(): Unit \ Assert = { + assertEq(expected = Map#{ + 1 => 0, + 2 => 1 + }, Graph.inDegrees(graph02())) + } + + @Test + def inDegrees03(): Unit \ Assert = { + assertEq(expected = Map#{ + 1 => 0, + 2 => 1, + 3 => 0, + 4 => 1 + }, Graph.inDegrees(graph03())) + } + + @Test + def inDegrees04(): Unit \ Assert = { + assertEq(expected = Map#{ + 1 => 0, + 2 => 1, + 3 => 1 + }, Graph.inDegrees(graph04())) + } + + @Test + def inDegrees05(): Unit \ Assert = { + assertEq(expected = Map#{ + 1 => 0, + 2 => 1, + 3 => 1, + 4 => 1 + }, Graph.inDegrees(graph05())) + } + + @Test + def inDegrees06(): Unit \ Assert = { + assertEq(expected = Map#{ + 1 => 1, + 2 => 1, + 3 => 1, + 4 => 1 + }, Graph.inDegrees(graph06())) + } + + @Test + def inDegrees07(): Unit \ Assert = { + assertEq(expected = Map#{ + 1 => 1, + 2 => 1, + 3 => 1, + 4 => 1, + 5 => 2, + 6 => 1 + }, Graph.inDegrees(graph07())) + } + + //////////////////////////////////////////////////////////////////////////// + // outDegrees // + //////////////////////////////////////////////////////////////////////////// + + @Test + def outDegrees01(): Unit \ Assert = { + assertEq(expected = Map#{}, Graph.outDegrees(graph01())) + } + + @Test + def outDegrees02(): Unit \ Assert = { + assertEq(expected = Map#{ + 1 => 1, + 2 => 0 + }, Graph.outDegrees(graph02())) + } + + @Test + def outDegrees03(): Unit \ Assert = { + assertEq(expected = Map#{ + 1 => 1, + 2 => 0, + 3 => 1, + 4 => 0 + }, Graph.outDegrees(graph03())) + } + + @Test + def outDegrees04(): Unit \ Assert = { + assertEq(expected = Map#{ + 1 => 1, + 2 => 1, + 3 => 0 + }, Graph.outDegrees(graph04())) + } + + @Test + def outDegrees05(): Unit \ Assert = { + assertEq(expected = Map#{ + 1 => 1, + 2 => 1, + 3 => 1, + 4 => 0 + }, Graph.outDegrees(graph05())) + } + + @Test + def outDegrees06(): Unit \ Assert = { + assertEq(expected = Map#{ + 1 => 1, + 2 => 1, + 3 => 1, + 4 => 1 + }, Graph.outDegrees(graph06())) + } + + @Test + def outDegrees07(): Unit \ Assert = { + assertEq(expected = Map#{ + 1 => 1, + 2 => 2, + 3 => 1, + 4 => 1, + 5 => 1, + 6 => 1 + }, Graph.outDegrees(graph07())) + } + + //////////////////////////////////////////////////////////////////////////// + // toGraphviz // + //////////////////////////////////////////////////////////////////////////// + + @Test + def toGraphviz01(): Unit \ Assert = { + assertEq(expected = String.unlines( + "digraph {" :: + "}" :: + Nil + ), Graph.toGraphviz(graph01())) + } + + @Test + def toGraphviz02(): Unit \ Assert = { + let g = Set#{ + ("node \"A\"", "node \"B\"") + }; + assertEq(expected = String.unlines( + "digraph {" :: + " \"node \\\"A\\\"\" -> \"node \\\"B\\\"\"" :: + "}" :: + Nil + ), Graph.toGraphviz(g)) + } + + @Test + def toGraphviz03(): Unit \ Assert = { + let result = Graph.toGraphviz(graph03()); + let test1 = result == String.unlines( + "digraph {" :: + " \"1\" -> \"2\"" :: + " \"3\" -> \"4\"" :: + "}" :: + Nil + ); + let test2 = result == String.unlines( + "digraph {" :: + " \"3\" -> \"4\"" :: + " \"1\" -> \"2\"" :: + "}" :: + Nil + ); + assertTrue(test1 or test2) + } + + //////////////////////////////////////////////////////////////////////////// + // toGraphvizLabeled // + //////////////////////////////////////////////////////////////////////////// + + @Test + def toGraphvizLabeled01(): Unit \ Assert = { + assertEq(expected = String.unlines( + "digraph {" :: + "}" :: + Nil + ), Graph.toGraphvizLabeled(graphWithDist01())) + } + + @Test + def toGraphvizLabeled02(): Unit \ Assert = { + let g = Set#{ + ("node \"A\"", -42, "node \"B\"") + }; + assertEq(expected = String.unlines( + "digraph {" :: + " \"node \\\"A\\\"\" -> \"node \\\"B\\\"\" [label = -42]" :: + "}" :: + Nil + ), Graph.toGraphvizLabeled(g)) + } + + @Test + def toGraphvizLabeled03(): Unit \ Assert = { + let gv = Graph.toGraphvizLabeled(graphWithDist03()); + let test1 = gv == String.unlines( + "digraph {" :: + " \"1\" -> \"2\" [label = 4]" :: + " \"3\" -> \"4\" [label = 7]" :: + "}" :: + Nil + ); + let test2 = gv == String.unlines( + "digraph {" :: + " \"3\" -> \"4\" [label = 7]" :: + " \"1\" -> \"2\" [label = 4]" :: + "}" :: + Nil + ); + assertTrue(test1 or test2) + } + + //////////////////////////////////////////////////////////////////////////// + // flipEdges // + //////////////////////////////////////////////////////////////////////////// + + @Test + def flipEdges01(): Unit \ Assert = { + assertEq(expected = Set#{}, Graph.flipEdges(graph01())) + } + + @Test + def flipEdges02(): Unit \ Assert = { + assertEq(expected = Set#{(2, 1)}, Graph.flipEdges(graph02())) + } + + @Test + def flipEdges03(): Unit \ Assert = { + assertEq(expected = Set#{(2, 1), (4, 3)}, Graph.flipEdges(graph03())) + } + + @Test + def flipEdges04(): Unit \ Assert = { + assertEq(expected = Set#{(2, 1), (3, 2)}, Graph.flipEdges(graph04())) + } + + @Test + def flipEdges05(): Unit \ Assert = { + assertEq(expected = Set#{(2, 1), (3, 2), (4, 3)}, Graph.flipEdges(graph05())) + } + + @Test + def flipEdges06(): Unit \ Assert = { + assertEq(expected = Set#{(2, 1), (3, 2), (4, 3), (1, 4)}, Graph.flipEdges(graph06())) + } + + @Test + def flipEdges07(): Unit \ Assert = { + assertEq(expected = Set#{ + (2, 1), (3, 2), (4, 3), (1, 4), (5, 2), (6, 5), (5, 6) + }, Graph.flipEdges(graph07())) + } + + //////////////////////////////////////////////////////////////////////////// + // invert // + //////////////////////////////////////////////////////////////////////////// + + @Test + def invert01(): Unit \ Assert = { + assertEq(expected = Set#{}, Graph.invert(graph01())) + } + + @Test + def invert02(): Unit \ Assert = { + assertEq(expected = Set#{(2, 1)}, Graph.invert(graph02())) + } + + @Test + def invert03(): Unit \ Assert = { + assertEq(expected = Set#{ + (1, 3), (1, 4), + (2, 1), (2, 3), (2, 4), + (3, 1), (3, 2), + (4, 1), (4, 2), (4, 3) + }, Graph.invert(graph03())) + } + + @Test + def invert04(): Unit \ Assert = { + assertEq(expected = Set#{ + (1, 3), + (2, 1), + (3, 1), (3, 2) + }, Graph.invert(graph04())) + } + + @Test + def invert05(): Unit \ Assert = { + assertEq(expected = Set#{ + (1, 3), (1, 4), + (2, 1), (2, 4), + (3, 1), (3, 2), + (4, 1), (4, 2), (4, 3) + }, Graph.invert(graph05())) + } + + @Test + def invert06(): Unit \ Assert = { + assertEq(expected = Set#{ + (1, 3), (1, 4), + (2, 1), (2, 4), + (3, 1), (3, 2), + (4, 2), (4, 3) + }, Graph.invert(graph06())) + } + + @Test + def invert07(): Unit \ Assert = { + assertEq(expected = Set#{ + (1, 3), (1, 4), (1, 5), (1, 6), + (2, 1), (2, 4), (2, 6), + (3, 1), (3, 2), (3, 5), (3, 6), + (4, 2), (4, 3), (4, 5), (4, 6), + (5, 1), (5, 2), (5, 3), (5, 4), + (6, 1), (6, 2), (6, 3), (6, 4) + }, Graph.invert(graph07())) + } + + @Test + def invert08(): Unit \ Assert = { + assertEq(expected = Set#{}, Graph.invert(Set#{(1, 1)})) + } + + @Test + def invert09(): Unit \ Assert = { + assertEq(expected = Set#{ + (1, 2), (1, 3), + (2, 1), + (3, 1), (3, 2) + }, Graph.invert(Set#{(1, 1), (2, 3)})) + } + + //////////////////////////////////////////////////////////////////////////// + // distances // + //////////////////////////////////////////////////////////////////////////// + + @Test + def stableDistances01(): Unit \ Assert = { + let g = graphWithDist01(); + assertEq(expected = Some(Graph.boundedDistances(g)), Graph.distances(g)) + } + + @Test + def stableDistances02(): Unit \ Assert = { + let g = graphWithDist02(); + assertEq(expected = Some(Graph.boundedDistances(g)), Graph.distances(g)) + } + + @Test + def stableDistances03(): Unit \ Assert = { + let g = graphWithDist03(); + assertEq(expected = Some(Graph.boundedDistances(g)), Graph.distances(g)) + } + + @Test + def stableDistances04(): Unit \ Assert = { + let g = graphWithDist04(); + assertEq(expected = Some(Graph.boundedDistances(g)), Graph.distances(g)) + } + + @Test + def stableDistances05(): Unit \ Assert = { + let g = graphWithDist05(); + assertEq(expected = Some(Graph.boundedDistances(g)), Graph.distances(g)) + } + + @Test + def stableDistances06(): Unit \ Assert = { + let g = graphWithDist06(); + assertEq(expected = Some(Graph.boundedDistances(g)), Graph.distances(g)) + } + + @Test + def stableDistances07(): Unit \ Assert = { + let g = Set#{("a", 1, "b"), ("b", 2, "c"), ("c", -4, "a")}; + assertEq(expected = None, Graph.distances(g)) + } + + //////////////////////////////////////////////////////////////////////////// + // withinDistanceOf // + //////////////////////////////////////////////////////////////////////////// + + @Test + def withinDistanceOf01(): Unit \ Assert = { + assertEq(expected = Set#{42}, Graph.withinDistanceOf(42, 999, graphWithDist01())) + } + + @Test + def withinDistanceOf02(): Unit \ Assert = { + assertEq(expected = Set#{}, Graph.withinDistanceOf(1, -42, graphWithDist02())) + } + + @Test + def withinDistanceOf03(): Unit \ Assert = { + assertEq(expected = Set#{1}, Graph.withinDistanceOf(1, 3, graphWithDist02())) + } + + @Test + def withinDistanceOf04(): Unit \ Assert = { + assertEq(expected = Set#{1, 2}, Graph.withinDistanceOf(1, 4, graphWithDist02())) + } + + @Test + def withinDistanceOf05(): Unit \ Assert = { + assertEq(expected = Set#{1, 2}, Graph.withinDistanceOf(1, 99, graphWithDist02())) + } + + @Test + def withinDistanceOf06(): Unit \ Assert = { + assertEq(expected = Set#{3, 4}, Graph.withinDistanceOf(3, 7, graphWithDist03())) + } + + @Test + def withinDistanceOf07(): Unit \ Assert = { + assertEq(expected = Set#{1, 2, 3}, Graph.withinDistanceOf(1, 11, graphWithDist04())) + } + + @Test + def withinDistanceOf08(): Unit \ Assert = { + assertEq(expected = Set#{2, 3, 4}, Graph.withinDistanceOf(2, 7, graphWithDist05())) + } + + @Test + def withinDistanceOf09(): Unit \ Assert = { + assertEq(expected = Set#{4, 5, 6}, Graph.withinDistanceOf(4, 3, graphWithDist06())) + } + + //////////////////////////////////////////////////////////////////////////// + // withinEdgesOf // + //////////////////////////////////////////////////////////////////////////// + + @Test + def withinEdgesOf01(): Unit \ Assert = { + assertEq(expected = Set#{42}, Graph.withinEdgesOf(42, 999, graph01())) + } + + @Test + def withinEdgesOf02(): Unit \ Assert = { + assertEq(expected = Set#{}, Graph.withinEdgesOf(1, -42, graph02())) + } + + @Test + def withinEdgesOf03(): Unit \ Assert = { + assertEq(expected = Set#{1, 2}, Graph.withinEdgesOf(1, 1, graph02())) + } + + @Test + def withinEdgesOf04(): Unit \ Assert = { + assertEq(expected = Set#{1, 2}, Graph.withinEdgesOf(1, 4, graph02())) + } + + @Test + def withinEdgesOf05(): Unit \ Assert = { + assertEq(expected = Set#{3, 4}, Graph.withinEdgesOf(3, 1, graph03())) + } + + @Test + def withinEdgesOf06(): Unit \ Assert = { + assertEq(expected = Set#{1, 2, 3}, Graph.withinEdgesOf(1, 2, graph04())) + } + + @Test + def withinEdgesOf07(): Unit \ Assert = { + assertEq(expected = Set#{1, 2, 3}, Graph.withinEdgesOf(1, 2, graph05())) + } + + @Test + def withinEdgesOf08(): Unit \ Assert = { + assertEq(expected = Set#{1, 4}, Graph.withinEdgesOf(4, 1, graph06())) + } + + @Test + def withinEdgesOf09(): Unit \ Assert = { + assertEq(expected = Set#{1, 2, 3, 5}, Graph.withinEdgesOf(1, 2, graph07())) + } + + //////////////////////////////////////////////////////////////////////////// + // topologicalSort // + //////////////////////////////////////////////////////////////////////////// + + @Test + def topologicalSort01(): Unit \ Assert = { + assertEq(expected = Nil, Graph.topologicalSort(graph01())) + } + + @Test + def topologicalSort02(): Unit \ Assert = { + assertEq(expected = 1 :: 2 :: Nil, Graph.topologicalSort(graph02())) + } + + @Test + def topologicalSort03(): Unit \ Assert = { + assertEq(expected = 1 :: 3 :: 2 :: 4 :: Nil, Graph.topologicalSort(graph03())) + } + + @Test + def topologicalSort04(): Unit \ Assert = { + assertEq(expected = 1 :: 2 :: 3 :: Nil, Graph.topologicalSort(graph04())) + } + + @Test + def topologicalSort05(): Unit \ Assert = { + assertEq(expected = 1 :: 2 :: 3 :: 4 :: Nil, Graph.topologicalSort(graph05())) + } + + //////////////////////////////////////////////////////////////////////////// + // degrees // + //////////////////////////////////////////////////////////////////////////// + + @Test + def degrees01(): Unit \ Assert = { + assertEq(expected = Map#{}, Graph.degrees(graph01())) + } + + @Test + def degrees02(): Unit \ Assert = { + assertEq(expected = Map#{1 => 1, 2 => 1}, Graph.degrees(graph02())) + } + + @Test + def degrees03(): Unit \ Assert = { + assertEq(expected = Map#{1 => 1, 2 => 1, 3 => 1, 4 => 1}, Graph.degrees(graph03())) + } + + @Test + def degrees04(): Unit \ Assert = { + assertEq(expected = Map#{1 => 1, 2 => 2, 3 => 1}, Graph.degrees(graph04())) + } + + @Test + def degrees05(): Unit \ Assert = { + assertEq(expected = Map#{1 => 1, 2 => 2, 3 => 2, 4 => 1}, Graph.degrees(graph05())) + } + + @Test + def degrees06(): Unit \ Assert = { + assertEq(expected = Map#{1 => 2, 2 => 2, 3 => 2, 4 => 2}, Graph.degrees(graph06())) + } + + @Test + def degrees07(): Unit \ Assert = { + assertEq(expected = Map#{ + 1 => 2, 2 => 3, 3 => 2, + 4 => 2, 5 => 3, 6 => 2 + }, Graph.degrees(graph07())) + } + + //////////////////////////////////////////////////////////////////////////// + // frontiersFrom // + //////////////////////////////////////////////////////////////////////////// + + @Test + def frontiersFrom01(): Unit \ Assert = { + assertEq(expected = Map#{0 => Set#{42}}, Graph.frontiersFrom(42, graph01())) + } + + @Test + def frontiersFrom02(): Unit \ Assert = { + assertEq(expected = Map#{0 => Set#{1}, 1 => Set#{2}}, Graph.frontiersFrom(1, graph02())) + } + + @Test + def frontiersFrom03(): Unit \ Assert = { + assertEq(expected = Map#{ + 0 => Set#{3}, + 1 => Set#{4} + }, Graph.frontiersFrom(3, graph03())) + } + + @Test + def frontiersFrom04(): Unit \ Assert = { + assertEq(expected = Map#{ + 0 => Set#{1}, + 1 => Set#{2}, + 2 => Set#{3} + }, Graph.frontiersFrom(1, graph04())) + } + + @Test + def frontiersFrom05(): Unit \ Assert = { + assertEq(expected = Map#{ + 0 => Set#{2}, + 1 => Set#{3}, + 2 => Set#{4} + }, Graph.frontiersFrom(2, graph05())) + } + + @Test + def frontiersFrom06(): Unit \ Assert = { + assertEq(expected = Map#{ + 0 => Set#{3}, + 1 => Set#{4}, + 2 => Set#{1}, + 3 => Set#{2} + }, Graph.frontiersFrom(3, graph06())) + } + + @Test + def frontiersFrom07(): Unit \ Assert = { + assertEq(expected = Map#{ + 0 => Set#{4}, + 1 => Set#{1}, + 2 => Set#{2}, + 3 => Set#{3, 5}, + 4 => Set#{6} + }, Graph.frontiersFrom(4, graph07())) + } + + @Test + def frontiersFrom08(): Unit \ Assert = { + assertEq(expected = Map#{ + 0 => Set#{5}, + 1 => Set#{6} + }, Graph.frontiersFrom(5, graph07())) + } + + //////////////////////////////////////////////////////////////////////////// + // cutPoints // + //////////////////////////////////////////////////////////////////////////// + + @Test + def cutPoints01(): Unit \ Assert = { + assertEq(expected = Set#{}, Graph.cutPoints(graph01())) + } + + @Test + def cutPoints02(): Unit \ Assert = { + assertEq(expected = Set#{}, Graph.cutPoints(graph02())) + } + + @Test + def cutPoints03(): Unit \ Assert = { + assertEq(expected = Set#{}, Graph.cutPoints(graph03())) + } + + @Test + def cutPoints04(): Unit \ Assert = { + assertEq(expected = Set#{(1, 2, 3)}, Graph.cutPoints(graph04())) + } + + @Test + def cutPoints05(): Unit \ Assert = { + assertEq(expected = Set#{ + (1, 2, 3), (1, 3, 4), (2, 3, 4) + }, Graph.cutPoints(graph05())) + } + + @Test + def cutPoints06(): Unit \ Assert = { + assertEq(expected = Set#{ + (1, 2, 3), (1, 3, 4), + (2, 3, 4), (2, 4, 1), + (3, 4, 1), (3, 4, 2), + (4, 1, 2), (4, 2, 3) + }, Graph.cutPoints(graph06())) + } + + @Test + def cutPoints07(): Unit \ Assert = { + assertEq(expected = Set#{ + (1, 2, 3), (1, 3, 4), (1, 2, 5), (1, 5, 6), + (2, 3, 4), (2, 4, 1), (2, 5, 6), + (3, 4, 1), (3, 4, 2), (3, 4, 5), (3, 5, 6), + (4, 1, 2), (4, 2, 3), (4, 2, 5), (4, 5, 6) + }, Graph.cutPoints(graph07())) + } + +}