use std::cmp::max;
use ahash::{AHashMap, AHashSet};
use crate::common::fixed_length_priority_queue::FixedLengthPriorityQueue;
use crate::common::types::ScoreType;
use crate::segment::types::{PointIdType, ScoredPoint, SeqNumberType};
const LARGEST_REASONABLE_ALLOCATION_SIZE: usize = 1_048_576;
pub struct SearchResultAggregator {
queue: Option<FixedLengthPriorityQueue<ScoredPoint>>,
seen: AHashSet<PointIdType>, }
impl SearchResultAggregator {
pub fn new(limit: usize) -> Self {
SearchResultAggregator {
queue: if limit > 0 {
Some(FixedLengthPriorityQueue::new(limit))
} else {
None
},
seen: AHashSet::with_capacity(limit.min(LARGEST_REASONABLE_ALLOCATION_SIZE)),
}
}
pub fn push(&mut self, point: ScoredPoint) {
let Some(queue) = self.queue.as_mut() else {
return;
};
if self.seen.insert(point.id) {
queue.push(point);
}
}
pub fn into_vec(self) -> Vec<ScoredPoint> {
self.queue
.map(|queue| queue.into_sorted_vec())
.unwrap_or_default()
}
pub fn lowest(&self) -> Option<&ScoredPoint> {
self.queue.as_ref().and_then(|queue| queue.top())
}
}
pub struct BatchResultAggregator {
batch_aggregators: Vec<SearchResultAggregator>,
point_versions: AHashMap<PointIdType, SeqNumberType>,
}
impl BatchResultAggregator {
pub fn new(tops: impl IntoIterator<Item = usize>) -> Self {
let mut merged_results_per_batch = vec![];
for top in tops {
merged_results_per_batch.push(SearchResultAggregator::new(top));
}
BatchResultAggregator {
batch_aggregators: merged_results_per_batch,
point_versions: AHashMap::new(),
}
}
pub fn update_point_versions<'a>(
&mut self,
all_searches_results: impl IntoIterator<Item = &'a ScoredPoint>,
) {
for point in all_searches_results {
let point_id = point.id;
self.point_versions
.entry(point_id)
.and_modify(|version| *version = max(*version, point.version))
.or_insert(point.version);
}
}
pub fn update_batch_results(
&mut self,
batch_id: usize,
search_results: impl IntoIterator<Item = ScoredPoint>,
) {
let aggregator = &mut self.batch_aggregators[batch_id];
for scored_point in search_results {
debug_assert!(self.point_versions.contains_key(&scored_point.id));
if let Some(point_max_version) = self.point_versions.get(&scored_point.id)
&& scored_point.version >= *point_max_version
{
aggregator.push(scored_point);
}
}
}
pub fn batch_lowest_scores(&self, batch_id: usize) -> Option<ScoreType> {
let batch_scores = &self.batch_aggregators[batch_id];
batch_scores.lowest().map(|x| x.score)
}
pub fn into_topk(self) -> Vec<Vec<ScoredPoint>> {
self.batch_aggregators
.into_iter()
.map(|x| x.into_vec())
.collect()
}
}