use std::collections::BTreeMap;
use crate::id::stable_id;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DupKind {
Exact,
Near,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct Duplicate {
pub kind: DupKind,
pub kept_seq: usize,
}
pub(crate) fn find_duplicates(
contents: &[&str],
near: bool,
media_hashes: &[Option<u64>],
media_superseded: &[bool],
) -> Vec<Option<Duplicate>> {
let mut exact_seen: BTreeMap<u64, usize> = BTreeMap::new();
let mut near_seen: BTreeMap<u64, usize> = BTreeMap::new();
let mut media_seen: BTreeMap<u64, (usize, bool)> = BTreeMap::new();
let mut results: Vec<Option<Duplicate>> = Vec::with_capacity(contents.len());
for (seq, (content, media_hash)) in contents.iter().zip(media_hashes).enumerate() {
if let Some(&hash) = media_hash.as_ref() {
let current_superseded = media_superseded.get(seq).copied().unwrap_or(false);
let verdict =
media_verdict(&mut media_seen, &mut results, hash, seq, current_superseded);
results.push(verdict);
continue;
}
let exact_hash = stable_id(content);
let near_hash = near.then(|| stable_id(&normalize(content)));
let verdict = check(exact_hash, near_hash, &exact_seen, &near_seen);
let near_root = verdict.map_or(seq, |dup| dup.kept_seq);
let exact_anchor = match verdict {
Some(dup) if dup.kind == DupKind::Exact => dup.kept_seq,
_ => seq,
};
exact_seen.entry(exact_hash).or_insert(exact_anchor);
if let Some(near_hash) = near_hash {
near_seen.entry(near_hash).or_insert(near_root);
}
results.push(verdict);
}
results
}
fn media_verdict(
media_seen: &mut BTreeMap<u64, (usize, bool)>,
results: &mut [Option<Duplicate>],
hash: u64,
seq: usize,
current_superseded: bool,
) -> Option<Duplicate> {
match media_seen.get(&hash) {
Some(&(anchor_seq, anchor_superseded)) if anchor_superseded && !current_superseded => {
results[anchor_seq] = Some(Duplicate {
kind: DupKind::Exact,
kept_seq: seq,
});
media_seen.insert(hash, (seq, current_superseded));
None
}
Some(&(anchor_seq, _)) => Some(Duplicate {
kind: DupKind::Exact,
kept_seq: anchor_seq,
}),
None => {
media_seen.insert(hash, (seq, current_superseded));
None
}
}
}
fn check(
exact_hash: u64,
near_hash: Option<u64>,
exact_seen: &BTreeMap<u64, usize>,
near_seen: &BTreeMap<u64, usize>,
) -> Option<Duplicate> {
if let Some(&kept_seq) = exact_seen.get(&exact_hash) {
return Some(Duplicate {
kind: DupKind::Exact,
kept_seq,
});
}
near_seen.get(&near_hash?).map(|&kept_seq| Duplicate {
kind: DupKind::Near,
kept_seq,
})
}
fn normalize(content: &str) -> String {
content
.split_whitespace()
.map(str::to_lowercase)
.collect::<Vec<_>>()
.join(" ")
}
#[cfg(test)]
#[path = "dedup_tests.rs"]
mod tests;