use std::collections::{HashSet, VecDeque};
pub(crate) struct Worklist {
queue: VecDeque<String>,
visited: HashSet<String>,
}
impl Worklist {
pub(crate) fn new(endpoints: Vec<String>) -> Self {
Worklist {
queue: endpoints.into(),
visited: HashSet::new(),
}
}
pub(crate) fn next(&mut self) -> Option<String> {
while let Some(endpoint) = self.queue.pop_front() {
if self.visited.insert(endpoint.clone()) {
return Some(endpoint);
}
}
None
}
pub(crate) fn redirect_to(&mut self, endpoint: String) {
self.queue.push_front(endpoint);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn visits_each_endpoint_once_despite_duplicates() {
let mut worklist = Worklist::new(vec!["a:1".into(), "b:1".into(), "a:1".into()]);
assert_eq!(worklist.next().as_deref(), Some("a:1"));
assert_eq!(worklist.next().as_deref(), Some("b:1"));
assert_eq!(worklist.next(), None, "the duplicate a:1 must be skipped");
}
#[test]
fn redirect_to_dials_the_hinted_endpoint_next() {
let mut worklist = Worklist::new(vec!["a:1".into(), "b:1".into()]);
assert_eq!(worklist.next().as_deref(), Some("a:1"));
worklist.redirect_to("c:1".into());
assert_eq!(
worklist.next().as_deref(),
Some("c:1"),
"the hinted leader must jump ahead of the queued b:1",
);
assert_eq!(worklist.next().as_deref(), Some("b:1"));
}
#[test]
fn redirect_to_visited_endpoint_is_skipped() {
let mut worklist = Worklist::new(vec!["a:1".into(), "b:1".into()]);
assert_eq!(worklist.next().as_deref(), Some("a:1"));
worklist.redirect_to("a:1".into());
assert_eq!(
worklist.next().as_deref(),
Some("b:1"),
"a redirect to the already-visited a:1 must be skipped",
);
assert_eq!(worklist.next(), None);
}
}