use std::cmp::Ordering;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SortDirection {
Ascending,
Descending,
}
impl SortDirection {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Ascending => "ascending",
Self::Descending => "descending",
}
}
}
#[must_use]
pub fn next_direction(current: Option<(usize, SortDirection)>, column: usize) -> SortDirection {
match current {
Some((sorted, SortDirection::Ascending)) if sorted == column => SortDirection::Descending,
_ => SortDirection::Ascending,
}
}
#[must_use]
pub fn as_number(value: &str) -> Option<f64> {
let cleaned: String = value
.chars()
.filter(|c| !matches!(c, ',' | ' ' | '\u{a0}' | '\u{202f}') && !c.is_whitespace())
.collect();
if cleaned.is_empty() {
return None;
}
cleaned.parse::<f64>().ok().filter(|n| n.is_finite())
}
#[must_use]
pub fn compare(a: &str, b: &str) -> Ordering {
match (as_number(a), as_number(b)) {
(Some(x), Some(y)) => x.partial_cmp(&y).unwrap_or(Ordering::Equal),
_ => natural(a, b),
}
}
fn natural(a: &str, b: &str) -> Ordering {
let mut left = a.chars().peekable();
let mut right = b.chars().peekable();
loop {
match (left.peek().copied(), right.peek().copied()) {
(None, None) => return Ordering::Equal,
(None, Some(_)) => return Ordering::Less,
(Some(_), None) => return Ordering::Greater,
(Some(x), Some(y)) if x.is_ascii_digit() && y.is_ascii_digit() => {
let take = |it: &mut std::iter::Peekable<std::str::Chars<'_>>| {
let mut digits = String::new();
while let Some(c) = it.peek().copied().filter(char::is_ascii_digit) {
digits.push(c);
it.next();
}
digits
};
let (x, y) = (take(&mut left), take(&mut right));
let (xs, ys) = (x.trim_start_matches('0'), y.trim_start_matches('0'));
let order = xs.len().cmp(&ys.len()).then_with(|| xs.cmp(ys));
if order != Ordering::Equal {
return order;
}
}
(Some(x), Some(y)) => {
let order = x.to_lowercase().cmp(y.to_lowercase());
if order != Ordering::Equal {
return order;
}
left.next();
right.next();
}
}
}
}
#[must_use]
pub fn order(keys: &[&str], direction: SortDirection) -> Vec<usize> {
let mut indices: Vec<usize> = (0..keys.len()).collect();
indices.sort_by(|&a, &b| {
let ordering = compare(
keys.get(a).copied().unwrap_or(""),
keys.get(b).copied().unwrap_or(""),
);
match direction {
SortDirection::Ascending => ordering,
SortDirection::Descending => ordering.reverse(),
}
});
indices
}