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