sparse_vector/wand/
postings.rs1use super::cursor::SliceCursor;
4use super::{Posting, RecordId, Weight};
5
6#[derive(Clone, Debug, Default, PartialEq)]
9pub struct Postings {
10 items: Vec<Posting>,
11}
12
13impl Postings {
14 pub fn new() -> Self {
16 Self::default()
17 }
18
19 pub fn from_pairs<I>(pairs: I) -> Self
22 where
23 I: IntoIterator<Item = (RecordId, Weight)>,
24 {
25 let mut builder = PostingsBuilder::new();
26 for (id, w) in pairs {
27 builder.add(id, w);
28 }
29 builder.build()
30 }
31
32 pub fn from_sorted_pairs(pairs: &[(RecordId, Weight)]) -> Self {
35 debug_assert!(pairs.windows(2).all(|w| w[0].0 < w[1].0));
36 let mut items: Vec<Posting> = pairs
37 .iter()
38 .map(|&(id, w)| Posting::solo(id, w))
39 .collect();
40 rebuild_tail_max(&mut items);
41 Self { items }
42 }
43
44 pub fn as_slice(&self) -> &[Posting] {
46 &self.items
47 }
48
49 pub fn len(&self) -> usize {
50 self.items.len()
51 }
52
53 pub fn is_empty(&self) -> bool {
54 self.items.is_empty()
55 }
56
57 pub fn cursor(&self) -> SliceCursor<'_> {
59 SliceCursor::new(&self.items)
60 }
61
62 pub fn get(&self, id: RecordId) -> Option<Weight> {
64 self.locate(id).ok().map(|i| self.items[i].weight)
65 }
66
67 pub fn upsert(&mut self, id: RecordId, weight: Weight) -> Option<Weight> {
75 match self.locate(id) {
76 Ok(i) => {
77 let old = self.items[i].weight;
78 if old == weight {
79 return Some(old);
80 }
81 self.items[i].weight = weight;
82 self.items[i].tail_max = weight.max(suffix_ceiling(&self.items, i + 1));
83 repair_tail_max(&mut self.items, i);
84 Some(old)
85 }
86 Err(i) => {
87 let tail_max = weight.max(suffix_ceiling(&self.items, i));
88 self.items.insert(
89 i,
90 Posting {
91 id,
92 weight,
93 tail_max,
94 },
95 );
96 repair_tail_max(&mut self.items, i);
97 None
98 }
99 }
100 }
101
102 pub fn delete(&mut self, id: RecordId) -> Option<Weight> {
104 let i = self.locate(id).ok()?;
105 let removed = self.items.remove(i);
106 repair_tail_max(&mut self.items, i);
109 Some(removed.weight)
110 }
111
112 pub fn recompute_tail_max(&mut self) {
115 rebuild_tail_max(&mut self.items);
116 }
117
118 pub fn items_mut(&mut self) -> &mut Vec<Posting> {
122 &mut self.items
123 }
124
125 pub fn check_invariants(&self) -> Result<(), String> {
129 check_ceilings(self.items.iter().copied())
130 }
131
132 fn locate(&self, id: RecordId) -> Result<usize, usize> {
133 self.items.binary_search_by(|p| p.id.cmp(&id))
134 }
135}
136
137#[inline]
140fn suffix_ceiling(items: &[Posting], from: usize) -> Weight {
141 items.get(from).map_or(Weight::NEG_INFINITY, |p| p.tail_max)
142}
143
144fn rebuild_tail_max(items: &mut [Posting]) {
146 let mut running = Weight::NEG_INFINITY;
147 for p in items.iter_mut().rev() {
148 running = running.max(p.weight);
149 p.tail_max = running;
150 }
151}
152
153fn repair_tail_max(items: &mut [Posting], end: usize) {
164 let end = end.min(items.len());
165 let mut running = suffix_ceiling(items, end);
166 for p in items[..end].iter_mut().rev() {
167 running = running.max(p.weight);
168 if p.tail_max == running {
169 break;
170 }
171 p.tail_max = running;
172 }
173}
174
175pub fn check_ceilings(items: impl IntoIterator<Item = Posting>) -> Result<(), String> {
178 let items: Vec<Posting> = items.into_iter().collect();
179 for (i, w) in items.windows(2).enumerate() {
180 if w[0].id >= w[1].id {
181 return Err(format!(
182 "ids not strictly increasing at {i}: {} then {}",
183 w[0].id, w[1].id
184 ));
185 }
186 }
187 let mut running = Weight::NEG_INFINITY;
188 for (i, p) in items.iter().enumerate().rev() {
189 running = running.max(p.weight);
190 if p.tail_max < running {
191 return Err(format!(
192 "tail_max {} at index {i} (id {}) below suffix max {running}",
193 p.tail_max, p.id
194 ));
195 }
196 }
197 Ok(())
198}
199
200#[derive(Clone, Debug, Default)]
202pub struct PostingsBuilder {
203 pairs: Vec<(RecordId, Weight)>,
204}
205
206impl PostingsBuilder {
207 pub fn new() -> Self {
208 Self::default()
209 }
210
211 pub fn add(&mut self, id: RecordId, weight: Weight) -> &mut Self {
213 self.pairs.push((id, weight));
214 self
215 }
216
217 pub fn len(&self) -> usize {
218 self.pairs.len()
219 }
220
221 pub fn is_empty(&self) -> bool {
222 self.pairs.is_empty()
223 }
224
225 pub fn build(mut self) -> Postings {
227 self.pairs.sort_by_key(|&(id, _)| id);
230 let mut unique: Vec<(RecordId, Weight)> = Vec::with_capacity(self.pairs.len());
231 for (id, w) in self.pairs {
232 match unique.last_mut() {
233 Some(last) if last.0 == id => last.1 = w,
234 _ => unique.push((id, w)),
235 }
236 }
237 Postings::from_sorted_pairs(&unique)
238 }
239}