#![cfg_attr(docsrs, feature(doc_cfg))]
use std::{
borrow::Borrow,
collections::{HashMap, HashSet, hash_map},
fmt,
hash::Hash,
iter::{FromIterator, FusedIterator},
};
#[derive(Clone, Debug)]
struct Node<T> {
num_prec: usize,
succ: HashSet<T>,
}
impl<T> Node<T>
where
T: Eq + Hash,
{
fn new() -> Node<T> {
Node {
num_prec: 0,
succ: HashSet::new(),
}
}
fn is_ready(&self) -> bool {
self.num_prec == 0
}
}
#[derive(Clone)]
pub struct TopologicalSort<T> {
nodes: HashMap<T, Node<T>>,
}
impl<T> Default for TopologicalSort<T> {
fn default() -> TopologicalSort<T> {
TopologicalSort {
nodes: HashMap::new(),
}
}
}
impl<T> fmt::Debug for TopologicalSort<T>
where
T: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_map()
.entries(self.nodes.iter().map(|(k, dep)| (k, &dep.succ)))
.finish()
}
}
impl<T> TopologicalSort<T>
where
T: Clone + Eq + Hash,
{
#[inline]
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.nodes.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
pub fn add_dependency<P, S>(&mut self, prec: P, succ: S) -> bool
where
P: Into<T>,
S: Into<T>,
{
let prec = prec.into();
let succ = succ.into();
let prec_node = self.nodes.entry(prec).or_insert_with(Node::new);
if !prec_node.succ.insert(succ.clone()) {
return false;
}
self.nodes.entry(succ).or_insert_with(Node::new).num_prec += 1;
true
}
pub fn add_link(&mut self, link: DependencyLink<T>) -> bool {
self.add_dependency(link.prec, link.succ)
}
pub fn insert<U>(&mut self, item: U) -> bool
where
U: Into<T>,
{
match self.nodes.entry(item.into()) {
hash_map::Entry::Vacant(e) => {
e.insert(Node::new());
true
}
hash_map::Entry::Occupied(_) => false,
}
}
pub fn pop(&mut self) -> Option<T> {
let (item, node) = self.nodes.extract_if(|_, node| node.is_ready()).next()?;
for succ in node.succ {
if let Some(succ_node) = self.nodes.get_mut(&succ) {
succ_node.num_prec -= 1;
}
}
Some(item)
}
pub fn pop_iter(&mut self) -> PopIter<'_, T> {
PopIter { ts: self }
}
#[deprecated(
since = "0.3.0",
note = "Use `pop_batch` instead, which returns an arbitrary collection containing all ready items."
)]
pub fn pop_all(&mut self) -> Vec<T> {
self.pop_batch()
}
pub fn pop_batch<R>(&mut self) -> R
where
R: Default + Extend<T>,
{
let (items, nodes) = self
.nodes
.extract_if(|_, node| node.is_ready())
.collect::<(R, Vec<_>)>();
for node in nodes {
for succ in node.succ {
if let Some(succ_node) = self.nodes.get_mut(&succ) {
succ_node.num_prec -= 1;
}
}
}
items
}
#[must_use]
pub fn peek(&self) -> Option<&T> {
let (item, _) = self.nodes.iter().find(|&(_, node)| node.is_ready())?;
Some(item)
}
#[deprecated(
since = "0.3.0",
note = "Use `peek_batch` instead, which returns an iterator over all ready items."
)]
#[must_use]
pub fn peek_all(&self) -> Vec<&T> {
self.peek_batch().collect()
}
pub fn peek_batch(&self) -> PeekBatch<'_, T> {
PeekBatch {
iter: self.nodes.iter(),
}
}
pub fn items(&self) -> Items<'_, T> {
Items {
iter: self.nodes.keys(),
}
}
pub fn into_items(self) -> IntoItems<T> {
IntoItems {
iter: self.nodes.into_keys(),
}
}
pub fn remove<Q>(&mut self, item: &Q) -> Option<T>
where
T: Borrow<Q>,
Q: Eq + Hash + ?Sized,
{
let node = self.nodes.get(item)?;
if !node.is_ready() {
return None;
}
let (item, node) = self.nodes.remove_entry(item)?;
for succ in node.succ {
if let Some(succ_node) = self.nodes.get_mut(succ.borrow()) {
succ_node.num_prec -= 1;
}
}
Some(item)
}
}
#[derive(Copy, Clone, Debug)]
pub struct DependencyLink<T> {
pub prec: T,
pub succ: T,
}
impl<T> FromIterator<DependencyLink<T>> for TopologicalSort<T>
where
T: Clone + Eq + Hash,
{
fn from_iter<I>(iter: I) -> TopologicalSort<T>
where
I: IntoIterator<Item = DependencyLink<T>>,
{
let mut ts = TopologicalSort::new();
ts.extend(iter);
ts
}
}
impl<T> Extend<DependencyLink<T>> for TopologicalSort<T>
where
T: Clone + Eq + Hash,
{
fn extend<I>(&mut self, iter: I)
where
I: IntoIterator<Item = DependencyLink<T>>,
{
for link in iter {
self.add_link(link);
}
}
}
#[derive(Debug)]
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct PopIter<'a, T> {
ts: &'a mut TopologicalSort<T>,
}
impl<T> Iterator for PopIter<'_, T>
where
T: Clone + Eq + Hash,
{
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
self.ts.pop()
}
fn size_hint(&self) -> (usize, Option<usize>) {
(0, Some(self.ts.len()))
}
}
impl<T> FusedIterator for PopIter<'_, T> where T: Clone + Eq + Hash {}
#[derive(Debug)]
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct PeekBatch<'a, T> {
iter: hash_map::Iter<'a, T, Node<T>>,
}
impl<'a, T> Iterator for PeekBatch<'a, T>
where
T: Clone + Eq + Hash,
{
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
let (item, _) = self.iter.find(|&(_, node)| node.is_ready())?;
Some(item)
}
fn size_hint(&self) -> (usize, Option<usize>) {
(0, Some(self.iter.len()))
}
}
impl<T> FusedIterator for PeekBatch<'_, T> where T: Clone + Eq + Hash {}
#[derive(Debug)]
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct Items<'a, T> {
iter: hash_map::Keys<'a, T, Node<T>>,
}
impl<'a, T> Iterator for Items<'a, T>
where
T: Clone + Eq + Hash,
{
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
impl<T> ExactSizeIterator for Items<'_, T> where T: Clone + Eq + Hash {}
impl<T> FusedIterator for Items<'_, T> where T: Clone + Eq + Hash {}
#[derive(Debug)]
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct IntoItems<T> {
iter: hash_map::IntoKeys<T, Node<T>>,
}
impl<T> Iterator for IntoItems<T>
where
T: Clone + Eq + Hash,
{
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
impl<T> ExactSizeIterator for IntoItems<T> where T: Clone + Eq + Hash {}
impl<T> FusedIterator for IntoItems<T> where T: Clone + Eq + Hash {}
#[cfg(test)]
mod tests {
use quickcheck_macros::quickcheck;
use super::*;
#[test]
fn add_dependency_returns_true_if_new_dependency_link_created() {
let mut ts = TopologicalSort::<&str>::new();
assert!(ts.add_dependency("stone", "sharp"));
assert_eq!(ts.len(), 2);
assert!(!ts.add_dependency("stone", "sharp"));
assert_eq!(ts.len(), 2);
assert!(ts.add_dependency("sharp", "paper"));
assert_eq!(ts.len(), 3);
assert!(!ts.add_dependency("sharp", "paper"));
assert_eq!(ts.len(), 3);
assert!(ts.add_dependency("paper", "stone"));
assert_eq!(ts.len(), 3);
assert!(!ts.add_dependency("paper", "stone"));
assert_eq!(ts.len(), 3);
}
#[test]
fn add_link_returns_true_if_new_dependency_link_created() {
let mut ts = TopologicalSort::<&str>::new();
assert!(ts.add_link(DependencyLink {
prec: "stone",
succ: "sharp",
}));
assert!(!ts.add_link(DependencyLink {
prec: "stone",
succ: "sharp",
}));
assert_eq!(ts.len(), 2);
}
#[test]
fn pop_iter_iterates_all_items_in_topological_order() {
let mut ts = TopologicalSort::<i32>::new();
ts.add_dependency(1, 2);
ts.add_dependency(2, 3);
ts.add_dependency(3, 4);
ts.add_dependency(4, 5);
ts.add_dependency(5, 6);
let mut it = ts.pop_iter();
assert_eq!(Some(1), it.next());
assert_eq!(Some(2), it.next());
assert_eq!(Some(3), it.next());
assert_eq!(Some(4), it.next());
assert_eq!(Some(5), it.next());
assert_eq!(Some(6), it.next());
assert_eq!(None, it.next());
assert_eq!(None, it.next());
}
#[test]
fn pop_iter_stops_on_a_cycle() {
let mut ts = TopologicalSort::<i32>::new();
ts.add_dependency(1, 2);
ts.add_dependency(2, 3);
ts.add_dependency(3, 4);
ts.add_dependency(4, 5);
ts.add_dependency(5, 5);
ts.add_dependency(5, 6);
let mut it = ts.pop_iter();
assert_eq!(Some(1), it.next());
assert_eq!(Some(2), it.next());
assert_eq!(Some(3), it.next());
assert_eq!(Some(4), it.next());
assert_eq!(None, it.next());
assert_eq!(None, it.next());
}
#[test]
fn pop_batch_returns_all_currently_ready_items() {
fn check(result: &[i32], ts: &mut TopologicalSort<i32>) {
let l = ts.len();
let mut v = ts.pop_batch::<Vec<_>>();
v.sort_unstable();
assert_eq!(result, &v[..]);
assert_eq!(l - result.len(), ts.len());
}
let mut ts = TopologicalSort::new();
ts.add_dependency(7, 11);
assert_eq!(2, ts.len());
ts.add_dependency(7, 8);
assert_eq!(3, ts.len());
ts.add_dependency(5, 11);
assert_eq!(4, ts.len());
ts.add_dependency(3, 8);
assert_eq!(5, ts.len());
ts.add_dependency(3, 10);
assert_eq!(6, ts.len());
ts.add_dependency(11, 2);
assert_eq!(7, ts.len());
ts.add_dependency(11, 9);
assert_eq!(8, ts.len());
ts.add_dependency(11, 10);
assert_eq!(8, ts.len());
ts.add_dependency(8, 9);
assert_eq!(8, ts.len());
check(&[3, 5, 7], &mut ts);
check(&[8, 11], &mut ts);
check(&[2, 9, 10], &mut ts);
check(&[], &mut ts);
}
#[test]
fn self_dependency_blocks_the_remaining_element() {
let mut ts = TopologicalSort::<&str>::new();
ts.add_dependency("stone", "sharp");
ts.add_dependency("sharp", "sharp");
ts.add_dependency("sharp", "water");
assert_eq!(ts.len(), 3);
assert_eq!(ts.pop(), Some("stone"));
assert_eq!(ts.len(), 2);
assert_eq!(ts.pop(), None);
}
#[test]
fn pop_returns_none_when_remaining_elements_are_cyclic() {
let mut ts = TopologicalSort::new();
ts.add_dependency("stone", "sharp");
ts.add_dependency("bucket", "hole");
ts.add_dependency("hole", "straw");
ts.add_dependency("straw", "axe");
ts.add_dependency("axe", "sharp");
ts.add_dependency("sharp", "water");
ts.add_dependency("water", "bucket");
assert_eq!(ts.pop(), Some("stone"));
assert!(ts.pop().is_none());
}
#[test]
fn add_link_can_create_a_cycle_that_blocks_remaining_elements() {
let mut ts = TopologicalSort::<&str>::new();
ts.add_link(DependencyLink {
prec: "omelet",
succ: "egg",
});
ts.add_link(DependencyLink {
prec: "egg",
succ: "chicken",
});
ts.add_link(DependencyLink {
prec: "chicken",
succ: "egg",
});
assert_eq!(ts.len(), 3);
assert_eq!(ts.pop(), Some("omelet"));
assert_eq!(ts.pop(), None);
}
#[test]
fn remove_removes_item_only_if_exists_and_ready() {
let mut ts = TopologicalSort::<&str>::new();
ts.add_dependency("a", "b");
ts.add_dependency("b", "c");
ts.add_dependency("c", "d");
assert!(ts.remove("x").is_none());
assert!(ts.remove("c").is_none());
assert_eq!(ts.remove("a").unwrap(), "a");
assert!(ts.remove("c").is_none());
assert_eq!(ts.remove("b").unwrap(), "b");
assert_eq!(ts.remove("c").unwrap(), "c");
}
#[test]
fn items_and_into_items_iterate_all_remaining_items() {
let mut ts = TopologicalSort::<&str>::new();
ts.add_dependency("a", "b");
ts.add_dependency("b", "c");
ts.add_dependency("c", "d");
let mut items = ts.items().copied().collect::<Vec<_>>();
items.sort_unstable();
assert_eq!(items, ["a", "b", "c", "d"]);
let mut into_items = ts.into_items().collect::<Vec<_>>();
into_items.sort_unstable();
assert_eq!(into_items, ["a", "b", "c", "d"]);
}
#[quickcheck]
fn quickcheck_topological_sort_invariants(n: usize, edges: Vec<(usize, usize)>) {
use std::collections::{HashMap, HashSet};
let n = n.clamp(1, 1000);
let mut marked = vec![false; n];
let edges = edges
.into_iter()
.map(|(x, y)| (x % n, y % n))
.collect::<Vec<_>>();
let mut deps = HashMap::new();
let mut toposort = TopologicalSort::<usize>::new();
for i in 0..n {
deps.insert(i, HashSet::new());
assert!(toposort.insert(i));
}
for (op, inp) in edges.iter().map(|(x, y)| (y, x)) {
let inps = deps.get_mut(op).unwrap();
inps.insert(*inp);
}
let deps = deps;
for (inp, op) in edges {
toposort.add_dependency(inp, op);
}
while let Some(x) = toposort.pop() {
for dep in &deps[&x] {
assert!(marked[*dep]);
}
marked[x] = true;
}
if toposort.is_empty() {
assert!(marked.into_iter().all(|x| x));
} else {
let dep_fixed = {
let mut ret = (0..n)
.map(|i| (i, HashSet::new()))
.collect::<HashMap<_, _>>();
let mut new_to_add = deps;
while !new_to_add.is_empty() {
for (k, v) in new_to_add.drain() {
let inps = ret.get_mut(&k).unwrap();
inps.extend(v.into_iter());
}
for (k, vs) in &ret {
for k2 in vs {
for v2 in &ret[k2] {
if !vs.contains(v2) {
new_to_add
.entry(*k)
.or_insert_with(HashSet::new)
.insert(*v2);
}
}
}
}
}
ret
};
assert!(dep_fixed.into_iter().any(|(op, deps)| deps.contains(&op)));
}
}
}