extern crate alloc;
#[cfg(feature = "std")]
pub(crate) type HashMap<K, V> = std::collections::HashMap<K, V, ahash::RandomState>;
#[cfg(not(feature = "std"))]
pub(crate) use alloc::collections::BTreeMap as HashMap;
#[cfg(feature = "std")]
#[inline]
pub(crate) fn hashmap_with_capacity<K: core::hash::Hash + Eq, V>(cap: usize) -> HashMap<K, V> {
HashMap::with_capacity_and_hasher(cap, ahash::RandomState::default())
}
#[cfg(feature = "std")]
#[inline]
pub(crate) fn hashmap_new<K: core::hash::Hash + Eq, V>() -> HashMap<K, V> {
HashMap::with_hasher(ahash::RandomState::default())
}
#[cfg(not(feature = "std"))]
#[inline]
pub(crate) fn hashmap_with_capacity<K: Ord, V>(_cap: usize) -> HashMap<K, V> {
HashMap::new()
}
#[cfg(not(feature = "std"))]
#[inline]
pub(crate) fn hashmap_new<K: Ord, V>() -> HashMap<K, V> {
HashMap::new()
}
use alloc::vec;
use alloc::vec::Vec;
#[derive(Clone, Debug)]
pub(crate) struct SortedPostings {
hashes: Vec<u32>,
starts: Vec<u32>,
anchors: Vec<u32>,
}
impl SortedPostings {
pub(crate) fn build(pairs: &[(u32, u32)], max_postings: u32) -> Option<Self> {
if pairs.is_empty() {
return None;
}
let n = pairs.len();
let mut keys: Vec<u32> = Vec::with_capacity(n);
let mut vals: Vec<u32> = Vec::with_capacity(n);
for &(hash, t_anchor) in pairs {
keys.push(hash);
vals.push(t_anchor);
}
let mut indices: Vec<usize> = (0..n).collect();
indices.sort_by(|&a, &b| keys[a].cmp(&keys[b]).then(vals[a].cmp(&vals[b])));
let mut sorted_keys = vec![0u32; n];
let mut sorted_vals = vec![0u32; n];
for (dst, &idx) in indices.iter().enumerate() {
sorted_keys[dst] = keys[idx];
sorted_vals[dst] = vals[idx];
}
let mut hashes = Vec::new();
let mut starts = Vec::new();
let mut anchors = Vec::new();
let mut i = 0;
while i < n {
let hash = sorted_keys[i];
let run_start = i;
while i < n && sorted_keys[i] == hash {
i += 1;
}
let run_end = i;
let count = (run_end - run_start) as u32;
if count <= max_postings {
hashes.push(hash);
starts.push(anchors.len() as u32);
anchors.extend_from_slice(&sorted_vals[run_start..run_end]);
}
}
if hashes.is_empty() {
return None;
}
starts.push(anchors.len() as u32);
Some(Self {
hashes,
starts,
anchors,
})
}
#[inline]
pub(crate) fn get(&self, hash: u32) -> &[u32] {
match self.hashes.binary_search(&hash) {
Ok(idx) => {
let lo = self.starts[idx] as usize;
let hi = self.starts[idx + 1] as usize;
&self.anchors[lo..hi]
}
Err(_) => &[],
}
}
#[inline]
#[allow(dead_code)]
pub(crate) fn is_empty(&self) -> bool {
self.anchors.is_empty()
}
#[inline]
#[allow(dead_code)]
pub(crate) fn len(&self) -> usize {
self.anchors.len()
}
#[inline]
#[allow(dead_code)]
pub(crate) fn num_hashes(&self) -> usize {
self.hashes.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_input() {
assert!(SortedPostings::build(&[], 100).is_none());
}
#[test]
fn single_hash() {
let sp = SortedPostings::build(&[(42, 10)], 100).unwrap();
assert_eq!(sp.get(42), &[10]);
assert!(sp.get(99).is_empty());
}
#[test]
fn multiple_anchors_same_hash_sorted() {
let sp = SortedPostings::build(&[(7, 100), (7, 200), (7, 50)], 100).unwrap();
assert_eq!(sp.get(7), &[50, 100, 200]);
}
#[test]
fn multiple_hashes() {
let sp =
SortedPostings::build(&[(1, 10), (2, 20), (1, 30), (3, 40), (2, 50)], 100).unwrap();
assert_eq!(sp.get(1), &[10, 30]);
assert_eq!(sp.get(2), &[20, 50]);
assert_eq!(sp.get(3), &[40]);
assert!(sp.get(99).is_empty());
}
#[test]
fn stop_hash_filtered() {
let sp = SortedPostings::build(&[(0, 1), (0, 2), (0, 3), (1, 10)], 2).unwrap();
assert!(sp.get(0).is_empty());
assert_eq!(sp.get(1), &[10]);
}
#[test]
fn all_stop_hashes() {
assert!(SortedPostings::build(&[(0, 1), (0, 2), (0, 3)], 2).is_none());
}
#[test]
fn large_input() {
let mut pairs = Vec::new();
for hash in 0..1000u32 {
for t in 0..5u32 {
pairs.push((hash, t * 10));
}
}
let sp = SortedPostings::build(&pairs, 10).unwrap();
for hash in 0..1000 {
assert_eq!(sp.get(hash).len(), 5);
}
}
#[test]
fn deterministic_order() {
let sp1 = SortedPostings::build(&[(3, 30), (1, 10), (2, 20)], 100).unwrap();
let sp2 = SortedPostings::build(&[(2, 20), (3, 30), (1, 10)], 100).unwrap();
assert_eq!(sp1.get(1), sp2.get(1));
assert_eq!(sp1.get(2), sp2.get(2));
assert_eq!(sp1.get(3), sp2.get(3));
}
#[test]
fn build_is_single_alloc_per_vector() {
let pairs: Vec<(u32, u32)> = (0..500u32).map(|i| (i % 50, i)).collect();
let sp = SortedPostings::build(&pairs, 100).unwrap();
assert_eq!(sp.hashes.len(), 50); assert_eq!(sp.anchors.len(), 500);
assert!(!sp.is_empty());
}
}