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>],
) -> 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> = BTreeMap::new();
contents
.iter()
.zip(media_hashes)
.enumerate()
.map(|(seq, (content, media_hash))| {
if let Some(&hash) = media_hash.as_ref() {
let verdict = media_seen.get(&hash).map(|&kept_seq| Duplicate {
kind: DupKind::Exact,
kept_seq,
});
media_seen.entry(hash).or_insert(seq);
return verdict;
}
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);
}
verdict
})
.collect()
}
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;