khive_score/
comparator.rs1use crate::DeterministicScore;
4use std::cmp::Ordering;
5
6#[derive(Clone, Debug, Eq, PartialEq)]
8pub struct Ranked<T: Ord> {
9 score: DeterministicScore,
10 id: T,
11}
12
13impl<T: Ord> Ranked<T> {
14 #[inline]
15 pub fn new(score: DeterministicScore, id: T) -> Self {
16 Self { score, id }
17 }
18
19 #[inline]
20 pub fn score(&self) -> DeterministicScore {
21 self.score
22 }
23
24 #[inline]
25 pub fn id(&self) -> &T {
26 &self.id
27 }
28
29 #[inline]
30 pub fn into_id(self) -> T {
31 self.id
32 }
33
34 #[inline]
35 pub fn into_parts(self) -> (DeterministicScore, T) {
36 (self.score, self.id)
37 }
38}
39
40impl<T: Ord> Ord for Ranked<T> {
41 #[inline]
42 fn cmp(&self, other: &Self) -> Ordering {
43 self.score
44 .cmp(&other.score)
45 .then_with(|| other.id.cmp(&self.id))
46 }
47}
48
49impl<T: Ord> PartialOrd for Ranked<T> {
50 #[inline]
51 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
52 Some(self.cmp(other))
53 }
54}
55
56#[inline(always)]
58pub fn cmp_desc_then_id<T: Ord>(
59 a_score: DeterministicScore,
60 a_id: &T,
61 b_score: DeterministicScore,
62 b_id: &T,
63) -> Ordering {
64 b_score.cmp(&a_score).then_with(|| a_id.cmp(b_id))
65}
66
67#[inline(always)]
69pub fn cmp_asc_then_id<T: Ord>(
70 a_score: DeterministicScore,
71 a_id: &T,
72 b_score: DeterministicScore,
73 b_id: &T,
74) -> Ordering {
75 a_score.cmp(&b_score).then_with(|| a_id.cmp(b_id))
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 use std::collections::BinaryHeap;
82
83 #[test]
84 fn ranked_heap_determinism() {
85 let mut heap: BinaryHeap<Ranked<u64>> = BinaryHeap::new();
86 heap.push(Ranked::new(DeterministicScore::from_f64(0.95), 3));
87 heap.push(Ranked::new(DeterministicScore::from_f64(0.95), 1));
88 heap.push(Ranked::new(DeterministicScore::from_f64(0.95), 2));
89 heap.push(Ranked::new(DeterministicScore::from_f64(0.87), 4));
90
91 let results: Vec<_> = std::iter::from_fn(|| heap.pop()).collect();
92 assert_eq!(results[0].id(), &1);
93 assert_eq!(results[1].id(), &2);
94 assert_eq!(results[2].id(), &3);
95 assert_eq!(results[3].id(), &4);
96 }
97
98 #[test]
99 fn cmp_desc() {
100 let mut items = [
101 (DeterministicScore::from_f64(0.9), 2u64),
102 (DeterministicScore::from_f64(0.9), 1u64),
103 (DeterministicScore::from_f64(0.8), 3u64),
104 ];
105 items.sort_by(|(sa, ia), (sb, ib)| cmp_desc_then_id(*sa, ia, *sb, ib));
106 assert_eq!(items[0].1, 1);
107 assert_eq!(items[1].1, 2);
108 assert_eq!(items[2].1, 3);
109 }
110
111 #[test]
112 fn cmp_asc_then_id_tie_lower_id_wins() {
113 let score = DeterministicScore::from_f64(0.5);
114 let mut items = [(score, 3u64), (score, 1u64), (score, 2u64)];
115 items.sort_by(|(sa, ia), (sb, ib)| cmp_asc_then_id(*sa, ia, *sb, ib));
116 assert_eq!(items[0].1, 1);
117 assert_eq!(items[1].1, 2);
118 assert_eq!(items[2].1, 3);
119 }
120
121 #[test]
122 fn ranked_into_parts_returns_score_and_id() {
123 let score = DeterministicScore::from_f64(0.75);
124 let ranked = Ranked::new(score, 42u64);
125 let (s, id) = ranked.into_parts();
126 assert_eq!(s, score);
127 assert_eq!(id, 42u64);
128 }
129}