sorting_race/services/sorters/
quick.rs

1//! Quick Sort implementation
2
3use crate::models::traits::{Sorter, StepResult, Telemetry, Markers};
4use std::any::Any;
5
6/// Stack frame for Quick Sort recursion simulation
7#[derive(Debug, Clone)]
8struct StackFrame {
9    low: usize,
10    high: usize,
11}
12
13/// State for incremental partitioning
14#[derive(Debug, Clone)]
15enum PartitionState {
16    /// No partitioning in progress
17    NotStarted,
18    /// Partitioning in progress with current indices and pivot value
19    InProgress {
20        current_j: usize,
21        current_i: usize,
22        pivot: i32,
23        low: usize,
24        high: usize,
25    },
26    /// Partitioning completed
27    Complete,
28}
29
30/// Quick Sort algorithm implementation
31#[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    /// Create a new QuickSort instance
46    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    /// Start a new partition operation
61    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    /// Continue or complete partition operation with given budget
74    /// Returns Some(pivot_index) if partition completes, None if more work needed
75    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                // Continue partitioning from where we left off
89                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                    // Partitioning complete - place pivot in final position
105                    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                    // More work needed - update state
114                    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, // Should not happen when called appropriately
125        }
126    }
127
128    /// Get partition progress (0.0 to 1.0)
129    #[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        // Handle ongoing partition if in progress
166        if matches!(self.partition_state, PartitionState::InProgress { .. })
167            && let Some(pivot_pos) = self.continue_partition(&mut remaining_budget) {
168                // Partition completed - add sub-problems to stack
169                if let Some(frame) = self.stack.last() {
170                    let current_frame = frame.clone();
171                    self.stack.pop(); // Remove current frame
172                    
173                    // Add right subarray if it has more than one element
174                    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                    // Add left subarray if it has more than one element
182                    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            // If partition didn't complete, we'll continue it in the next step
192
193        // Start new partitions if budget allows and no partition is in progress
194        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(); // Remove trivial frame
201                continue;
202            }
203
204            // Start new partition
205            self.start_partition(frame.low, frame.high);
206            
207            // Try to make progress on the new partition
208            if let Some(pivot_pos) = self.continue_partition(&mut remaining_budget) {
209                // Partition completed immediately
210                self.stack.pop(); // Remove current frame
211                
212                // Add right subarray if it has more than one element
213                if pivot_pos + 1 < frame.high {
214                    self.stack.push(StackFrame {
215                        low: pivot_pos + 1,
216                        high: frame.high,
217                    });
218                }
219                
220                // Add left subarray if it has more than one element
221                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                // Partition started but not completed - will continue next step
231                break;
232            }
233        }
234
235        // Check completion
236        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        // Update progress tracking for monotonicity
244        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        // Add cursors for current partition state
265        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        // Data array + stack memory
338        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    /// Calculate overall progress of the sorting algorithm (monotonic)
353    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        // Base progress on comparisons made vs expected total comparisons
363        let n = self.data.len() as f32;
364        let expected_comparisons = n * n.log2(); // O(n log n) average case
365        let base_progress = (self.comparisons as f32 / expected_comparisons).min(0.95);
366
367        // Add fine-grained progress from current partition
368        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 // Small contribution for smoothness
375                } 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        // Ensure monotonicity: never decrease progress
385        self.max_progress_seen.max(current_progress)
386    }
387
388    /// Update progress tracking after a step
389    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}