summaryrefslogtreecommitdiffstats
path: root/ranking/src/scorer.rs
blob: 329709f2da9fa41f0ed68d62298b4c516684fe6b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
use std::borrow::Borrow;

#[derive(Debug, Clone, Default)]
pub struct Score {
    scores: Vec<(f64, f64, &'static str)>,
    total: f64,
}

#[derive(Debug, Default)]
pub struct ScoreAdj<'a> {
    score: Option<&'a mut f64>,
}

impl Score {
    pub fn new() -> Self {
        Self::default()
    }

    #[inline]
    /// Add score if it has the given property
    pub fn has(&mut self, for_what: &'static str, score: u32, has_it: bool) -> ScoreAdj<'_> {
        self.score_f(for_what, score as f64, if has_it { score as f64 } else { 0. })
    }

    #[inline]
    /// Add this much score, up to the max
    pub fn n(&mut self, for_what: &'static str, max_score: u32, n: impl Into<i64>) -> ScoreAdj<'_> {
        self.score_f(for_what, max_score as f64, n.into() as f64)
    }

    /// Add `max_score` * `n` where n is in 0..1
    pub fn frac(&mut self, for_what: &'static str, max_score: u32, n: impl Into<f64>) -> ScoreAdj<'_> {
        let n = n.into();
        assert!(n >= 0.);
        assert!(n <= 1.);
        let max_score = max_score as f64;
        self.score_f(for_what, max_score, n * max_score)
    }

    #[inline]
    /// Add `n` of `max_score` points
    pub fn score_f(&mut self, for_what: &'static str, max_score: f64, n: impl Into<f64>) -> ScoreAdj<'_> {
        let n = n.into();
        self.total += max_score;
        self.scores.push((n.max(0.), max_score, for_what));
        ScoreAdj { score: self.scores.last_mut().map(|(s, ..)| s) }
    }

    /// Start a new group of scores, and `max_score` is the max total score of the group
    pub fn group<'a>(&mut self, for_what: &'static str, max_score: u32, group: impl Borrow<Score>) -> ScoreAdj<'_> {
        self.frac(for_what, max_score, group.borrow().total())
    }

    /// Get total score
    pub fn total(&self) -> f64 {
        let sum = self.scores.iter().map(|&(v, limit, _)| v.max(0.).min(limit)).sum::<f64>();
        sum / self.total as f64
    }
}

impl<'a> ScoreAdj<'a> {
    pub fn mul(&mut self, by: f64) {
        self.adj(|n| n * by)
    }

    pub fn adj(&mut self, adj_with: impl FnOnce(f64) -> f64) {
        if let Some(s) = self.score.as_mut() {
            **s = adj_with(**s);
        }
    }
}

#[test]
fn scores() {
    let mut s1 = Score::new();
    s1.has("foo", 5, true);
    assert_eq!(1., s1.total());
    s1.has("bar", 15, false);
    assert!(s1.total() <= 0.26);
    assert!(s1.total() >= 0.24);
    let mut s2 = Score::new();
    s2.n("baz", 10, 5);
    s2.frac("baz2", 28, 0.5);
    assert!(s2.total() >= 0.49);
    assert!(s2.total() <= 0.51);
    let mut s3 = Score::new();
    s3.group("prev", 100, s1);
    s3.group("prev", 10, s2);
    assert!(s3.total() >= 0.26);
    assert!(s3.total() <= 0.28);
}