use std::collections::hash_map::RandomState;
use std::collections::{HashMap, VecDeque};
use std::hash::{BuildHasher, Hash};
#[cfg(test)]
thread_local! {
pub(crate) static MOVES: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
}
type Pos = (i64, usize);
enum Slot {
One(Pos),
Many(Vec<Pos>),
}
impl Slot {
fn positions(&self) -> &[Pos] {
match self {
Slot::One(p) => std::slice::from_ref(p),
Slot::Many(v) => v,
}
}
}
pub(crate) struct MergeAcc<K, V> {
chunks: VecDeque<Vec<Option<(K, V)>>>,
index: HashMap<u64, Slot>,
hasher: RandomState,
live: usize,
front: i64,
}
impl<K: Hash + Eq, V> Default for MergeAcc<K, V> {
fn default() -> Self {
Self {
chunks: VecDeque::new(),
index: HashMap::new(),
hasher: RandomState::new(),
live: 0,
front: 0,
}
}
}
impl<K: Hash + Eq, V> MergeAcc<K, V> {
pub(crate) fn from_entries(entries: impl IntoIterator<Item = (K, V)>) -> Self {
let mut acc = Self::default();
acc.put(false, entries);
acc
}
pub(crate) fn len(&self) -> usize {
self.live
}
fn slot_of(&self, key: i64) -> Option<usize> {
usize::try_from(key.checked_sub(self.front)?).ok()
}
fn entry(&self, (c, i): Pos) -> Option<&Option<(K, V)>> {
self.chunks.get(self.slot_of(c)?)?.get(i)
}
fn find(&self, h: u64, key: &K) -> Option<Pos> {
let slot = self.index.get(&h)?;
slot.positions()
.iter()
.copied()
.find(|p| matches!(self.entry(*p), Some(Some((k, _))) if k == key))
}
#[cfg(test)]
pub(crate) fn contains(&self, key: &K) -> bool {
self.find(self.hasher.hash_one(key), key).is_some()
}
fn put(&mut self, front: bool, entries: impl IntoIterator<Item = (K, V)>) {
let at = if front {
let Some(at) = self.front.checked_sub(1) else {
return;
};
self.chunks.push_front(Vec::new());
self.front = at;
at
} else {
let len = i64::try_from(self.chunks.len()).unwrap_or(i64::MAX);
let Some(at) = self.front.checked_add(len) else {
return;
};
self.chunks.push_back(Vec::new());
at
};
for (k, v) in entries {
let h = self.hasher.hash_one(&k);
if self.find(h, &k).is_some() {
continue;
}
let chunk = if front {
self.chunks.front_mut()
} else {
self.chunks.back_mut()
};
let Some(chunk) = chunk else {
break;
};
#[cfg(test)]
MOVES.with(|c| c.set(c.get().saturating_add(1)));
let pos = (at, chunk.len());
chunk.push(Some((k, v)));
match self.index.get_mut(&h) {
None => {
self.index.insert(h, Slot::One(pos));
}
Some(slot) => match slot {
Slot::One(p) => *slot = Slot::Many(vec![*p, pos]),
Slot::Many(v) => v.push(pos),
},
}
self.live = self.live.saturating_add(1);
}
if front {
if self.chunks.front().is_some_and(Vec::is_empty) {
self.chunks.pop_front();
self.front = self.front.saturating_add(1);
}
} else if self.chunks.back().is_some_and(Vec::is_empty) {
self.chunks.pop_back();
}
}
fn shadow(&mut self, key: &K) {
let h = self.hasher.hash_one(key);
let Some(pos) = self.find(h, key) else {
return;
};
let empty = match self.index.get_mut(&h) {
Some(Slot::Many(v)) => {
v.retain(|p| *p != pos);
v.is_empty()
}
_ => true,
};
if empty {
self.index.remove(&h);
}
let entry = self
.slot_of(pos.0)
.and_then(|c| self.chunks.get_mut(c))
.and_then(|ch| ch.get_mut(pos.1));
if let Some(e) = entry {
*e = None;
self.live = self.live.saturating_sub(1);
}
}
#[must_use]
pub(crate) fn append_later(mut self, later: Self) -> Self {
self.absorb_later(later);
self
}
pub(crate) fn absorb_later(&mut self, mut later: Self) {
if later.len() == 0 {
return;
}
if self.len() >= later.len() {
self.put(false, later.into_entries());
} else {
for (k, _) in self.chunks.iter().flatten().flatten() {
later.shadow(k);
}
let earlier = std::mem::replace(self, later);
self.put(true, earlier.into_entries());
}
}
pub(crate) fn into_entries(self) -> impl Iterator<Item = (K, V)> {
self.chunks.into_iter().flatten().flatten()
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing)]
use super::*;
fn acc(pairs: &[(&'static str, i32)]) -> MergeAcc<&'static str, i32> {
MergeAcc::from_entries(pairs.iter().copied())
}
fn out(a: MergeAcc<&'static str, i32>) -> Vec<(&'static str, i32)> {
a.into_entries().collect()
}
#[test]
fn earlier_wins_order_kept_both_directions() {
let a = acc(&[("a", 1), ("b", 1), ("c", 1)]).append_later(acc(&[("b", 2), ("d", 2)]));
assert_eq!(out(a), [("a", 1), ("b", 1), ("c", 1), ("d", 2)]);
let a = acc(&[("b", 1)]).append_later(acc(&[("a", 2), ("b", 2), ("c", 2)]));
assert_eq!(out(a), [("b", 1), ("a", 2), ("c", 2)]);
assert_eq!(out(acc(&[("x", 1), ("x", 2)])), [("x", 1)]);
let e: MergeAcc<&str, i32> = MergeAcc::default();
assert_eq!(e.len(), 0);
let a = e.append_later(acc(&[("q", 1)]));
assert!(a.contains(&"q"));
assert_eq!(out(a.append_later(MergeAcc::default())), [("q", 1)]);
}
#[test]
fn repeated_unions_match_a_naive_model() {
let mut a = acc(&[("k0", 0)]);
let mut model = vec![("k0", 0)];
let keys = ["k0", "k1", "k2", "k3", "k4", "k5", "k6", "k7"];
for i in 0..40i32 {
let k = keys[(i as usize * 5) % keys.len()];
let side = acc(&[(k, i), (keys[(i as usize) % keys.len()], -i)]);
let side_model: Vec<_> = out(acc(&[(k, i), (keys[(i as usize) % keys.len()], -i)]));
if i % 3 == 0 {
a = side.append_later(a);
let mut m = side_model.clone();
m.extend(
model
.iter()
.filter(|(x, _)| !side_model.iter().any(|(y, _)| y == x)),
);
model = m;
} else {
a = a.append_later(side);
let extra: Vec<_> = side_model
.into_iter()
.filter(|(x, _)| !model.iter().any(|(y, _)| y == x))
.collect();
model.extend(extra);
}
}
assert_eq!(out(a), model);
}
}