use std::{cmp::Ordering, collections::BinaryHeap};
use super::{cursor::TermCursor, filter::ExcludeFilter, metadata::NormTable};
use crate::superfile::fts::bm25;
#[derive(Debug, Copy, Clone)]
pub(super) struct TopKEntry(pub(super) f32, pub(super) u32);
impl PartialEq for TopKEntry {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0 && self.1 == other.1
}
}
impl Eq for TopKEntry {}
impl PartialOrd for TopKEntry {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for TopKEntry {
fn cmp(&self, other: &Self) -> Ordering {
other
.0
.partial_cmp(&self.0)
.unwrap_or(Ordering::Equal)
.then_with(|| self.1.cmp(&other.1))
}
}
pub(super) fn drain_top_k_desc(heap: BinaryHeap<TopKEntry>) -> Vec<(u32, f32)> {
let mut out: Vec<(u32, f32)> = heap.into_iter().map(|TopKEntry(s, d)| (d, s)).collect();
out.sort_unstable_by(|a, b| {
b.1.partial_cmp(&a.1)
.unwrap_or(Ordering::Equal)
.then(a.0.cmp(&b.0))
});
out
}
pub(super) trait AndSink {
fn bar(&self) -> f32 {
f32::NEG_INFINITY
}
fn needs_score(&self) -> bool;
fn emit(&mut self, doc: u32, score: f32);
}
pub(super) struct ScoreSink<'a> {
pub(super) heap: &'a mut BinaryHeap<TopKEntry>,
pub(super) k: usize,
pub(super) filter: Option<&'a mut ExcludeFilter>,
pub(super) floor_eff: f32,
}
impl AndSink for ScoreSink<'_> {
fn bar(&self) -> f32 {
if self.heap.len() >= self.k {
self.heap
.peek()
.expect("heap len == k")
.0
.max(self.floor_eff)
} else {
self.floor_eff
}
}
fn needs_score(&self) -> bool {
true
}
fn emit(&mut self, doc: u32, score: f32) {
if score > self.floor_eff {
and_heap_push(self.heap, self.k, self.filter.as_deref_mut(), score, doc);
}
}
}
pub(super) struct MustShouldSink<'a> {
pub(super) heap: &'a mut BinaryHeap<TopKEntry>,
pub(super) k: usize,
pub(super) filter: Option<&'a mut ExcludeFilter>,
pub(super) floor_eff: f32,
pub(super) shoulds: Vec<TermCursor>,
pub(super) should_ub: f32,
pub(super) dl_norm_k1: &'a NormTable,
}
impl AndSink for MustShouldSink<'_> {
fn bar(&self) -> f32 {
let full_bar = if self.heap.len() >= self.k {
self.heap
.peek()
.expect("heap len == k")
.0
.max(self.floor_eff)
} else {
self.floor_eff
};
full_bar - self.should_ub
}
fn needs_score(&self) -> bool {
true
}
fn emit(&mut self, doc: u32, must_score: f32) {
let norm = self.dl_norm_k1.get(doc);
let mut score = must_score;
for c in &mut self.shoulds {
c.skip_to(doc);
if !c.is_exhausted() && c.current_doc_id() == doc {
score += bm25::score_with_dl_norm_k1(c.idf_x_k1p1, c.current_tf(), norm);
}
}
if score > self.floor_eff {
and_heap_push(self.heap, self.k, self.filter.as_deref_mut(), score, doc);
}
}
}
pub(super) struct CollectSink {
pub(super) out: Vec<u32>,
}
impl AndSink for CollectSink {
fn needs_score(&self) -> bool {
false
}
fn emit(&mut self, doc: u32, _score: f32) {
self.out.push(doc);
}
}
pub(super) struct CountSink {
pub(super) n: u64,
}
impl AndSink for CountSink {
fn needs_score(&self) -> bool {
false
}
fn emit(&mut self, _doc: u32, _score: f32) {
self.n += 1;
}
}
#[inline]
pub(super) fn and_heap_push(
heap: &mut BinaryHeap<TopKEntry>,
k: usize,
filter: Option<&mut ExcludeFilter>,
score: f32,
doc_id: u32,
) {
if let Some(f) = filter
&& !f.admits(doc_id)
{
return;
}
if heap.len() < k {
heap.push(TopKEntry(score, doc_id));
} else if let Some(&worst) = heap.peek()
&& (score > worst.0 || (score == worst.0 && doc_id < worst.1))
{
heap.pop();
heap.push(TopKEntry(score, doc_id));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn drain_top_k_desc_orders_descending_with_tiebreak() {
let mut heap: BinaryHeap<TopKEntry> = BinaryHeap::new();
heap.push(TopKEntry(1.0, 4));
heap.push(TopKEntry(2.0, 1));
heap.push(TopKEntry(2.0, 0)); let out = drain_top_k_desc(heap);
assert_eq!(out, vec![(0, 2.0), (1, 2.0), (4, 1.0)]);
}
}