2020-12-12 08:00:42 +00:00
|
|
|
// Copyright 2020 Google LLC
|
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
|
|
|
|
use std::collections::HashSet;
|
2021-03-14 17:37:28 +00:00
|
|
|
use std::hash::Hash;
|
2020-12-12 08:00:42 +00:00
|
|
|
use std::iter::Iterator;
|
|
|
|
|
|
|
|
use crate::commit::Commit;
|
|
|
|
|
|
|
|
pub struct BfsIter<'id_fn, 'neighbors_fn, T, ID, NI> {
|
|
|
|
id_fn: Box<dyn Fn(&T) -> ID + 'id_fn>,
|
|
|
|
neighbors_fn: Box<dyn FnMut(&T) -> NI + 'neighbors_fn>,
|
|
|
|
work: Vec<T>,
|
|
|
|
visited: HashSet<ID>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T, ID, NI> Iterator for BfsIter<'_, '_, T, ID, NI>
|
|
|
|
where
|
|
|
|
ID: Hash + Eq,
|
|
|
|
NI: IntoIterator<Item = T>,
|
|
|
|
{
|
|
|
|
type Item = T;
|
|
|
|
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
2022-04-23 01:01:49 +00:00
|
|
|
loop {
|
|
|
|
let c = self.work.pop()?;
|
2020-12-12 08:00:42 +00:00
|
|
|
let id = (self.id_fn)(&c);
|
|
|
|
if self.visited.contains(&id) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
for p in (self.neighbors_fn)(&c) {
|
|
|
|
self.work.push(p);
|
|
|
|
}
|
|
|
|
self.visited.insert(id);
|
|
|
|
return Some(c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn bfs<'id_fn, 'neighbors_fn, T, ID, II, NI>(
|
|
|
|
start: II,
|
|
|
|
id_fn: Box<dyn Fn(&T) -> ID + 'id_fn>,
|
|
|
|
neighbors_fn: Box<dyn FnMut(&T) -> NI + 'neighbors_fn>,
|
|
|
|
) -> BfsIter<'id_fn, 'neighbors_fn, T, ID, NI>
|
|
|
|
where
|
|
|
|
ID: Hash + Eq,
|
|
|
|
II: IntoIterator<Item = T>,
|
|
|
|
NI: IntoIterator<Item = T>,
|
|
|
|
{
|
|
|
|
BfsIter {
|
|
|
|
id_fn,
|
|
|
|
neighbors_fn,
|
|
|
|
work: start.into_iter().collect(),
|
|
|
|
visited: Default::default(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns neighbors before the node itself.
|
2021-01-31 02:50:27 +00:00
|
|
|
pub fn topo_order_reverse<'a, T, ID, II, NI>(
|
2020-12-12 08:00:42 +00:00
|
|
|
start: II,
|
2021-01-31 02:50:27 +00:00
|
|
|
id_fn: Box<dyn Fn(&T) -> ID + 'a>,
|
|
|
|
mut neighbors_fn: Box<dyn FnMut(&T) -> NI + 'a>,
|
2020-12-12 08:00:42 +00:00
|
|
|
) -> Vec<T>
|
|
|
|
where
|
|
|
|
T: Hash + Eq + Clone,
|
|
|
|
ID: Hash + Eq + Clone,
|
|
|
|
II: IntoIterator<Item = T>,
|
|
|
|
NI: IntoIterator<Item = T>,
|
|
|
|
{
|
|
|
|
let mut visiting = HashSet::new();
|
|
|
|
let mut emitted = HashSet::new();
|
|
|
|
let mut result = vec![];
|
|
|
|
|
2021-06-09 20:57:48 +00:00
|
|
|
let mut start_nodes: Vec<T> = start.into_iter().collect();
|
2020-12-12 08:00:42 +00:00
|
|
|
start_nodes.reverse();
|
|
|
|
|
|
|
|
for start_node in start_nodes {
|
|
|
|
let mut stack = vec![(start_node, false)];
|
2022-05-01 05:55:58 +00:00
|
|
|
while let Some((node, neighbors_visited)) = stack.pop() {
|
2020-12-12 08:00:42 +00:00
|
|
|
let id = id_fn(&node);
|
|
|
|
if emitted.contains(&id) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if !neighbors_visited {
|
|
|
|
assert!(visiting.insert(id.clone()), "graph has cycle");
|
|
|
|
let neighbors = neighbors_fn(&node);
|
|
|
|
stack.push((node, true));
|
|
|
|
for neighbor in neighbors {
|
|
|
|
stack.push((neighbor, false));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
visiting.remove(&id);
|
|
|
|
emitted.insert(id);
|
|
|
|
result.push(node);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result.reverse();
|
|
|
|
result
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn leaves<T, ID, II, NI>(
|
|
|
|
start: II,
|
|
|
|
neighbors_fn: &mut impl FnMut(&T) -> NI,
|
|
|
|
id_fn: &impl Fn(&T) -> ID,
|
|
|
|
) -> HashSet<T>
|
|
|
|
where
|
|
|
|
T: Hash + Eq + Clone,
|
|
|
|
ID: Hash + Eq,
|
|
|
|
II: IntoIterator<Item = T>,
|
|
|
|
NI: IntoIterator<Item = T>,
|
|
|
|
{
|
|
|
|
let mut visited = HashSet::new();
|
|
|
|
let mut work: Vec<T> = start.into_iter().collect();
|
|
|
|
let mut leaves: HashSet<T> = work.iter().cloned().collect();
|
|
|
|
let mut non_leaves = HashSet::new();
|
|
|
|
while !work.is_empty() {
|
|
|
|
// TODO: make this not waste so much memory on the sets
|
|
|
|
let mut new_work = vec![];
|
|
|
|
for c in work {
|
|
|
|
let id: ID = id_fn(&c);
|
|
|
|
if visited.contains(&id) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
for p in neighbors_fn(&c) {
|
|
|
|
non_leaves.insert(c.clone());
|
|
|
|
new_work.push(p);
|
|
|
|
}
|
|
|
|
visited.insert(id);
|
|
|
|
leaves.insert(c);
|
|
|
|
}
|
|
|
|
work = new_work;
|
|
|
|
}
|
|
|
|
leaves.difference(&non_leaves).cloned().collect()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Find nodes in the start set that are not reachable from other nodes in the
|
|
|
|
/// start set.
|
2021-03-17 06:39:22 +00:00
|
|
|
pub fn heads<T, ID, II, NI>(
|
2020-12-12 08:00:42 +00:00
|
|
|
start: II,
|
|
|
|
neighbors_fn: &impl Fn(&T) -> NI,
|
|
|
|
id_fn: &impl Fn(&T) -> ID,
|
|
|
|
) -> HashSet<T>
|
|
|
|
where
|
|
|
|
T: Hash + Eq + Clone,
|
|
|
|
ID: Hash + Eq,
|
|
|
|
II: IntoIterator<Item = T>,
|
|
|
|
NI: IntoIterator<Item = T>,
|
|
|
|
{
|
|
|
|
let start: Vec<T> = start.into_iter().collect();
|
|
|
|
let mut reachable: HashSet<T> = start.iter().cloned().collect();
|
|
|
|
for _node in bfs(
|
|
|
|
start.into_iter(),
|
|
|
|
Box::new(id_fn),
|
|
|
|
Box::new(|node| {
|
|
|
|
let neighbors: Vec<T> = neighbors_fn(node).into_iter().collect();
|
|
|
|
for neighbor in &neighbors {
|
2021-06-14 07:18:38 +00:00
|
|
|
reachable.remove(neighbor);
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
neighbors
|
|
|
|
}),
|
|
|
|
) {}
|
|
|
|
reachable
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn common_ancestor<'a, I1, I2>(set1: I1, set2: I2) -> Commit
|
|
|
|
where
|
|
|
|
I1: IntoIterator<Item = &'a Commit>,
|
|
|
|
I2: IntoIterator<Item = &'a Commit>,
|
|
|
|
{
|
|
|
|
let set1: Vec<Commit> = set1.into_iter().cloned().collect();
|
|
|
|
let set2: Vec<Commit> = set2.into_iter().cloned().collect();
|
|
|
|
closest_common_node(set1, set2, &|commit| commit.parents(), &|commit| {
|
|
|
|
commit.id().clone()
|
|
|
|
})
|
|
|
|
.unwrap()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn closest_common_node<T, ID, II1, II2, NI>(
|
|
|
|
set1: II1,
|
|
|
|
set2: II2,
|
|
|
|
neighbors_fn: &impl Fn(&T) -> NI,
|
|
|
|
id_fn: &impl Fn(&T) -> ID,
|
|
|
|
) -> Option<T>
|
|
|
|
where
|
|
|
|
T: Hash + Eq + Clone,
|
|
|
|
ID: Hash + Eq,
|
|
|
|
II1: IntoIterator<Item = T>,
|
|
|
|
II2: IntoIterator<Item = T>,
|
|
|
|
NI: IntoIterator<Item = T>,
|
|
|
|
{
|
|
|
|
let mut visited1 = HashSet::new();
|
|
|
|
let mut visited2 = HashSet::new();
|
|
|
|
|
|
|
|
let mut work1: Vec<T> = set1.into_iter().collect();
|
|
|
|
let mut work2: Vec<T> = set2.into_iter().collect();
|
|
|
|
while !work1.is_empty() || !work2.is_empty() {
|
|
|
|
let mut new_work1 = vec![];
|
|
|
|
for node in work1 {
|
|
|
|
let id: ID = id_fn(&node);
|
|
|
|
if visited2.contains(&id) {
|
|
|
|
return 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 id: ID = id_fn(&node);
|
|
|
|
if visited1.contains(&id) {
|
|
|
|
return Some(node);
|
|
|
|
}
|
|
|
|
if visited2.insert(id) {
|
|
|
|
for neighbor in neighbors_fn(&node) {
|
|
|
|
new_work2.push(neighbor);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
work2 = new_work2;
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2022-09-22 04:52:04 +00:00
|
|
|
use maplit::{hashmap, hashset};
|
|
|
|
|
2020-12-12 08:00:42 +00:00
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
2021-03-17 06:39:22 +00:00
|
|
|
fn test_topo_order_reverse_linear() {
|
2020-12-12 08:00:42 +00:00
|
|
|
// This graph:
|
|
|
|
// o C
|
|
|
|
// o B
|
|
|
|
// o A
|
|
|
|
|
2020-12-23 17:39:09 +00:00
|
|
|
let neighbors = hashmap! {
|
|
|
|
'A' => vec![],
|
|
|
|
'B' => vec!['A'],
|
|
|
|
'C' => vec!['B'],
|
|
|
|
};
|
2020-12-12 08:00:42 +00:00
|
|
|
|
|
|
|
let common = topo_order_reverse(
|
|
|
|
vec!['C'],
|
|
|
|
Box::new(|node| *node),
|
|
|
|
Box::new(move |node| neighbors[node].clone()),
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(common, vec!['C', 'B', 'A']);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2021-03-17 06:39:22 +00:00
|
|
|
fn test_topo_order_reverse_merge() {
|
2020-12-12 08:00:42 +00:00
|
|
|
// This graph:
|
|
|
|
// o F
|
|
|
|
// |\
|
|
|
|
// o | E
|
|
|
|
// | o D
|
|
|
|
// | o C
|
|
|
|
// | o B
|
|
|
|
// |/
|
|
|
|
// o A
|
|
|
|
|
2020-12-23 17:39:09 +00:00
|
|
|
let neighbors = hashmap! {
|
|
|
|
'A' => vec![],
|
|
|
|
'B' => vec!['A'],
|
|
|
|
'C' => vec!['B'],
|
|
|
|
'D' => vec!['C'],
|
|
|
|
'E' => vec!['A'],
|
|
|
|
'F' => vec!['E', 'D'],
|
|
|
|
};
|
2020-12-12 08:00:42 +00:00
|
|
|
|
|
|
|
let common = topo_order_reverse(
|
|
|
|
vec!['F'],
|
|
|
|
Box::new(|node| *node),
|
|
|
|
Box::new(move |node| neighbors[node].clone()),
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(common, vec!['F', 'E', 'D', 'C', 'B', 'A']);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2021-03-17 06:39:22 +00:00
|
|
|
fn test_topo_order_reverse_multiple_heads() {
|
2020-12-12 08:00:42 +00:00
|
|
|
// This graph:
|
|
|
|
// o F
|
|
|
|
// |\
|
|
|
|
// o | E
|
|
|
|
// | o D
|
|
|
|
// | | o C
|
|
|
|
// | | |
|
|
|
|
// | | o B
|
|
|
|
// | |/
|
|
|
|
// |/
|
|
|
|
// o A
|
|
|
|
|
2020-12-23 17:39:09 +00:00
|
|
|
let neighbors = hashmap! {
|
|
|
|
'A' => vec![],
|
|
|
|
'B' => vec!['A'],
|
|
|
|
'C' => vec!['B'],
|
|
|
|
'D' => vec!['A'],
|
|
|
|
'E' => vec!['A'],
|
|
|
|
'F' => vec!['E', 'D'],
|
|
|
|
};
|
2020-12-12 08:00:42 +00:00
|
|
|
|
|
|
|
let common = topo_order_reverse(
|
|
|
|
vec!['F', 'C'],
|
|
|
|
Box::new(|node| *node),
|
|
|
|
Box::new(move |node| neighbors[node].clone()),
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(common, vec!['F', 'E', 'D', 'C', 'B', 'A']);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2021-03-17 06:39:22 +00:00
|
|
|
fn test_closest_common_node_tricky() {
|
2020-12-12 08:00:42 +00:00
|
|
|
// 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
|
|
|
|
|
2020-12-23 17:39:09 +00:00
|
|
|
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'],
|
|
|
|
};
|
2020-12-12 08:00:42 +00:00
|
|
|
|
|
|
|
let common = closest_common_node(
|
|
|
|
vec!['E'],
|
|
|
|
vec!['H'],
|
|
|
|
&|node| neighbors[node].clone(),
|
|
|
|
&|node| *node,
|
|
|
|
);
|
|
|
|
|
|
|
|
// TODO: fix the implementation to return B
|
|
|
|
assert_eq!(common, Some('A'));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2021-03-17 06:39:22 +00:00
|
|
|
fn test_heads_mixed() {
|
2020-12-12 08:00:42 +00:00
|
|
|
// Test the uppercase letters are in the start set
|
|
|
|
//
|
|
|
|
// D F
|
|
|
|
// |/|
|
|
|
|
// C e
|
|
|
|
// |/
|
|
|
|
// b
|
|
|
|
// |
|
|
|
|
// A
|
|
|
|
|
2020-12-23 17:39:09 +00:00
|
|
|
let neighbors = hashmap! {
|
|
|
|
'A' => vec![],
|
|
|
|
'b' => vec!['A'],
|
|
|
|
'C' => vec!['b'],
|
|
|
|
'D' => vec!['C'],
|
|
|
|
'e' => vec!['b'],
|
|
|
|
'F' => vec!['C', 'e'],
|
|
|
|
};
|
2020-12-12 08:00:42 +00:00
|
|
|
|
2021-03-17 06:39:22 +00:00
|
|
|
let actual = heads(
|
2020-12-12 08:00:42 +00:00
|
|
|
vec!['A', 'C', 'D', 'F'],
|
|
|
|
&|node| neighbors[node].clone(),
|
|
|
|
&|node| *node,
|
|
|
|
);
|
2021-06-09 20:57:48 +00:00
|
|
|
assert_eq!(actual, hashset!['D', 'F']);
|
2020-12-12 08:00:42 +00:00
|
|
|
|
|
|
|
// Check with a different order in the start set
|
2021-03-17 06:39:22 +00:00
|
|
|
let actual = heads(
|
2020-12-12 08:00:42 +00:00
|
|
|
vec!['F', 'D', 'C', 'A'],
|
|
|
|
&|node| neighbors[node].clone(),
|
|
|
|
&|node| *node,
|
|
|
|
);
|
2021-06-09 20:57:48 +00:00
|
|
|
assert_eq!(actual, hashset!['D', 'F']);
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
}
|