compile_time_sort/
lib.rs

1//! # Description
2//!
3//! This crate provides functions for sorting arrays and slices of primitives in `const` contexts.
4//!
5//! Arrays and slices of `bool`s, `u8`s, and `i8`s are sorted with counting sort while arrays of other types
6//! are sorted with quicksort.
7//!
8//! This implementation is usable on stable before the [`const_trait_impl`](https://github.com/rust-lang/rust/issues/67792) feature is stabilized,
9//! but that means that it unfortunately can not be generic,
10//! and so there are separate functions for every primitive type.
11//!
12//! Functions with the naming convention `into_sorted_*_array` take an array by value,
13//! and functions with the naming convention `sort_*_slice` take a mutable reference to a slice.
14//!
15//! # Examples
16//!
17//! Sort an array by value:
18//!
19//! ```
20//! use compile_time_sort::into_sorted_i32_array;
21//!
22//! const ARRAY: [i32; 5] = [-3, 3, 2, i32::MAX, 0];
23//! const SORTED_ARRAY: [i32; 5] = into_sorted_i32_array(ARRAY);
24//!
25//! assert_eq!(SORTED_ARRAY, [-3, 0, 2, 3, i32::MAX]);
26//! ```
27//!
28//! Sort an array by reference:
29#![cfg_attr(
30    feature = "sort_slices",
31    doc = r"```
32use compile_time_sort::sort_i32_slice;
33
34const SORTED_ARRAY: [i32; 5] = {
35    let mut arr = [5, i32::MIN, 0, -2, 0];
36    sort_i32_slice(&mut arr);
37    arr
38};
39
40assert_eq!(SORTED_ARRAY, [i32::MIN, -2, 0, 0, 5]);
41```"
42)]
43//!
44//! # Features
45//!
46//! `sort_slices`: enables the `sort_*_slice` functions and raises the MSRV of the crate from 1.59.0 to 1.83.0.
47
48#![no_std]
49#![cfg_attr(docsrs, feature(doc_auto_cfg))]
50
51#[cfg(feature = "sort_slices")]
52/// Defines a `const` function with the given name that takes in a mutable reference to a slice of the given type
53/// and sorts it using the quicksort algorithm.
54macro_rules! const_slice_quicksort {
55    ($name:ident, $tpe:ty) => {
56        const fn $name(slice: &mut [$tpe], left: usize, right: usize) {
57            let pivot_candidate_1 = left;
58            let pivot_candidate_2 = left + (right - left) / 2;
59            let pivot_candidate_3 = right;
60            let mut pivot_index = if slice[pivot_candidate_1] < slice[pivot_candidate_2] {
61                if slice[pivot_candidate_3] < slice[pivot_candidate_2] {
62                    if slice[pivot_candidate_1] < slice[pivot_candidate_3] {
63                        pivot_candidate_3
64                    } else {
65                        pivot_candidate_1
66                    }
67                } else {
68                    pivot_candidate_2
69                }
70            } else {
71                if slice[pivot_candidate_3] < slice[pivot_candidate_1] {
72                    if slice[pivot_candidate_2] < slice[pivot_candidate_3] {
73                        pivot_candidate_3
74                    } else {
75                        pivot_candidate_2
76                    }
77                } else {
78                    pivot_candidate_1
79                }
80            };
81
82            let mut l = left;
83            let mut r = right;
84
85            while l < r {
86                while (slice[pivot_index] < slice[r]) && (l < r) {
87                    r -= 1;
88                }
89                if l != r {
90                    (slice[pivot_index], slice[r]) = (slice[r], slice[pivot_index]);
91                    pivot_index = r;
92                }
93                while (slice[l] < slice[pivot_index]) && (l < r) {
94                    l += 1;
95                }
96                if l != r {
97                    (slice[pivot_index], slice[l]) = (slice[l], slice[pivot_index]);
98                    pivot_index = l;
99                }
100                if l != r && slice[l] == slice[r] {
101                    // Break out of infinite loops
102                    // if the elements at l and r are the same.
103                    break;
104                }
105            }
106            if left < l {
107                $name(slice, left, l - 1);
108            }
109            if right > l {
110                $name(slice, l + 1, right);
111            }
112        }
113    };
114}
115
116/// Defines a `const` function with the given name that sorts an array of the given type with the quicksort algorithm.
117macro_rules! const_array_quicksort {
118    ($name:ident, $tpe:ty) => {
119        const fn $name<const N: usize>(
120            mut array: [$tpe; N],
121            left: usize,
122            right: usize,
123        ) -> [$tpe; N] {
124            let pivot_candidate_1 = left;
125            let pivot_candidate_2 = left + (right - left) / 2;
126            let pivot_candidate_3 = right;
127            let mut pivot_index = if array[pivot_candidate_1] < array[pivot_candidate_2] {
128                if array[pivot_candidate_3] < array[pivot_candidate_2] {
129                    if array[pivot_candidate_1] < array[pivot_candidate_3] {
130                        pivot_candidate_3
131                    } else {
132                        pivot_candidate_1
133                    }
134                } else {
135                    pivot_candidate_2
136                }
137            } else {
138                if array[pivot_candidate_3] < array[pivot_candidate_1] {
139                    if array[pivot_candidate_2] < array[pivot_candidate_3] {
140                        pivot_candidate_3
141                    } else {
142                        pivot_candidate_2
143                    }
144                } else {
145                    pivot_candidate_1
146                }
147            };
148
149            let mut l = left;
150            let mut r = right;
151
152            while l < r {
153                while (array[pivot_index] < array[r]) && (l < r) {
154                    r -= 1;
155                }
156                if l != r {
157                    (array[pivot_index], array[r]) = (array[r], array[pivot_index]);
158                    pivot_index = r;
159                }
160                while (array[l] < array[pivot_index]) && (l < r) {
161                    l += 1;
162                }
163                if l != r {
164                    (array[pivot_index], array[l]) = (array[l], array[pivot_index]);
165                    pivot_index = l;
166                }
167                if l != r && array[l] == array[r] {
168                    break;
169                }
170            }
171            if left < l {
172                array = $name(array, left, l - 1);
173            }
174            if right > l {
175                array = $name(array, l + 1, right);
176            }
177            array
178        }
179    };
180}
181
182macro_rules! impl_const_quicksort {
183    ($pub_name_array:ident, $pub_name_slice:ident, $qsort_slice_name:ident, $qsort_array_name:ident, $tpe:ty, $tpe_name: literal) => {
184        #[cfg(feature = "sort_slices")]
185        const_slice_quicksort!{$qsort_slice_name, $tpe}
186
187        const_array_quicksort!{$qsort_array_name, $tpe}
188
189        #[doc = concat!("Sorts the given array of `", $tpe_name, "`s using the quicksort algorithm")]
190        pub const fn $pub_name_array<const N: usize>(array: [$tpe; N]) -> [$tpe; N] {
191            if N == 0 || N == 1 {
192                return array;
193            }
194            $qsort_array_name(array, 0, N - 1)
195        }
196
197        #[cfg(feature = "sort_slices")]
198        #[doc = concat!("Sorts the given slice of `", $tpe_name, "`s using the quicksort algorithm")]
199        pub const fn $pub_name_slice(slice: &mut [$tpe]) {
200            if slice.is_empty() || slice.len() == 1 {
201                return;
202            }
203            let last = slice.len() - 1;
204            $qsort_slice_name(slice, 0, last);
205        }
206    };
207}
208
209impl_const_quicksort!(
210    into_sorted_char_array,
211    sort_char_slice,
212    qsort_char_slice,
213    qsort_char_array,
214    char,
215    "char"
216);
217impl_const_quicksort!(
218    into_sorted_u16_array,
219    sort_u16_slice,
220    qsort_u16_slice,
221    qsort_u16_array,
222    u16,
223    "u16"
224);
225impl_const_quicksort!(
226    into_sorted_i16_array,
227    sort_i16_slice,
228    qsort_i16_slice,
229    qsort_i16_array,
230    i16,
231    "i16"
232);
233impl_const_quicksort!(
234    into_sorted_u32_array,
235    sort_u32_slice,
236    qsort_u32_slice,
237    qsort_u32_array,
238    u32,
239    "u32"
240);
241impl_const_quicksort!(
242    into_sorted_i32_array,
243    sort_i32_slice,
244    qsort_i32_slice,
245    qsort_i32_array,
246    i32,
247    "i32"
248);
249impl_const_quicksort!(
250    into_sorted_u64_array,
251    sort_u64_slice,
252    qsort_u64_slice,
253    qsort_u64_array,
254    u64,
255    "u64"
256);
257impl_const_quicksort!(
258    into_sorted_i64_array,
259    sort_i64_slice,
260    qsort_i64_slice,
261    qsort_i64_array,
262    i64,
263    "i64"
264);
265impl_const_quicksort!(
266    into_sorted_u128_array,
267    sort_u128_slice,
268    qsort_u128_slice,
269    qsort_u128_array,
270    u128,
271    "u128"
272);
273impl_const_quicksort!(
274    into_sorted_i128_array,
275    sort_i128_slice,
276    qsort_i128_slice,
277    qsort_i128_array,
278    i128,
279    "i128"
280);
281impl_const_quicksort!(
282    into_sorted_usize_array,
283    sort_usize_slice,
284    qsort_usize_slice,
285    qsort_usize_array,
286    usize,
287    "usize"
288);
289impl_const_quicksort!(
290    into_sorted_isize_array,
291    sort_isize_slice,
292    qsort_isize_slice,
293    qsort_isize_array,
294    isize,
295    "isize"
296);
297
298#[cfg(feature = "sort_slices")]
299/// Sorts the given slice of `i8`s using the counting sort algorithm.
300pub const fn sort_i8_slice(slice: &mut [i8]) {
301    if slice.is_empty() || slice.len() == 1 {
302        return;
303    }
304    let mut counts = [0_usize; u8::MAX as usize + 1];
305    let mut i = 0;
306    let n = slice.len();
307    while i < n {
308        counts[(slice[i] as i16 + i8::MIN.unsigned_abs() as i16) as usize] += 1;
309        i += 1;
310    }
311    i = 0;
312    let mut j = 0;
313    'outer: while i < n {
314        while counts[j] == 0 {
315            if j + 1 > u8::MAX as usize {
316                break 'outer;
317            }
318            j += 1;
319        }
320        slice[i] = (j as i16 + i8::MIN.unsigned_abs() as i16) as i8;
321        counts[j] -= 1;
322        i += 1;
323    }
324}
325
326/// Sorts the given array of `i8`s using the counting sort algorithm.
327pub const fn into_sorted_i8_array<const N: usize>(mut array: [i8; N]) -> [i8; N] {
328    if N == 0 || N == 1 {
329        return array;
330    }
331    let mut counts = [0_usize; u8::MAX as usize + 1];
332    let mut i = 0;
333    while i < N {
334        counts[(array[i] as i16 + i8::MIN.unsigned_abs() as i16) as usize] += 1;
335        i += 1;
336    }
337
338    i = 0;
339    let mut j = 0;
340    'outer: while i < N {
341        while counts[j] == 0 {
342            if j + 1 > u8::MAX as usize {
343                break 'outer;
344            }
345            j += 1;
346        }
347        array[i] = (j as i16 + i8::MIN.unsigned_abs() as i16) as i8;
348        counts[j] -= 1;
349        i += 1;
350    }
351
352    array
353}
354
355#[cfg(feature = "sort_slices")]
356/// Sorts the given slice of `u8`s using the counting sort algorithm.
357pub const fn sort_u8_slice(slice: &mut [u8]) {
358    if slice.is_empty() || slice.len() == 1 {
359        return;
360    }
361    let mut counts = [0_usize; u8::MAX as usize + 1];
362    let mut i = 0;
363    let n = slice.len();
364    while i < n {
365        counts[slice[i] as usize] += 1;
366        i += 1;
367    }
368    i = 0;
369    let mut j = 0;
370    'outer: while i < n {
371        while counts[j] == 0 {
372            if j + 1 > u8::MAX as usize {
373                break 'outer;
374            }
375            j += 1;
376        }
377        slice[i] = j as u8;
378        counts[j] -= 1;
379        i += 1;
380    }
381}
382
383/// Sorts the given array of `u8`s using the counting sort algorithm.
384pub const fn into_sorted_u8_array<const N: usize>(mut array: [u8; N]) -> [u8; N] {
385    if N == 0 || N == 1 {
386        return array;
387    }
388    let mut counts = [0_usize; u8::MAX as usize + 1];
389    let mut i = 0;
390    while i < N {
391        counts[array[i] as usize] += 1;
392        i += 1;
393    }
394    i = 0;
395    let mut j = 0;
396    'outer: while i < N {
397        while counts[j] == 0 {
398            if j + 1 > u8::MAX as usize {
399                break 'outer;
400            }
401            j += 1;
402        }
403        array[i] = j as u8;
404        counts[j] -= 1;
405        i += 1;
406    }
407    array
408}
409
410#[cfg(feature = "sort_slices")]
411/// Sorts the given slice of `bool`s using the counting sort algorithm.
412pub const fn sort_bool_slice(slice: &mut [bool]) {
413    if slice.is_empty() || slice.len() == 1 {
414        return;
415    }
416    let mut falses = 0;
417    let mut i = 0;
418    let n = slice.len();
419    while i < n {
420        if !slice[i] {
421            falses += 1;
422        }
423        i += 1;
424    }
425
426    i = 0;
427    while i < n {
428        if falses > 0 {
429            slice[i] = false;
430            falses -= 1;
431        } else {
432            slice[i] = true;
433        }
434        i += 1;
435    }
436}
437
438/// Sorts the given array of `bool`s using the counting sort algorithm.
439pub const fn into_sorted_bool_array<const N: usize>(mut array: [bool; N]) -> [bool; N] {
440    if N == 0 || N == 1 {
441        return array;
442    }
443    let mut falses = 0;
444    let mut i = 0;
445    while i < N {
446        if !array[i] {
447            falses += 1;
448        }
449        i += 1;
450    }
451
452    i = 0;
453    while i < N {
454        if falses > 0 {
455            array[i] = false;
456            falses -= 1;
457        } else {
458            array[i] = true;
459        }
460        i += 1;
461    }
462
463    array
464}