428 lines
14 KiB
OCaml
428 lines
14 KiB
OCaml
(** A module for graphs.
|
|
|
|
The implementation is based on purely functional datastructures
|
|
only: we make use of OCaml's standard modules Map and Set.
|
|
|
|
The implementation is generic with respect to the type of labels
|
|
for nodes and for edges. The module is therefore a functor
|
|
parameterized by the two descriptions of these types and their
|
|
operations.
|
|
|
|
*)
|
|
|
|
(** The type for edge labels.
|
|
|
|
We assume that there is a relatively small number of edge labels.
|
|
These labels are comparable and enumerable.
|
|
|
|
*)
|
|
module type EdgeLabelSig = sig
|
|
include Set.OrderedType
|
|
(** [all] enumerates all the possible edge labels. *)
|
|
val all : t list
|
|
(** [to_string e] converts [e] in a human readable value. *)
|
|
val to_string : t -> string
|
|
end
|
|
|
|
(** The type for node labels.
|
|
|
|
Node labels must be comparable.
|
|
|
|
*)
|
|
module type NodeLabelSig = sig
|
|
include Set.OrderedType
|
|
(** [to_string n] converts [n] in a human readable value. *)
|
|
val to_string : t -> string
|
|
end
|
|
|
|
(** The functor is parameterized by the previous two signatures. *)
|
|
module Make (EdgeLabel : EdgeLabelSig) (NodeLabel : NodeLabelSig) =
|
|
struct
|
|
|
|
(** A type for maps whose keys are integers. *)
|
|
module IntMap = Map.Make (struct type t = int let compare = compare end)
|
|
let int_map_update k m d f = ExtStd.update IntMap.find IntMap.add k m d f
|
|
|
|
(** A type for maps whose keys are edge labels. *)
|
|
module EdgeLabelMap = Map.Make (EdgeLabel)
|
|
|
|
(** A type for maps whose keys are node labels. *)
|
|
module NodeLabelMap = Map.Make (NodeLabel)
|
|
|
|
(** Internally, each node has an identifier which is a small integer. *)
|
|
type nodeid = NodeId of int
|
|
module IdCmp = struct type t = nodeid let compare = compare end
|
|
|
|
(** A type for maps whose keys are node identifiers. *)
|
|
module NodeIdMap = Map.Make (IdCmp)
|
|
let nodeid_map_update k m d f =
|
|
ExtStd.update NodeIdMap.find NodeIdMap.add k m d f
|
|
|
|
(** A type for sets of node identifiers. *)
|
|
module NodeIdSet = Set.Make (IdCmp)
|
|
|
|
(** The type for graphs.
|
|
|
|
The datastructure maintains redundant information about the
|
|
graph using multiple maps. Each map provides a logarithmic
|
|
complexity for important services of the datastructure, namely
|
|
the computation of nodes of least [degrees] and the computation
|
|
of a node [neighbourhood].
|
|
|
|
A node is externally characterized by a list of node labels
|
|
while internally it is characterized by a node identifier. We
|
|
also maintain this mapping using maps, namely [node_of_label]
|
|
and [labels].
|
|
|
|
[next_node_id] is a counter that helps determining the identifier
|
|
for a newly created node.
|
|
*)
|
|
|
|
type t = {
|
|
next_node_id : int;
|
|
node_of_label : nodeid NodeLabelMap.t;
|
|
labels : NodeLabel.t list NodeIdMap.t;
|
|
neighbours : NodeIdSet.t NodeIdMap.t EdgeLabelMap.t;
|
|
degrees : NodeIdSet.t IntMap.t EdgeLabelMap.t
|
|
}
|
|
|
|
let string_of_nodeid (NodeId x) = string_of_int x
|
|
|
|
(** [dump g] returns a text-based representation of the graph, for
|
|
debugging. *)
|
|
let dump g =
|
|
let neighbours =
|
|
EdgeLabelMap.bindings g.neighbours |> List.map (fun (c, m) ->
|
|
NodeIdMap.bindings m |> List.map (fun (id, ids) ->
|
|
Printf.sprintf "%s -%s-> %s"
|
|
(string_of_nodeid id)
|
|
(EdgeLabel.to_string c)
|
|
(String.concat "," (List.map string_of_nodeid (NodeIdSet.elements ids)))
|
|
)
|
|
) |> List.flatten |> String.concat "\n"
|
|
in
|
|
let degrees =
|
|
EdgeLabelMap.bindings g.degrees |> List.map (fun (c, m) ->
|
|
IntMap.bindings m |> List.map (fun (d, ids) ->
|
|
Printf.sprintf "%d =%s=> %s"
|
|
d
|
|
(EdgeLabel.to_string c)
|
|
(String.concat "," (List.map string_of_nodeid (NodeIdSet.elements ids)))
|
|
)
|
|
) |> List.flatten |> String.concat "\n"
|
|
in
|
|
Printf.sprintf "%s\n%s\n" neighbours degrees
|
|
|
|
(** The empty graph. *)
|
|
let empty =
|
|
let degrees =
|
|
List.fold_left
|
|
(fun m e -> EdgeLabelMap.add e IntMap.empty m)
|
|
EdgeLabelMap.empty
|
|
EdgeLabel.all
|
|
in
|
|
let neighbours =
|
|
List.fold_left
|
|
(fun m e -> EdgeLabelMap.add e NodeIdMap.empty m)
|
|
EdgeLabelMap.empty
|
|
EdgeLabel.all
|
|
in
|
|
{
|
|
next_node_id = 0;
|
|
node_of_label = NodeLabelMap.empty;
|
|
labels = NodeIdMap.empty;
|
|
neighbours;
|
|
degrees;
|
|
}
|
|
|
|
exception InvalidNode
|
|
exception InvalidEdge
|
|
|
|
let defined_node g n = NodeLabelMap.mem n g.node_of_label
|
|
|
|
exception UnboundNode of NodeLabel.t
|
|
let id_of_node g n = try
|
|
NodeLabelMap.find n g.node_of_label
|
|
with Not_found ->
|
|
raise (UnboundNode n)
|
|
|
|
let nodes_of_id g n = NodeIdMap.find n g.labels
|
|
|
|
let nodes g =
|
|
List.map snd (NodeIdMap.bindings g.labels)
|
|
|
|
(** Sanity check for the data structure. *)
|
|
let sanity_check = false
|
|
exception InconsistentDegree
|
|
let check_consistent_degree g =
|
|
(** The number of neighbours of [x] is the degree of [x]. *)
|
|
let valid_degree c x ngb =
|
|
let xdegree = NodeIdSet.cardinal ngb in
|
|
NodeIdSet.mem x (IntMap.find xdegree (EdgeLabelMap.find c g.degrees))
|
|
in
|
|
EdgeLabelMap.iter (fun c ngbs ->
|
|
NodeIdMap.iter (fun x ngb ->
|
|
if not (valid_degree c x ngb) then (
|
|
raise InconsistentDegree
|
|
)
|
|
) ngbs
|
|
) g.neighbours
|
|
|
|
let update_neighbour update_set g id1 e id2 =
|
|
(** We focus on [e]. *)
|
|
let nbg = EdgeLabelMap.find e g.neighbours
|
|
and deg = EdgeLabelMap.find e g.degrees in
|
|
|
|
(** What is the degree of id1? *)
|
|
let id1_nbg = try NodeIdMap.find id1 nbg with _ -> assert false in
|
|
let id1_deg = NodeIdSet.cardinal id1_nbg in
|
|
|
|
(** Update the neighbours of id1 with update_set id2. *)
|
|
let nbg = nodeid_map_update id1 nbg NodeIdSet.empty (update_set id2) in
|
|
|
|
(** Update the degree of id1. *)
|
|
let deg = int_map_update id1_deg deg NodeIdSet.empty (NodeIdSet.remove id1) in
|
|
let deg =
|
|
if IntMap.find id1_deg deg = NodeIdSet.empty then
|
|
IntMap.remove id1_deg deg
|
|
else
|
|
deg
|
|
in
|
|
let id1_nbg = try NodeIdMap.find id1 nbg with _ -> assert false in
|
|
let id1_deg = NodeIdSet.cardinal id1_nbg in
|
|
let deg = int_map_update id1_deg deg NodeIdSet.empty (NodeIdSet.add id1) in
|
|
|
|
(** Finally, update the graph. *)
|
|
let neighbours = EdgeLabelMap.add e nbg g.neighbours
|
|
and degrees = EdgeLabelMap.add e deg g.degrees in
|
|
let g = { g with neighbours; degrees } in
|
|
|
|
(** If you suspect a bug in the implementation of the graph data
|
|
structure, which is always possible. Activating sanity check
|
|
might help you to track it down. *)
|
|
if sanity_check then check_consistent_degree g;
|
|
g
|
|
|
|
let add_neighbour = update_neighbour NodeIdSet.add
|
|
let del_neighbour = update_neighbour NodeIdSet.remove
|
|
|
|
(** [add_node g [n1;...;nN]] returns a new graph that extends [g] with
|
|
a new node labelled by [n1;...;nN]. None of the [nI] can be used
|
|
by another node in [g]. Otherwise, [InvalidNode] is raised.
|
|
|
|
In the sequel, the new node can be identified by any [nI].
|
|
*)
|
|
let add_node g ns =
|
|
(** First, a fresh identifier for the node. *)
|
|
let nodeid = NodeId g.next_node_id in
|
|
let next_node_id = g.next_node_id + 1 in
|
|
|
|
(** Second, we check that [ns] are not used by any other node. *)
|
|
if List.exists (defined_node g) ns then
|
|
raise InvalidNode;
|
|
|
|
(** Third, update maps. *)
|
|
let node_of_label =
|
|
List.fold_left (fun m n -> NodeLabelMap.add n nodeid m) g.node_of_label ns
|
|
in
|
|
let labels = NodeIdMap.add nodeid ns g.labels in
|
|
let neighbours =
|
|
EdgeLabelMap.map (fun nbg ->
|
|
NodeIdMap.add nodeid NodeIdSet.empty nbg
|
|
) g.neighbours
|
|
in
|
|
(** Initially, the node has a degree 0 since it has no neighbour. *)
|
|
let degrees =
|
|
EdgeLabelMap.map
|
|
(fun deg -> int_map_update 0 deg NodeIdSet.empty (NodeIdSet.add nodeid))
|
|
g.degrees
|
|
in
|
|
{ next_node_id; node_of_label; labels; degrees; neighbours }
|
|
|
|
(** [add_edge g n1 e n2] returns a new graph that extends [g] with a
|
|
new edge between [n1] and [n2]. The edge is labelled by [e]. If [n1]
|
|
or [n2] does not exist, then [InvalidNode] is raised. *)
|
|
let add_edge g n1 e n2 =
|
|
if not (defined_node g n1 && defined_node g n2) then
|
|
raise InvalidNode;
|
|
let id1 = id_of_node g n1 and id2 = id_of_node g n2 in
|
|
let g = add_neighbour g id1 e id2 in
|
|
let g = add_neighbour g id2 e id1 in
|
|
g
|
|
|
|
(** [neighbours g e n] returns the neighbours of [n] in [g]. *)
|
|
let neighbours g e n =
|
|
let id = id_of_node g n in
|
|
let ids =
|
|
NodeIdSet.elements (NodeIdMap.find id (EdgeLabelMap.find e g.neighbours))
|
|
in
|
|
List.map (fun id -> NodeIdMap.find id g.labels) ids
|
|
|
|
(** [del_node g n] returns a new graph that contains [g] minus the
|
|
node [n] and its edges. *)
|
|
let del_node g n =
|
|
let id = id_of_node g n in
|
|
let g =
|
|
EdgeLabelMap.fold (fun e nbg g ->
|
|
let nnbg = NodeIdMap.find id nbg in
|
|
NodeIdSet.fold (fun id' g ->
|
|
let g = del_neighbour g id' e id in
|
|
let g = del_neighbour g id e id' in
|
|
g
|
|
) nnbg g
|
|
) g.neighbours g
|
|
in
|
|
let neighbours =
|
|
EdgeLabelMap.map (fun nbg ->
|
|
NodeIdMap.remove id nbg
|
|
) g.neighbours
|
|
in
|
|
let degrees =
|
|
EdgeLabelMap.map (fun deg ->
|
|
let deg0 = IntMap.find 0 deg in
|
|
let deg0 = NodeIdSet.remove id deg0 in
|
|
if deg0 = NodeIdSet.empty then
|
|
IntMap.remove 0 deg
|
|
else
|
|
IntMap.add 0 deg0 deg
|
|
) g.degrees
|
|
in
|
|
let node_of_label = List.fold_left (fun node_of_label l ->
|
|
NodeLabelMap.remove l node_of_label
|
|
) g.node_of_label (NodeIdMap.find id g.labels)
|
|
in
|
|
let labels = NodeIdMap.remove id g.labels in
|
|
{ g with node_of_label; neighbours; labels; degrees }
|
|
|
|
(** [del_edge g n1 e n2] *)
|
|
let del_edge g n1 e n2 =
|
|
let i1 = id_of_node g n1 and i2 = id_of_node g n2 in
|
|
let g = del_neighbour g i1 e i2 in
|
|
del_neighbour g i2 e i1
|
|
|
|
(** [edges g e] returns all the edges of kind [e] in [g]. *)
|
|
let edges g e =
|
|
let nbg = EdgeLabelMap.find e g.neighbours in
|
|
let edges =
|
|
NodeIdMap.fold (fun id ids edges ->
|
|
NodeIdSet.fold (fun id' edges ->
|
|
(NodeIdMap.find id g.labels, NodeIdMap.find id' g.labels) :: edges
|
|
) ids edges) nbg []
|
|
in
|
|
let edges = List.map (fun (n1, n2) ->
|
|
if n1 < n2 then (n1, n2) else (n2, n1)
|
|
) edges
|
|
in
|
|
let edges = List.sort compare edges in
|
|
ExtStd.List.uniq edges
|
|
|
|
let min_degree exclusion_criteria g c nc =
|
|
let cdegrees = EdgeLabelMap.find c g.degrees in
|
|
let forbidden = EdgeLabelMap.find nc g.neighbours in
|
|
let rec aux degrees =
|
|
try
|
|
let k, ids = IntMap.min_binding degrees in
|
|
let rec aux' ids =
|
|
try
|
|
let id = NodeIdSet.choose ids in
|
|
let excluded = List.exists exclusion_criteria (nodes_of_id g id) in
|
|
if not excluded
|
|
&& NodeIdMap.find id forbidden = NodeIdSet.empty then
|
|
Some (k, List.hd (NodeIdMap.find id g.labels))
|
|
else
|
|
aux' (NodeIdSet.remove id ids)
|
|
with Not_found ->
|
|
aux (IntMap.remove k degrees)
|
|
in
|
|
aux' ids
|
|
with Not_found -> None
|
|
in
|
|
aux cdegrees
|
|
|
|
(** [are_connected g n1 e n2] *)
|
|
let are_connected g n1 e n2 =
|
|
let id1 = id_of_node g n1 in
|
|
let id2 = id_of_node g n2 in
|
|
NodeIdSet.mem id2 (NodeIdMap.find id1 (EdgeLabelMap.find e g.neighbours))
|
|
|
|
(** [pick_edge g e] *)
|
|
let pick_edge g e =
|
|
try
|
|
let degrees = EdgeLabelMap.find e g.degrees in
|
|
let k, ids = IntMap.max_binding degrees in
|
|
if k = 0 then None
|
|
else
|
|
let id = NodeIdSet.choose ids in
|
|
let nbg = EdgeLabelMap.find e g.neighbours in
|
|
let nbgid = NodeIdMap.find id nbg in
|
|
let id2 = NodeIdSet.choose nbgid in
|
|
Some (List.hd (nodes_of_id g id), List.hd (nodes_of_id g id2))
|
|
with Not_found ->
|
|
None
|
|
|
|
(** [merge g n1 n2] *)
|
|
let merge g n1 n2 =
|
|
let i1 = id_of_node g n1 and i2 = id_of_node g n2 in
|
|
let nodes1 = nodes_of_id g i1 and nodes2 = nodes_of_id g i2 in
|
|
let nbgs =
|
|
List.map
|
|
(fun e ->
|
|
(e, List.filter (fun n -> not (List.mem n1 n) && not (List.mem n2 n))
|
|
(neighbours g e n1 @ neighbours g e n2)))
|
|
EdgeLabel.all
|
|
in
|
|
let g = del_node g n1 in
|
|
let g = del_node g n2 in
|
|
let g = add_node g (nodes1 @ nodes2) in
|
|
List.fold_left (fun g (e, nbgs) ->
|
|
List.fold_left (fun g ns ->
|
|
add_edge g n1 e (List.hd ns)
|
|
) g nbgs
|
|
) g nbgs
|
|
|
|
(** [all_labels g n] *)
|
|
let all_labels g n =
|
|
let i = id_of_node g n in
|
|
nodes_of_id g i
|
|
|
|
(** [show g labels] represents the graph [g] in the DOT format and
|
|
uses [dotty] to display it. *)
|
|
let show g labels =
|
|
let dot_node (NodeId n, ns) =
|
|
let ns =
|
|
String.concat "," (List.map (fun n ->
|
|
NodeLabel.to_string n ^ (match labels n with None -> "" | Some s -> " => " ^ s)
|
|
) ns)
|
|
in
|
|
Printf.sprintf "n%d [label=\"%s\"];" n ns
|
|
in
|
|
let dot_nodes =
|
|
String.concat "\n" (List.map dot_node (NodeIdMap.bindings g.labels))
|
|
in
|
|
let seen = Hashtbl.create 13 in
|
|
let dot_edge (NodeId n) c (NodeId n') =
|
|
let n, n' = min n n', max n n' in
|
|
if not (Hashtbl.mem seen (n, n')) then (
|
|
Hashtbl.add seen (n, n') ();
|
|
Printf.sprintf "n%d -- n%d [label=\"%s\"];" n n' (EdgeLabel.to_string c)
|
|
) else ""
|
|
in
|
|
let neighbour c (id, ids) =
|
|
String.concat "\n" (List.map (dot_edge id c) (NodeIdSet.elements ids))
|
|
in
|
|
let dot_edges_of_kind (c, ngb) =
|
|
String.concat "\n" (List.map (neighbour c) (NodeIdMap.bindings ngb))
|
|
in
|
|
let dot_edges =
|
|
String.concat "\n" (List.map dot_edges_of_kind (EdgeLabelMap.bindings g.neighbours))
|
|
in
|
|
let dot =
|
|
Printf.sprintf "graph g {\n%s\n%s\n}" dot_nodes dot_edges
|
|
in
|
|
let fname, cout = Filename.open_temp_file "flap" ".dot" in
|
|
output_string cout dot;
|
|
close_out cout;
|
|
Printf.printf "Graph written in %s. (You need to install dotty to display it.)\n%!" fname;
|
|
ignore (Sys.command ("dotty " ^ fname ^ "&"))
|
|
|
|
end
|