// Copyright 2020 The Jujutsu Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! General-purpose DAG algorithms. use std::collections::BinaryHeap; use std::collections::HashMap; use std::collections::HashSet; use std::convert::Infallible; use std::hash::Hash; use std::iter; use std::mem; use itertools::Itertools as _; use smallvec::SmallVec; use smallvec::smallvec_inline; /// Traverses nodes from `start` in depth-first order. pub fn dfs( start: II, id_fn: impl Fn(&T) -> ID, mut neighbors_fn: impl FnMut(&T) -> NI, ) -> impl Iterator where ID: Hash + Eq, II: IntoIterator, NI: IntoIterator, { let neighbors_fn = move |node: &T| to_infallibe_iter(neighbors_fn(node)); dfs_ok(to_infallibe_iter(start), id_fn, neighbors_fn).map(|Ok(node)| node) } /// Traverses nodes from `start` in depth-first order. /// /// An `Err` is emitted as a node with no neighbors. Caller may decide to /// short-circuit on it. pub fn dfs_ok( start: II, id_fn: impl Fn(&T) -> ID, mut neighbors_fn: impl FnMut(&T) -> NI, ) -> impl Iterator> where ID: Hash + Eq, II: IntoIterator>, NI: IntoIterator>, { let mut work: Vec> = start.into_iter().collect(); let mut visited: HashSet = HashSet::new(); iter::from_fn(move || { loop { let c = match work.pop() { Some(Ok(c)) => c, r @ (Some(Err(_)) | None) => return r, }; let id = id_fn(&c); if visited.contains(&id) { continue; } for p in neighbors_fn(&c) { work.push(p); } visited.insert(id); return Some(Ok(c)); } }) } /// Builds a list of nodes reachable from the `start` where neighbors come /// before the node itself. /// /// If the graph has cycle, `cycle_fn()` is called with one of the nodes /// involved in the cycle. pub fn topo_order_forward( start: II, id_fn: impl Fn(&T) -> ID, mut neighbors_fn: impl FnMut(&T) -> NI, cycle_fn: impl FnOnce(T) -> E, ) -> Result, E> where ID: Hash + Eq + Clone, II: IntoIterator, NI: IntoIterator, { let neighbors_fn = move |node: &T| to_ok_iter(neighbors_fn(node)); topo_order_forward_ok(to_ok_iter(start), id_fn, neighbors_fn, cycle_fn) } /// Builds a list of `Ok` nodes reachable from the `start` where neighbors come /// before the node itself. /// /// If `start` or `neighbors_fn()` yields an `Err`, this function terminates and /// returns the error. If the graph has cycle, `cycle_fn()` is called with one /// of the nodes involved in the cycle. pub fn topo_order_forward_ok( start: II, id_fn: impl Fn(&T) -> ID, mut neighbors_fn: impl FnMut(&T) -> NI, cycle_fn: impl FnOnce(T) -> E, ) -> Result, E> where ID: Hash + Eq + Clone, II: IntoIterator>, NI: IntoIterator>, { let mut stack: Vec<(T, bool)> = start.into_iter().map(|r| Ok((r?, false))).try_collect()?; let mut visiting = HashSet::new(); let mut emitted = HashSet::new(); let mut result = vec![]; while let Some((node, neighbors_visited)) = stack.pop() { let id = id_fn(&node); if emitted.contains(&id) { continue; } if !neighbors_visited { if !visiting.insert(id.clone()) { return Err(cycle_fn(node)); } let neighbors_iter = neighbors_fn(&node).into_iter(); stack.reserve(neighbors_iter.size_hint().0 + 1); stack.push((node, true)); for neighbor in neighbors_iter { stack.push((neighbor?, false)); } } else { visiting.remove(&id); emitted.insert(id); result.push(node); } } Ok(result) } /// Builds a list of nodes reachable from the `start` where neighbors come after /// the node itself. /// /// If the graph has cycle, `cycle_fn()` is called with one of the nodes /// involved in the cycle. pub fn topo_order_reverse( start: II, id_fn: impl Fn(&T) -> ID, mut neighbors_fn: impl FnMut(&T) -> NI, cycle_fn: impl FnOnce(T) -> E, ) -> Result, E> where ID: Hash + Eq + Clone, II: IntoIterator, NI: IntoIterator, { let neighbors_fn = move |node: &T| to_ok_iter(neighbors_fn(node)); topo_order_reverse_ok(to_ok_iter(start), id_fn, neighbors_fn, cycle_fn) } /// Builds a list of `Ok` nodes reachable from the `start` where neighbors come /// after the node itself. /// /// If `start` or `neighbors_fn()` yields an `Err`, this function terminates and /// returns the error. If the graph has cycle, `cycle_fn()` is called with one /// of the nodes involved in the cycle. pub fn topo_order_reverse_ok( start: II, id_fn: impl Fn(&T) -> ID, neighbors_fn: impl FnMut(&T) -> NI, cycle_fn: impl FnOnce(T) -> E, ) -> Result, E> where ID: Hash + Eq + Clone, II: IntoIterator>, NI: IntoIterator>, { let mut result = topo_order_forward_ok(start, id_fn, neighbors_fn, cycle_fn)?; result.reverse(); Ok(result) } /// Like `topo_order_reverse()`, but can iterate linear DAG lazily. /// /// The DAG is supposed to be (mostly) topologically ordered by `T: Ord`. /// For example, topological order of chronological data should respect /// timestamp (except a few outliers caused by clock skew.) /// /// Use `topo_order_reverse()` if the DAG is heavily branched. This can /// only process linear part lazily. /// /// If the graph has cycle, `cycle_fn()` is called with one of the nodes /// involved in the cycle. pub fn topo_order_reverse_lazy( start: II, id_fn: impl Fn(&T) -> ID, mut neighbors_fn: impl FnMut(&T) -> NI, cycle_fn: impl FnMut(T) -> E, ) -> impl Iterator> where T: Ord, ID: Hash + Eq + Clone, II: IntoIterator, NI: IntoIterator, { let neighbors_fn = move |node: &T| to_ok_iter(neighbors_fn(node)); topo_order_reverse_lazy_ok(to_ok_iter(start), id_fn, neighbors_fn, cycle_fn) } /// Like `topo_order_reverse_ok()`, but can iterate linear DAG lazily. /// /// The returned iterator short-circuits at an `Err`. Pending non-linear nodes /// before the `Err` will be discarded. If the graph has cycle, `cycle_fn()` is /// called with one of the nodes involved in the cycle. pub fn topo_order_reverse_lazy_ok( start: II, id_fn: impl Fn(&T) -> ID, mut neighbors_fn: impl FnMut(&T) -> NI, mut cycle_fn: impl FnMut(T) -> E, ) -> impl Iterator> where T: Ord, ID: Hash + Eq + Clone, II: IntoIterator>, NI: IntoIterator>, { let mut inner = TopoOrderReverseLazyInner::empty(); inner.extend(start); iter::from_fn(move || inner.next(&id_fn, &mut neighbors_fn, &mut cycle_fn)) } #[derive(Clone, Debug)] struct TopoOrderReverseLazyInner { start: Vec, result: Vec>, emitted: HashSet, } impl TopoOrderReverseLazyInner { fn empty() -> Self { Self { start: Vec::new(), result: Vec::new(), emitted: HashSet::new(), } } fn extend(&mut self, iter: impl IntoIterator>) { let iter = iter.into_iter(); self.start.reserve(iter.size_hint().0); for res in iter { if let Ok(node) = res { self.start.push(node); } else { // Emit the error and terminate self.start.clear(); self.result.insert(0, res); return; } } } fn next>>( &mut self, id_fn: impl Fn(&T) -> ID, mut neighbors_fn: impl FnMut(&T) -> NI, mut cycle_fn: impl FnMut(T) -> E, ) -> Option> { if let Some(res) = self.result.pop() { return Some(res); } // Fast path for linear DAG if self.start.len() <= 1 { let node = self.start.pop()?; self.extend(neighbors_fn(&node)); if self.emitted.insert(id_fn(&node)) { return Some(Ok(node)); } else { return Some(Err(cycle_fn(node))); } } // Extract graph nodes based on T's order, and sort them by using ids // (because we wouldn't want to clone T itself) let start_ids = self.start.iter().map(&id_fn).collect_vec(); match look_ahead_sub_graph(mem::take(&mut self.start), &id_fn, &mut neighbors_fn) { Ok((mut node_map, neighbor_ids_map, remainder)) => { self.start = remainder; let sorted_ids = match topo_order_forward_ok( start_ids.iter().map(Ok), |id| *id, |id| neighbor_ids_map[id].iter().map(Ok), |id| cycle_fn(node_map.remove(id).unwrap()), ) { Ok(ids) => ids, Err(err) => return Some(Err(err)), }; self.result.reserve(sorted_ids.len()); for id in sorted_ids { let (id, node) = node_map.remove_entry(id).unwrap(); if self.emitted.insert(id) { self.result.push(Ok(node)); } else { self.result.push(Err(cycle_fn(node))); } } self.result.pop() } Err(err) => Some(Err(err)), } } } /// Splits DAG at the first single fork point, and builds a list of nodes /// reachable from the `start` where neighbors come after the node itself. /// /// This is a building block for lazy DAG iterators similar to /// [`topo_order_reverse_lazy()`]. The `start` list will be updated to include /// the next nodes to visit. /// /// If the split chunk of the graph has cycle, `cycle_fn()` is called with one /// of the nodes involved in the cycle. pub fn topo_order_reverse_chunked( start: &mut Vec, id_fn: impl Fn(&T) -> ID, mut neighbors_fn: impl FnMut(&T) -> NI, mut cycle_fn: impl FnMut(T) -> E, ) -> Result, E> where T: Ord, ID: Hash + Eq + Clone, NI: IntoIterator>, { // Fast path for linear DAG if start.len() <= 1 { let Some(node) = start.pop() else { return Ok(SmallVec::new()); }; let neighbors_iter = neighbors_fn(&node).into_iter(); start.reserve(neighbors_iter.size_hint().0); for neighbor in neighbors_iter { start.push(neighbor?); } return Ok(smallvec_inline![node]); } // Extract graph nodes based on T's order, and sort them by using ids // (because we wouldn't want to clone T itself) let start_ids = start.iter().map(&id_fn).collect_vec(); let (mut node_map, neighbor_ids_map, remainder) = look_ahead_sub_graph(mem::take(start), &id_fn, &mut neighbors_fn)?; *start = remainder; let sorted_ids = topo_order_forward_ok( start_ids.iter().map(Ok), |id| *id, |id| neighbor_ids_map[id].iter().map(Ok), |id| cycle_fn(node_map.remove(id).unwrap()), )?; let sorted_nodes = sorted_ids .iter() .rev() .map(|&id| node_map.remove(id).unwrap()) .collect(); Ok(sorted_nodes) } /// Splits DAG at single fork point, and extracts branchy part as sub graph. /// /// ```text /// o | C /// | o B /// |/ <---- split here (A->B or A->C would create cycle) /// o A /// ``` /// /// If a branch reached to root (empty neighbors), the graph can't be split /// anymore because the other branch may be connected to a descendant of /// the rooted branch. /// /// ```text /// o | C /// | o B /// | <---- can't split here (there may be edge A->B) /// o A /// ``` /// /// We assume the graph is (mostly) topologically ordered by `T: Ord`. #[expect(clippy::type_complexity)] fn look_ahead_sub_graph( start: Vec, id_fn: impl Fn(&T) -> ID, mut neighbors_fn: impl FnMut(&T) -> NI, ) -> Result<(HashMap, HashMap>, Vec), E> where T: Ord, ID: Hash + Eq + Clone, NI: IntoIterator>, { let mut queue: BinaryHeap = start.into(); // Build separate node/neighbors maps since lifetime is different at caller let mut node_map: HashMap = HashMap::new(); let mut neighbor_ids_map: HashMap> = HashMap::new(); let mut has_reached_root = false; while queue.len() > 1 || node_map.is_empty() || has_reached_root { let Some(node) = queue.pop() else { break; }; let node_id = id_fn(&node); if node_map.contains_key(&node_id) { continue; } let mut neighbor_ids = Vec::new(); let mut neighbors_iter = neighbors_fn(&node).into_iter().peekable(); has_reached_root |= neighbors_iter.peek().is_none(); for neighbor in neighbors_iter { let neighbor = neighbor?; neighbor_ids.push(id_fn(&neighbor)); queue.push(neighbor); } node_map.insert(node_id.clone(), node); neighbor_ids_map.insert(node_id, neighbor_ids); } assert!(queue.len() <= 1, "order of remainder shouldn't matter"); let remainder = queue.into_vec(); // Omit unvisited neighbors if let Some(unvisited_id) = remainder.first().map(&id_fn) { for neighbor_ids in neighbor_ids_map.values_mut() { neighbor_ids.retain(|id| *id != unvisited_id); } } Ok((node_map, neighbor_ids_map, remainder)) } /// Builds a list of nodes reachable from the `start` where neighbors come after /// the node itself. /// /// Unlike `topo_order_reverse()`, nodes are sorted in reverse `T: Ord` order so /// long as they can respect the topological requirement. /// /// If the graph has cycle, `cycle_fn()` is called with one of the nodes /// involved in the cycle. pub fn topo_order_reverse_ord( start: II, id_fn: impl Fn(&T) -> ID, mut neighbors_fn: impl FnMut(&T) -> NI, cycle_fn: impl FnOnce(T) -> E, ) -> Result, E> where T: Ord, ID: Hash + Eq + Clone, II: IntoIterator, NI: IntoIterator, { let neighbors_fn = move |node: &T| to_ok_iter(neighbors_fn(node)); topo_order_reverse_ord_ok(to_ok_iter(start), id_fn, neighbors_fn, cycle_fn) } /// Builds a list of `Ok` nodes reachable from the `start` where neighbors come /// after the node itself. /// /// Unlike `topo_order_reverse_ok()`, nodes are sorted in reverse `T: Ord` order /// so long as they can respect the topological requirement. /// /// If `start` or `neighbors_fn()` yields an `Err`, this function terminates and /// returns the error. If the graph has cycle, `cycle_fn()` is called with one /// of the nodes involved in the cycle. pub fn topo_order_reverse_ord_ok( start: II, id_fn: impl Fn(&T) -> ID, mut neighbors_fn: impl FnMut(&T) -> NI, cycle_fn: impl FnOnce(T) -> E, ) -> Result, E> where T: Ord, ID: Hash + Eq + Clone, II: IntoIterator>, NI: IntoIterator>, { struct InnerNode { node: Option, indegree: usize, } // DFS to accumulate incoming edges let mut stack: Vec = start.into_iter().try_collect()?; let mut head_node_map: HashMap = HashMap::new(); let mut inner_node_map: HashMap> = HashMap::new(); let mut neighbor_ids_map: HashMap> = HashMap::new(); while let Some(node) = stack.pop() { let node_id = id_fn(&node); if neighbor_ids_map.contains_key(&node_id) { continue; // Already visited } let neighbors_iter = neighbors_fn(&node).into_iter(); let pos = stack.len(); stack.reserve(neighbors_iter.size_hint().0); for neighbor in neighbors_iter { stack.push(neighbor?); } let neighbor_ids = stack[pos..].iter().map(&id_fn).collect_vec(); if let Some(inner) = inner_node_map.get_mut(&node_id) { inner.node = Some(node); } else { head_node_map.insert(node_id.clone(), node); } for neighbor_id in &neighbor_ids { if let Some(inner) = inner_node_map.get_mut(neighbor_id) { inner.indegree += 1; } else { let inner = InnerNode { node: head_node_map.remove(neighbor_id), indegree: 1, }; inner_node_map.insert(neighbor_id.clone(), inner); } } neighbor_ids_map.insert(node_id, neighbor_ids); } debug_assert!( head_node_map .keys() .all(|id| !inner_node_map.contains_key(id)) ); debug_assert!(inner_node_map.values().all(|inner| inner.node.is_some())); debug_assert!(inner_node_map.values().all(|inner| inner.indegree > 0)); // Using Kahn's algorithm let mut queue: BinaryHeap = head_node_map.into_values().collect(); let mut result = Vec::new(); while let Some(node) = queue.pop() { let node_id = id_fn(&node); result.push(node); for neighbor_id in neighbor_ids_map.remove(&node_id).unwrap() { let inner = inner_node_map.get_mut(&neighbor_id).unwrap(); inner.indegree -= 1; if inner.indegree == 0 { queue.push(inner.node.take().unwrap()); inner_node_map.remove(&neighbor_id); } } } if let Some(inner) = inner_node_map.into_values().next() { Err(cycle_fn(inner.node.unwrap())) } else { Ok(result) } } /// Find nodes in the start set that are not reachable from other nodes in the /// start set. pub fn heads( start: II, id_fn: impl Fn(&T) -> ID, mut neighbors_fn: impl FnMut(&T) -> NI, ) -> HashSet where T: Hash + Eq + Clone, ID: Hash + Eq, II: IntoIterator, NI: IntoIterator, { let neighbors_fn = move |node: &T| to_infallibe_iter(neighbors_fn(node)); let Ok(node) = heads_ok(to_infallibe_iter(start), id_fn, neighbors_fn); node } /// Finds `Ok` nodes in the start set that are not reachable from other nodes in /// the start set. /// /// If `start` or `neighbors_fn()` yields an `Err`, this function terminates and /// returns the error. pub fn heads_ok( start: II, id_fn: impl Fn(&T) -> ID, mut neighbors_fn: impl FnMut(&T) -> NI, ) -> Result, E> where T: Hash + Eq + Clone, ID: Hash + Eq, II: IntoIterator>, NI: IntoIterator>, { let mut heads: HashSet = start.into_iter().try_collect()?; // Do a BFS until we have only one item left in the frontier. That frontier must // have originated from one of the heads, and since there can't be cycles, // it won't be able to eliminate any other heads. let mut frontier: Vec = heads.iter().cloned().collect(); let mut visited: HashSet = heads.iter().map(&id_fn).collect(); let mut root_reached = false; while frontier.len() > 1 || (!frontier.is_empty() && root_reached) { frontier = frontier .iter() .flat_map(|node| { let neighbors = neighbors_fn(node).into_iter().collect_vec(); if neighbors.is_empty() { root_reached = true; } neighbors }) .try_collect()?; for node in &frontier { heads.remove(node); } frontier.retain(|node| visited.insert(id_fn(node))); } Ok(heads) } /// Finds the closest common neighbor among the `set1` and `set2`. pub fn closest_common_node( set1: II1, set2: II2, id_fn: impl Fn(&T) -> ID, mut neighbors_fn: impl FnMut(&T) -> NI, ) -> Option where ID: Hash + Eq, II1: IntoIterator, II2: IntoIterator, NI: IntoIterator, { let neighbors_fn = move |node: &T| to_infallibe_iter(neighbors_fn(node)); let Ok(node) = closest_common_node_ok( to_infallibe_iter(set1), to_infallibe_iter(set2), id_fn, neighbors_fn, ); node } /// Finds the closest common `Ok` neighbor among the `set1` and `set2`. /// /// If the traverse reached to an `Err`, this function terminates and returns /// the error. pub fn closest_common_node_ok( set1: II1, set2: II2, id_fn: impl Fn(&T) -> ID, mut neighbors_fn: impl FnMut(&T) -> NI, ) -> Result, E> where ID: Hash + Eq, II1: IntoIterator>, II2: IntoIterator>, NI: IntoIterator>, { let mut visited1 = HashSet::new(); let mut visited2 = HashSet::new(); // TODO: might be better to leave an Err so long as the work contains at // least one Ok node. If a work1 node is included in visited2, it should be // the closest node even if work2 had previously contained an Err. let mut work1: Vec> = set1.into_iter().collect(); let mut work2: Vec> = set2.into_iter().collect(); while !work1.is_empty() || !work2.is_empty() { let mut new_work1 = vec![]; for node in work1 { let node = node?; let id: ID = id_fn(&node); if visited2.contains(&id) { return Ok(Some(node)); } if visited1.insert(id) { for neighbor in neighbors_fn(&node) { new_work1.push(neighbor); } } } work1 = new_work1; let mut new_work2 = vec![]; for node in work2 { let node = node?; let id: ID = id_fn(&node); if visited1.contains(&id) { return Ok(Some(node)); } if visited2.insert(id) { for neighbor in neighbors_fn(&node) { new_work2.push(neighbor); } } } work2 = new_work2; } Ok(None) } fn to_ok_iter(iter: impl IntoIterator) -> impl Iterator> { iter.into_iter().map(Ok) } fn to_infallibe_iter( iter: impl IntoIterator, ) -> impl Iterator> { to_ok_iter(iter) } #[cfg(test)] mod tests { use assert_matches::assert_matches; use maplit::hashmap; use maplit::hashset; use super::*; #[test] fn test_dfs_ok() { let neighbors = hashmap! { 'A' => vec![], 'B' => vec![Ok('A'), Err('X')], 'C' => vec![Ok('B')], }; let id_fn = |node: &char| *node; let neighbors_fn = |node: &char| neighbors[node].clone(); // Self and neighbor nodes shouldn't be lost at the error. let nodes = dfs_ok([Ok('C')], id_fn, neighbors_fn).collect_vec(); assert_eq!(nodes, [Ok('C'), Ok('B'), Err('X'), Ok('A')]); } #[test] fn test_topo_order_reverse_linear() { // This graph: // o C // o B // o A let neighbors = hashmap! { 'A' => vec![], 'B' => vec!['A'], 'C' => vec!['B'], }; let id_fn = |node: &char| *node; let neighbors_fn = |node: &char| neighbors[node].clone(); let cycle_fn = |id| id; let common = topo_order_reverse(vec!['C'], id_fn, neighbors_fn, cycle_fn).unwrap(); assert_eq!(common, vec!['C', 'B', 'A']); let common = topo_order_reverse(vec!['C', 'B'], id_fn, neighbors_fn, cycle_fn).unwrap(); assert_eq!(common, vec!['C', 'B', 'A']); let common = topo_order_reverse(vec!['B', 'C'], id_fn, neighbors_fn, cycle_fn).unwrap(); assert_eq!(common, vec!['C', 'B', 'A']); let common: Vec<_> = topo_order_reverse_lazy(vec!['C'], id_fn, neighbors_fn, cycle_fn) .try_collect() .unwrap(); assert_eq!(common, vec!['C', 'B', 'A']); let common: Vec<_> = topo_order_reverse_lazy(vec!['C', 'B'], id_fn, neighbors_fn, cycle_fn) .try_collect() .unwrap(); assert_eq!(common, vec!['C', 'B', 'A']); let common: Vec<_> = topo_order_reverse_lazy(vec!['B', 'C'], id_fn, neighbors_fn, cycle_fn) .try_collect() .unwrap(); assert_eq!(common, vec!['C', 'B', 'A']); let common = topo_order_reverse_ord(vec!['C'], id_fn, neighbors_fn, cycle_fn).unwrap(); assert_eq!(common, vec!['C', 'B', 'A']); let common = topo_order_reverse_ord(vec!['C', 'B'], id_fn, neighbors_fn, cycle_fn).unwrap(); assert_eq!(common, vec!['C', 'B', 'A']); let common = topo_order_reverse_ord(vec!['B', 'C'], id_fn, neighbors_fn, cycle_fn).unwrap(); assert_eq!(common, vec!['C', 'B', 'A']); } #[test] fn test_topo_order_reverse_merge() { // This graph: // o F // |\ // o | E // | o D // | o C // | o B // |/ // o A let neighbors = hashmap! { 'A' => vec![], 'B' => vec!['A'], 'C' => vec!['B'], 'D' => vec!['C'], 'E' => vec!['A'], 'F' => vec!['E', 'D'], }; let id_fn = |node: &char| *node; let neighbors_fn = |node: &char| neighbors[node].clone(); let cycle_fn = |id| id; let common = topo_order_reverse(vec!['F'], id_fn, neighbors_fn, cycle_fn).unwrap(); assert_eq!(common, vec!['F', 'E', 'D', 'C', 'B', 'A']); let common = topo_order_reverse(vec!['F', 'E', 'C'], id_fn, neighbors_fn, cycle_fn).unwrap(); assert_eq!(common, vec!['F', 'D', 'E', 'C', 'B', 'A']); let common = topo_order_reverse(vec!['F', 'D', 'E'], id_fn, neighbors_fn, cycle_fn).unwrap(); assert_eq!(common, vec!['F', 'D', 'C', 'B', 'E', 'A']); let common: Vec<_> = topo_order_reverse_lazy(vec!['F'], id_fn, neighbors_fn, cycle_fn) .try_collect() .unwrap(); assert_eq!(common, vec!['F', 'E', 'D', 'C', 'B', 'A']); let common: Vec<_> = topo_order_reverse_lazy(vec!['F', 'E', 'C'], id_fn, neighbors_fn, cycle_fn) .try_collect() .unwrap(); assert_eq!(common, vec!['F', 'D', 'E', 'C', 'B', 'A']); let common: Vec<_> = topo_order_reverse_lazy(vec!['F', 'D', 'E'], id_fn, neighbors_fn, cycle_fn) .try_collect() .unwrap(); assert_eq!(common, vec!['F', 'D', 'C', 'B', 'E', 'A']); let common = topo_order_reverse_ord(vec!['F'], id_fn, neighbors_fn, cycle_fn).unwrap(); assert_eq!(common, vec!['F', 'E', 'D', 'C', 'B', 'A']); let common = topo_order_reverse_ord(vec!['F', 'E', 'C'], id_fn, neighbors_fn, cycle_fn).unwrap(); assert_eq!(common, vec!['F', 'E', 'D', 'C', 'B', 'A']); let common = topo_order_reverse_ord(vec!['F', 'D', 'E'], id_fn, neighbors_fn, cycle_fn).unwrap(); assert_eq!(common, vec!['F', 'E', 'D', 'C', 'B', 'A']); } #[test] fn test_topo_order_reverse_nested_merges() { // This graph: // o I // |\ // | o H // | |\ // | | o G // | o | F // | | o E // o |/ D // | o C // o | B // |/ // o A let neighbors = hashmap! { 'A' => vec![], 'B' => vec!['A'], 'C' => vec!['A'], 'D' => vec!['B'], 'E' => vec!['C'], 'F' => vec!['C'], 'G' => vec!['E'], 'H' => vec!['F', 'G'], 'I' => vec!['D', 'H'], }; let id_fn = |node: &char| *node; let neighbors_fn = |node: &char| neighbors[node].clone(); let cycle_fn = |id| id; let common = topo_order_reverse(vec!['I'], id_fn, neighbors_fn, cycle_fn).unwrap(); assert_eq!(common, vec!['I', 'D', 'B', 'H', 'F', 'G', 'E', 'C', 'A']); let common: Vec<_> = topo_order_reverse_lazy(vec!['I'], id_fn, neighbors_fn, cycle_fn) .try_collect() .unwrap(); assert_eq!(common, vec!['I', 'D', 'B', 'H', 'F', 'G', 'E', 'C', 'A']); let common = topo_order_reverse_ord(vec!['I'], id_fn, neighbors_fn, cycle_fn).unwrap(); assert_eq!(common, vec!['I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A']); } #[test] fn test_topo_order_reverse_nested_merges_bad_order() { // This graph: // o I // |\ // | |\ // | | |\ // | | | o h (h > I) // | | |/| // | | o | G // | |/| o f // | o |/ e (e > I, G) // |/| o D // o |/ C // | o b (b > D) // |/ // o A let neighbors = hashmap! { 'A' => vec![], 'b' => vec!['A'], 'C' => vec!['A'], 'D' => vec!['b'], 'e' => vec!['C', 'b'], 'f' => vec!['D'], 'G' => vec!['e', 'D'], 'h' => vec!['G', 'f'], 'I' => vec!['C', 'e', 'G', 'h'], }; let id_fn = |node: &char| *node; let neighbors_fn = |node: &char| neighbors[node].clone(); let cycle_fn = |id| id; let common = topo_order_reverse(vec!['I'], id_fn, neighbors_fn, cycle_fn).unwrap(); assert_eq!(common, vec!['I', 'h', 'G', 'e', 'C', 'f', 'D', 'b', 'A']); let common: Vec<_> = topo_order_reverse_lazy(vec!['I'], id_fn, neighbors_fn, cycle_fn) .try_collect() .unwrap(); assert_eq!(common, vec!['I', 'h', 'G', 'e', 'C', 'f', 'D', 'b', 'A']); let common = topo_order_reverse_ord(vec!['I'], id_fn, neighbors_fn, cycle_fn).unwrap(); assert_eq!(common, vec!['I', 'h', 'f', 'G', 'e', 'D', 'b', 'C', 'A']); } #[test] fn test_topo_order_reverse_merge_bad_fork_order_at_root() { // This graph: // o E // |\ // o | D // | o C // | o B // |/ // o a (a > D, B) let neighbors = hashmap! { 'a' => vec![], 'B' => vec!['a'], 'C' => vec!['B'], 'D' => vec!['a'], 'E' => vec!['D', 'C'], }; let id_fn = |node: &char| *node; let neighbors_fn = |node: &char| neighbors[node].clone(); let cycle_fn = |id| id; let common = topo_order_reverse(vec!['E'], id_fn, neighbors_fn, cycle_fn).unwrap(); assert_eq!(common, vec!['E', 'D', 'C', 'B', 'a']); // The root node 'a' is visited before 'C'. If the graph were split there, // the branch 'C->B->a' would be orphaned. let common: Vec<_> = topo_order_reverse_lazy(vec!['E'], id_fn, neighbors_fn, cycle_fn) .try_collect() .unwrap(); assert_eq!(common, vec!['E', 'D', 'C', 'B', 'a']); let common = topo_order_reverse_ord(vec!['E'], id_fn, neighbors_fn, cycle_fn).unwrap(); assert_eq!(common, vec!['E', 'D', 'C', 'B', 'a']); } #[test] fn test_topo_order_reverse_merge_and_linear() { // This graph: // o G // |\ // | o F // o | E // | o D // |/ // o C // o B // o A let neighbors = hashmap! { 'A' => vec![], 'B' => vec!['A'], 'C' => vec!['B'], 'D' => vec!['C'], 'E' => vec!['C'], 'F' => vec!['D'], 'G' => vec!['E', 'F'], }; let id_fn = |node: &char| *node; let neighbors_fn = |node: &char| neighbors[node].clone(); let cycle_fn = |id| id; let common = topo_order_reverse(vec!['G'], id_fn, neighbors_fn, cycle_fn).unwrap(); assert_eq!(common, vec!['G', 'E', 'F', 'D', 'C', 'B', 'A']); let common: Vec<_> = topo_order_reverse_lazy(vec!['G'], id_fn, neighbors_fn, cycle_fn) .try_collect() .unwrap(); assert_eq!(common, vec!['G', 'E', 'F', 'D', 'C', 'B', 'A']); let common = topo_order_reverse_ord(vec!['G'], id_fn, neighbors_fn, cycle_fn).unwrap(); assert_eq!(common, vec!['G', 'F', 'E', 'D', 'C', 'B', 'A']); // Iterator can be lazy for linear chunks. let neighbors_fn = |node: &char| to_ok_iter(neighbors[node].iter().copied()); let mut inner_iter = TopoOrderReverseLazyInner::empty(); inner_iter.extend([Ok('G')]); assert_eq!( inner_iter.next(id_fn, neighbors_fn, cycle_fn), Some(Ok('G')) ); assert!(!inner_iter.start.is_empty()); assert!(inner_iter.result.is_empty()); assert_eq!( iter::from_fn(|| inner_iter.next(id_fn, neighbors_fn, cycle_fn)) .take(4) .collect_vec(), ['E', 'F', 'D', 'C'].map(Ok), ); assert!(!inner_iter.start.is_empty()); assert!(inner_iter.result.is_empty()); // Run each step of lazy iterator by using low-level function. let mut start = vec!['G']; let next = |start: &mut Vec| { topo_order_reverse_chunked(start, id_fn, neighbors_fn, cycle_fn).unwrap() }; assert_eq!(next(&mut start), ['G'].into()); assert_eq!(start, ['E', 'F']); assert_eq!(next(&mut start), ['E', 'F', 'D', 'C'].into()); assert_eq!(start, ['B']); assert_eq!(next(&mut start), ['B'].into()); assert_eq!(start, ['A']); assert_eq!(next(&mut start), ['A'].into()); assert!(start.is_empty()); assert!(next(&mut start).is_empty()); assert!(start.is_empty()); } #[test] fn test_topo_order_reverse_merge_and_linear_bad_fork_order() { // This graph: // o G // |\ // o | F // o | E // | o D // |/ // o c (c > E, D) // o B // o A let neighbors = hashmap! { 'A' => vec![], 'B' => vec!['A'], 'c' => vec!['B'], 'D' => vec!['c'], 'E' => vec!['c'], 'F' => vec!['E'], 'G' => vec!['F', 'D'], }; let id_fn = |node: &char| *node; let neighbors_fn = |node: &char| neighbors[node].clone(); let cycle_fn = |id| id; let common = topo_order_reverse(vec!['G'], id_fn, neighbors_fn, cycle_fn).unwrap(); assert_eq!(common, vec!['G', 'F', 'E', 'D', 'c', 'B', 'A']); let common: Vec<_> = topo_order_reverse_lazy(vec!['G'], id_fn, neighbors_fn, cycle_fn) .try_collect() .unwrap(); assert_eq!(common, vec!['G', 'F', 'E', 'D', 'c', 'B', 'A']); let common = topo_order_reverse_ord(vec!['G'], id_fn, neighbors_fn, cycle_fn).unwrap(); assert_eq!(common, vec!['G', 'F', 'E', 'D', 'c', 'B', 'A']); // Iterator can be lazy for linear chunks. The node 'c' is visited before 'D', // but it will be processed lazily. let neighbors_fn = |node: &char| to_ok_iter(neighbors[node].iter().copied()); let mut inner_iter = TopoOrderReverseLazyInner::empty(); inner_iter.extend([Ok('G')]); assert_eq!( inner_iter.next(id_fn, neighbors_fn, cycle_fn), Some(Ok('G')) ); assert!(!inner_iter.start.is_empty()); assert!(inner_iter.result.is_empty()); assert_eq!( iter::from_fn(|| inner_iter.next(id_fn, neighbors_fn, cycle_fn)) .take(4) .collect_vec(), ['F', 'E', 'D', 'c'].map(Ok), ); assert!(!inner_iter.start.is_empty()); assert!(inner_iter.result.is_empty()); // Run each step of lazy iterator by using low-level function. let mut start = vec!['G']; let next = |start: &mut Vec| { topo_order_reverse_chunked(start, id_fn, neighbors_fn, cycle_fn).unwrap() }; assert_eq!(next(&mut start), ['G'].into()); assert_eq!(start, ['F', 'D']); assert_eq!(next(&mut start), ['F', 'E', 'D', 'c'].into()); assert_eq!(start, ['B']); assert_eq!(next(&mut start), ['B'].into()); assert_eq!(start, ['A']); assert_eq!(next(&mut start), ['A'].into()); assert!(start.is_empty()); assert!(next(&mut start).is_empty()); assert!(start.is_empty()); } #[test] fn test_topo_order_reverse_merge_and_linear_bad_merge_order() { // This graph: // o G // |\ // o | f (f > G) // o | e // | o d (d > G) // |/ // o C // o B // o A let neighbors = hashmap! { 'A' => vec![], 'B' => vec!['A'], 'C' => vec!['B'], 'd' => vec!['C'], 'e' => vec!['C'], 'f' => vec!['e'], 'G' => vec!['f', 'd'], }; let id_fn = |node: &char| *node; let neighbors_fn = |node: &char| neighbors[node].clone(); let cycle_fn = |id| id; let common = topo_order_reverse(vec!['G'], id_fn, neighbors_fn, cycle_fn).unwrap(); assert_eq!(common, vec!['G', 'f', 'e', 'd', 'C', 'B', 'A']); let common: Vec<_> = topo_order_reverse_lazy(vec!['G'], id_fn, neighbors_fn, cycle_fn) .try_collect() .unwrap(); assert_eq!(common, vec!['G', 'f', 'e', 'd', 'C', 'B', 'A']); let common = topo_order_reverse_ord(vec!['G'], id_fn, neighbors_fn, cycle_fn).unwrap(); assert_eq!(common, vec!['G', 'f', 'e', 'd', 'C', 'B', 'A']); // Iterator can be lazy for linear chunks. let neighbors_fn = |node: &char| to_ok_iter(neighbors[node].iter().copied()); let mut inner_iter = TopoOrderReverseLazyInner::empty(); inner_iter.extend([Ok('G')]); assert_eq!( inner_iter.next(id_fn, neighbors_fn, cycle_fn), Some(Ok('G')) ); assert!(!inner_iter.start.is_empty()); assert!(inner_iter.result.is_empty()); assert_eq!( iter::from_fn(|| inner_iter.next(id_fn, neighbors_fn, cycle_fn)) .take(4) .collect_vec(), ['f', 'e', 'd', 'C'].map(Ok), ); assert!(!inner_iter.start.is_empty()); assert!(inner_iter.result.is_empty()); // Run each step of lazy iterator by using low-level function. let mut start = vec!['G']; let next = |start: &mut Vec| { topo_order_reverse_chunked(start, id_fn, neighbors_fn, cycle_fn).unwrap() }; assert_eq!(next(&mut start), ['G'].into()); assert_eq!(start, ['f', 'd']); assert_eq!(next(&mut start), ['f', 'e', 'd', 'C'].into()); assert_eq!(start, ['B']); assert_eq!(next(&mut start), ['B'].into()); assert_eq!(start, ['A']); assert_eq!(next(&mut start), ['A'].into()); assert!(start.is_empty()); assert!(next(&mut start).is_empty()); assert!(start.is_empty()); } #[test] fn test_topo_order_reverse_multiple_heads() { // This graph: // o F // |\ // o | E // | o D // | | o C // | | | // | | o B // | |/ // |/ // o A let neighbors = hashmap! { 'A' => vec![], 'B' => vec!['A'], 'C' => vec!['B'], 'D' => vec!['A'], 'E' => vec!['A'], 'F' => vec!['E', 'D'], }; let id_fn = |node: &char| *node; let neighbors_fn = |node: &char| neighbors[node].clone(); let cycle_fn = |id| id; let common = topo_order_reverse(vec!['F', 'C'], id_fn, neighbors_fn, cycle_fn).unwrap(); assert_eq!(common, vec!['F', 'E', 'D', 'C', 'B', 'A']); let common: Vec<_> = topo_order_reverse_lazy(vec!['F', 'C'], id_fn, neighbors_fn, cycle_fn) .try_collect() .unwrap(); assert_eq!(common, vec!['F', 'E', 'D', 'C', 'B', 'A']); let common = topo_order_reverse_ord(vec!['F', 'C'], id_fn, neighbors_fn, cycle_fn).unwrap(); assert_eq!(common, vec!['F', 'E', 'D', 'C', 'B', 'A']); } #[test] fn test_topo_order_reverse_multiple_roots() { // This graph: // o D // | \ // o | C // o B // o A let neighbors = hashmap! { 'A' => vec![], 'B' => vec!['A'], 'C' => vec![], 'D' => vec!['C', 'B'], }; let id_fn = |node: &char| *node; let neighbors_fn = |node: &char| neighbors[node].clone(); let cycle_fn = |id| id; let common = topo_order_reverse(vec!['D'], id_fn, neighbors_fn, cycle_fn).unwrap(); assert_eq!(common, vec!['D', 'C', 'B', 'A']); let common: Vec<_> = topo_order_reverse_lazy(vec!['D'], id_fn, neighbors_fn, cycle_fn) .try_collect() .unwrap(); assert_eq!(common, vec!['D', 'C', 'B', 'A']); let common = topo_order_reverse_ord(vec!['D'], id_fn, neighbors_fn, cycle_fn).unwrap(); assert_eq!(common, vec!['D', 'C', 'B', 'A']); } #[test] fn test_topo_order_reverse_cycle_linear() { // This graph: // o C // o B // o A (to C) let neighbors = hashmap! { 'A' => vec!['C'], 'B' => vec!['A'], 'C' => vec!['B'], }; let id_fn = |node: &char| *node; let neighbors_fn = |node: &char| neighbors[node].clone(); let cycle_fn = |id| id; let result: Result, _> = topo_order_reverse(vec!['C'], id_fn, neighbors_fn, cycle_fn); assert_matches!(result, Err('C' | 'B' | 'A')); let result = topo_order_reverse_lazy(vec!['C'], id_fn, neighbors_fn, cycle_fn) .take(4) .collect_vec(); assert_matches!( result[..], [Ok('C'), Ok('B'), Ok('A'), Err('C' | 'B' | 'A')] ); let result = topo_order_reverse_ord(vec!['C'], id_fn, neighbors_fn, cycle_fn); assert_matches!(result, Err('C' | 'B' | 'A')); } #[test] fn test_topo_order_reverse_cycle_to_branchy_sub_graph() { // This graph: // o D // |\ // | o C // |/ // o B // o A (to C) let neighbors = hashmap! { 'A' => vec!['C'], 'B' => vec!['A'], 'C' => vec!['B'], 'D' => vec!['B', 'C'], }; let id_fn = |node: &char| *node; let neighbors_fn = |node: &char| neighbors[node].clone(); let cycle_fn = |id| id; let result = topo_order_reverse(vec!['D'], id_fn, neighbors_fn, cycle_fn); assert_matches!(result, Err('C' | 'B' | 'A')); let result = topo_order_reverse_lazy(vec!['D'], id_fn, neighbors_fn, cycle_fn) .take(5) .collect_vec(); assert_matches!( result[..], [Ok('D'), Ok('C'), Ok('B'), Ok('A'), Err('C' | 'B' | 'A')] ); let result: Result, _> = topo_order_reverse_ord(vec!['D'], id_fn, neighbors_fn, cycle_fn); assert_matches!(result, Err('C' | 'B' | 'A')); } #[test] fn test_topo_order_reverse_lazy_cycle_within_branchy_sub_graph() { // This graph: // o D // |\ // | o C // |/ // o B (to C) // o A let neighbors = hashmap! { 'A' => vec![], 'B' => vec!['A', 'C'], 'C' => vec!['B'], 'D' => vec!['B', 'C'], }; let id_fn = |node: &char| *node; let neighbors_fn = |node: &char| neighbors[node].clone(); let cycle_fn = |id| id; let result = topo_order_reverse_lazy(['D'], id_fn, neighbors_fn, cycle_fn) .nth(1) .unwrap(); assert!(result.is_err()); // Try again with non-panicking cycle handler let neighbors_fn = |node: &char| neighbors[node].iter().copied().map(Ok); let cycle_fn = |node: char| node; assert_matches!( topo_order_reverse_lazy_ok([Ok('D')], id_fn, neighbors_fn, cycle_fn).nth(1), Some(Err('C' | 'B')) ); // Try again with low-level function let mut start = vec!['D']; topo_order_reverse_chunked(&mut start, id_fn, neighbors_fn, cycle_fn).unwrap(); assert_matches!( topo_order_reverse_chunked(&mut start, id_fn, neighbors_fn, cycle_fn), Err('C' | 'B') ); } #[test] fn test_topo_order_ok() { let neighbors = hashmap! { 'A' => vec![Err('Y')], 'B' => vec![Ok('A'), Err('X')], 'C' => vec![Ok('B')], }; let id_fn = |node: &char| *node; let neighbors_fn = |node: &char| neighbors[node].clone(); let cycle_fn = |id| id; // Terminates at Err('X') no matter if the sorting order is forward or // reverse. The visiting order matters. let result = topo_order_forward_ok([Ok('C')], id_fn, neighbors_fn, cycle_fn); assert_eq!(result, Err('X')); let result = topo_order_reverse_ok([Ok('C')], id_fn, neighbors_fn, cycle_fn); assert_eq!(result, Err('X')); let nodes = topo_order_reverse_lazy_ok([Ok('C')], id_fn, neighbors_fn, cycle_fn).collect_vec(); assert_eq!(nodes, [Ok('C'), Ok('B'), Err('X')]); let result = topo_order_reverse_ord_ok([Ok('C')], id_fn, neighbors_fn, cycle_fn); assert_eq!(result, Err('X')); } #[test] fn test_closest_common_node_tricky() { // Test this case where A is the shortest distance away, but we still want the // result to be B because A is an ancestor of B. In other words, we want // to minimize the longest distance. // // E H // |\ /| // | D G | // | C F | // \ \ / / // \ B / // \|/ // A let neighbors = hashmap! { 'A' => vec![], 'B' => vec!['A'], 'C' => vec!['B'], 'D' => vec!['C'], 'E' => vec!['A','D'], 'F' => vec!['B'], 'G' => vec!['F'], 'H' => vec!['A', 'G'], }; let common = closest_common_node( vec!['E'], vec!['H'], |node| *node, |node| neighbors[node].clone(), ); // TODO: fix the implementation to return B assert_eq!(common, Some('A')); } #[test] fn test_closest_common_node_ok() { let neighbors = hashmap! { 'A' => vec![Err('Y')], 'B' => vec![Ok('A')], 'C' => vec![Ok('A')], 'D' => vec![Err('X')], }; let id_fn = |node: &char| *node; let neighbors_fn = |node: &char| neighbors[node].clone(); let result = closest_common_node_ok([Ok('B')], [Ok('C')], id_fn, neighbors_fn); assert_eq!(result, Ok(Some('A'))); let result = closest_common_node_ok([Ok('C')], [Ok('D')], id_fn, neighbors_fn); assert_eq!(result, Err('X')); let result = closest_common_node_ok([Ok('C')], [Err('Z')], id_fn, neighbors_fn); assert_eq!(result, Err('Z')); } #[test] fn test_heads_mixed() { // Test the uppercase letters are in the start set // // D F // |/| // C e // |/ // b // | // A let neighbors = hashmap! { 'A' => vec![], 'b' => vec!['A'], 'C' => vec!['b'], 'D' => vec!['C'], 'e' => vec!['b'], 'F' => vec!['C', 'e'], }; let actual = heads( vec!['A', 'C', 'D', 'F'], |node| *node, |node| neighbors[node].clone(), ); assert_eq!(actual, hashset!['D', 'F']); // Check with a different order in the start set let actual = heads( vec!['F', 'D', 'C', 'A'], |node| *node, |node| neighbors[node].clone(), ); assert_eq!(actual, hashset!['D', 'F']); } #[test] fn test_heads_ok() { let neighbors = hashmap! { 'A' => vec![], 'B' => vec![Ok('A'), Err('X')], 'C' => vec![Ok('B')], }; let id_fn = |node: &char| *node; let neighbors_fn = |node: &char| neighbors[node].clone(); let result = heads_ok([Ok('C')], id_fn, neighbors_fn); assert_eq!(result, Ok(hashset! {'C'})); let result = heads_ok([Ok('B')], id_fn, neighbors_fn); assert_eq!(result, Ok(hashset! {'B'})); let result = heads_ok([Ok('A')], id_fn, neighbors_fn); assert_eq!(result, Ok(hashset! {'A'})); let result = heads_ok([Ok('C'), Ok('B')], id_fn, neighbors_fn); assert_eq!(result, Err('X')); let result = heads_ok([Ok('C'), Ok('A')], id_fn, neighbors_fn); assert_eq!(result, Err('X')); } }