1use crate::DeterministicScore;
4use std::cmp::Ordering;
5
6#[derive(Clone, Debug, Eq, PartialEq)]
11pub struct Ranked<T: Ord> {
12 score: DeterministicScore,
13 id: T,
14}
15
16impl<T: Ord> Ranked<T> {
17 #[inline]
19 pub fn new(score: DeterministicScore, id: T) -> Self {
20 Self { score, id }
21 }
22
23 #[inline]
25 pub fn score(&self) -> DeterministicScore {
26 self.score
27 }
28
29 #[inline]
31 pub fn id(&self) -> &T {
32 &self.id
33 }
34
35 #[inline]
37 pub fn into_id(self) -> T {
38 self.id
39 }
40
41 #[inline]
43 pub fn into_parts(self) -> (DeterministicScore, T) {
44 (self.score, self.id)
45 }
46}
47
48impl<T: Ord> Ord for Ranked<T> {
49 #[inline]
50 fn cmp(&self, other: &Self) -> Ordering {
51 self.score
52 .cmp(&other.score)
53 .then_with(|| other.id.cmp(&self.id))
54 }
55}
56
57impl<T: Ord> PartialOrd for Ranked<T> {
58 #[inline]
59 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
60 Some(self.cmp(other))
61 }
62}
63
64#[inline(always)]
66pub fn cmp_desc_then_id<T: Ord>(
67 a_score: DeterministicScore,
68 a_id: &T,
69 b_score: DeterministicScore,
70 b_id: &T,
71) -> Ordering {
72 b_score.cmp(&a_score).then_with(|| a_id.cmp(b_id))
73}
74
75#[inline(always)]
77pub fn cmp_asc_then_id<T: Ord>(
78 a_score: DeterministicScore,
79 a_id: &T,
80 b_score: DeterministicScore,
81 b_id: &T,
82) -> Ordering {
83 a_score.cmp(&b_score).then_with(|| a_id.cmp(b_id))
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use std::collections::BinaryHeap;
90
91 #[test]
92 fn ranked_heap_determinism() {
93 let mut heap: BinaryHeap<Ranked<u64>> = BinaryHeap::new();
94 heap.push(Ranked::new(DeterministicScore::from_f64(0.95), 3));
95 heap.push(Ranked::new(DeterministicScore::from_f64(0.95), 1));
96 heap.push(Ranked::new(DeterministicScore::from_f64(0.95), 2));
97 heap.push(Ranked::new(DeterministicScore::from_f64(0.87), 4));
98
99 let results: Vec<_> = std::iter::from_fn(|| heap.pop()).collect();
100 assert_eq!(results[0].id(), &1);
101 assert_eq!(results[1].id(), &2);
102 assert_eq!(results[2].id(), &3);
103 assert_eq!(results[3].id(), &4);
104 }
105
106 #[test]
107 fn cmp_desc() {
108 let mut items = [
109 (DeterministicScore::from_f64(0.9), 2u64),
110 (DeterministicScore::from_f64(0.9), 1u64),
111 (DeterministicScore::from_f64(0.8), 3u64),
112 ];
113 items.sort_by(|(sa, ia), (sb, ib)| cmp_desc_then_id(*sa, ia, *sb, ib));
114 assert_eq!(items[0].1, 1);
115 assert_eq!(items[1].1, 2);
116 assert_eq!(items[2].1, 3);
117 }
118
119 #[test]
120 fn cmp_asc_then_id_tie_lower_id_wins() {
121 let score = DeterministicScore::from_f64(0.5);
122 let mut items = [(score, 3u64), (score, 1u64), (score, 2u64)];
123 items.sort_by(|(sa, ia), (sb, ib)| cmp_asc_then_id(*sa, ia, *sb, ib));
124 assert_eq!(items[0].1, 1);
125 assert_eq!(items[1].1, 2);
126 assert_eq!(items[2].1, 3);
127 }
128
129 #[test]
130 fn ranked_into_parts_returns_score_and_id() {
131 let score = DeterministicScore::from_f64(0.75);
132 let ranked = Ranked::new(score, 42u64);
133 let (s, id) = ranked.into_parts();
134 assert_eq!(s, score);
135 assert_eq!(id, 42u64);
136 }
137
138 #[test]
140 fn ranked_heap_pops_highest_score_first() {
141 use std::collections::BinaryHeap;
142 let mut heap = BinaryHeap::new();
143 heap.push(Ranked::new(DeterministicScore::from_f64(0.3), 3u64));
144 heap.push(Ranked::new(DeterministicScore::from_f64(0.9), 1u64));
145 heap.push(Ranked::new(DeterministicScore::from_f64(0.5), 2u64));
146
147 let first = heap.pop().unwrap();
148 assert_eq!(first.score(), DeterministicScore::from_f64(0.9));
149 assert_eq!(first.id(), &1u64);
150 }
151
152 #[test]
154 fn ranked_vec_sort_is_ascending_not_ranking_order() {
155 let mut items = [
156 Ranked::new(DeterministicScore::from_f64(0.9), 1u64),
157 Ranked::new(DeterministicScore::from_f64(0.3), 3u64),
158 Ranked::new(DeterministicScore::from_f64(0.5), 2u64),
159 ];
160 items.sort();
161 assert_eq!(items[0].score(), DeterministicScore::from_f64(0.3));
163 assert_eq!(items[2].score(), DeterministicScore::from_f64(0.9));
164 }
165
166 #[test]
168 fn cmp_desc_then_id_gives_descending_order() {
169 let mut items: Vec<(DeterministicScore, u64)> = vec![
170 (DeterministicScore::from_f64(0.3), 3),
171 (DeterministicScore::from_f64(0.9), 1),
172 (DeterministicScore::from_f64(0.5), 2),
173 ];
174 items.sort_unstable_by(|(sa, ia), (sb, ib)| cmp_desc_then_id(*sa, ia, *sb, ib));
175 assert_eq!(items[0].1, 1); assert_eq!(items[1].1, 2); assert_eq!(items[2].1, 3); }
179}