diff --git a/.gitignore b/.gitignore index 2f88dba..d889fbf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target **/*.rs.bk -Cargo.lock \ No newline at end of file +Cargo.lock +.idea diff --git a/Cargo.toml b/Cargo.toml index d0e190b..01c4f78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,4 +16,4 @@ include = ["Cargo.toml", "rustfmt.toml", "src/**/*.rs", "examples/**/*.rs", "REA status = "actively-developed" [badges.travis-ci] -repository = "slightlyoutofphase/staticsort" \ No newline at end of file +repository = "slightlyoutofphase/staticsort" diff --git a/examples/main.rs b/examples/main.rs index eee9a9a..16e410f 100644 --- a/examples/main.rs +++ b/examples/main.rs @@ -26,6 +26,13 @@ static ZZ: [usize; 12] = staticsort!( [1, 6, 2, 5, 3, 4, 7, 12, 8, 11, 9, 10] ); +/// This is wildly unsound and will only work on ASCII strings +/// in the same (lower/upper)case and numbers. +static STRS: [&'static str; 4] = staticsort!( + &'static str, 0, 3, + ["please", "order", "me", "zzz"] +); + fn main() { // Prints: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] println!("XX: {:?}", XX); @@ -33,4 +40,19 @@ fn main() { println!("YY: {:?}", YY); // Prints: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] println!("ZZ: {:?}", ZZ); + // Prints: ["me", "order", "please", "zzz"] + println!("STRS: {:?}", STRS); + + // Prints: (key, value) pairs: ([32, 42, 69, 82], ["monica", "elon", "mark", "andrzej"]) + const AGES: [u8; 4] = [69, 42, 32, 82]; + let names = ["mark", "elon", "monica", "andrzej"]; + println!( + "(key, value) pairs: {:?}", + staticsort!(u8, 0, 3, AGES, names) + ); + + const SIZE: usize = 4; + let but_not_const_array: [u8; SIZE] = [3,1,2,0]; + // but_not_const_array: [0, 1, 2, 3] + println!("but_not_const_array: {:?}", staticsort!(u8, 0, 3, but_not_const_array, len SIZE)); } diff --git a/src/lib.rs b/src/lib.rs index eb4955c..40212d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,72 +1,113 @@ #![no_std] #![allow(incomplete_features)] -#![feature(const_fn_floating_point_arithmetic, const_generics)] +#![allow(unused_variables)] +#![feature(const_fn_floating_point_arithmetic, const_generics, const_fn_trait_bound)] #[doc(hidden)] pub struct __StaticSorter { marker: core::marker::PhantomData, } -/// This is a hack to work around fully generic (and non-primitive in general) -/// types not being comparable in `const fn` contexts yet. Once they are, the -/// macro impls for specific types will be removed. -#[doc(hidden)] -macro_rules! impl_static_sorter { - ($type:ty) => { +macro_rules! impl_quick_sort { + ($name:ident, $type:ty, $values:ident, $compared:ident, $i:ident, $j:ident, $p:ident, $cmp:block $(,$add_swap:block)? $(,arg $extra_name:ident: $extra_type:ty),* $(,gen $gen_name:ident : $gen_type:path),*) => { impl __StaticSorter<$type, N> { #[inline] - pub const fn __static_sort( - mut values: [$type; N], - mut low: isize, - mut high: isize, - ) -> [$type; N] { - let range = high - low; - if range <= 0 || range >= values.len() as isize { - return values; - } + pub const fn $name<$($gen_name: $gen_type,)*>( + mut $values: [$type; N], $(mut $extra_name: $extra_type,)* mut low: isize, mut high: isize + ) -> ([$type; N], $($extra_type),*) { + let range = high - low; + if range <= 0 || range >= $values.len() as isize { + return ($values, $($extra_name),*); + } + loop { + let mut $i = low; + let mut $j = high; + let $p = $values[(low + ((high - low) >> 1)) as usize]; loop { - let mut i = low; - let mut j = high; - let p = values[(low + ((high - low) >> 1)) as usize]; - loop { - while values[i as usize] < p { - i += 1; - } - while values[j as usize] > p { - j -= 1; - } - if i <= j { - if i != j { - let q = values[i as usize]; - values[i as usize] = values[j as usize]; - values[j as usize] = q; - } - i += 1; - j -= 1; - } - if i > j { - break; - } + let mut $compared = $i; + while let Ordering::Smaller = $cmp { + $compared += 1; } - if j - low < high - i { - if low < j { - values = Self::__static_sort(values, low, j); - } - low = i; - } else { - if i < high { - values = Self::__static_sort(values, i, high) + $i = $compared; + let mut $compared = $j; + while let Ordering::Greater = $cmp { + $compared -= 1; + } + $j = $compared; + if $i <= $j { + if $i != $j { + let q = $values[$i as usize]; + $values[$i as usize] = $values[$j as usize]; + $values[$j as usize] = q; + + $($add_swap;)? } - high = j; + $i += 1; + $j -= 1; } - if low >= high { + if $i > $j { break; } } - values + if $j - low < high - $i { + if low < $j { + let ($values, $($extra_name),*) = Self::$name::<$($gen_name,)*>($values, $($extra_name,)* low, $j); + } + low = $i; + } else { + if $i < high { + let ($values, $($extra_name),*) = Self::$name::<$($gen_name,)*>($values, $($extra_name,)* $i, high); + } + high = $j; + } + if low >= high { + break; + } } + ($values, $($extra_name),*) } - }; + } +};} + +/// This is a hack to work around fully generic (and non-primitive in general) +/// types not being comparable (PartialOrd's `.cmp()` not being const) in `const fn` contexts yet. +/// Once they are, the macro impls for specific types will be removed. +#[doc(hidden)] +macro_rules! impl_static_sorter { + ($type:ty) => { + impl_quick_sort!( + __static_sort, $type, values, compared,i,j,p, + { + if values[compared as usize] < p { + Ordering::Smaller + } else if values[compared as usize] == p { + Ordering::Equal + } else { + Ordering::Greater + } + }); + + impl_quick_sort!( + __static_co_sort, $type, + values,compared,i,j,p, + { + if values[compared as usize] < p { + Ordering::Smaller + } else if values[compared as usize] == p { + Ordering::Equal + } else { + Ordering::Greater + } + }, + { + let q = keys[i as usize]; + keys[i as usize] = keys[j as usize]; + keys[j as usize] = q; + }, + arg keys: [K; N], + gen K: ::core::marker::Copy + ); + } } impl_static_sorter!(bool); @@ -86,6 +127,88 @@ impl_static_sorter!(isize); impl_static_sorter!(f32); impl_static_sorter!(f64); +impl_quick_sort!( + __static_sort, &'static str, + values,compared, i, j, p, + {str_ord(values[compared as usize], p)} +); + +impl_quick_sort!( + __static_co_sort, &'static str, + values,compared,i,j,p, + { + str_ord(values[compared as usize], p) + }, + { + let q = keys[i as usize]; + keys[i as usize] = keys[j as usize]; + keys[j as usize] = q; + }, + arg keys: [K; N], + gen K: ::core::marker::Copy); + +enum Ordering { + /// Yeah it should be called smaller not less imo + Smaller, + Equal, + Greater +} + +/// This is wildly unsound and will only work on ASCII strings +/// in the same (lower/upper)case and numbers. +const fn str_ord(a: &'static str, b: &'static str) -> Ordering { + let a_bytes = a.as_bytes(); + let a_bytes_len = a_bytes.len(); + let b_bytes = b.as_bytes(); + let b_bytes_len = b_bytes.len(); + let len = if a_bytes_len > b_bytes_len { b_bytes_len } else { a_bytes_len }; + let mut i = 0; + loop { + if i == len { + break; + } + if a_bytes[i] > b_bytes[i] { + return Ordering::Greater; + } else if a_bytes[i] < b_bytes[i] { + return Ordering::Smaller; + } + + i += 1; + } + return if a_bytes_len == b_bytes_len { + Ordering::Equal + } else if a_bytes_len > b_bytes_len { + Ordering::Greater + } else { + Ordering::Smaller + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn ord_eq(a: Ordering, b: Ordering) -> bool { + match (a,b) { + (Ordering::Greater, Ordering::Greater) => true, + (Ordering::Smaller, Ordering::Smaller) => true, + (Ordering::Equal, Ordering::Equal) => true, + _ => false + } + } + + #[test] + fn test_str_ord() { + assert!(ord_eq(str_ord("a", "z"), Ordering::Smaller)); + + assert!(ord_eq(str_ord("aa", "aa"), Ordering::Equal)); + assert!(ord_eq(str_ord("a", "aa"), Ordering::Smaller)); + assert!(ord_eq(str_ord("aa", "a"), Ordering::Greater)); + + assert!(ord_eq(str_ord("ba", "aa"), Ordering::Greater)); + } +} + /// This macro takes the following parameters in the order they're listed: type to sort, index to /// start at, index to end at, and either the name of an existing `const` array variable or just a /// a directly-passed "anonymous" array. @@ -93,6 +216,16 @@ impl_static_sorter!(f64); macro_rules! staticsort { ($type:ty, $low:expr, $high:expr, $values:expr) => {{ const LEN: usize = $values.len(); - $crate::__StaticSorter::<$type, LEN>::__static_sort($values, $low, $high) + $crate::__StaticSorter::<$type, LEN>::__static_sort($values, $low, $high).0 + };}; + ($type:ty, $low:expr, $high:expr, $values:expr, len $len:expr) => {{ + $crate::__StaticSorter::<$type, $len>::__static_sort($values, $low, $high).0 + };}; + ($type:ty, $low:expr, $high:expr, $values:expr, $keys:expr) => {{ + const LEN: usize = $values.len(); + $crate::__StaticSorter::<$type, LEN>::__static_co_sort($values, $keys, $low, $high) + };}; + ($type:ty, $low:expr, $high:expr, $values:expr, $keys:expr, len $len:expr) => {{ + $crate::__StaticSorter::<$type, $len>::__static_co_sort($values, $keys, $low, $high) };}; }