Skip to content

Commit 48e1f81

Browse files
chore(dep): Remove the ordered_float crate, because after v4.0 it will need the FloatCore trait to use the NotNan trait, which will cause conflict with num::Float. Since we are only using NotNan from ordered_float, we can simply manually cause panic when the value is nan. (#10)
1 parent 31ca840 commit 48e1f81

File tree

2 files changed

+220
-21
lines changed

2 files changed

+220
-21
lines changed

Cargo.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ exclude = [".github", ".pre-commit-config.yaml"]
1111
readme = "README.md"
1212
[dependencies]
1313
num = "0.4"
14-
ordered-float = { version = "3.9", features = ["serde"] }
15-
1614
serde = { version = "1.0", features = ["derive"] }
1715
serde_json = "1.0"
1816

src/sorted_window.rs

Lines changed: 220 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use num::{Float, FromPrimitive};
2-
use ordered_float::NotNan;
32
use serde::{Deserialize, Serialize};
43
use std::{
54
collections::VecDeque,
@@ -9,7 +8,7 @@ use std::{
98
#[doc(hidden)]
109
#[derive(Serialize, Deserialize)]
1110
pub struct SortedWindow<F: Float + FromPrimitive + AddAssign + SubAssign> {
12-
pub(crate) sorted_window: VecDeque<NotNan<F>>,
11+
pub(crate) sorted_window: VecDeque<F>,
1312
pub(crate) unsorted_window: VecDeque<F>,
1413
window_size: usize,
1514
}
@@ -30,42 +29,244 @@ impl<F: Float + FromPrimitive + AddAssign + SubAssign> SortedWindow<F> {
3029
}
3130

3231
pub fn front(&self) -> F {
33-
self.sorted_window
34-
.front()
35-
.expect("The value is Nan")
36-
.into_inner()
32+
*self.sorted_window.front().expect("Window is empty")
3733
}
34+
3835
pub fn back(&self) -> F {
39-
self.sorted_window
40-
.back()
41-
.expect("The value is NaN")
42-
.into_inner()
36+
*self.sorted_window.back().expect("Window is empty")
4337
}
38+
4439
pub fn push_back(&mut self, value: F) {
40+
// This will panic if `value` is NaN, which is the desired behavior
41+
// to maintain a sorted list of non-NaN floats.
42+
if value.is_nan() {
43+
panic!("Cannot push a NaN value into SortedWindow");
44+
}
45+
4546
// Before add the newest value to the sorted window
4647
// we should remove the oldest value
4748
if self.sorted_window.len() == self.window_size {
48-
let last_unsorted = self.unsorted_window.pop_front().unwrap();
49+
let oldest_unsorted = self
50+
.unsorted_window
51+
.pop_front()
52+
.expect("Unsorted window should not be empty when sorted window is full");
4953

50-
let last_unsorted_pos = self
54+
// Find the position of the value to remove using a custom comparison.
55+
// `partial_cmp` returns None for NaN comparisons, so `expect` will panic,
56+
// which is consistent with the behavior of NotNan.
57+
let pos_to_remove = self
5158
.sorted_window
52-
.binary_search(&NotNan::new(last_unsorted).expect("Value is NaN"))
53-
.expect("The value is Not in the sorted window");
54-
self.sorted_window.remove(last_unsorted_pos);
59+
.binary_search_by(|probe| {
60+
probe
61+
.partial_cmp(&oldest_unsorted)
62+
.expect("Stored values should not be NaN")
63+
})
64+
.expect("The value to remove was not found in the sorted window");
65+
66+
self.sorted_window.remove(pos_to_remove);
5567
}
68+
5669
self.unsorted_window.push_back(value);
5770

5871
let sorted_pos = self
5972
.sorted_window
60-
.binary_search(&NotNan::new(value).expect("Value is NaN"))
73+
.binary_search_by(|probe| {
74+
probe
75+
.partial_cmp(&value)
76+
.expect("Stored values should not be NaN")
77+
})
6178
.unwrap_or_else(|e| e);
62-
self.sorted_window
63-
.insert(sorted_pos, NotNan::new(value).expect("Value is NaN"));
79+
self.sorted_window.insert(sorted_pos, value);
6480
}
6581
}
6682
impl<F: Float + FromPrimitive + AddAssign + SubAssign> Index<usize> for SortedWindow<F> {
83+
type Output = F;
84+
6785
fn index(&self, index: usize) -> &Self::Output {
6886
&self.sorted_window[index]
6987
}
70-
type Output = F;
7188
}
89+
90+
#[cfg(test)]
91+
mod tests {
92+
use super::*;
93+
use std::f64;
94+
95+
#[test]
96+
fn test_new_and_empty() {
97+
let window: SortedWindow<f64> = SortedWindow::new(5);
98+
assert!(window.is_empty());
99+
assert_eq!(window.len(), 0);
100+
assert_eq!(window.window_size, 5);
101+
}
102+
103+
#[test]
104+
fn test_push_and_sort() {
105+
let mut window = SortedWindow::new(5);
106+
window.push_back(10.0);
107+
window.push_back(5.0);
108+
window.push_back(15.0);
109+
110+
assert!(!window.is_empty());
111+
assert_eq!(window.len(), 3);
112+
113+
// Check sorted order
114+
assert_eq!(window[0], 5.0);
115+
assert_eq!(window[1], 10.0);
116+
assert_eq!(window[2], 15.0);
117+
118+
// Check accessors
119+
assert_eq!(window.front(), 5.0);
120+
assert_eq!(window.back(), 15.0);
121+
122+
// Check unsorted (insertion) order
123+
assert_eq!(
124+
window.unsorted_window.iter().copied().collect::<Vec<_>>(),
125+
vec![10.0, 5.0, 15.0]
126+
);
127+
}
128+
129+
#[test]
130+
fn test_window_full_cycle() {
131+
let mut window = SortedWindow::new(3);
132+
133+
// 1. Fill the window
134+
window.push_back(10.0); // unsorted: [10], sorted: [10]
135+
window.push_back(20.0); // unsorted: [10, 20], sorted: [10, 20]
136+
window.push_back(5.0); // unsorted: [10, 20, 5], sorted: [5, 10, 20]
137+
138+
assert_eq!(window.len(), 3);
139+
assert_eq!(window.front(), 5.0);
140+
assert_eq!(window.back(), 20.0);
141+
assert_eq!(
142+
window.sorted_window.iter().copied().collect::<Vec<_>>(),
143+
vec![5.0, 10.0, 20.0]
144+
);
145+
146+
// 2. Push a new element, should remove the oldest (10.0)
147+
window.push_back(15.0); // oldest '10.0' is removed
148+
// unsorted: [20, 5, 15], sorted: [5, 15, 20]
149+
150+
assert_eq!(window.len(), 3);
151+
assert_eq!(window.front(), 5.0);
152+
assert_eq!(window.back(), 20.0);
153+
assert_eq!(
154+
window.sorted_window.iter().copied().collect::<Vec<_>>(),
155+
vec![5.0, 15.0, 20.0]
156+
);
157+
assert_eq!(
158+
window.unsorted_window.iter().copied().collect::<Vec<_>>(),
159+
vec![20.0, 5.0, 15.0]
160+
);
161+
162+
// 3. Push another new element, should remove the oldest (20.0)
163+
window.push_back(2.0); // oldest '20.0' is removed
164+
// unsorted: [5, 15, 2], sorted: [2, 5, 15]
165+
166+
assert_eq!(window.len(), 3);
167+
assert_eq!(window.front(), 2.0);
168+
assert_eq!(window.back(), 15.0);
169+
assert_eq!(
170+
window.sorted_window.iter().copied().collect::<Vec<_>>(),
171+
vec![2.0, 5.0, 15.0]
172+
);
173+
assert_eq!(
174+
window.unsorted_window.iter().copied().collect::<Vec<_>>(),
175+
vec![5.0, 15.0, 2.0]
176+
);
177+
}
178+
179+
#[test]
180+
fn test_with_duplicate_values() {
181+
let mut window = SortedWindow::new(4);
182+
window.push_back(10.0);
183+
window.push_back(5.0);
184+
window.push_back(10.0); // Duplicate value
185+
186+
assert_eq!(window.len(), 3);
187+
assert_eq!(window.front(), 5.0);
188+
assert_eq!(window.back(), 10.0);
189+
assert_eq!(
190+
window.sorted_window.iter().copied().collect::<Vec<_>>(),
191+
vec![5.0, 10.0, 10.0]
192+
);
193+
194+
// Fill window
195+
window.push_back(20.0);
196+
assert_eq!(
197+
window.sorted_window.iter().copied().collect::<Vec<_>>(),
198+
vec![5.0, 10.0, 10.0, 20.0]
199+
);
200+
201+
// Push another value, oldest (the first 10.0) should be removed
202+
window.push_back(1.0);
203+
assert_eq!(
204+
window.sorted_window.iter().copied().collect::<Vec<_>>(),
205+
vec![1.0, 5.0, 10.0, 20.0]
206+
);
207+
assert_eq!(
208+
window.unsorted_window.iter().copied().collect::<Vec<_>>(),
209+
vec![5.0, 10.0, 20.0, 1.0]
210+
);
211+
}
212+
213+
#[test]
214+
fn test_window_size_one() {
215+
let mut window = SortedWindow::new(1);
216+
217+
window.push_back(10.0);
218+
assert_eq!(window.len(), 1);
219+
assert_eq!(window.front(), 10.0);
220+
assert_eq!(window[0], 10.0);
221+
222+
window.push_back(5.0);
223+
assert_eq!(window.len(), 1);
224+
assert_eq!(window.front(), 5.0);
225+
assert_eq!(window[0], 5.0);
226+
227+
window.push_back(20.0);
228+
assert_eq!(window.len(), 1);
229+
assert_eq!(window.front(), 20.0);
230+
assert_eq!(window[0], 20.0);
231+
}
232+
233+
#[test]
234+
#[should_panic]
235+
fn test_window_size_zero() {
236+
let mut window = SortedWindow::new(0);
237+
window.push_back(10.0);
238+
239+
assert_eq!(window.len(), 0);
240+
assert!(window.is_empty());
241+
assert!(window.unsorted_window.is_empty());
242+
}
243+
244+
#[test]
245+
#[should_panic(expected = "Cannot push a NaN value into SortedWindow")]
246+
fn test_panic_on_nan_push() {
247+
let mut window = SortedWindow::new(3);
248+
window.push_back(f64::NAN);
249+
}
250+
251+
#[test]
252+
#[should_panic(expected = "Window is empty")]
253+
fn test_panic_on_front_empty() {
254+
let window: SortedWindow<f64> = SortedWindow::new(3);
255+
window.front();
256+
}
257+
258+
#[test]
259+
#[should_panic(expected = "Window is empty")]
260+
fn test_panic_on_back_empty() {
261+
let window: SortedWindow<f64> = SortedWindow::new(3);
262+
window.back();
263+
}
264+
265+
#[test]
266+
#[should_panic]
267+
fn test_panic_on_index_out_of_bounds() {
268+
let mut window = SortedWindow::new(3);
269+
window.push_back(1.0);
270+
let _ = window[1]; // Should panic
271+
}
272+
}

0 commit comments

Comments
 (0)