pub fn ndcg_at_k(relevance_scores: &[f64], k: usize) -> f64 {
contract_pre_ndcg_at_k!();
if k == 0 || relevance_scores.is_empty() {
return 0.0;
}
let k = k.min(relevance_scores.len());
let actual_dcg = dcg(&relevance_scores[..k]);
let mut ideal_scores: Vec<f64> = relevance_scores.to_vec();
ideal_scores.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
let idcg = dcg(&ideal_scores[..k]);
if idcg == 0.0 {
return 0.0;
}
actual_dcg / idcg
}
fn dcg(scores: &[f64]) -> f64 {
scores
.iter()
.enumerate()
.map(|(i, &rel)| {
let gain = (2.0f64).powf(rel) - 1.0;
let discount = ((i + 2) as f64).max(f64::MIN_POSITIVE).log2(); gain / discount
})
.sum()
}