diff --git a/CHANGES.md b/CHANGES.md index e7c9fa86..d31de8c6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,6 @@ + - #31: fixed documentation of [map_vertex]: the supplied function + must be injective - #110: ensure that map_vertex applies the function only once per vertex # 2.0.0 (October 2, 2020) diff --git a/src/sig.mli b/src/sig.mli index abe28f94..b77e8cbd 100644 --- a/src/sig.mli +++ b/src/sig.mli @@ -189,7 +189,13 @@ module type G = sig (** Fold on all edges of a graph. *) val map_vertex : (vertex -> vertex) -> t -> t - (** Map on all vertices of a graph. *) + (** Map on all vertices of a graph. + + The current implementation requires the supplied function to be + injective. Said otherwise, [map_vertex] cannot be used to contract + a graph by mapping several vertices to the same vertex. + To contract a graph, use instead [create], [add_vertex], + and [add_edge]. *) (** {2 Vertex iterators} diff --git a/tests/test_31.ml b/tests/test_31.ml new file mode 100644 index 00000000..2c593681 --- /dev/null +++ b/tests/test_31.ml @@ -0,0 +1,32 @@ + +(* Issue #31 + + This would fail in the current state of OCamlGraph, + as map_vertex requires a supplied function that is injective. +*) + +module String = struct include String let hash = Hashtbl.hash end +module G = Graph.Persistent.Digraph.ConcreteBidirectional(String) + +(* Make a persistent graph where: + A -> B + B -> C + C -> B *) +let g = List.fold_left (fun g (x, y) -> + G.add_edge g x y) G.empty [("A", "B"); ("B", "C"); ("C", "B")] + +(* Contract the SCC *) +module C = Graph.Components.Make(G) + +let contract g = + let names = Array.map (String.concat ",") (C.scc_array g) in + let (_, numbering) = C.scc g in + Array.fold_left (fun g x -> G.remove_edge g x x) + (G.map_vertex (fun x -> names.(numbering x)) g) + names + +let g' = contract g +(* this is now "A" -> "C,B" *) + +let () = + assert (G.in_degree g' "C,B" = 1)