use comp_cat_rs::foundation::semilattice::JoinSemilattice;
use std::fmt;
#[derive(Debug, Clone, Copy)]
pub struct Score(f64);
impl Score {
#[must_use]
pub fn new(value: f64) -> Option<Self> {
if value.is_nan() {
None
} else {
Some(Self(value))
}
}
#[must_use]
pub fn zero() -> Self {
Self(0.0)
}
#[must_use]
pub fn value(self) -> f64 {
self.0
}
}
impl std::ops::Add for Score {
type Output = Self;
fn add(self, other: Self) -> Self {
Self(self.0 + other.0)
}
}
impl std::ops::Mul for Score {
type Output = Self;
fn mul(self, other: Self) -> Self {
Self(self.0 * other.0)
}
}
impl PartialEq for Score {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl Eq for Score {}
impl PartialOrd for Score {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Score {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0.total_cmp(&other.0)
}
}
impl JoinSemilattice for Score {
fn join(&self, other: &Self) -> Self {
match self.cmp(other) {
std::cmp::Ordering::Less => *other,
_ => *self,
}
}
}
impl fmt::Display for Score {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.4}", self.0)
}
}
#[must_use]
#[derive(Debug, Clone)]
pub struct ScoredCandidate<I> {
item: I,
score: Score,
}
impl<I> ScoredCandidate<I> {
pub fn new(item: I, score: Score) -> Self {
Self { item, score }
}
#[must_use]
pub fn item(&self) -> &I {
&self.item
}
#[must_use]
pub fn score(&self) -> Score {
self.score
}
#[must_use]
pub fn into_item(self) -> I {
self.item
}
#[must_use]
pub fn into_parts(self) -> (I, Score) {
(self.item, self.score)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::ops::Add;
#[test]
fn score_rejects_nan() {
assert!(Score::new(f64::NAN).is_none());
}
#[test]
fn score_accepts_finite() {
assert!(Score::new(1.0).is_some());
assert!(Score::new(-3.5).is_some());
assert!(Score::new(0.0).is_some());
}
#[test]
fn score_accepts_infinity() {
assert!(Score::new(f64::INFINITY).is_some());
assert!(Score::new(f64::NEG_INFINITY).is_some());
}
#[test]
fn join_is_max() -> Result<(), &'static str> {
let a = Score::new(1.0).ok_or("a")?;
let b = Score::new(3.0).ok_or("b")?;
assert_eq!(a.join(&b), b);
Ok(())
}
#[test]
fn join_is_commutative() -> Result<(), &'static str> {
let a = Score::new(2.0).ok_or("a")?;
let b = Score::new(5.0).ok_or("b")?;
assert_eq!(a.join(&b), b.join(&a));
Ok(())
}
#[test]
fn join_is_idempotent() -> Result<(), &'static str> {
let a = Score::new(4.0).ok_or("a")?;
assert_eq!(a.join(&a), a);
Ok(())
}
#[test]
#[allow(clippy::float_cmp)] fn additive_combination() -> Result<(), &'static str> {
let a = Score::new(1.0).ok_or("a")?;
let b = Score::new(2.0).ok_or("b")?;
assert_eq!(a.add(b).value(), 3.0);
Ok(())
}
}