sparse_vector/wand/
sink.rs1use std::cmp::{Ordering, Reverse};
4use std::collections::BinaryHeap;
5
6use ordered_float::OrderedFloat;
7
8use super::RecordId;
9
10#[derive(Clone, Copy, Debug, PartialEq)]
13pub struct Hit {
14 pub id: RecordId,
15 pub score: f32,
16}
17
18impl Eq for Hit {}
19
20impl PartialOrd for Hit {
21 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
22 Some(self.cmp(other))
23 }
24}
25
26impl Ord for Hit {
27 fn cmp(&self, other: &Self) -> Ordering {
28 OrderedFloat(self.score)
29 .cmp(&OrderedFloat(other.score))
30 .then_with(|| other.id.cmp(&self.id))
31 }
32}
33
34pub trait ScoreSink {
42 fn offer(&mut self, id: RecordId, score: f32);
44
45 fn threshold(&self) -> Option<f32>;
48
49 fn into_results(self) -> Vec<(RecordId, f32)>;
51}
52
53#[derive(Debug)]
55pub struct TopKSink {
56 k: usize,
57 heap: BinaryHeap<Reverse<Hit>>,
59}
60
61impl TopKSink {
62 pub fn new(k: usize) -> Self {
63 Self {
64 k,
65 heap: BinaryHeap::with_capacity(k.saturating_add(1)),
66 }
67 }
68
69 pub fn len(&self) -> usize {
70 self.heap.len()
71 }
72
73 pub fn is_empty(&self) -> bool {
74 self.heap.is_empty()
75 }
76
77 pub fn capacity(&self) -> usize {
78 self.k
79 }
80
81 fn worst(&self) -> Option<Hit> {
82 self.heap.peek().map(|r| r.0)
83 }
84}
85
86impl ScoreSink for TopKSink {
87 fn offer(&mut self, id: RecordId, score: f32) {
88 if self.k == 0 {
89 return;
90 }
91 let hit = Hit { id, score };
92 if self.heap.len() < self.k {
93 self.heap.push(Reverse(hit));
94 } else if let Some(worst) = self.worst() {
95 if hit > worst {
96 self.heap.pop();
97 self.heap.push(Reverse(hit));
98 }
99 }
100 }
101
102 fn threshold(&self) -> Option<f32> {
103 if self.k == 0 {
104 return Some(f32::INFINITY);
105 }
106 if self.heap.len() < self.k {
107 None
108 } else {
109 self.worst().map(|h| h.score)
110 }
111 }
112
113 fn into_results(self) -> Vec<(RecordId, f32)> {
114 let mut hits: Vec<Hit> = self.heap.into_iter().map(|r| r.0).collect();
115 hits.sort_by(|a, b| b.cmp(a));
116 hits.into_iter().map(|h| (h.id, h.score)).collect()
117 }
118}
119
120#[derive(Debug, Default)]
122pub struct CollectAll {
123 hits: Vec<Hit>,
124}
125
126impl CollectAll {
127 pub fn new() -> Self {
128 Self::default()
129 }
130
131 pub fn len(&self) -> usize {
132 self.hits.len()
133 }
134
135 pub fn is_empty(&self) -> bool {
136 self.hits.is_empty()
137 }
138}
139
140impl ScoreSink for CollectAll {
141 fn offer(&mut self, id: RecordId, score: f32) {
142 self.hits.push(Hit { id, score });
143 }
144
145 fn threshold(&self) -> Option<f32> {
146 None
147 }
148
149 fn into_results(mut self) -> Vec<(RecordId, f32)> {
150 self.hits.sort_by(|a, b| b.cmp(a));
151 self.hits.into_iter().map(|h| (h.id, h.score)).collect()
152 }
153}