use super::cursor::SliceCursor;
use super::{Posting, RecordId, Weight};
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Postings {
items: Vec<Posting>,
}
impl Postings {
pub fn new() -> Self {
Self::default()
}
pub fn from_pairs<I>(pairs: I) -> Self
where
I: IntoIterator<Item = (RecordId, Weight)>,
{
let mut builder = PostingsBuilder::new();
for (id, w) in pairs {
builder.add(id, w);
}
builder.build()
}
pub fn from_sorted_pairs(pairs: &[(RecordId, Weight)]) -> Self {
debug_assert!(pairs.windows(2).all(|w| w[0].0 < w[1].0));
let mut items: Vec<Posting> = pairs
.iter()
.map(|&(id, w)| Posting::solo(id, w))
.collect();
rebuild_tail_max(&mut items);
Self { items }
}
pub fn as_slice(&self) -> &[Posting] {
&self.items
}
pub fn len(&self) -> usize {
self.items.len()
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub fn cursor(&self) -> SliceCursor<'_> {
SliceCursor::new(&self.items)
}
pub fn get(&self, id: RecordId) -> Option<Weight> {
self.locate(id).ok().map(|i| self.items[i].weight)
}
pub fn upsert(&mut self, id: RecordId, weight: Weight) -> Option<Weight> {
match self.locate(id) {
Ok(i) => {
let old = self.items[i].weight;
if old == weight {
return Some(old);
}
self.items[i].weight = weight;
self.items[i].tail_max = weight.max(suffix_ceiling(&self.items, i + 1));
repair_tail_max(&mut self.items, i);
Some(old)
}
Err(i) => {
let tail_max = weight.max(suffix_ceiling(&self.items, i));
self.items.insert(
i,
Posting {
id,
weight,
tail_max,
},
);
repair_tail_max(&mut self.items, i);
None
}
}
}
pub fn delete(&mut self, id: RecordId) -> Option<Weight> {
let i = self.locate(id).ok()?;
let removed = self.items.remove(i);
repair_tail_max(&mut self.items, i);
Some(removed.weight)
}
pub fn recompute_tail_max(&mut self) {
rebuild_tail_max(&mut self.items);
}
pub fn items_mut(&mut self) -> &mut Vec<Posting> {
&mut self.items
}
pub fn check_invariants(&self) -> Result<(), String> {
check_ceilings(self.items.iter().copied())
}
fn locate(&self, id: RecordId) -> Result<usize, usize> {
self.items.binary_search_by(|p| p.id.cmp(&id))
}
}
#[inline]
fn suffix_ceiling(items: &[Posting], from: usize) -> Weight {
items.get(from).map_or(Weight::NEG_INFINITY, |p| p.tail_max)
}
fn rebuild_tail_max(items: &mut [Posting]) {
let mut running = Weight::NEG_INFINITY;
for p in items.iter_mut().rev() {
running = running.max(p.weight);
p.tail_max = running;
}
}
fn repair_tail_max(items: &mut [Posting], end: usize) {
let end = end.min(items.len());
let mut running = suffix_ceiling(items, end);
for p in items[..end].iter_mut().rev() {
running = running.max(p.weight);
if p.tail_max == running {
break;
}
p.tail_max = running;
}
}
pub fn check_ceilings(items: impl IntoIterator<Item = Posting>) -> Result<(), String> {
let items: Vec<Posting> = items.into_iter().collect();
for (i, w) in items.windows(2).enumerate() {
if w[0].id >= w[1].id {
return Err(format!(
"ids not strictly increasing at {i}: {} then {}",
w[0].id, w[1].id
));
}
}
let mut running = Weight::NEG_INFINITY;
for (i, p) in items.iter().enumerate().rev() {
running = running.max(p.weight);
if p.tail_max < running {
return Err(format!(
"tail_max {} at index {i} (id {}) below suffix max {running}",
p.tail_max, p.id
));
}
}
Ok(())
}
#[derive(Clone, Debug, Default)]
pub struct PostingsBuilder {
pairs: Vec<(RecordId, Weight)>,
}
impl PostingsBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, id: RecordId, weight: Weight) -> &mut Self {
self.pairs.push((id, weight));
self
}
pub fn len(&self) -> usize {
self.pairs.len()
}
pub fn is_empty(&self) -> bool {
self.pairs.is_empty()
}
pub fn build(mut self) -> Postings {
self.pairs.sort_by_key(|&(id, _)| id);
let mut unique: Vec<(RecordId, Weight)> = Vec::with_capacity(self.pairs.len());
for (id, w) in self.pairs {
match unique.last_mut() {
Some(last) if last.0 == id => last.1 = w,
_ => unique.push((id, w)),
}
}
Postings::from_sorted_pairs(&unique)
}
}