Skip to main content

zrx_graph/graph/iter/
common_ancestors.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 ancestors of a set of nodes.
27
28use std::collections::BTreeSet;
29
30use crate::graph::topology::Distance;
31use crate::graph::Graph;
32
33// ----------------------------------------------------------------------------
34// Structs
35// ----------------------------------------------------------------------------
36
37/// Iterator over common ancestors of a set of nodes.
38pub struct CommonAncestors<'a> {
39    /// Distance matrix.
40    distance: &'a Distance,
41    /// Set of common ancestors.
42    ancestors: BTreeSet<usize>,
43}
44
45// ----------------------------------------------------------------------------
46// Implementations
47// ----------------------------------------------------------------------------
48
49impl<T> Graph<T> {
50    /// Creates an iterator over the common ancestors of the set of nodes.
51    ///
52    /// This method creates an iterator over the common ancestores of a given
53    /// set of nodes, and emits them in layers, starting with the lowest common
54    /// ancestors (LCA). In directed acyclic graphs, nodes might have multiple
55    /// common ancestors, since there can be multiple paths leading to the nodes
56    /// in the provided set. The iterator peels these common ancestors layer by
57    /// layer, emitting all sinks among the remaining nodes at each step.
58    ///
59    /// # Panics
60    ///
61    /// Panics if a node does not exist, as this indicates that there's a bug
62    /// in the code that creates or uses the iterator. While the [`Builder`][]
63    /// is designed to be fallible to ensure the structure is valid, methods
64    /// that operate on [`Graph`] panic on violated invariants.
65    ///
66    /// [`Builder`]: crate::graph::Builder
67    ///
68    /// # Examples
69    ///
70    /// ```
71    /// # use std::error::Error;
72    /// # fn main() -> Result<(), Box<dyn Error>> {
73    /// use zrx_graph::Graph;
74    ///
75    /// // Create graph builder and add nodes
76    /// let mut builder = Graph::builder();
77    /// let a = builder.add_node("a");
78    /// let b = builder.add_node("b");
79    /// let c = builder.add_node("c");
80    ///
81    /// // Create edges between nodes
82    /// builder.add_edge(a, b)?;
83    /// builder.add_edge(a, c)?;
84    ///
85    /// // Create graph from builder
86    /// let graph = builder.build();
87    ///
88    /// // Create iterator over common ancestors
89    /// for nodes in graph.common_ancestors([b, c]) {
90    ///     println!("{nodes:?}");
91    /// }
92    /// # Ok(())
93    /// # }
94    /// ```
95    pub fn common_ancestors<N>(&self, nodes: N) -> CommonAncestors<'_>
96    where
97        N: AsRef<[usize]>,
98    {
99        let distance = self.topology.distance();
100        let nodes = nodes.as_ref();
101
102        // Compute common ancestors by ensuring that each node in the given set
103        // of nodes is reachable from the current node being considered
104        let mut ancestors = BTreeSet::new();
105        for ancestor in self {
106            let mut iter = nodes.iter();
107            if iter.all(|&node| distance[ancestor][node] != u8::MAX) {
108                ancestors.insert(ancestor);
109            }
110        }
111
112        // Create and return iterator
113        CommonAncestors {
114            distance: self.topology.distance(),
115            ancestors,
116        }
117    }
118}
119
120// ----------------------------------------------------------------------------
121// Trait implementations
122// ----------------------------------------------------------------------------
123
124impl Iterator for CommonAncestors<'_> {
125    type Item = Vec<usize>;
126
127    /// Returns the next layer of common ancestors.
128    fn next(&mut self) -> Option<Self::Item> {
129        if self.ancestors.is_empty() {
130            return None;
131        }
132
133        // Compute the next layer of common ancestors - all nodes that aren't
134        // ancestors of any other remaining common ancestor. This process is
135        // commonly referred to as peeling, where we iteratively remove layers
136        // from the set of common ancestors.
137        let mut layer = Vec::new();
138        for &ancestor in &self.ancestors {
139            let mut iter = self.ancestors.iter();
140            if !iter.any(|&node| {
141                node != ancestor && self.distance[ancestor][node] != u8::MAX
142            }) {
143                layer.push(ancestor);
144            }
145        }
146
147        // Remove all nodes in the layer from the set of common ancestors, and
148        // return the layer if it's not empty. Otherwise, we're done.
149        self.ancestors.retain(|node| !layer.contains(node));
150        (!layer.is_empty()).then_some(layer)
151    }
152}
153
154// ----------------------------------------------------------------------------
155// Tests
156// ----------------------------------------------------------------------------
157
158#[cfg(test)]
159mod tests {
160
161    mod common_ancestors {
162        use crate::graph;
163
164        #[test]
165        fn handles_graph() {
166            let graph = graph! {
167                "a" => "d",
168                "b" => "d", "b" => "e",
169                "c" => "f", "c" => "g",
170                "d" => "f", "d" => "g",
171                "e" => "g",
172            };
173            assert_eq!(
174                graph.common_ancestors([5, 6]).collect::<Vec<_>>(),
175                vec![vec![1, 4], vec![0, 2]]
176            );
177        }
178
179        #[test]
180        fn handles_multi_graph() {
181            let graph = graph! {
182                "a" => "d",
183                "b" => "d", "b" => "e", "b" => "e",
184                "c" => "f", "c" => "g",
185                "d" => "f", "d" => "g",
186                "e" => "g",
187            };
188            assert_eq!(
189                graph.common_ancestors([5, 6]).collect::<Vec<_>>(),
190                vec![vec![1, 4], vec![0, 2]]
191            );
192        }
193    }
194}