1#[derive(Clone, Copy, Debug)]
32pub struct Window<const N: usize> {
33 samples: [f32; N],
34 len: usize,
35 next: usize,
36}
37
38impl<const N: usize> Window<N> {
39 pub fn new() -> Self {
45 Self {
46 samples: [0.0; N],
47 len: 0,
48 next: 0,
49 }
50 }
51
52 pub fn push(&mut self, reading: f32) {
58 if N == 0 {
59 return;
60 }
61 self.samples[self.next] = reading;
62 self.next = (self.next + 1) % N;
63 if self.len < N {
64 self.len += 1;
65 }
66 }
67
68 pub fn len(&self) -> usize {
70 self.len
71 }
72
73 pub fn is_empty(&self) -> bool {
75 self.len == 0
76 }
77
78 pub fn is_full(&self) -> bool {
80 self.len == N
81 }
82
83 pub fn capacity(&self) -> usize {
85 N
86 }
87
88 pub fn latest(&self) -> Option<f32> {
90 if self.len == 0 {
91 return None;
92 }
93 Some(self.samples[(self.next + N - 1) % N])
94 }
95
96 pub fn oldest(&self) -> Option<f32> {
98 if self.len == 0 {
99 return None;
100 }
101 let index = if self.is_full() { self.next } else { 0 };
102 Some(self.samples[index])
103 }
104
105 pub fn min(&self) -> Option<f32> {
107 self.samples[..self.len].iter().copied().reduce(f32::min)
108 }
109
110 pub fn max(&self) -> Option<f32> {
112 self.samples[..self.len].iter().copied().reduce(f32::max)
113 }
114
115 pub fn range(&self) -> Option<f32> {
117 Some(self.max()? - self.min()?)
118 }
119
120 pub fn mean(&self) -> Option<f32> {
122 if self.len == 0 {
123 return None;
124 }
125 let sum: f32 = self.samples[..self.len].iter().sum();
126 Some(sum / self.len as f32)
127 }
128
129 pub fn variance(&self) -> Option<f32> {
135 let mean = self.mean()?;
136 let sum_squared: f32 = self.samples[..self.len]
137 .iter()
138 .map(|reading| {
139 let deviation = reading - mean;
140 deviation * deviation
141 })
142 .sum();
143 Some(sum_squared / self.len as f32)
144 }
145}
146
147impl<const N: usize> Default for Window<N> {
148 fn default() -> Self {
149 Self::new()
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 fn approx(a: f32, b: f32) -> bool {
158 (a - b).abs() < 1e-4
159 }
160
161 #[test]
162 fn an_empty_window_has_no_statistics() {
163 let window = Window::<3>::new();
164 assert!(window.is_empty());
165 assert_eq!(window.len(), 0);
166 assert_eq!(window.min(), None);
167 assert_eq!(window.max(), None);
168 assert_eq!(window.mean(), None);
169 assert_eq!(window.range(), None);
170 assert_eq!(window.variance(), None);
171 assert_eq!(window.latest(), None);
172 assert_eq!(window.oldest(), None);
173 }
174
175 #[test]
176 fn fills_to_capacity_then_stays_full() {
177 let mut window = Window::<3>::new();
178 window.push(1.0);
179 assert_eq!(window.len(), 1);
180 assert!(!window.is_full());
181 window.push(2.0);
182 window.push(3.0);
183 assert!(window.is_full());
184 window.push(4.0);
185 assert_eq!(window.len(), 3);
186 assert_eq!(window.capacity(), 3);
187 }
188
189 #[test]
190 fn reports_spread_over_the_held_readings() {
191 let mut window = Window::<4>::new();
192 for reading in [40.0, 42.0, 38.0, 41.0] {
193 window.push(reading);
194 }
195 assert_eq!(window.min(), Some(38.0));
196 assert_eq!(window.max(), Some(42.0));
197 assert_eq!(window.range(), Some(4.0));
198 assert!(approx(window.mean().unwrap(), 40.25));
199 }
200
201 #[test]
202 fn variance_matches_the_hand_computed_value() {
203 let mut window = Window::<3>::new();
205 for reading in [2.0, 4.0, 6.0] {
206 window.push(reading);
207 }
208 assert!(approx(window.variance().unwrap(), 8.0 / 3.0));
209 }
210
211 #[test]
212 fn the_oldest_reading_is_evicted_when_full() {
213 let mut window = Window::<3>::new();
214 for reading in [10.0, 20.0, 30.0, 40.0] {
215 window.push(reading);
216 }
217 assert_eq!(window.oldest(), Some(20.0));
219 assert_eq!(window.latest(), Some(40.0));
220 assert_eq!(window.min(), Some(20.0));
221 assert_eq!(window.max(), Some(40.0));
222 }
223
224 #[test]
225 fn latest_and_oldest_track_before_the_window_fills() {
226 let mut window = Window::<5>::new();
227 window.push(7.0);
228 assert_eq!(window.latest(), Some(7.0));
229 assert_eq!(window.oldest(), Some(7.0));
230 window.push(9.0);
231 assert_eq!(window.latest(), Some(9.0));
232 assert_eq!(window.oldest(), Some(7.0));
233 }
234}