Add GraphTopology::common_neighbors
This commit is contained in:
@@ -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