Compare commits
21
Commits
0.3.0
..
generators
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
783a82fc18 | ||
|
|
be7acfce1f | ||
|
|
f5bb3ad07a | ||
|
|
baa6775156 | ||
|
|
32fae0b24c | ||
|
|
026005fe46 | ||
|
|
421207a1cd | ||
|
|
9b11090e82 | ||
|
|
d1463544f7 | ||
|
|
ba3717f246 | ||
|
|
f34a7e6541 | ||
|
|
5fc586f58e | ||
|
|
0cd965db4f | ||
|
|
6f557a92aa | ||
|
|
536a39c82b | ||
|
|
6403862977 | ||
|
|
3ae0aa9b67 | ||
|
|
5b6ad13f0e | ||
|
|
f41b77903f | ||
|
|
3fb3e67e8e | ||
|
|
8c57f5d2c4 |
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "grapherity"
|
name = "grapherity"
|
||||||
version = "0.3.0"
|
version = "0.3.1"
|
||||||
authors = ["Stefan Müller"]
|
authors = ["Stefan Müller"]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.85.0"
|
rust-version = "1.85.0"
|
||||||
|
|||||||
+548
-37
@@ -1,6 +1,119 @@
|
|||||||
//! Generators for common graphs.
|
//! Generators for common graphs.
|
||||||
|
|
||||||
use crate::traits::{GraphTopology, GraphTopologyAddition};
|
use crate::models::AppendGraph;
|
||||||
|
use crate::traits::{GraphTopology, GraphTopologyAddition, GraphTopologyConversion};
|
||||||
|
|
||||||
|
/// Builds a graph of type `G` by running a generator on a temporary graph.
|
||||||
|
///
|
||||||
|
/// `generate_as` fills a temporary [`AppendGraph`] and converts it to `G`. It returns the handles
|
||||||
|
/// of the elements it created as handles of the resulting graph. This makes it possible to obtain
|
||||||
|
/// graph types directly that do not implement [`GraphTopologyAddition`], such as [`FrozenGraph`].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use grapherity::prelude::*;
|
||||||
|
/// use grapherity::generators;
|
||||||
|
/// use grapherity::models::FrozenGraph;
|
||||||
|
///
|
||||||
|
/// // Generates a parameterless Petersen graph as a FrozenGraph model.
|
||||||
|
/// let (graph, vertices, edges) = generators::generate_as::<FrozenGraph>(generators::petersen);
|
||||||
|
/// assert_eq!(graph.vertex_count(), 10);
|
||||||
|
/// assert!(graph.vertices().all(|v| graph.degree(v) == 3));
|
||||||
|
///
|
||||||
|
/// // Generates a parameterized complete graph as a FrozenGraph model.
|
||||||
|
/// let (graph, vertices, edges) = generators::generate_as::<FrozenGraph>(|g| generators::complete(g, 5));
|
||||||
|
/// assert_eq!(graph.vertex_count(), 5);
|
||||||
|
/// assert!(graph.vertices().all(|v| graph.degree(v) == 4));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`FrozenGraph`]: crate::models::FrozenGraph
|
||||||
|
pub fn generate_as<G>(
|
||||||
|
generator: impl FnOnce(
|
||||||
|
&mut AppendGraph,
|
||||||
|
) -> (
|
||||||
|
Vec<<AppendGraph as GraphTopology>::Vertex>,
|
||||||
|
Vec<<AppendGraph as GraphTopology>::Edge>,
|
||||||
|
),
|
||||||
|
) -> (G, Vec<G::Vertex>, Vec<G::Edge>)
|
||||||
|
where
|
||||||
|
G: GraphTopologyConversion,
|
||||||
|
{
|
||||||
|
let mut builder = AppendGraph::new();
|
||||||
|
let (vertices, edges) = generator(&mut builder);
|
||||||
|
let (graph, iso) = G::convert(&builder);
|
||||||
|
(
|
||||||
|
graph,
|
||||||
|
vertices.into_iter().map(|v| iso.vertex(v)).collect(),
|
||||||
|
edges.into_iter().map(|e| iso.edge(e)).collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates a path graph.
|
||||||
|
///
|
||||||
|
/// Generates a [path graph] *P(n)* with `n` vertices and `n - 1` edges in `graph` as a new
|
||||||
|
/// connected component and returns the new vertices *v0, v1, ..., v(n - 1)* and edges
|
||||||
|
/// *e0, e1, ..., e(n - 2)* in order, i.e. *e0 = (v0, v1)*, *e1 = (v1, v2)*, etc. and *v0* and
|
||||||
|
/// *v(n-1)* being the two terminal vertices
|
||||||
|
///
|
||||||
|
/// For `n == 0`, nothing is added. For `n == 1`, only a single vertex and no edges are added.
|
||||||
|
///
|
||||||
|
/// [Path graph]: https://en.wikipedia.org/wiki/Path_graph
|
||||||
|
pub fn path<G: GraphTopologyAddition>(graph: &mut G, n: usize) -> (Vec<G::Vertex>, Vec<G::Edge>) {
|
||||||
|
let edge_capacity = n.saturating_sub(1);
|
||||||
|
graph.reserve_vertices(n);
|
||||||
|
graph.reserve_edges(edge_capacity);
|
||||||
|
let vertices: Vec<_> = (0..n).map(|_| graph.add_vertex()).collect();
|
||||||
|
let edges = (0..edge_capacity)
|
||||||
|
.map(|i| graph.add_edge(vertices[i], vertices[i + 1]))
|
||||||
|
.collect();
|
||||||
|
(vertices, edges)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates a cycle graph.
|
||||||
|
///
|
||||||
|
/// Generates a [cycle graph] *C(n)* with `n` vertices and `n` edges in `graph` as a new connected
|
||||||
|
/// component and returns the new vertices *v0, v1, ..., v(n - 1)* and edges *e0, e1, ..., e(n - 1)*
|
||||||
|
/// in order, i.e. *e0 = (v0, v1), e1 = (v1, v2), ..., e(n - 1) = (v(n - 1), v0)*.
|
||||||
|
///
|
||||||
|
/// For `n == 0`, nothing is added. For `n == 1`, a single vertex with a loop is added. For
|
||||||
|
/// `n == 2`, two vertices and two edges between them (multi-edge) are added.
|
||||||
|
///
|
||||||
|
/// [Cycle graph]: https://en.wikipedia.org/wiki/Cycle_graph
|
||||||
|
pub fn cycle<G: GraphTopologyAddition>(graph: &mut G, n: usize) -> (Vec<G::Vertex>, Vec<G::Edge>) {
|
||||||
|
graph.reserve_vertices(n);
|
||||||
|
graph.reserve_edges(n);
|
||||||
|
let vertices: Vec<_> = (0..n).map(|_| graph.add_vertex()).collect();
|
||||||
|
let edges = (0..n)
|
||||||
|
.map(|i| graph.add_edge(vertices[i], vertices[if i < n - 1 { i + 1 } else { 0 }]))
|
||||||
|
.collect();
|
||||||
|
(vertices, edges)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates a complete graph.
|
||||||
|
///
|
||||||
|
/// Generates a [complete graph] *K(n)* with `n` vertices and `n * (n - 1) / 2` edges in `graph` as
|
||||||
|
/// a new connected component and returns the new vertices and edges.
|
||||||
|
///
|
||||||
|
/// For `n == 0`, nothing is added. For `n == 1`, only a single vertex is added.
|
||||||
|
///
|
||||||
|
/// [Complete graph]: https://en.wikipedia.org/wiki/Complete_graph
|
||||||
|
pub fn complete<G: GraphTopologyAddition>(
|
||||||
|
graph: &mut G,
|
||||||
|
n: usize,
|
||||||
|
) -> (Vec<G::Vertex>, Vec<G::Edge>) {
|
||||||
|
let edge_capacity = n * n.saturating_sub(1) / 2;
|
||||||
|
graph.reserve_vertices(n);
|
||||||
|
graph.reserve_edges(edge_capacity);
|
||||||
|
let vertices: Vec<_> = (0..n).map(|_| graph.add_vertex()).collect();
|
||||||
|
let mut edges = Vec::with_capacity(edge_capacity);
|
||||||
|
for (i, &u) in vertices.iter().enumerate() {
|
||||||
|
for &v in &vertices[i + 1..] {
|
||||||
|
edges.push(graph.add_edge(u, v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(vertices, edges)
|
||||||
|
}
|
||||||
|
|
||||||
/// Generates a Petersen graph.
|
/// Generates a Petersen graph.
|
||||||
///
|
///
|
||||||
@@ -8,22 +121,15 @@ use crate::traits::{GraphTopology, GraphTopologyAddition};
|
|||||||
/// vertices and edges. This graph is also the Kneser graph *KG(5, 2)*.
|
/// vertices and edges. This graph is also the Kneser graph *KG(5, 2)*.
|
||||||
///
|
///
|
||||||
/// [Petersen graph]: https://en.wikipedia.org/wiki/Petersen_graph
|
/// [Petersen graph]: https://en.wikipedia.org/wiki/Petersen_graph
|
||||||
pub fn petersen<G>(
|
pub fn petersen<G: GraphTopologyAddition>(graph: &mut G) -> (Vec<G::Vertex>, Vec<G::Edge>) {
|
||||||
graph: &mut G,
|
|
||||||
) -> (
|
|
||||||
[<G as GraphTopology>::Vertex; 10],
|
|
||||||
[<G as GraphTopology>::Edge; 15],
|
|
||||||
)
|
|
||||||
where
|
|
||||||
G: GraphTopologyAddition,
|
|
||||||
{
|
|
||||||
const N: usize = 5;
|
const N: usize = 5;
|
||||||
const K: usize = 2;
|
const K: usize = 2;
|
||||||
|
|
||||||
graph.reserve_vertices(10);
|
graph.reserve_vertices(10);
|
||||||
graph.reserve_edges(15);
|
graph.reserve_edges(15);
|
||||||
let vertices = core::array::from_fn(|_| graph.add_vertex());
|
let vertices: Vec<_> = (0..10).map(|_| graph.add_vertex()).collect();
|
||||||
let edges = core::array::from_fn(|j| {
|
let edges = (0..15)
|
||||||
|
.map(|j| {
|
||||||
let i = j / 3;
|
let i = j / 3;
|
||||||
match j % 3 {
|
match j % 3 {
|
||||||
0 => {
|
0 => {
|
||||||
@@ -36,44 +142,449 @@ where
|
|||||||
}
|
}
|
||||||
_ => graph.add_edge(vertices[i], vertices[i + N]),
|
_ => graph.add_edge(vertices[i], vertices[i + N]),
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.collect();
|
||||||
(vertices, edges)
|
(vertices, edges)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::models::AppendGraph;
|
use crate::testing::fixtures::MakeTestGraph;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
enum Spec {
|
||||||
|
Path(usize),
|
||||||
|
Cycle(usize),
|
||||||
|
Complete(usize),
|
||||||
|
Petersen,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Spec {
|
||||||
|
fn generate<G: GraphTopologyAddition>(
|
||||||
|
&self,
|
||||||
|
graph: &mut G,
|
||||||
|
) -> (Vec<G::Vertex>, Vec<G::Edge>) {
|
||||||
|
match *self {
|
||||||
|
Spec::Path(n) => path(graph, n),
|
||||||
|
Spec::Cycle(n) => cycle(graph, n),
|
||||||
|
Spec::Complete(n) => complete(graph, n),
|
||||||
|
Spec::Petersen => petersen(graph),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert<G: GraphTopology>(&self, built: &Built<G>)
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
match *self {
|
||||||
|
Spec::Path(n) => assert_path(built, n),
|
||||||
|
Spec::Cycle(n) => assert_cycle(built, n),
|
||||||
|
Spec::Complete(n) => assert_complete(built, n),
|
||||||
|
Spec::Petersen => assert_petersen(built),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Built<G: GraphTopology> {
|
||||||
|
graph: G,
|
||||||
|
vertices: Vec<G::Vertex>,
|
||||||
|
edges: Vec<G::Edge>,
|
||||||
|
initial_vertex_count: usize,
|
||||||
|
initial_edge_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates the test for the `append_graph` harness for the given `Spec` expression.
|
||||||
|
macro_rules! min_harness_tests {
|
||||||
|
($spec:expr) => {
|
||||||
|
#[test]
|
||||||
|
fn append_graph() {
|
||||||
|
run($spec, add_to_empty::<$crate::models::AppendGraph>);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates one test per harness for the given `Spec` expression.
|
||||||
|
macro_rules! all_harness_tests {
|
||||||
|
($spec:expr) => {
|
||||||
|
min_harness_tests!($spec);
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn petersen() {
|
fn append_graph_non_empty() {
|
||||||
let mut graph = AppendGraph::new();
|
run($spec, add_to_standard::<$crate::models::AppendGraph>);
|
||||||
let (vertices, edges) = super::petersen(&mut graph);
|
|
||||||
assert_eq!(
|
|
||||||
graph.vertex_count(),
|
|
||||||
10,
|
|
||||||
"unexpected number of added vertices"
|
|
||||||
);
|
|
||||||
assert_eq!(graph.edge_count(), 15, "unexpected number of added edges");
|
|
||||||
assert!(
|
|
||||||
graph.vertices().all(|v| graph.degree(v) == 3),
|
|
||||||
"all vertices should have degree 3"
|
|
||||||
);
|
|
||||||
assert_handles_in_graph(&graph, &vertices, &edges);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_handles_in_graph<G: GraphTopology>(
|
#[test]
|
||||||
graph: &G,
|
fn graph() {
|
||||||
vertices: &[G::Vertex],
|
run($spec, add_to_empty::<$crate::models::Graph>);
|
||||||
edges: &[G::Edge],
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn graph_non_empty() {
|
||||||
|
run($spec, add_to_standard::<$crate::models::Graph>);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn frozen_graph() {
|
||||||
|
run($spec, conv_generate_as::<$crate::models::FrozenGraph>);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates the module tree `<group>::<case>::<harness>` for each generator case.
|
||||||
|
///
|
||||||
|
/// The `all_harnesses` cases run on every harness, the `min_harnesses` cases only on a reduced
|
||||||
|
/// set of harnesses.
|
||||||
|
macro_rules! generator_tests {
|
||||||
|
($($group:ident {
|
||||||
|
all { $($all_case:ident => $all_generator:expr),+ $(,)? }
|
||||||
|
min { $($min_case:ident => $min_generator:expr),* $(,)? }
|
||||||
|
})+) => {
|
||||||
|
$(
|
||||||
|
mod $group {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
$(
|
||||||
|
mod $all_case {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
all_harness_tests!($all_generator);
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
|
||||||
|
$(
|
||||||
|
mod $min_case {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
min_harness_tests!($min_generator);
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
generator_tests! {
|
||||||
|
path {
|
||||||
|
all {
|
||||||
|
small => Spec::Path(4),
|
||||||
|
}
|
||||||
|
min {
|
||||||
|
empty => Spec::Path(0),
|
||||||
|
single_vertex => Spec::Path(1),
|
||||||
|
single_edge => Spec::Path(2),
|
||||||
|
large => Spec::Path(21),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cycle {
|
||||||
|
all {
|
||||||
|
small_even => Spec::Cycle(4),
|
||||||
|
}
|
||||||
|
min {
|
||||||
|
empty => Spec::Cycle(0),
|
||||||
|
loop_edge => Spec::Cycle(1),
|
||||||
|
multi_edge => Spec::Cycle(2),
|
||||||
|
large_odd => Spec::Cycle(21),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
complete {
|
||||||
|
all {
|
||||||
|
small_even => Spec::Complete(4),
|
||||||
|
}
|
||||||
|
min {
|
||||||
|
empty => Spec::Complete(0),
|
||||||
|
single_vertex => Spec::Complete(1),
|
||||||
|
single_edge => Spec::Complete(2),
|
||||||
|
large_odd => Spec::Complete(13),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod petersen {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
all_harness_tests!(Spec::Petersen);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds a graph with `harness` and asserts that it matches what `spec` should produce.
|
||||||
|
fn run<G: GraphTopology>(spec: Spec, harness: impl FnOnce(&Spec) -> Built<G>)
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
let built = harness(&spec);
|
||||||
|
spec.assert(&built);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs `spec` on an empty graph of type `G`.
|
||||||
|
fn add_to_empty<G: GraphTopologyAddition>(spec: &Spec) -> Built<G> {
|
||||||
|
let mut graph = G::default();
|
||||||
|
let (vertices, edges) = spec.generate(&mut graph);
|
||||||
|
Built {
|
||||||
|
graph,
|
||||||
|
vertices,
|
||||||
|
edges,
|
||||||
|
initial_vertex_count: 0,
|
||||||
|
initial_edge_count: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs `spec` on a non-empty test graph of type `G`.
|
||||||
|
fn add_to_standard<G: GraphTopologyAddition>(spec: &Spec) -> Built<G> {
|
||||||
|
let (mut graph, initial_vertices, initial_edges, _) = G::standard();
|
||||||
|
let (vertices, edges) = spec.generate(&mut graph);
|
||||||
|
Built {
|
||||||
|
graph,
|
||||||
|
vertices,
|
||||||
|
edges,
|
||||||
|
initial_vertex_count: initial_vertices.len(),
|
||||||
|
initial_edge_count: initial_edges.len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs `spec` within `generate_as` for type `G`.
|
||||||
|
fn conv_generate_as<G: GraphTopologyConversion>(spec: &Spec) -> Built<G> {
|
||||||
|
let (graph, vertices, edges) = generate_as::<G>(|graph| spec.generate(graph));
|
||||||
|
Built {
|
||||||
|
graph,
|
||||||
|
vertices,
|
||||||
|
edges,
|
||||||
|
initial_vertex_count: 0,
|
||||||
|
initial_edge_count: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_path<G: GraphTopology>(built: &Built<G>, n: usize)
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
assert_element_counts(built, n, n.saturating_sub(1));
|
||||||
|
assert_handles(built.graph.vertices(), &built.vertices, "vertex");
|
||||||
|
assert_handles(built.graph.edges(), &built.edges, "edge");
|
||||||
|
assert_no_loops(built);
|
||||||
|
assert_no_multi_edges(built);
|
||||||
|
match n {
|
||||||
|
0 => {}
|
||||||
|
1 => assert_eq!(
|
||||||
|
built.graph.degree(built.vertices[0]),
|
||||||
|
0,
|
||||||
|
"path n == 1 should be an isolated vertex"
|
||||||
|
),
|
||||||
|
_ => {
|
||||||
|
for (i, &v) in built.vertices.iter().enumerate() {
|
||||||
|
assert_eq!(
|
||||||
|
built.graph.degree(v),
|
||||||
|
if i == 0 || i == n - 1 { 1 } else { 2 },
|
||||||
|
"unexpected degree of vertex {v:?} in path n == {n}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_path_order(built);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_cycle<G: GraphTopology>(built: &Built<G>, n: usize)
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
assert_element_counts(built, n, n);
|
||||||
|
assert_handles(built.graph.vertices(), &built.vertices, "vertex");
|
||||||
|
assert_handles(built.graph.edges(), &built.edges, "edge");
|
||||||
|
if n != 1 {
|
||||||
|
assert_no_loops(built);
|
||||||
|
}
|
||||||
|
if n != 2 {
|
||||||
|
assert_no_multi_edges(built);
|
||||||
|
}
|
||||||
|
assert!(
|
||||||
|
built.vertices.iter().all(|&v| built.graph.degree(v) == 2),
|
||||||
|
"all new vertices should have degree 2"
|
||||||
|
);
|
||||||
|
assert_path_order(built);
|
||||||
|
// Asserts the closing edge e(n - 1) == (v(n - 1), v0).
|
||||||
|
if n > 0 {
|
||||||
|
// For n == 1, u == v and this validates the loop. For n == 2, the above asserted one of
|
||||||
|
// the two multi-edges, and this asserts the other.
|
||||||
|
let e = built.edges[n - 1];
|
||||||
|
let u = built.vertices[0];
|
||||||
|
let v = built.vertices[n - 1];
|
||||||
|
let incident_vertices = built.graph.incident_vertices(e);
|
||||||
|
assert!(
|
||||||
|
incident_vertices == (u, v) || incident_vertices == (v, u),
|
||||||
|
"edge {e:?} should have incident vertices {u:?} and {v:?}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_complete<G: GraphTopology>(built: &Built<G>, n: usize)
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
assert_element_counts(built, n, n * n.saturating_sub(1) / 2);
|
||||||
|
assert_handles(built.graph.vertices(), &built.vertices, "vertex");
|
||||||
|
assert_handles(built.graph.edges(), &built.edges, "edge");
|
||||||
|
// The predicate in `all` is only executed if `vertices` has any elements, i.e. for n > 0.
|
||||||
|
assert!(
|
||||||
|
built
|
||||||
|
.vertices
|
||||||
|
.iter()
|
||||||
|
.all(|&v| built.graph.degree(v) == n - 1),
|
||||||
|
"all new vertices should have degree {}",
|
||||||
|
n - 1
|
||||||
|
);
|
||||||
|
// Asserts that all pairs of vertices have exactly one edge between them. This also includes
|
||||||
|
// checks for loops and multi-edges. If there are no duplicates, then this implies that all
|
||||||
|
// expected edges were also matched due to the validated counts from above.
|
||||||
|
let mut seen = vec![false; n * n];
|
||||||
|
let position = |v, e| {
|
||||||
|
built
|
||||||
|
.vertices
|
||||||
|
.iter()
|
||||||
|
.position(|&x| x == v)
|
||||||
|
.unwrap_or_else(|| panic!("edge {e:?} has an invalid incident vertex {v:?}"))
|
||||||
|
};
|
||||||
|
for &e in &built.edges {
|
||||||
|
let (u, v) = built.graph.incident_vertices(e);
|
||||||
|
assert_ne!(u, v, "unexpected loop edge {e:?} at vertex {u:?}");
|
||||||
|
let (i, j) = (position(u, e), position(v, e));
|
||||||
|
let (i, j) = (i.min(j), i.max(j));
|
||||||
|
assert!(
|
||||||
|
!seen[i * n + j],
|
||||||
|
"unexpected multi-edge {e:?} between vertices {u:?} and {v:?}"
|
||||||
|
);
|
||||||
|
seen[i * n + j] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_petersen<G: GraphTopology>(built: &Built<G>)
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
assert_element_counts(built, 10, 15);
|
||||||
|
assert_handles(built.graph.vertices(), &built.vertices, "vertex");
|
||||||
|
assert_handles(built.graph.edges(), &built.edges, "edge");
|
||||||
|
assert_no_loops(built);
|
||||||
|
assert_no_multi_edges(built);
|
||||||
|
assert!(
|
||||||
|
built.vertices.iter().all(|&v| built.graph.degree(v) == 3),
|
||||||
|
"all new vertices should have degree 3"
|
||||||
|
);
|
||||||
|
// Checks that each pair of vertices (u, v) has no common neighbor if they are adjacent, and
|
||||||
|
// exactly one common neighbor otherwise.
|
||||||
|
for (i, &v) in built.vertices.iter().enumerate() {
|
||||||
|
for &u in &built.vertices[i + 1..] {
|
||||||
|
if built.graph.are_adjacent(v, u) {
|
||||||
|
assert!(
|
||||||
|
built.graph.common_neighbors(u, v).next().is_none(),
|
||||||
|
"adjacent vertices {u:?} and {v:?} should not have a common neighbor"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
assert_eq!(
|
||||||
|
built.graph.common_neighbors(u, v).count(),
|
||||||
|
1,
|
||||||
|
"non-adjacent vertices {u:?} and {v:?} should have exactly one common neighbor"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_element_counts<G: GraphTopology>(
|
||||||
|
built: &Built<G>,
|
||||||
|
expected_vertex_count: usize,
|
||||||
|
expected_edge_count: usize,
|
||||||
) {
|
) {
|
||||||
assert!(
|
assert_eq!(
|
||||||
vertices.iter().all(|&v| graph.vertices().any(|u| u == v)),
|
built.graph.vertex_count(),
|
||||||
"all returned vertices should be in the graph"
|
built.initial_vertex_count + expected_vertex_count,
|
||||||
|
"unexpected number of added vertices"
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
built.vertices.len(),
|
||||||
|
expected_vertex_count,
|
||||||
|
"unexpected number of returned vertices"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
built.graph.edge_count(),
|
||||||
|
built.initial_edge_count + expected_edge_count,
|
||||||
|
"unexpected number of added edges"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
built.edges.len(),
|
||||||
|
expected_edge_count,
|
||||||
|
"unexpected number of returned edges"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts that every handle in `returned` is unique and in `all`.
|
||||||
|
fn assert_handles<T: PartialEq + Debug>(
|
||||||
|
all: impl Iterator<Item = T>,
|
||||||
|
returned: &[T],
|
||||||
|
kind: &str,
|
||||||
|
) {
|
||||||
|
let mut remaining: Vec<T> = all.collect();
|
||||||
|
for e in returned {
|
||||||
|
let Some(position) = remaining.iter().position(|x| x == e) else {
|
||||||
|
panic!("returned {kind} {e:?} is not in the graph or appears more than once");
|
||||||
|
};
|
||||||
|
remaining.swap_remove(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_no_loops<G: GraphTopology>(built: &Built<G>)
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
for &e in &built.edges {
|
||||||
|
let (u, v) = built.graph.incident_vertices(e);
|
||||||
|
assert_ne!(u, v, "unexpected loop edge {e:?} at vertex {u:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_no_multi_edges<G: GraphTopology>(built: &Built<G>)
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
for &v in &built.vertices {
|
||||||
|
let mut neighbors = Vec::new();
|
||||||
|
let mut loop_count = 0;
|
||||||
|
for u in built.graph.adjacent_vertices(v) {
|
||||||
|
if u == v {
|
||||||
|
loop_count += 1;
|
||||||
|
assert!(loop_count < 3, "unexpected multi-edge loop at vertex {v:?}");
|
||||||
|
} else {
|
||||||
assert!(
|
assert!(
|
||||||
edges.iter().all(|&e| graph.edges().any(|f| f == e)),
|
!neighbors.contains(&u),
|
||||||
"all returned edges should be in the graph"
|
"unexpected multi-edge between vertices {v:?} and {u:?}"
|
||||||
|
);
|
||||||
|
neighbors.push(u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts that each returned edge *ei* connects the returned vertices *vi* and *v(i + 1)*.
|
||||||
|
fn assert_path_order<G: GraphTopology>(built: &Built<G>)
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
for (w, &e) in built.vertices.windows(2).zip(&built.edges) {
|
||||||
|
let incident_vertices = built.graph.incident_vertices(e);
|
||||||
|
assert!(
|
||||||
|
incident_vertices == (w[0], w[1]) || incident_vertices == (w[1], w[0]),
|
||||||
|
"edge {e:?} should have incident vertices {:?} and {:?}",
|
||||||
|
w[0],
|
||||||
|
w[1]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
+2
-1
@@ -141,9 +141,10 @@ impl<E: Copy, T: Clone> IndexMut<E> for ElementMap<E, T> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn initial_values_are_default() {
|
fn initial_values_are_default() {
|
||||||
let map = ElementMap::new(42, |e| e, 6);
|
let map = ElementMap::new(42, |e| e, 6);
|
||||||
|
|||||||
+19
-7
@@ -5,6 +5,7 @@ use crate::models::{AppendGraph, FrozenGraph};
|
|||||||
use crate::traits::{GraphTopology, GraphTopologyAddition, GraphTopologyConversion, Incidence};
|
use crate::traits::{GraphTopology, GraphTopologyAddition, GraphTopologyConversion, Incidence};
|
||||||
|
|
||||||
pub trait MakeTestGraph: GraphTopology + Sized {
|
pub trait MakeTestGraph: GraphTopology + Sized {
|
||||||
|
/// Returns a versatile, pre-defined standard test graph.
|
||||||
// Since this is only a test fixture, the complex return type here is deemed acceptable.
|
// Since this is only a test fixture, the complex return type here is deemed acceptable.
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
fn standard() -> (
|
fn standard() -> (
|
||||||
@@ -14,20 +15,32 @@ pub trait MakeTestGraph: GraphTopology + Sized {
|
|||||||
[Vec<Incidence<Self::Vertex, Self::Edge>>; 10],
|
[Vec<Incidence<Self::Vertex, Self::Edge>>; 10],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Returns an empty graph.
|
||||||
fn empty() -> Self;
|
fn empty() -> Self;
|
||||||
|
|
||||||
|
/// Returns a graph with a single vertex and no edges.
|
||||||
fn single_vertex() -> (Self, Self::Vertex);
|
fn single_vertex() -> (Self, Self::Vertex);
|
||||||
|
|
||||||
|
/// Returns a graph with two vertices connected by a single edge.
|
||||||
fn single_edge() -> (Self, [Self::Vertex; 2], Self::Edge);
|
fn single_edge() -> (Self, [Self::Vertex; 2], Self::Edge);
|
||||||
|
|
||||||
|
/// Returns a graph with vertices *v0*, *v1*, and *v2*, where *v0* is isolated, and the other
|
||||||
|
/// two are connected by a single edge.
|
||||||
fn disconnected() -> (Self, [Self::Vertex; 3]);
|
fn disconnected() -> (Self, [Self::Vertex; 3]);
|
||||||
|
|
||||||
|
/// Returns a graph with a single vertex that has a single loop edge.
|
||||||
fn loop_edge() -> (Self, Self::Vertex, Self::Edge);
|
fn loop_edge() -> (Self, Self::Vertex, Self::Edge);
|
||||||
|
|
||||||
|
/// Returns a graph with two vertices that are connected by `K` multi-edges.
|
||||||
fn multiple_edges<const K: usize>() -> (Self, [Self::Vertex; 2], [Self::Edge; K]);
|
fn multiple_edges<const K: usize>() -> (Self, [Self::Vertex; 2], [Self::Edge; K]);
|
||||||
|
|
||||||
|
/// Returns a graph that is a path with three vertices and two edges between them, returned in
|
||||||
|
/// order.
|
||||||
fn two_edge_path() -> (Self, [Self::Vertex; 3], [Self::Edge; 2]);
|
fn two_edge_path() -> (Self, [Self::Vertex; 3], [Self::Edge; 2]);
|
||||||
|
|
||||||
|
/// Returns a graph that is a path with three vertices *v0*, *v1*, and *v2*, and two edges
|
||||||
|
/// *e0 = (v0, v1)* and *e1 = (v1, v2)* between them, and two loops on the first two vertices
|
||||||
|
/// *e2 = (v0, v0)* and *e3 = (v1, v1)*.
|
||||||
fn two_edge_path_with_loops() -> (Self, [Self::Vertex; 3], [Self::Edge; 4]);
|
fn two_edge_path_with_loops() -> (Self, [Self::Vertex; 3], [Self::Edge; 4]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,13 +281,12 @@ where
|
|||||||
F: Fn(V) -> Option<u32>,
|
F: Fn(V) -> Option<u32>,
|
||||||
{
|
{
|
||||||
let expected = [0, 1, 2, 2, 2, 3, 3, 3, 3, 4];
|
let expected = [0, 1, 2, 2, 2, 3, 3, 3, 3, 4];
|
||||||
for (i, v) in vertices.iter().enumerate() {
|
for (i, &v) in vertices.iter().enumerate() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actual_for(*v),
|
actual_for(v),
|
||||||
Some(expected[i]),
|
Some(expected[i]),
|
||||||
"unexpected distance from vertex {:?} to vertex {:?}",
|
"unexpected distance from vertex {:?} to vertex {v:?}",
|
||||||
vertices[0],
|
vertices[0]
|
||||||
vertices[i]
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -308,8 +320,8 @@ pub fn assert_standard_unweighted_predecessors_v0<V: Debug + Copy + PartialEq>(
|
|||||||
/// Asserts that the path given as `path_option` is a valid path in `graph`
|
/// Asserts that the path given as `path_option` is a valid path in `graph`
|
||||||
///
|
///
|
||||||
/// Walks the path and asserts that it is `Some`, that it is empty if and only if
|
/// Walks the path and asserts that it is `Some`, that it is empty if and only if
|
||||||
/// `source == target`, that its edges are pairwise incident in the order given, and that it starts
|
/// `source == target` for any `target` in `targets`, that its edges are pairwise incident in the
|
||||||
/// at `source` and ends at `target`.
|
/// order given, and that it starts at `source` and ends at any `target` in `targets`.
|
||||||
pub fn assert_valid_path<G: GraphTopology>(
|
pub fn assert_valid_path<G: GraphTopology>(
|
||||||
graph: &G,
|
graph: &G,
|
||||||
path_option: Option<Vec<G::Edge>>,
|
path_option: Option<Vec<G::Edge>>,
|
||||||
|
|||||||
@@ -49,6 +49,12 @@ macro_rules! graph_topology_tests {
|
|||||||
incidence_cursor_copy,
|
incidence_cursor_copy,
|
||||||
incident_vertices_incidences_consistency,
|
incident_vertices_incidences_consistency,
|
||||||
incident_edges_incidences_consistency,
|
incident_edges_incidences_consistency,
|
||||||
|
common_neighbors,
|
||||||
|
common_neighbors_same_vertex,
|
||||||
|
common_neighbors_single,
|
||||||
|
common_neighbors_disconnected,
|
||||||
|
common_neighbors_multiple_edges,
|
||||||
|
common_neighbors_loop_edge,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -771,3 +777,118 @@ where
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn common_neighbors<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
let (graph, vertices, _, _) = G::standard();
|
||||||
|
// Pairs of vertex indices with the indices of their expected common neighbors.
|
||||||
|
let cases: [(usize, usize, &[usize]); _] = [
|
||||||
|
(0, 3, &[1]),
|
||||||
|
(1, 6, &[2, 3]),
|
||||||
|
(5, 8, &[]),
|
||||||
|
(2, 9, &[5, 6]),
|
||||||
|
(7, 3, &[]),
|
||||||
|
(0, 1, &[]),
|
||||||
|
(6, 7, &[9]),
|
||||||
|
// Loop only on one side, adjacent.
|
||||||
|
(2, 6, &[2]),
|
||||||
|
// Loop only on one side, not adjacent.
|
||||||
|
(2, 7, &[4]),
|
||||||
|
// Adjacent through a double edge, and both have a loop.
|
||||||
|
(2, 4, &[1, 2, 4]),
|
||||||
|
];
|
||||||
|
for (i, j, expected) in cases {
|
||||||
|
let expected: Vec<_> = expected.iter().map(|&k| vertices[k]).collect();
|
||||||
|
assert_common_neighbors(&graph, vertices[i], vertices[j], &expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn common_neighbors_same_vertex<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
let (graph, vertices, _, _) = G::standard();
|
||||||
|
// Vertex 1 has a double edge to vertex 0, but no loop.
|
||||||
|
assert_common_neighbors(
|
||||||
|
&graph,
|
||||||
|
vertices[1],
|
||||||
|
vertices[1],
|
||||||
|
&[vertices[0], vertices[2], vertices[3], vertices[4]],
|
||||||
|
);
|
||||||
|
// Vertex 4 has a loop, and is therefore its own common neighbor with itself.
|
||||||
|
assert_common_neighbors(
|
||||||
|
&graph,
|
||||||
|
vertices[4],
|
||||||
|
vertices[4],
|
||||||
|
&[
|
||||||
|
vertices[1],
|
||||||
|
vertices[2],
|
||||||
|
vertices[4],
|
||||||
|
vertices[7],
|
||||||
|
vertices[8],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn common_neighbors_single<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
let (graph, v) = G::single_vertex();
|
||||||
|
assert_common_neighbors(&graph, v, v, &[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn common_neighbors_disconnected<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
let (graph, v) = G::disconnected();
|
||||||
|
assert_common_neighbors(&graph, v[0], v[0], &[]);
|
||||||
|
assert_common_neighbors(&graph, v[0], v[1], &[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn common_neighbors_multiple_edges<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
let (graph, [v0, v1], _) = G::multiple_edges::<3>();
|
||||||
|
assert_common_neighbors(&graph, v0, v1, &[]);
|
||||||
|
assert_common_neighbors(&graph, v1, v1, &[v0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn common_neighbors_loop_edge<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
let (graph, v, _) = G::loop_edge();
|
||||||
|
assert_common_neighbors(&graph, v, v, &[v]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts that `u` and `v` have exactly the `expected` common neighbors, each yielded once, for
|
||||||
|
/// both argument orders.
|
||||||
|
fn assert_common_neighbors<G: MakeTestGraph>(
|
||||||
|
graph: &G,
|
||||||
|
u: G::Vertex,
|
||||||
|
v: G::Vertex,
|
||||||
|
expected: &[G::Vertex],
|
||||||
|
) where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
for (a, b) in [(u, v), (v, u)] {
|
||||||
|
let mut remaining = expected.to_vec();
|
||||||
|
for w in graph.common_neighbors(a, b) {
|
||||||
|
let pos = remaining.iter().position(|&x| x == w).unwrap_or_else(|| {
|
||||||
|
panic!(
|
||||||
|
"unexpected or repeated common neighbor {w:?} of vertices {a:?} and {b:?} from iterator"
|
||||||
|
)
|
||||||
|
});
|
||||||
|
remaining.swap_remove(pos);
|
||||||
|
}
|
||||||
|
assert!(
|
||||||
|
remaining.is_empty(),
|
||||||
|
"expected common neighbors {remaining:?} of vertices {a:?} and {b:?} were not matched by iterator"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -175,6 +175,58 @@ pub trait GraphTopology {
|
|||||||
///
|
///
|
||||||
/// Panics if `v` is not a valid vertex of this graph.
|
/// Panics if `v` is not a valid vertex of this graph.
|
||||||
fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor;
|
fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor;
|
||||||
|
|
||||||
|
/// Returns an iterator over all vertices that are adjacent to both `u` and `v`.
|
||||||
|
///
|
||||||
|
/// Every common neighbor is yielded exactly once, in unspecified order, regardless of how
|
||||||
|
/// many parallel edges connect it to `u` or `v`. This method is symmetric up to permutations,
|
||||||
|
/// i.e. swapping the arguments will yield the same vertices, but not necessarily in the same
|
||||||
|
/// order.
|
||||||
|
///
|
||||||
|
/// If `u` has a loop and is adjacent to `v`, then `u` is a common neighbor, and will be yielded
|
||||||
|
/// exactly once, and analogously for a loop at `v`.
|
||||||
|
///
|
||||||
|
/// If `u == v`, then the common neighbors are all neighbors of `u`, including `u` itself if and
|
||||||
|
/// only if it has a loop.
|
||||||
|
///
|
||||||
|
/// All neighbors of `u` are collected when this method is called, while those of `v` are
|
||||||
|
/// traversed lazily.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if `u` or `v` is not a valid vertex of this graph.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use grapherity::models::AppendGraph;
|
||||||
|
/// use grapherity::prelude::*;
|
||||||
|
///
|
||||||
|
/// let mut graph = AppendGraph::new();
|
||||||
|
/// let [u, w, v] = core::array::from_fn(|_| graph.add_vertex());
|
||||||
|
/// // Parallel edges do not yield `w` more than once.
|
||||||
|
/// graph.add_edge(u, w);
|
||||||
|
/// graph.add_edge(u, w);
|
||||||
|
/// graph.add_edge(w, v);
|
||||||
|
/// assert!(graph.common_neighbors(u, v).eq([w]));
|
||||||
|
/// ```
|
||||||
|
// TODO: FrozenGraph could provide a sorted-merge override, if its vertices were sorted.
|
||||||
|
fn common_neighbors(
|
||||||
|
&self,
|
||||||
|
u: Self::Vertex,
|
||||||
|
v: Self::Vertex,
|
||||||
|
) -> impl Iterator<Item = Self::Vertex> {
|
||||||
|
// TODO: Instead of Vec, this could use a vertex ElementMap with marked neighbors if the product of the degrees of u and v is large compared to |V|.
|
||||||
|
// TODO: Instead of Vec, this could use a reusable, user-provided(?) "scratch-pad" vertex ElementMap, to not pay its allocation.
|
||||||
|
let mut u_neighbors: Vec<_> = self.adjacent_vertices(u).collect();
|
||||||
|
let mut v_neighbors = self.adjacent_vertices(v);
|
||||||
|
std::iter::from_fn(move || {
|
||||||
|
let w = v_neighbors.find(|w| u_neighbors.contains(w))?;
|
||||||
|
// Removes all parallel entries, so that `w` is not yielded again.
|
||||||
|
u_neighbors.retain(|x| *x != w);
|
||||||
|
Some(w)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait that adds construction operations to an undirected graph topology.
|
/// A trait that adds construction operations to an undirected graph topology.
|
||||||
|
|||||||
Reference in New Issue
Block a user