From 0aa385d5844baac13a615881ce5fde1a8f114d4b Mon Sep 17 00:00:00 2001 From: Malo <57839069+MDLC01@users.noreply.github.com> Date: Tue, 24 Dec 2024 15:48:38 +0100 Subject: [PATCH] Re-implement sort --- crates/typst-library/src/foundations/array.rs | 113 +++++++++++++++++- tests/suite/foundations/array.typ | 2 +- 2 files changed, 113 insertions(+), 2 deletions(-) diff --git a/crates/typst-library/src/foundations/array.rs b/crates/typst-library/src/foundations/array.rs index 8ce3b09a9..97e403841 100644 --- a/crates/typst-library/src/foundations/array.rs +++ b/crates/typst-library/src/foundations/array.rs @@ -872,7 +872,7 @@ impl Array { None => ops::compare(&x, &y).at(span), } }; - vec.make_mut().sort_by(|a, b| { + sort(vec.make_mut(), |a, b| { // Until we get `try` blocks :) compare(a.clone(), b.clone()).unwrap_or_else(|err| { if result.is_ok() { @@ -1158,3 +1158,114 @@ fn out_of_bounds_no_default(index: i64, len: usize) -> EcoString { and no default value was specified", ) } + +/// Sorts a slice according to a comparison function. This sort is stable. +/// +/// As opposed to [`<[T]>::sort_by`], this does not panic. +/// +/// This is implemented with an in-place stable insertion sort. +fn sort(array: &mut [T], mut cmp: impl FnMut(&T, &T) -> Ordering) { + for i in 0..array.len() { + for j in (0..i).rev() { + if cmp(&array[j], &array[j + 1]) == Ordering::Greater { + array.swap(j, j + 1); + } else { + break; + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn sort_empty() { + let mut array = []; + sort(&mut array, Ordering::cmp); + assert_eq!(array, []); + } + + #[test] + fn sort_singleton() { + let mut array = [0]; + sort(&mut array, |x, y| x.cmp(y)); + assert_eq!(array, [0]); + } + + #[test] + fn sort_pair() { + let mut array = [0, 1]; + sort(&mut array, |x, y| x.cmp(y)); + assert_eq!(array, [0, 1]); + + let mut array = [1, 0]; + sort(&mut array, |x, y| x.cmp(y)); + assert_eq!(array, [0, 1]); + } + + #[test] + fn sort_triplet() { + let arrays = [[0, 1, 2], [1, 0, 2], [0, 2, 1], [1, 2, 0], [2, 0, 1], [2, 1, 0]]; + + for mut array in arrays { + sort(&mut array, |x, y| x.cmp(y)); + assert_eq!(array, [0, 1, 2]); + } + } + + #[test] + fn sort_quadruplet() { + let arrays = [ + [0, 1, 2, 3], + [1, 0, 2, 3], + [0, 2, 1, 3], + [1, 2, 0, 3], + [2, 0, 1, 3], + [2, 1, 0, 3], + [0, 1, 3, 2], + [1, 0, 3, 2], + [0, 2, 3, 1], + [1, 2, 3, 0], + [2, 0, 3, 1], + [2, 1, 3, 0], + [0, 3, 1, 2], + [1, 3, 0, 2], + [0, 3, 2, 1], + [1, 3, 2, 0], + [2, 3, 0, 1], + [2, 3, 1, 0], + [3, 0, 1, 2], + [3, 1, 0, 2], + [3, 0, 2, 1], + [3, 1, 2, 0], + [3, 2, 0, 1], + [3, 2, 1, 0], + ]; + + for mut array in arrays { + sort(&mut array, |x, y| x.cmp(y)); + assert_eq!(array, [0, 1, 2, 3]); + } + } + + fn compare_first(x: &(T, U), y: &(T, U)) -> Ordering { + x.0.cmp(&y.0) + } + + #[test] + fn random() { + for m in 0..=u8::MAX { + // Generates a sort of "pseudorandom" array. + // Xoring with `k` prevents the array from being sorted by default. + let k = 0b01010101; + let mut array: [_; 100] = + std::array::from_fn(|i| ((i as u8 ^ k) & m, i as u8 ^ k ^ m)); + let mut copy = array; + sort(&mut array, compare_first); + copy.sort_by(compare_first); + assert_eq!(array, copy); + } + } +} diff --git a/tests/suite/foundations/array.typ b/tests/suite/foundations/array.typ index 2fbd185d6..330ae774a 100644 --- a/tests/suite/foundations/array.typ +++ b/tests/suite/foundations/array.typ @@ -447,7 +447,7 @@ #([Hi], [There]).sorted() --- array-sorted-uncomparable-lengths --- -// Error: 2-26 cannot compare 3em with 2pt +// Error: 2-26 cannot compare 2pt with 3em #(1pt, 2pt, 3em).sorted() --- array-sorted-key-function-positional-2 ---