use std::collections::{HashMap, VecDeque};
use std::fmt::Debug;
use std::hash::Hash;
pub(crate) fn linearise<Item: Clone + Debug + Eq + Hash + PartialEq>(
target: &Item,
parents: &HashMap<Item, Vec<Item>>,
) -> Option<Vec<Item>> {
let mut linearisations: HashMap<Item, Vec<Item>> = HashMap::new();
let mut queue: VecDeque<Item> = VecDeque::new();
queue.push_back(target.clone());
let mut checkpoint = None;
while let Some(item) = queue.pop_front() {
if linearisations.contains_key(&item) {
continue;
}
let item_parents = &parents[&item];
let mut merge_set = Vec::new();
for parent in item_parents {
match linearisations.get(parent) {
Some(parent_linearisation) => {
merge_set.push(parent_linearisation.clone());
}
None => {
if !queue.iter().any(|queued| queued == parent) {
queue.push_front(parent.clone());
}
}
}
}
if merge_set.len() == item_parents.len() {
merge_set.push(item_parents.clone());
let merge_result = merge(merge_set)?;
let mut result = Vec::new();
result.push(item.clone());
result.extend(merge_result);
if matches!(checkpoint, Some((ref check_item, _)) if *check_item == item) {
checkpoint = None;
}
linearisations.insert(item, result);
} else {
match checkpoint {
Some((ref check_item, items_linearised)) => {
if *check_item == item {
if items_linearised == linearisations.len() {
return None;
}
checkpoint = Some((item.clone(), linearisations.len()));
}
}
None => {
checkpoint = Some((item.clone(), linearisations.len()));
}
}
queue.push_back(item);
}
}
linearisations.remove(target)
}
fn merge<Item: Clone + Debug + PartialEq>(mut set: Vec<Vec<Item>>) -> Option<Vec<Item>> {
set = set
.into_iter()
.filter_map(|mut subset| {
if subset.is_empty() {
None
} else {
subset.reverse();
Some(subset)
}
})
.collect();
if set.is_empty() {
return Some(Vec::new());
}
let mut result = Vec::new();
while let Some(found) = find_candidate(&set) {
set = remove_candidate_from_set(set, &found);
result.push(found);
}
if set.is_empty() {
Some(result)
} else {
None
}
}
fn find_candidate<Item: Clone + PartialEq>(set: &Vec<Vec<Item>>) -> Option<Item> {
for subset in set {
let Some(candidate) = subset.last() else {
continue;
};
if set.iter().all(
|subset| match subset.iter().position(|item| item == candidate) {
Some(position) => position == subset.len() - 1,
None => true,
},
) {
return Some(candidate.clone());
}
}
None
}
fn remove_candidate_from_set<Item: PartialEq>(
set: Vec<Vec<Item>>,
element: &Item,
) -> Vec<Vec<Item>> {
set.into_iter()
.filter_map(|mut subset| {
if subset.last().expect("every vector in the set is not empty") == element {
subset.pop();
}
if subset.is_empty() {
None
} else {
Some(subset)
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_merge_no_elements() {
assert_eq!(Some(vec![]), merge::<()>(vec![vec![]]));
}
#[test]
fn test_merge_single_parent() {
assert_eq!(Some(vec!['A']), merge(vec![vec!['A'], vec!['A']]));
}
#[test]
fn test_merge_two_parents() {
assert_eq!(
Some(vec!['A', 'B']),
merge(vec![vec!['A'], vec!['B'], vec!['A', 'B']])
);
}
#[test]
fn test_merge_not_linearisable() {
assert_eq!(None, merge(vec![vec!['A', 'X'], vec![], vec!['X', 'A']]));
}
#[test]
fn test_linearise() {
let mut parents = HashMap::new();
parents.insert('A', vec![]);
parents.insert('B', vec!['A']);
parents.insert('C', vec!['A']);
parents.insert('D', vec!['B', 'C']);
parents.insert('E', vec!['C', 'B']);
assert_eq!(Some(vec!['D', 'B', 'C', 'A']), linearise(&'D', &parents));
assert_eq!(Some(vec!['E', 'C', 'B', 'A']), linearise(&'E', &parents));
}
#[test]
fn test_linearise_not_linearisable() {
let mut parents = HashMap::new();
parents.insert('X', vec![]);
parents.insert('A', vec!['X']);
parents.insert('C', vec!['X', 'A']);
assert_eq!(None, linearise(&'C', &parents));
}
#[test]
fn test_linearise_with_shallow_cycles() {
let mut parents = HashMap::new();
parents.insert('B', vec!['A']);
parents.insert('A', vec!['B']);
assert_eq!(None, linearise(&'A', &parents));
assert_eq!(None, linearise(&'B', &parents));
}
#[test]
fn test_linearise_with_deep_cycles() {
let mut parents = HashMap::new();
parents.insert('X', vec!['Y']);
parents.insert('Y', vec!['Z']);
parents.insert('Z', vec!['X']);
assert_eq!(None, linearise(&'X', &parents));
assert_eq!(None, linearise(&'Y', &parents));
assert_eq!(None, linearise(&'Z', &parents));
}
}