Skip to main content

zrx_graph/graph/iter/
common_descendants.rs

1// Copyright (c) 2025-2026 Zensical and contributors
2
3// SPDX-License-Identifier: MIT
4// All contributions are certified under the DCO
5
6// Permission is hereby granted, free of charge, to any person obtaining a copy
7// of this software and associated documentation files (the "Software"), to
8// deal in the Software without restriction, including without limitation the
9// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10// sell copies of the Software, and to permit persons to whom the Software is
11// furnished to do so, subject to the following conditions:
12
13// The above copyright notice and this permission notice shall be included in
14// all copies or substantial portions of the Software.
15
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22// IN THE SOFTWARE.
23
24// ----------------------------------------------------------------------------
25
26//! Iterator over common descendants of a set of nodes.
27
28use std::collections::BTreeSet;
29
30use crate::graph::topology::{Topology, Transitive};
31use crate::graph::Graph;
32
33// ----------------------------------------------------------------------------
34// Structs
35// ----------------------------------------------------------------------------
36
37/// Iterator over common descendants of a set of nodes.
38pub struct CommonDescendants<'a> {
39    /// Graph topology.
40    topology: &'a Topology<Transitive>,
41    /// Set of common descendants.
42    descendants: BTreeSet<usize>,
43}
44
45// ----------------------------------------------------------------------------
46// Implementations
47// ----------------------------------------------------------------------------
48
49impl<T> Graph<T, Transitive> {
50    /// Creates an iterator over the common descendants of the set of nodes.
51    ///
52    /// # Panics
53    ///
54    /// Panics if a node does not exist, as this indicates that there's a bug
55    /// in the code that creates or uses the iterator. While the [`Builder`][]
56    /// is designed to be fallible to ensure the structure is valid, methods
57    /// that operate on [`Graph`] panic on violated invariants.
58    ///
59    /// [`Builder`]: crate::graph::Builder
60    ///
61    /// # Examples
62    ///
63    /// ```
64    /// # use std::error::Error;
65    /// # fn main() -> Result<(), Box<dyn Error>> {
66    /// use zrx_graph::Graph;
67    ///
68    /// // Create graph builder and add nodes
69    /// let mut builder = Graph::builder();
70    /// let a = builder.add_node("a");
71    /// let b = builder.add_node("b");
72    /// let c = builder.add_node("c");
73    ///
74    /// // Create edges between nodes
75    /// builder.add_edge(a, b)?;
76    /// builder.add_edge(a, c)?;
77    ///
78    /// // Create graph from builder
79    /// let graph = builder.build().into_transitive();
80    ///
81    /// // Create iterator over common descendants
82    /// for nodes in graph.common_descendants([a]) {
83    ///     println!("{nodes:?}");
84    /// }
85    /// # Ok(())
86    /// # }
87    /// ```
88    pub fn common_descendants<N>(&self, nodes: N) -> CommonDescendants<'_>
89    where
90        N: AsRef<[usize]>,
91    {
92        let nodes = nodes.as_ref();
93
94        // Compute common descendants by ensuring that each node in the given
95        // set of nodes is reachable from the current node being considered
96        let mut descendants = BTreeSet::new();
97        for descendant in self {
98            let mut iter = nodes.iter();
99            if iter.all(|&node| self.topology.has_path(node, descendant)) {
100                descendants.insert(descendant);
101            }
102        }
103
104        // Create and return iterator
105        CommonDescendants {
106            topology: &self.topology,
107            descendants,
108        }
109    }
110}
111
112// ----------------------------------------------------------------------------
113// Trait implementations
114// ----------------------------------------------------------------------------
115
116impl Iterator for CommonDescendants<'_> {
117    type Item = Vec<usize>;
118
119    /// Returns the next layer of common descendants.
120    fn next(&mut self) -> Option<Self::Item> {
121        if self.descendants.is_empty() {
122            return None;
123        }
124
125        // Compute the next layer of common descendants - all nodes that aren't
126        // descendants of any other remaining common descendant. This process is
127        // commonly referred to as peeling, where we iteratively remove layers
128        // from the set of common descendants.
129        let mut layer = Vec::new();
130        for &descendant in &self.descendants {
131            let mut iter = self.descendants.iter();
132            if !iter.any(|&node| {
133                node != descendant && self.topology.has_path(node, descendant)
134            }) {
135                layer.push(descendant);
136            }
137        }
138
139        // Remove all nodes in the layer from the set of common descendants,
140        // and return the layer if it's not empty. Otherwise, we're done.
141        self.descendants.retain(|node| !layer.contains(node));
142        (!layer.is_empty()).then_some(layer)
143    }
144}
145
146// ----------------------------------------------------------------------------
147// Tests
148// ----------------------------------------------------------------------------
149
150#[cfg(test)]
151mod tests {
152
153    mod common_descendants {
154        use crate::graph;
155
156        #[test]
157        fn handles_graph() {
158            let graph = graph! {
159                transitive;
160                "a" => "d",
161                "b" => "d", "b" => "e",
162                "c" => "f", "c" => "g",
163                "d" => "f", "d" => "g",
164                "e" => "g",
165            };
166            assert_eq!(
167                graph.common_descendants([0, 2]).collect::<Vec<_>>(),
168                vec![vec![1], vec![5, 6]]
169            );
170        }
171
172        #[test]
173        fn handles_multi_graph() {
174            let graph = graph! {
175                transitive;
176                "a" => "d",
177                "b" => "d", "b" => "e", "b" => "e",
178                "c" => "f", "c" => "g",
179                "d" => "f", "d" => "g",
180                "e" => "g",
181            };
182            assert_eq!(
183                graph.common_descendants([0, 2]).collect::<Vec<_>>(),
184                vec![vec![1], vec![5, 6]]
185            );
186        }
187    }
188}