use std::cmp::Ordering;
macro_rules! return_unless_equal {
($ord:expr) => {
match $ord {
Ordering::Equal => {}
ord => return ord,
}
};
}
#[inline(always)]
fn compare_left(left: &[u8], right: &[u8], li: &mut usize, ri: &mut usize) -> Ordering {
let mut l;
let mut r;
loop {
l = left.get(*li);
r = right.get(*ri);
match (l.is_some_and(|b| b.is_ascii_digit()), r.is_some_and(|b| b.is_ascii_digit())) {
(true, true) => {
return_unless_equal!(unsafe { l.unwrap_unchecked().cmp(r.unwrap_unchecked()) })
}
(true, false) => return Ordering::Greater,
(false, true) => return Ordering::Less,
(false, false) => return Ordering::Equal,
}
*li += 1;
*ri += 1;
}
}
#[inline(always)]
fn compare_right(left: &[u8], right: &[u8], li: &mut usize, ri: &mut usize) -> Ordering {
let mut l;
let mut r;
let mut bias = Ordering::Equal;
loop {
l = left.get(*li);
r = right.get(*ri);
match (l.is_some_and(|b| b.is_ascii_digit()), r.is_some_and(|b| b.is_ascii_digit())) {
(true, true) => {
if bias == Ordering::Equal {
bias = unsafe { l.unwrap_unchecked().cmp(r.unwrap_unchecked()) };
}
}
(true, false) => return Ordering::Greater,
(false, true) => return Ordering::Less,
(false, false) => return bias,
}
*li += 1;
*ri += 1;
}
}
pub fn natsort(left: &[u8], right: &[u8], insensitive: bool) -> Ordering {
let mut li = 0;
let mut ri = 0;
let mut l = left.get(li);
let mut r = right.get(ri);
macro_rules! left_next {
() => {{
li += 1;
l = left.get(li);
}};
}
macro_rules! right_next {
() => {{
ri += 1;
r = right.get(ri);
}};
}
loop {
while l.is_some_and(|c| c.is_ascii_whitespace()) {
left_next!();
}
while r.is_some_and(|c| c.is_ascii_whitespace()) {
right_next!();
}
match (l, r) {
(Some(&ll), Some(&rr)) => {
if ll.is_ascii_digit() && rr.is_ascii_digit() {
if ll == b'0' || rr == b'0' {
return_unless_equal!(compare_left(left, right, &mut li, &mut ri));
} else {
return_unless_equal!(compare_right(left, right, &mut li, &mut ri));
}
l = left.get(li);
r = right.get(ri);
continue;
}
if insensitive {
return_unless_equal!(ll.to_ascii_lowercase().cmp(&rr.to_ascii_lowercase()));
} else {
return_unless_equal!(ll.cmp(&rr));
}
}
(Some(_), None) => return Ordering::Greater,
(None, Some(_)) => return Ordering::Less,
(None, None) => return Ordering::Equal,
}
left_next!();
right_next!();
}
}
#[cfg(test)]
mod tests {
use super::*;
fn cmp(left: &[&str]) {
let mut right = left.to_vec();
right.sort_by(|a, b| natsort(a.as_bytes(), b.as_bytes(), true));
assert_eq!(left, right);
}
#[test]
fn test_natsort() {
let dates = ["1999-3-3", "1999-12-25", "2000-1-2", "2000-1-10", "2000-3-23"];
let fractions = [
"1.002.01", "1.002.03", "1.002.08", "1.009.02", "1.009.10", "1.009.20", "1.010.12",
"1.011.02",
];
let words = [
"1-02",
"1-2",
"1-20",
"10-20",
"fred",
"jane",
"pic01",
"pic02",
"pic02a",
"pic02000",
"pic05",
"pic2",
"pic3",
"pic4",
"pic 4 else",
"pic 5",
"pic 5 ",
"pic 5 something",
"pic 6",
"pic 7",
"pic100",
"pic100a",
"pic120",
"pic121",
"tom",
"x2-g8",
"x2-y08",
"x2-y7",
"x8-y8",
];
cmp(&dates);
cmp(&fractions);
cmp(&words);
}
}