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, f32)> {
41 let mut scores: HashMap<u64, f32> = HashMap::new();
42 for &(token, q_weight) in query {
43 if let Some(list) = self.postings.get(&token) {
44 for &(rid, d_weight) in list {
45 *scores.entry(rid.0).or_insert(0.0) += q_weight * d_weight;
46 }
47 }
48 }
49 let mut ranked: Vec<(RowId, f32)> = scores
50 .into_iter()
51 .map(|(rid, score)| (RowId(rid), score))
52 .collect();
53 ranked.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
54 ranked.truncate(k);
55 ranked
56 }
57
58 pub fn is_empty(&self) -> bool {
59 self.postings.is_empty()
60 }
61
62 pub fn entries(&self) -> Vec<(u32, Vec<(RowId, f32)>)> {
64 self.postings
65 .iter()
66 .map(|(t, list)| (*t, list.clone()))
67 .collect()
68 }
69
70 pub fn from_entries(entries: Vec<(u32, Vec<(RowId, f32)>)>) -> Self {
72 let mut postings = HashMap::new();
73 for (t, list) in entries {
74 postings.insert(t, list);
75 }
76 Self { postings }
77 }
78}
79
80impl Default for SparseIndex {
81 fn default() -> Self {
82 Self::new()
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn ranks_by_sparse_overlap() {
92 let mut idx = SparseIndex::new();
93 idx.insert(&[(1, 2.0), (2, 1.0)], RowId(0));
95 idx.insert(&[(1, 1.0), (3, 3.0)], RowId(1));
96 idx.insert(&[(2, 5.0)], RowId(2));
97 let top = idx.search(&[(1, 1.0), (2, 1.0)], 3);
99 assert_eq!(top[0], (RowId(2), 5.0));
100 assert_eq!(top[1], (RowId(0), 3.0));
101 assert_eq!(top[2], (RowId(1), 1.0));
102 }
103}