use serde::{Deserialize, Deserializer, Serialize};
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Serialize)]
#[serde(transparent)]
pub struct Normalized01(f32);
impl<'de> Deserialize<'de> for Normalized01 {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let v = f32::deserialize(deserializer)?;
Ok(Self::new(v))
}
}
impl Normalized01 {
pub const ZERO: Self = Self(0.0);
pub const HALF: Self = Self(0.5);
pub const ONE: Self = Self(1.0);
#[inline]
#[must_use]
pub fn new(value: f32) -> Self {
if value.is_finite() {
Self(value.clamp(0.0, 1.0))
} else {
Self(0.0)
}
}
#[inline]
#[must_use]
pub fn get(self) -> f32 {
self.0
}
}
impl Default for Normalized01 {
#[inline]
fn default() -> Self {
Self::ZERO
}
}
impl From<f32> for Normalized01 {
#[inline]
fn from(v: f32) -> Self {
Self::new(v)
}
}
impl core::fmt::Display for Normalized01 {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Serialize)]
#[serde(transparent)]
pub struct Balanced11(f32);
impl<'de> Deserialize<'de> for Balanced11 {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let v = f32::deserialize(deserializer)?;
Ok(Self::new(v))
}
}
impl Balanced11 {
pub const MIN: Self = Self(-1.0);
pub const ZERO: Self = Self(0.0);
pub const MAX: Self = Self(1.0);
#[inline]
#[must_use]
pub fn new(value: f32) -> Self {
if value.is_finite() {
Self(value.clamp(-1.0, 1.0))
} else {
Self(0.0)
}
}
#[inline]
#[must_use]
pub fn get(self) -> f32 {
self.0
}
}
impl Default for Balanced11 {
#[inline]
fn default() -> Self {
Self::ZERO
}
}
impl From<f32> for Balanced11 {
#[inline]
fn from(v: f32) -> Self {
Self::new(v)
}
}
impl core::fmt::Display for Balanced11 {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.0)
}
}
pub struct ThresholdClassifier<L: Copy + 'static> {
thresholds: &'static [(f32, L)],
default: L,
}
impl<L: Copy + 'static> ThresholdClassifier<L> {
#[inline]
#[must_use]
pub const fn new(thresholds: &'static [(f32, L)], default: L) -> Self {
Self {
thresholds,
default,
}
}
#[inline]
#[must_use]
pub fn classify(&self, value: f32) -> L {
for &(threshold, label) in self.thresholds {
if value >= threshold {
return label;
}
}
self.default
}
}
#[inline]
pub fn evict_min<T>(vec: &mut Vec<T>, score: impl Fn(&T) -> f64) -> Option<T> {
if vec.is_empty() {
return None;
}
let mut min_idx = 0;
let mut min_score = score(&vec[0]);
for (i, item) in vec.iter().enumerate().skip(1) {
let s = score(item);
if s < min_score {
min_score = s;
min_idx = i;
}
}
Some(vec.swap_remove(min_idx))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn normalized01_clamps_high() {
assert_eq!(Normalized01::new(1.5).get(), 1.0);
}
#[test]
fn normalized01_clamps_low() {
assert_eq!(Normalized01::new(-0.5).get(), 0.0);
}
#[test]
fn normalized01_passthrough() {
assert_eq!(Normalized01::new(0.42).get(), 0.42);
}
#[test]
fn normalized01_constants() {
assert_eq!(Normalized01::ZERO.get(), 0.0);
assert_eq!(Normalized01::HALF.get(), 0.5);
assert_eq!(Normalized01::ONE.get(), 1.0);
}
#[test]
fn normalized01_default() {
assert_eq!(Normalized01::default().get(), 0.0);
}
#[test]
fn normalized01_from_f32() {
let n: Normalized01 = 0.7.into();
assert_eq!(n.get(), 0.7);
let n: Normalized01 = 5.0.into();
assert_eq!(n.get(), 1.0);
}
#[test]
fn normalized01_serde_roundtrip() {
let n = Normalized01::new(0.75);
let json = serde_json::to_string(&n).unwrap();
assert_eq!(json, "0.75");
let back: Normalized01 = serde_json::from_str(&json).unwrap();
assert_eq!(back, n);
}
#[test]
fn normalized01_deserialize_clamps() {
let n: Normalized01 = serde_json::from_str("5.0").unwrap();
assert_eq!(n.get(), 1.0);
let n: Normalized01 = serde_json::from_str("-1.0").unwrap();
assert_eq!(n.get(), 0.0);
}
#[test]
fn normalized01_nan_becomes_zero() {
assert_eq!(Normalized01::new(f32::NAN).get(), 0.0);
assert_eq!(Normalized01::new(f32::INFINITY).get(), 0.0);
assert_eq!(Normalized01::new(f32::NEG_INFINITY).get(), 0.0);
}
#[test]
fn normalized01_display() {
assert_eq!(format!("{}", Normalized01::new(0.5)), "0.5");
}
#[test]
fn normalized01_partial_ord() {
assert!(Normalized01::new(0.3) < Normalized01::new(0.7));
assert!(Normalized01::new(1.0) > Normalized01::new(0.0));
}
#[test]
fn balanced11_clamps_high() {
assert_eq!(Balanced11::new(2.0).get(), 1.0);
}
#[test]
fn balanced11_clamps_low() {
assert_eq!(Balanced11::new(-3.0).get(), -1.0);
}
#[test]
fn balanced11_passthrough() {
assert_eq!(Balanced11::new(-0.5).get(), -0.5);
}
#[test]
fn balanced11_constants() {
assert_eq!(Balanced11::MIN.get(), -1.0);
assert_eq!(Balanced11::ZERO.get(), 0.0);
assert_eq!(Balanced11::MAX.get(), 1.0);
}
#[test]
fn balanced11_default() {
assert_eq!(Balanced11::default().get(), 0.0);
}
#[test]
fn balanced11_from_f32() {
let b: Balanced11 = (-0.3).into();
assert_eq!(b.get(), -0.3);
let b: Balanced11 = (-5.0).into();
assert_eq!(b.get(), -1.0);
}
#[test]
fn balanced11_nan_becomes_zero() {
assert_eq!(Balanced11::new(f32::NAN).get(), 0.0);
assert_eq!(Balanced11::new(f32::INFINITY).get(), 0.0);
assert_eq!(Balanced11::new(f32::NEG_INFINITY).get(), 0.0);
}
#[test]
fn balanced11_serde_roundtrip() {
let b = Balanced11::new(-0.25);
let json = serde_json::to_string(&b).unwrap();
assert_eq!(json, "-0.25");
let back: Balanced11 = serde_json::from_str(&json).unwrap();
assert_eq!(back, b);
}
#[test]
fn balanced11_deserialize_clamps() {
let b: Balanced11 = serde_json::from_str("5.0").unwrap();
assert_eq!(b.get(), 1.0);
let b: Balanced11 = serde_json::from_str("-5.0").unwrap();
assert_eq!(b.get(), -1.0);
}
#[test]
fn balanced11_display() {
assert_eq!(format!("{}", Balanced11::new(-0.5)), "-0.5");
}
#[test]
fn balanced11_partial_ord() {
assert!(Balanced11::new(-0.5) < Balanced11::new(0.5));
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum TestLevel {
High,
Medium,
Low,
}
const TEST_CLASSIFIER: ThresholdClassifier<TestLevel> = ThresholdClassifier::new(
&[(0.7, TestLevel::High), (0.3, TestLevel::Medium)],
TestLevel::Low,
);
#[test]
fn classifier_high() {
assert_eq!(TEST_CLASSIFIER.classify(0.9), TestLevel::High);
assert_eq!(TEST_CLASSIFIER.classify(0.7), TestLevel::High);
}
#[test]
fn classifier_medium() {
assert_eq!(TEST_CLASSIFIER.classify(0.5), TestLevel::Medium);
assert_eq!(TEST_CLASSIFIER.classify(0.3), TestLevel::Medium);
}
#[test]
fn classifier_low() {
assert_eq!(TEST_CLASSIFIER.classify(0.1), TestLevel::Low);
assert_eq!(TEST_CLASSIFIER.classify(0.0), TestLevel::Low);
}
#[test]
fn classifier_boundary() {
assert_eq!(TEST_CLASSIFIER.classify(0.7), TestLevel::High);
assert_eq!(TEST_CLASSIFIER.classify(0.3), TestLevel::Medium);
}
#[test]
fn evict_min_removes_lowest() {
let mut v = vec![10, 3, 7, 1, 5];
let removed = evict_min(&mut v, |x| *x as f64);
assert_eq!(removed, Some(1));
assert_eq!(v.len(), 4);
assert!(!v.contains(&1));
}
#[test]
fn evict_min_empty() {
let mut v: Vec<i32> = vec![];
assert_eq!(evict_min(&mut v, |x| *x as f64), None);
}
#[test]
fn evict_min_single() {
let mut v = vec![42];
assert_eq!(evict_min(&mut v, |x| *x as f64), Some(42));
assert!(v.is_empty());
}
#[test]
fn evict_min_by_field() {
#[derive(Debug, PartialEq)]
struct Item {
name: &'static str,
score: f64,
}
let mut v = vec![
Item {
name: "a",
score: 5.0,
},
Item {
name: "b",
score: 1.0,
},
Item {
name: "c",
score: 3.0,
},
];
let removed = evict_min(&mut v, |item| item.score);
assert_eq!(removed.unwrap().name, "b");
}
}