///|
/// Counts describe stored structure, not inferred expansion of hyperedges or locators.
pub(all) struct Statistics {
keys : Int32
graphs : Int32
nodes : Int32
edges : Int32
hyperedges : Int32
endpoints : Int32
ports : Int32
data_entries : Int32
max_graph_depth : Int32
has_mixed_direction : Bool
has_parallel_edges : Bool
has_self_loops : Bool
has_locators : Bool
has_xml_payloads : Bool
} derive(Eq, @debug.Debug)
///|
pub extend Statistics with Eq::{equal, not_equal}
///|
pub extend Statistics with @debug.Debug::{to_repr}
///|
priv struct StatsBuilder {
mut value : Statistics
}
///|
fn count_data(builder : StatsBuilder, data : Array[Datum]) -> Unit {
builder.value = {
..builder.value,
data_entries: builder.value.data_entries + data.length(),
has_xml_payloads: builder.value.has_xml_payloads ||
data.any(fn(datum) { datum.value is XmlPayload(_) }),
}
}
///|
fn count_port(builder : StatsBuilder, port : Port) -> Unit {
builder.value = { ..builder.value, ports: builder.value.ports + 1, }
count_data(builder, port.data)
for nested in port.ports {
count_port(builder, nested)
}
}
///|
fn count_graph(builder : StatsBuilder, graph : Graph, depth : Int32) -> Unit {
builder.value = {
..builder.value,
graphs: builder.value.graphs + 1,
nodes: builder.value.nodes + graph.nodes.length(),
edges: builder.value.edges + graph.edges.length(),
hyperedges: builder.value.hyperedges + graph.hyperedges.length(),
max_graph_depth: if depth > builder.value.max_graph_depth {
depth
} else {
builder.value.max_graph_depth
},
has_locators: builder.value.has_locators || graph.locator is Some(_),
}
count_data(builder, graph.data)
for node in graph.nodes {
count_data(builder, node.data)
builder.value = {
..builder.value,
has_locators: builder.value.has_locators || node.locator is Some(_),
}
for port in node.ports {
count_port(builder, port)
}
if node.graph is Some(nested) {
count_graph(builder, nested, depth + 1)
}
}
let mut has_directed = false
let mut has_undirected = false
let pairs : Map[(String, String, Bool), Bool] = Map([])
for edge in graph.edges {
let directed = edge.effective_direction(graph) == Directed
has_directed = has_directed || directed
has_undirected = has_undirected || !directed
let pair = if !directed && edge.target < edge.source {
(edge.target, edge.source, directed)
} else {
(edge.source, edge.target, directed)
}
builder.value = {
..builder.value,
has_parallel_edges: builder.value.has_parallel_edges ||
pairs.contains(pair),
has_self_loops: builder.value.has_self_loops || edge.source == edge.target,
}
pairs[pair] = true
count_data(builder, edge.data)
if edge.graph is Some(nested) {
count_graph(builder, nested, depth + 1)
}
}
builder.value = {
..builder.value,
has_mixed_direction: builder.value.has_mixed_direction ||
(has_directed && has_undirected),
}
for hyperedge in graph.hyperedges {
count_data(builder, hyperedge.data)
builder.value = {
..builder.value,
endpoints: builder.value.endpoints + hyperedge.endpoints.length(),
}
if hyperedge.graph is Some(nested) {
count_graph(builder, nested, depth + 1)
}
}
}
///|
/// Linear scan of structure plus one edge-pair map per graph, without layout or graph algorithms.
pub fn Document::statistics(self : Document) -> Statistics {
let builder : StatsBuilder = {
value: {
keys: self.keys.length(),
graphs: 0,
nodes: 0,
edges: 0,
hyperedges: 0,
endpoints: 0,
ports: 0,
data_entries: 0,
max_graph_depth: 0,
has_mixed_direction: false,
has_parallel_edges: false,
has_self_loops: false,
has_locators: false,
has_xml_payloads: self.keys.any(fn(key) {
key.default_value is Some(XmlPayload(_))
}),
},
}
count_data(builder, self.data)
for graph in self.graphs {
count_graph(builder, graph, 1)
}
builder.value
}