#![deny(missing_docs)]
use std::collections::HashMap;
use std::hash::Hash;
#[derive(Debug, Clone, Copy)]
pub struct RrfOpts {
pub k: f32,
}
impl Default for RrfOpts {
fn default() -> Self {
Self { k: 60.0 }
}
}
pub fn blend_weighted<'a, K: Eq + Hash + Clone + 'a>(
streams: &[(&'a [(K, f32)], f32)],
) -> Vec<(K, f32)> {
let mut totals: HashMap<K, f32> = HashMap::new();
for (stream, weight) in streams {
if stream.is_empty() {
continue;
}
let (mut min, mut max) = (f32::INFINITY, f32::NEG_INFINITY);
for (_, s) in stream.iter() {
if *s < min {
min = *s;
}
if *s > max {
max = *s;
}
}
let range = max - min;
if range == 0.0 {
continue;
}
for (k, s) in stream.iter() {
let norm = (s - min) / range;
*totals.entry(k.clone()).or_insert(0.0) += norm * weight;
}
}
let mut out: Vec<(K, f32)> = totals.into_iter().collect();
out.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
out
}
pub fn blend_rrf<'a, K: Eq + Hash + Clone + 'a>(
streams: &[&'a [(K, f32)]],
opts: RrfOpts,
) -> Vec<(K, f32)> {
let mut totals: HashMap<K, f32> = HashMap::new();
for stream in streams {
for (rank, (k, _)) in stream.iter().enumerate() {
let contribution = 1.0 / (opts.k + rank as f32 + 1.0);
*totals.entry(k.clone()).or_insert(0.0) += contribution;
}
}
let mut out: Vec<(K, f32)> = totals.into_iter().collect();
out.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
out
}