use serde::{Deserialize, Serialize};
use super::score::ReliabilityScore;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScoredCandidate {
pub name: String,
pub score: ReliabilityScore,
}
impl ScoredCandidate {
#[must_use]
pub fn new(name: impl Into<String>, score: ReliabilityScore) -> Self {
Self {
name: name.into(),
score,
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ScoreWeightedSelector;
impl ScoreWeightedSelector {
#[must_use]
pub fn pick_best(candidates: Vec<ScoredCandidate>) -> Option<ScoredCandidate> {
candidates.into_iter().reduce(|best, current| {
if current.score.overall > best.score.overall {
current
} else {
best
}
})
}
#[must_use]
pub fn pick_best_ref(candidates: &[ScoredCandidate]) -> Option<&ScoredCandidate> {
candidates.iter().reduce(|best, current| {
if current.score.overall > best.score.overall {
current
} else {
best
}
})
}
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing
)]
mod tests {
use super::*;
#[test]
fn test_pick_best_empty_input_returns_none() {
assert!(ScoreWeightedSelector::pick_best(vec![]).is_none());
let empty: Vec<ScoredCandidate> = vec![];
assert!(ScoreWeightedSelector::pick_best_ref(&empty).is_none());
}
#[test]
fn test_pick_best_single_candidate() {
let candidates = vec![ScoredCandidate::new(
"only",
ReliabilityScore::from_overall(0.5),
)];
let winner = ScoreWeightedSelector::pick_best(candidates).unwrap();
assert_eq!(winner.name, "only");
}
#[test]
fn test_pick_best_picks_highest() {
let candidates = vec![
ScoredCandidate::new("low", ReliabilityScore::from_overall(0.3)),
ScoredCandidate::new("high", ReliabilityScore::from_overall(0.9)),
ScoredCandidate::new("mid", ReliabilityScore::from_overall(0.6)),
];
let winner = ScoreWeightedSelector::pick_best(candidates).unwrap();
assert_eq!(winner.name, "high");
}
#[test]
fn test_pick_best_tie_broken_by_first() {
let candidates = vec![
ScoredCandidate::new("first", ReliabilityScore::from_overall(0.7)),
ScoredCandidate::new("second", ReliabilityScore::from_overall(0.7)),
];
let winner = ScoreWeightedSelector::pick_best(candidates).unwrap();
assert_eq!(
winner.name, "first",
"ties must be broken by registration order"
);
}
#[test]
fn test_pick_best_ref_matches_pick_best() {
let candidates = vec![
ScoredCandidate::new("a", ReliabilityScore::from_overall(0.4)),
ScoredCandidate::new("b", ReliabilityScore::from_overall(0.8)),
];
let winner_ref = ScoreWeightedSelector::pick_best_ref(&candidates).unwrap();
assert_eq!(winner_ref.name, "b");
}
}