use crate::rowid::RowId;
use std::collections::HashMap;
pub struct SparseIndex {
postings: HashMap<u32, Vec<(RowId, f32)>>,
}
impl SparseIndex {
pub fn new() -> Self {
Self {
postings: HashMap::new(),
}
}
pub fn insert(&mut self, terms: &[(u32, f32)], row_id: RowId) {
for &(token, weight) in terms {
self.postings
.entry(token)
.or_default()
.push((row_id, weight));
}
}
pub fn search(&self, query: &[(u32, f32)], k: usize) -> Vec<(RowId, f32)> {
self.search_filtered(query, k, |_| true)
}
pub fn search_filtered(
&self,
query: &[(u32, f32)],
k: usize,
allowed: impl Fn(RowId) -> bool,
) -> Vec<(RowId, f32)> {
let mut scores: HashMap<u64, f32> = HashMap::new();
for &(token, q_weight) in query {
if let Some(list) = self.postings.get(&token) {
for &(rid, d_weight) in list {
if allowed(rid) {
*scores.entry(rid.0).or_insert(0.0) += q_weight * d_weight;
}
}
}
}
let mut ranked: Vec<(RowId, f32)> = scores
.into_iter()
.map(|(rid, score)| (RowId(rid), score))
.collect();
ranked.sort_by(|a, b| b.1.total_cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
ranked.truncate(k);
ranked
}
pub fn candidate_row_ids(&self, query: &[(u32, f32)]) -> Vec<RowId> {
let mut row_ids = std::collections::HashSet::new();
for (token, _) in query {
if let Some(postings) = self.postings.get(token) {
row_ids.extend(postings.iter().map(|(row_id, _)| *row_id));
}
}
row_ids.into_iter().collect()
}
pub fn is_empty(&self) -> bool {
self.postings.is_empty()
}
pub fn entries(&self) -> Vec<(u32, Vec<(RowId, f32)>)> {
self.postings
.iter()
.map(|(t, list)| (*t, list.clone()))
.collect()
}
pub fn from_entries(entries: Vec<(u32, Vec<(RowId, f32)>)>) -> Self {
let mut postings = HashMap::new();
for (t, list) in entries {
postings.insert(t, list);
}
Self { postings }
}
}
impl Default for SparseIndex {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ranks_by_sparse_overlap() {
let mut idx = SparseIndex::new();
idx.insert(&[(1, 2.0), (2, 1.0)], RowId(0));
idx.insert(&[(1, 1.0), (3, 3.0)], RowId(1));
idx.insert(&[(2, 5.0)], RowId(2));
let top = idx.search(&[(1, 1.0), (2, 1.0)], 3);
assert_eq!(top[0], (RowId(2), 5.0));
assert_eq!(top[1], (RowId(0), 3.0));
assert_eq!(top[2], (RowId(1), 1.0));
}
}