mongreldb_core/index/
sparse.rs1use crate::rowid::RowId;
14use std::collections::HashMap;
15
16pub struct SparseIndex {
18 postings: HashMap<u32, Vec<(RowId, f32)>>,
19}
20
21impl SparseIndex {
22 pub fn new() -> Self {
23 Self {
24 postings: HashMap::new(),
25 }
26 }
27
28 pub fn insert(&mut self, terms: &[(u32, f32)], row_id: RowId) {
31 for &(token, weight) in terms {
32 self.postings
33 .entry(token)
34 .or_default()
35 .push((row_id, weight));
36 }
37 }
38
39 pub fn search(&self, query: &[(u32, f32)], k: usize) -> Vec<(RowId, f64)> {
41 self.search_filtered(query, k, |_| true)
42 }
43
44 pub fn search_filtered(
45 &self,
46 query: &[(u32, f32)],
47 k: usize,
48 allowed: impl Fn(RowId) -> bool,
49 ) -> Vec<(RowId, f64)> {
50 let mut scores: HashMap<u64, f64> = HashMap::new();
51 for &(token, q_weight) in query {
52 if let Some(list) = self.postings.get(&token) {
53 for &(rid, d_weight) in list {
54 if allowed(rid) {
55 *scores.entry(rid.0).or_insert(0.0) +=
56 f64::from(q_weight) * f64::from(d_weight);
57 }
58 }
59 }
60 }
61 let mut ranked: Vec<(RowId, f64)> = scores
62 .into_iter()
63 .map(|(rid, score)| (RowId(rid), score))
64 .collect();
65 ranked.sort_by(|a, b| b.1.total_cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
66 ranked.truncate(k);
67 ranked
68 }
69
70 pub fn candidate_row_ids(&self, query: &[(u32, f32)]) -> Vec<RowId> {
71 let mut row_ids = std::collections::HashSet::new();
72 for (token, _) in query {
73 if let Some(postings) = self.postings.get(token) {
74 row_ids.extend(postings.iter().map(|(row_id, _)| *row_id));
75 }
76 }
77 row_ids.into_iter().collect()
78 }
79
80 pub fn is_empty(&self) -> bool {
81 self.postings.is_empty()
82 }
83
84 pub fn entries(&self) -> Vec<(u32, Vec<(RowId, f32)>)> {
86 self.postings
87 .iter()
88 .map(|(t, list)| (*t, list.clone()))
89 .collect()
90 }
91
92 pub fn from_entries(entries: Vec<(u32, Vec<(RowId, f32)>)>) -> Self {
94 let mut postings = HashMap::new();
95 for (t, list) in entries {
96 postings.insert(t, list);
97 }
98 Self { postings }
99 }
100}
101
102impl Default for SparseIndex {
103 fn default() -> Self {
104 Self::new()
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn ranks_by_sparse_overlap() {
114 let mut idx = SparseIndex::new();
115 idx.insert(&[(1, 2.0), (2, 1.0)], RowId(0));
117 idx.insert(&[(1, 1.0), (3, 3.0)], RowId(1));
118 idx.insert(&[(2, 5.0)], RowId(2));
119 let top = idx.search(&[(1, 1.0), (2, 1.0)], 3);
121 assert_eq!(top[0], (RowId(2), 5.0));
122 assert_eq!(top[1], (RowId(0), 3.0));
123 assert_eq!(top[2], (RowId(1), 1.0));
124 }
125}