#![allow(dead_code)]
use std::collections::{HashMap, HashSet, VecDeque};
pub type ClipId = u64;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum RelationType {
Subclip,
Duplicate,
Alternate,
Version,
}
impl RelationType {
#[must_use]
pub fn is_derivative(&self) -> bool {
matches!(self, Self::Subclip | Self::Version)
}
#[must_use]
pub fn label(&self) -> &'static str {
match self {
Self::Subclip => "subclip",
Self::Duplicate => "duplicate",
Self::Alternate => "alternate",
Self::Version => "version",
}
}
}
#[derive(Debug, Clone)]
pub struct ClipRelation {
pub from_id: ClipId,
pub to_id: ClipId,
pub relation_type: RelationType,
}
impl ClipRelation {
#[must_use]
pub fn new(from_id: ClipId, to_id: ClipId, relation_type: RelationType) -> Self {
Self {
from_id,
to_id,
relation_type,
}
}
#[must_use]
pub fn is_forward(&self) -> bool {
self.relation_type.is_derivative()
}
}
#[derive(Debug, Default)]
pub struct ClipRelationGraph {
edges: HashMap<ClipId, Vec<(ClipId, RelationType)>>,
}
impl ClipRelationGraph {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, relation: ClipRelation) {
self.edges
.entry(relation.from_id)
.or_default()
.push((relation.to_id, relation.relation_type));
}
#[must_use]
pub fn find_related(&self, clip_id: ClipId) -> Vec<ClipId> {
self.edges
.get(&clip_id)
.map_or_else(Vec::new, |v| v.iter().map(|(id, _)| *id).collect())
}
#[must_use]
pub fn find_related_by_type(&self, clip_id: ClipId, rel_type: &RelationType) -> Vec<ClipId> {
self.edges.get(&clip_id).map_or_else(Vec::new, |v| {
v.iter()
.filter(|(_, t)| t == rel_type)
.map(|(id, _)| *id)
.collect()
})
}
#[must_use]
pub fn transitively_related(&self, clip_id: ClipId) -> Vec<ClipId> {
let mut visited: HashSet<ClipId> = HashSet::new();
let mut queue: VecDeque<ClipId> = VecDeque::new();
queue.push_back(clip_id);
visited.insert(clip_id);
while let Some(current) = queue.pop_front() {
if let Some(neighbors) = self.edges.get(¤t) {
for (neighbor, _) in neighbors {
if visited.insert(*neighbor) {
queue.push_back(*neighbor);
}
}
}
}
visited.remove(&clip_id);
visited.into_iter().collect()
}
#[must_use]
pub fn edge_count(&self) -> usize {
self.edges.values().map(Vec::len).sum()
}
#[must_use]
pub fn has_direct_relation(&self, from: ClipId, to: ClipId) -> bool {
self.edges
.get(&from)
.map_or(false, |v| v.iter().any(|(id, _)| *id == to))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_subclip_is_derivative() {
assert!(RelationType::Subclip.is_derivative());
}
#[test]
fn test_version_is_derivative() {
assert!(RelationType::Version.is_derivative());
}
#[test]
fn test_duplicate_not_derivative() {
assert!(!RelationType::Duplicate.is_derivative());
}
#[test]
fn test_alternate_not_derivative() {
assert!(!RelationType::Alternate.is_derivative());
}
#[test]
fn test_relation_labels() {
assert_eq!(RelationType::Subclip.label(), "subclip");
assert_eq!(RelationType::Duplicate.label(), "duplicate");
}
#[test]
fn test_clip_relation_is_forward_true() {
let r = ClipRelation::new(1, 2, RelationType::Subclip);
assert!(r.is_forward());
}
#[test]
fn test_clip_relation_is_forward_false() {
let r = ClipRelation::new(1, 2, RelationType::Alternate);
assert!(!r.is_forward());
}
#[test]
fn test_find_related_empty() {
let g = ClipRelationGraph::new();
assert!(g.find_related(1).is_empty());
}
#[test]
fn test_add_and_find_related() {
let mut g = ClipRelationGraph::new();
g.add(ClipRelation::new(1, 2, RelationType::Subclip));
g.add(ClipRelation::new(1, 3, RelationType::Duplicate));
let mut related = g.find_related(1);
related.sort_unstable();
assert_eq!(related, vec![2, 3]);
}
#[test]
fn test_find_related_by_type() {
let mut g = ClipRelationGraph::new();
g.add(ClipRelation::new(10, 20, RelationType::Version));
g.add(ClipRelation::new(10, 30, RelationType::Alternate));
let versions = g.find_related_by_type(10, &RelationType::Version);
assert_eq!(versions, vec![20]);
}
#[test]
fn test_transitively_related_chain() {
let mut g = ClipRelationGraph::new();
g.add(ClipRelation::new(1, 2, RelationType::Version));
g.add(ClipRelation::new(2, 3, RelationType::Version));
let mut tr = g.transitively_related(1);
tr.sort_unstable();
assert_eq!(tr, vec![2, 3]);
}
#[test]
fn test_transitively_related_no_cycle_explosion() {
let mut g = ClipRelationGraph::new();
g.add(ClipRelation::new(1, 2, RelationType::Duplicate));
g.add(ClipRelation::new(2, 1, RelationType::Duplicate));
let tr = g.transitively_related(1);
assert_eq!(tr.len(), 1);
}
#[test]
fn test_edge_count() {
let mut g = ClipRelationGraph::new();
g.add(ClipRelation::new(1, 2, RelationType::Subclip));
g.add(ClipRelation::new(1, 3, RelationType::Alternate));
assert_eq!(g.edge_count(), 2);
}
#[test]
fn test_has_direct_relation_true() {
let mut g = ClipRelationGraph::new();
g.add(ClipRelation::new(5, 6, RelationType::Version));
assert!(g.has_direct_relation(5, 6));
}
#[test]
fn test_has_direct_relation_false() {
let g = ClipRelationGraph::new();
assert!(!g.has_direct_relation(5, 6));
}
}