use crate::store::{Store, TokenId};
use std::collections::{BTreeSet, HashMap};
pub struct Sequence {
pub ids: Vec<TokenId>,
pub levels: Vec<u32>,
}
impl Sequence {
pub fn from_ids(store: &Store, ids: Vec<TokenId>) -> Self {
let levels = ids.iter().map(|&id| store.level_of(id)).collect();
Sequence { ids, levels }
}
pub fn len(&self) -> usize {
self.ids.len()
}
pub fn is_empty(&self) -> bool {
self.ids.is_empty()
}
}
type Pair = (TokenId, TokenId);
pub const MAX_GENERATIONS: usize = 200;
fn remove_occurrence(occurrences: &mut HashMap<Pair, BTreeSet<usize>>, pair: Pair, pos: usize) {
if let Some(set) = occurrences.get_mut(&pair) {
set.remove(&pos);
}
}
fn insert_occurrence(occurrences: &mut HashMap<Pair, BTreeSet<usize>>, pair: Pair, pos: usize) {
occurrences.entry(pair).or_default().insert(pos);
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub enum TieBreak {
BirthOrder,
ContentHash,
}
fn alive_snapshot(ids: &[TokenId], alive: &[bool]) -> Vec<TokenId> {
(0..ids.len()).filter(|&i| alive[i]).map(|i| ids[i]).collect()
}
pub fn run(store: &mut Store, seq: Sequence, threshold: u64, tie_break: TieBreak) -> Sequence {
run_traced(store, seq, threshold, tie_break).0
}
pub fn run_traced(
store: &mut Store,
seq: Sequence,
threshold: u64,
tie_break: TieBreak,
) -> (Sequence, Vec<usize>) {
run_with_generation_hook(store, seq, threshold, tie_break, |_, _| {})
}
pub fn run_with_generation_hook<F: FnMut(&Store, &[TokenId])>(
store: &mut Store,
seq: Sequence,
threshold: u64,
tie_break: TieBreak,
mut hook: F,
) -> (Sequence, Vec<usize>) {
let mut births_per_generation = Vec::new();
let n = seq.len();
if n < 2 {
return (seq, births_per_generation);
}
let threshold = threshold as usize;
let mut ids = seq.ids;
let mut levels = seq.levels;
let mut prev: Vec<i64> = (0..n as i64).map(|i| i - 1).collect();
let mut next: Vec<i64> = (0..n as i64)
.map(|i| if i + 1 < n as i64 { i + 1 } else { -1 })
.collect();
let mut alive = vec![true; n];
let mut occurrences: HashMap<Pair, BTreeSet<usize>> = HashMap::new();
for i in 0..n - 1 {
insert_occurrence(&mut occurrences, (ids[i], ids[i + 1]), i);
}
for _generation in 0..MAX_GENERATIONS {
let mut wave: Vec<(Pair, Vec<usize>)> = occurrences
.iter()
.filter(|(_, positions)| positions.len() >= threshold)
.map(|(&pair, positions)| (pair, positions.iter().copied().collect()))
.collect();
if wave.is_empty() {
break;
}
let snapshot = alive_snapshot(&ids, &alive);
hook(store, &snapshot);
wave.sort_by(|(pa, posa), (pb, posb)| {
posb.len().cmp(&posa.len()).then_with(|| match tie_break {
TieBreak::BirthOrder => pa.cmp(pb),
TieBreak::ContentHash => {
let ha = (store.tie_hash_of(pa.0), store.tie_hash_of(pa.1));
let hb = (store.tie_hash_of(pb.0), store.tie_hash_of(pb.1));
ha.cmp(&hb)
}
})
});
let mut births = 0usize;
for (pair, frozen_positions) in wave {
let new_level = store.level_of(pair.0).max(store.level_of(pair.1)) + 1;
let before = store.birth_log().len();
let new_id = store.intern(new_level, &[pair.0, pair.1]);
if store.birth_log().len() > before {
births += 1;
}
for pos in frozen_positions {
if !alive[pos] {
continue;
}
let j = next[pos];
if j == -1 || !alive[j as usize] {
continue;
}
let j = j as usize;
if ids[pos] != pair.0 || ids[j] != pair.1 {
continue;
}
remove_occurrence(&mut occurrences, pair, pos);
let p = prev[pos];
let q = next[j];
store.touch(new_id);
if p != -1 {
let p = p as usize;
let old_left = (ids[p], pair.0);
remove_occurrence(&mut occurrences, old_left, p);
insert_occurrence(&mut occurrences, (ids[p], new_id), p);
}
if q != -1 {
let qu = q as usize;
let old_right = (pair.1, ids[qu]);
remove_occurrence(&mut occurrences, old_right, j);
insert_occurrence(&mut occurrences, (new_id, ids[qu]), pos);
}
ids[pos] = new_id;
levels[pos] = new_level;
alive[j] = false;
next[pos] = q;
if q != -1 {
prev[q as usize] = pos as i64;
}
}
}
births_per_generation.push(births);
if births == 0 {
break;
}
}
let out_ids = alive_snapshot(&ids, &alive);
let out_levels: Vec<u32> = (0..n).filter(|&i| alive[i]).map(|i| levels[i]).collect();
(
Sequence {
ids: out_ids,
levels: out_levels,
},
births_per_generation,
)
}
#[cfg(test)]
mod tests {
use super::*;
fn intern_bytes(store: &mut Store, bytes: &[u8]) -> Vec<TokenId> {
bytes.iter().map(|&b| store.intern(0, &[b as u32])).collect()
}
fn rendered(store: &Store, seq: &Sequence) -> Vec<Vec<u8>> {
seq.ids.iter().map(|&id| store.bytes_of(id)).collect()
}
#[test]
fn non_overlapping_pairs_merge_like_naive_reference() {
let mut store = Store::new();
store.init_factory();
let ids = intern_bytes(&mut store, b"abab");
let seq = Sequence::from_ids(&store, ids);
let out = run(&mut store, seq, 2, TieBreak::BirthOrder);
assert_eq!(rendered(&store, &out), vec![b"ab".to_vec(), b"ab".to_vec()]);
}
#[test]
fn cascading_merge_builds_higher_level_token_over_generations() {
let mut store = Store::new();
store.init_factory();
let ids = intern_bytes(&mut store, b"abababab");
let seq = Sequence::from_ids(&store, ids);
let out = run(&mut store, seq, 2, TieBreak::BirthOrder);
assert_eq!(out.len(), 2);
for &id in &out.ids {
assert_eq!(store.bytes_of(id), b"abab");
assert!(store.level_of(id) >= 2);
}
}
#[test]
fn below_threshold_pair_is_never_merged() {
let mut store = Store::new();
store.init_factory();
let ids = intern_bytes(&mut store, b"ab");
let seq = Sequence::from_ids(&store, ids);
let out = run(&mut store, seq, 2, TieBreak::BirthOrder);
assert_eq!(out.len(), 2);
assert_eq!(store.level_of(out.ids[0]), 0);
assert_eq!(store.level_of(out.ids[1]), 0);
}
#[test]
fn triple_repeat_documents_left_greedy_overlap_choice() {
let mut store = Store::new();
store.init_factory();
let ids = intern_bytes(&mut store, b"aaa");
let seq = Sequence::from_ids(&store, ids);
let out = run(&mut store, seq, 2, TieBreak::BirthOrder);
assert_eq!(out.len(), 2);
assert_eq!(store.bytes_of(out.ids[0]), b"aa");
assert_eq!(store.level_of(out.ids[0]), 1);
assert_eq!(store.bytes_of(out.ids[1]), b"a");
assert_eq!(store.level_of(out.ids[1]), 0);
}
#[test]
fn dedup_same_composition_across_positions_yields_same_id() {
let mut store = Store::new();
store.init_factory();
let ids = intern_bytes(&mut store, b"xy xy");
let seq = Sequence::from_ids(&store, ids);
let out = run(&mut store, seq, 2, TieBreak::BirthOrder);
let xy_positions: Vec<TokenId> = out
.ids
.iter()
.copied()
.filter(|&id| store.bytes_of(id) == b"xy")
.collect();
assert_eq!(xy_positions.len(), 2);
assert_eq!(xy_positions[0], xy_positions[1]);
}
#[test]
fn new_pair_born_this_generation_waits_for_the_next() {
let mut store = Store::new();
store.init_factory();
let ids = intern_bytes(&mut store, b"aaaaaaaa"); let seq = Sequence::from_ids(&store, ids);
let out = run(&mut store, seq, 2, TieBreak::BirthOrder);
assert_eq!(out.len(), 2);
for &id in &out.ids {
assert_eq!(store.bytes_of(id), b"aaaa");
assert_eq!(store.level_of(id), 2);
}
}
#[test]
fn generation_hook_fires_once_per_converged_generation_with_correct_snapshot() {
let mut store = Store::new();
store.init_factory();
let ids = intern_bytes(&mut store, b"aaaaaaaa");
let seq = Sequence::from_ids(&store, ids);
let mut snapshots: Vec<Vec<u8>> = Vec::new();
let (_out, births) = run_with_generation_hook(&mut store, seq, 2, TieBreak::BirthOrder, |s, sequence| {
let rendered: Vec<u8> = sequence.iter().flat_map(|&id| s.bytes_of(id)).collect();
snapshots.push(rendered);
});
assert_eq!(births.len(), 2, "два поколения дают births, третье не запускается вовсе");
assert_eq!(snapshots.len(), 2, "хук обязан выстрелить ровно на сошедшиеся поколения");
assert_eq!(snapshots[0], b"aaaaaaaa", "поколение 1 видит исходный вход целиком");
assert_eq!(snapshots[1], b"aaaaaaaa", "поколение 2 видит тот же текст, уже как 4 токена 'aa'");
}
#[test]
fn hook_does_not_change_algorithm_behaviour() {
let mut store_a = Store::new();
store_a.init_factory();
let ids_a = intern_bytes(&mut store_a, b"the cat sat on the mat, the cat sat again");
let seq_a = Sequence::from_ids(&store_a, ids_a);
let out_a = run(&mut store_a, seq_a, 2, TieBreak::BirthOrder);
let mut store_b = Store::new();
store_b.init_factory();
let ids_b = intern_bytes(&mut store_b, b"the cat sat on the mat, the cat sat again");
let seq_b = Sequence::from_ids(&store_b, ids_b);
let mut hook_calls = 0usize;
let (out_b, _) = run_with_generation_hook(&mut store_b, seq_b, 2, TieBreak::BirthOrder, |_, _| {
hook_calls += 1;
});
assert!(hook_calls > 0);
assert_eq!(out_a.ids, out_b.ids);
assert_eq!(out_a.levels, out_b.levels);
}
}