dagops.rs
140 lines
| 4.3 KiB
| application/rls-services+xml
|
RustLexer
Georges Racinet on ishtar.racinet.fr
|
r41278 | // dagops.rs | ||
// | ||||
// Copyright 2019 Georges Racinet <georges.racinet@octobus.net> | ||||
// | ||||
// This software may be used and distributed according to the terms of the | ||||
// GNU General Public License version 2 or any later version. | ||||
//! Miscellaneous DAG operations | ||||
//! | ||||
//! # Terminology | ||||
//! - By *relative heads* of a collection of revision numbers (`Revision`), | ||||
//! we mean those revisions that have no children among the collection. | ||||
//! - Similarly *relative roots* of a collection of `Revision`, we mean | ||||
//! those whose parents, if any, don't belong to the collection. | ||||
use super::{Graph, GraphError, Revision, NULL_REVISION}; | ||||
use std::collections::HashSet; | ||||
fn remove_parents( | ||||
graph: &impl Graph, | ||||
rev: Revision, | ||||
set: &mut HashSet<Revision>, | ||||
) -> Result<(), GraphError> { | ||||
for parent in graph.parents(rev)?.iter() { | ||||
if *parent != NULL_REVISION { | ||||
set.remove(parent); | ||||
} | ||||
} | ||||
Ok(()) | ||||
} | ||||
/// Relative heads out of some revisions, passed as an iterator. | ||||
/// | ||||
/// These heads are defined as those revisions that have no children | ||||
/// among those emitted by the iterator. | ||||
/// | ||||
/// # Performance notes | ||||
/// Internally, this clones the iterator, and builds a `HashSet` out of it. | ||||
/// | ||||
/// This function takes an `Iterator` instead of `impl IntoIterator` to | ||||
/// guarantee that cloning the iterator doesn't result in cloning the full | ||||
/// construct it comes from. | ||||
pub fn heads<'a>( | ||||
graph: &impl Graph, | ||||
iter_revs: impl Clone + Iterator<Item = &'a Revision>, | ||||
) -> Result<HashSet<Revision>, GraphError> { | ||||
let mut heads: HashSet<Revision> = iter_revs.clone().cloned().collect(); | ||||
heads.remove(&NULL_REVISION); | ||||
for rev in iter_revs { | ||||
Georges Racinet
|
r41866 | if *rev != NULL_REVISION { | ||
remove_parents(graph, *rev, &mut heads)?; | ||||
} | ||||
Georges Racinet on ishtar.racinet.fr
|
r41278 | } | ||
Ok(heads) | ||||
} | ||||
/// Retain in `revs` only its relative heads. | ||||
/// | ||||
/// This is an in-place operation, so that control of the incoming | ||||
/// set is left to the caller. | ||||
/// - a direct Python binding would probably need to build its own `HashSet` | ||||
/// from an incoming iterable, even if its sole purpose is to extract the | ||||
/// heads. | ||||
/// - a Rust caller can decide whether cloning beforehand is appropriate | ||||
/// | ||||
/// # Performance notes | ||||
/// Internally, this function will store a full copy of `revs` in a `Vec`. | ||||
pub fn retain_heads( | ||||
graph: &impl Graph, | ||||
revs: &mut HashSet<Revision>, | ||||
) -> Result<(), GraphError> { | ||||
revs.remove(&NULL_REVISION); | ||||
// we need to construct an iterable copy of revs to avoid itering while | ||||
// mutating | ||||
let as_vec: Vec<Revision> = revs.iter().cloned().collect(); | ||||
for rev in as_vec { | ||||
Georges Racinet
|
r41866 | if rev != NULL_REVISION { | ||
remove_parents(graph, rev, revs)?; | ||||
} | ||||
Georges Racinet on ishtar.racinet.fr
|
r41278 | } | ||
Ok(()) | ||||
} | ||||
#[cfg(test)] | ||||
mod tests { | ||||
use super::*; | ||||
use crate::testing::SampleGraph; | ||||
/// Apply `retain_heads()` to the given slice and return as a sorted `Vec` | ||||
fn retain_heads_sorted( | ||||
graph: &impl Graph, | ||||
revs: &[Revision], | ||||
) -> Result<Vec<Revision>, GraphError> { | ||||
let mut revs: HashSet<Revision> = revs.iter().cloned().collect(); | ||||
retain_heads(graph, &mut revs)?; | ||||
let mut as_vec: Vec<Revision> = revs.iter().cloned().collect(); | ||||
as_vec.sort(); | ||||
Ok(as_vec) | ||||
} | ||||
#[test] | ||||
fn test_retain_heads() -> Result<(), GraphError> { | ||||
assert_eq!(retain_heads_sorted(&SampleGraph, &[4, 5, 6])?, vec![5, 6]); | ||||
assert_eq!( | ||||
retain_heads_sorted(&SampleGraph, &[4, 1, 6, 12, 0])?, | ||||
vec![1, 6, 12] | ||||
); | ||||
assert_eq!( | ||||
retain_heads_sorted(&SampleGraph, &[1, 2, 3, 4, 5, 6, 7, 8, 9])?, | ||||
vec![3, 5, 8, 9] | ||||
); | ||||
Ok(()) | ||||
} | ||||
/// Apply `heads()` to the given slice and return as a sorted `Vec` | ||||
fn heads_sorted( | ||||
graph: &impl Graph, | ||||
revs: &[Revision], | ||||
) -> Result<Vec<Revision>, GraphError> { | ||||
let heads = heads(graph, revs.iter())?; | ||||
let mut as_vec: Vec<Revision> = heads.iter().cloned().collect(); | ||||
as_vec.sort(); | ||||
Ok(as_vec) | ||||
} | ||||
#[test] | ||||
fn test_heads() -> Result<(), GraphError> { | ||||
assert_eq!(heads_sorted(&SampleGraph, &[4, 5, 6])?, vec![5, 6]); | ||||
assert_eq!( | ||||
heads_sorted(&SampleGraph, &[4, 1, 6, 12, 0])?, | ||||
vec![1, 6, 12] | ||||
); | ||||
assert_eq!( | ||||
heads_sorted(&SampleGraph, &[1, 2, 3, 4, 5, 6, 7, 8, 9])?, | ||||
vec![3, 5, 8, 9] | ||||
); | ||||
Ok(()) | ||||
} | ||||
} | ||||