use super::intpack::{IntpackReader, pack_u64s};
use crate::error::UrnaError;
use crate::layout::SECTION_DEDUP_MAP;
use std::collections::HashMap;
pub const DEDUP_MAP_V1: u8 = 0;
fn malformed(reason: impl Into<String>) -> UrnaError {
UrnaError::MalformedSectionPayload {
section_id: SECTION_DEDUP_MAP,
reason: reason.into(),
}
}
pub struct Deduped {
pub unique: Vec<String>,
pub back_refs: Vec<u32>,
}
pub fn dedup(texts: &[String]) -> Deduped {
let mut index: HashMap<&str, u32> = HashMap::with_capacity(texts.len());
let mut unique: Vec<String> = Vec::new();
let mut back_refs: Vec<u32> = Vec::with_capacity(texts.len());
for t in texts {
match index.get(t.as_str()) {
Some(&i) => back_refs.push(i),
None => {
let i = unique.len() as u32;
index.insert(t.as_str(), i);
unique.push(t.clone());
back_refs.push(i);
}
}
}
Deduped { unique, back_refs }
}
pub fn encode_map(back_refs: &[u32]) -> Vec<u8> {
let as_u64: Vec<u64> = back_refs.iter().map(|&r| r as u64).collect();
let packed = pack_u64s(&as_u64);
let mut out = Vec::with_capacity(1 + packed.len());
out.push(DEDUP_MAP_V1);
out.extend_from_slice(&packed);
out
}
pub fn decode_map(bytes: &[u8]) -> crate::Result<Vec<u32>> {
let (kind, rest) = bytes
.split_first()
.ok_or_else(|| malformed("dedup_map: empty"))?;
if *kind != DEDUP_MAP_V1 {
return Err(malformed(format!("dedup_map: unknown kind {}", *kind)));
}
let reader = IntpackReader::parse(rest)?;
let mut refs = Vec::with_capacity(reader.len().min(1 << 20));
for i in 0..reader.len() {
let v = reader.get(i)?;
let r = u32::try_from(v).map_err(|_| malformed("dedup_map: back-ref exceeds u32"))?;
refs.push(r);
}
Ok(refs)
}
pub fn expand(unique: &[String], back_refs: &[u32]) -> crate::Result<Vec<String>> {
let mut out = Vec::with_capacity(back_refs.len());
for &r in back_refs {
let s = unique
.get(r as usize)
.ok_or_else(|| malformed("dedup_map: back-ref out of unique-pool range"))?;
out.push(s.clone());
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
fn texts(items: &[&str]) -> Vec<String> {
items.iter().map(|s| s.to_string()).collect()
}
#[test]
fn dedup_then_expand_roundtrips() {
for corpus in [
texts(&[]),
texts(&["a", "b", "c"]), texts(&["x", "x", "x", "x"]), texts(&["a", "b", "a", "c", "b", "a"]), ] {
let d = dedup(&corpus);
let back = expand(&d.unique, &d.back_refs).unwrap();
assert_eq!(back, corpus, "expand must rebuild the original order");
let blob = encode_map(&d.back_refs);
assert_eq!(decode_map(&blob).unwrap(), d.back_refs);
}
}
#[test]
fn all_duplicate_collapses_to_one() {
let d = dedup(&texts(&["same", "same", "same"]));
assert_eq!(d.unique.len(), 1);
assert_eq!(d.back_refs, vec![0, 0, 0]);
}
#[test]
fn determinism_two_passes_identical() {
let corpus = texts(&["p", "q", "p", "r", "q"]);
let a = dedup(&corpus);
let b = dedup(&corpus);
assert_eq!(a.unique, b.unique);
assert_eq!(a.back_refs, b.back_refs);
assert_eq!(encode_map(&a.back_refs), encode_map(&b.back_refs));
}
}