sorting_race/services/sorters/
quick.rs1use crate::models::traits::{Sorter, StepResult, Telemetry, Markers};
4use std::any::Any;
5
6#[derive(Debug, Clone)]
8struct StackFrame {
9 low: usize,
10 high: usize,
11}
12
13#[derive(Debug, Clone)]
15enum PartitionState {
16 NotStarted,
18 InProgress {
20 current_j: usize,
21 current_i: usize,
22 pivot: i32,
23 low: usize,
24 high: usize,
25 },
26 Complete,
28}
29
30#[derive(Debug)]
32pub struct QuickSort {
33 data: Vec<i32>,
34 stack: Vec<StackFrame>,
35 comparisons: u64,
36 moves: u64,
37 complete: bool,
38 current_pivot: Option<usize>,
39 memory_usage: usize,
40 partition_state: PartitionState,
41 max_progress_seen: f32,
42}
43
44impl QuickSort {
45 pub fn new() -> Self {
47 Self {
48 data: Vec::new(),
49 stack: Vec::new(),
50 comparisons: 0,
51 moves: 0,
52 complete: false,
53 current_pivot: None,
54 memory_usage: 0,
55 partition_state: PartitionState::NotStarted,
56 max_progress_seen: 0.0,
57 }
58 }
59
60 fn start_partition(&mut self, low: usize, high: usize) {
62 let pivot = self.data[high];
63 self.partition_state = PartitionState::InProgress {
64 current_j: low,
65 current_i: low,
66 pivot,
67 low,
68 high,
69 };
70 self.current_pivot = Some(high);
71 }
72
73 fn continue_partition(&mut self, budget: &mut usize) -> Option<usize> {
76 if *budget == 0 {
77 return None;
78 }
79
80 match &self.partition_state.clone() {
81 PartitionState::InProgress { current_j, current_i, pivot, low, high } => {
82 let mut j = *current_j;
83 let mut i = *current_i;
84 let pivot_val = *pivot;
85 let low_bound = *low;
86 let high_bound = *high;
87
88 while j < high_bound && *budget > 0 {
90 *budget -= 1;
91 self.comparisons += 1;
92
93 if self.data[j] <= pivot_val {
94 if i != j {
95 self.data.swap(i, j);
96 self.moves += 1;
97 }
98 i += 1;
99 }
100 j += 1;
101 }
102
103 if j >= high_bound {
104 if i != high_bound {
106 self.data.swap(i, high_bound);
107 self.moves += 1;
108 }
109 self.partition_state = PartitionState::Complete;
110 self.current_pivot = None;
111 Some(i)
112 } else {
113 self.partition_state = PartitionState::InProgress {
115 current_j: j,
116 current_i: i,
117 pivot: pivot_val,
118 low: low_bound,
119 high: high_bound,
120 };
121 None
122 }
123 }
124 _ => None, }
126 }
127
128 #[allow(dead_code)]
130 fn get_partition_progress(&self) -> f32 {
131 match &self.partition_state {
132 PartitionState::NotStarted => 0.0,
133 PartitionState::Complete => 1.0,
134 PartitionState::InProgress { current_j, low, high, .. } => {
135 if *high <= *low {
136 1.0
137 } else {
138 (*current_j - *low) as f32 / (*high - *low) as f32
139 }
140 }
141 }
142 }
143}
144
145impl Default for QuickSort {
146 fn default() -> Self {
147 Self::new()
148 }
149}
150
151impl Sorter for QuickSort {
152 fn step(&mut self, budget: usize) -> StepResult {
153 if self.complete || self.data.len() <= 1 {
154 return StepResult {
155 comparisons_used: 0,
156 moves_made: 0,
157 continued: false,
158 };
159 }
160
161 let initial_comparisons = self.comparisons;
162 let initial_moves = self.moves;
163 let mut remaining_budget = budget;
164
165 if matches!(self.partition_state, PartitionState::InProgress { .. })
167 && let Some(pivot_pos) = self.continue_partition(&mut remaining_budget) {
168 if let Some(frame) = self.stack.last() {
170 let current_frame = frame.clone();
171 self.stack.pop(); if pivot_pos + 1 < current_frame.high {
175 self.stack.push(StackFrame {
176 low: pivot_pos + 1,
177 high: current_frame.high,
178 });
179 }
180
181 if current_frame.low < pivot_pos {
183 self.stack.push(StackFrame {
184 low: current_frame.low,
185 high: pivot_pos - 1,
186 });
187 }
188 }
189 self.partition_state = PartitionState::NotStarted;
190 }
191 while remaining_budget > 0 && !self.stack.is_empty() &&
195 matches!(self.partition_state, PartitionState::NotStarted) {
196
197 let frame = self.stack.last().unwrap().clone();
198
199 if frame.low >= frame.high {
200 self.stack.pop(); continue;
202 }
203
204 self.start_partition(frame.low, frame.high);
206
207 if let Some(pivot_pos) = self.continue_partition(&mut remaining_budget) {
209 self.stack.pop(); if pivot_pos + 1 < frame.high {
214 self.stack.push(StackFrame {
215 low: pivot_pos + 1,
216 high: frame.high,
217 });
218 }
219
220 if frame.low < pivot_pos {
222 self.stack.push(StackFrame {
223 low: frame.low,
224 high: pivot_pos - 1,
225 });
226 }
227
228 self.partition_state = PartitionState::NotStarted;
229 } else {
230 break;
232 }
233 }
234
235 if self.stack.is_empty() && matches!(self.partition_state, PartitionState::NotStarted) {
237 self.complete = true;
238 self.current_pivot = None;
239 }
240
241 self.memory_usage = self.stack.len() * std::mem::size_of::<StackFrame>();
242
243 self.update_progress();
245
246 StepResult {
247 comparisons_used: (self.comparisons - initial_comparisons) as usize,
248 moves_made: (self.moves - initial_moves) as usize,
249 continued: !self.complete,
250 }
251 }
252
253 fn is_complete(&self) -> bool {
254 self.complete
255 }
256
257 fn get_telemetry(&self) -> Telemetry {
258 let mut markers = Markers::default();
259
260 if let Some(pivot) = self.current_pivot {
261 markers.pivot = Some(pivot);
262 }
263
264 match &self.partition_state {
266 PartitionState::InProgress { current_j, current_i, low, high, .. } => {
267 markers.cursors = vec![*current_i, *current_j, *low, *high];
268 }
269 _ => {
270 if let Some(frame) = self.stack.last() {
271 markers.cursors = vec![frame.low, frame.high];
272 }
273 }
274 }
275
276 let progress_hint = self.calculate_progress();
277
278 let status_text = if self.complete {
279 "Completed".to_string()
280 } else {
281 match &self.partition_state {
282 PartitionState::InProgress { current_j, low, high, .. } => {
283 format!("Partitioning range [{}, {}] - progress: {}/{}",
284 low, high, current_j - low, high - low)
285 }
286 _ => {
287 if let Some(frame) = self.stack.last() {
288 format!("Partitioning range [{}, {}]", frame.low, frame.high)
289 } else {
290 "Processing".to_string()
291 }
292 }
293 }
294 };
295
296 Telemetry {
297 total_comparisons: self.comparisons,
298 total_moves: self.moves,
299 memory_current: self.get_memory_usage(),
300 memory_peak: self.get_memory_usage(),
301 highlights: markers.cursors.clone(),
302 markers,
303 status_text,
304 progress_hint,
305 }
306 }
307
308 fn reset(&mut self, data: Vec<i32>) {
309 self.data = data;
310 self.stack.clear();
311 self.comparisons = 0;
312 self.moves = 0;
313 self.complete = self.data.len() <= 1;
314 self.current_pivot = None;
315 self.memory_usage = 0;
316 self.partition_state = PartitionState::NotStarted;
317 self.max_progress_seen = 0.0;
318
319 if !self.complete {
320 self.stack.push(StackFrame {
321 low: 0,
322 high: self.data.len() - 1,
323 });
324 self.memory_usage = std::mem::size_of::<StackFrame>();
325 }
326 }
327
328 fn name(&self) -> &str {
329 "Quick Sort"
330 }
331
332 fn get_array(&self) -> &[i32] {
333 &self.data
334 }
335
336 fn get_memory_usage(&self) -> usize {
337 self.data.len() * std::mem::size_of::<i32>() +
339 self.stack.len() * std::mem::size_of::<StackFrame>()
340 }
341
342 fn as_any(&self) -> &dyn Any {
343 self
344 }
345
346 fn as_any_mut(&mut self) -> &mut dyn Any {
347 self
348 }
349}
350
351impl QuickSort {
352 fn calculate_progress(&self) -> f32 {
354 if self.data.len() <= 1 {
355 return 1.0;
356 }
357
358 if self.complete {
359 return 1.0;
360 }
361
362 let n = self.data.len() as f32;
364 let expected_comparisons = n * n.log2(); let base_progress = (self.comparisons as f32 / expected_comparisons).min(0.95);
366
367 let partition_progress = match &self.partition_state {
369 PartitionState::InProgress { current_j, low, high, .. } => {
370 if *high > *low {
371 let current_partition_size = high - low;
372 let partition_weight = current_partition_size as f32 / n;
373 let local_progress = (*current_j - *low) as f32 / (*high - *low) as f32;
374 partition_weight * local_progress * 0.05 } else {
376 0.0
377 }
378 }
379 _ => 0.0,
380 };
381
382 let current_progress = (base_progress + partition_progress).min(1.0).max(0.0);
383
384 self.max_progress_seen.max(current_progress)
386 }
387
388 fn update_progress(&mut self) {
390 let current_progress = self.calculate_progress();
391 self.max_progress_seen = self.max_progress_seen.max(current_progress);
392 }
393}