use std::cmp::Ordering;
#[derive(Debug, PartialEq, Eq)]
enum Segment<'a> {
Text(&'a str),
Number(u64),
}
impl<'a> PartialOrd for Segment<'a> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<'a> Ord for Segment<'a> {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(Segment::Text(a), Segment::Text(b)) => {
for (ca, cb) in a
.chars()
.map(|c| c.to_ascii_lowercase())
.zip(b.chars().map(|c| c.to_ascii_lowercase()))
{
match ca.cmp(&cb) {
Ordering::Equal => continue,
other => return other,
}
}
a.len().cmp(&b.len())
}
(Segment::Number(a), Segment::Number(b)) => a.cmp(b),
(Segment::Number(_), Segment::Text(_)) => Ordering::Less,
(Segment::Text(_), Segment::Number(_)) => Ordering::Greater,
}
}
}
fn parse_segments(s: &str) -> Vec<Segment<'_>> {
let mut segments = Vec::new();
let mut chars = s.char_indices().peekable();
while let Some((start, c)) = chars.next() {
if c.is_ascii_digit() {
let mut end = start + c.len_utf8();
while let Some(&(i, next_c)) = chars.peek() {
if next_c.is_ascii_digit() {
end = i + next_c.len_utf8();
chars.next();
} else {
break;
}
}
if let Ok(num) = s[start..end].parse::<u64>() {
segments.push(Segment::Number(num));
} else {
segments.push(Segment::Text(&s[start..end]));
}
} else {
let mut end = start + c.len_utf8();
while let Some(&(i, next_c)) = chars.peek() {
if !next_c.is_ascii_digit() {
end = i + next_c.len_utf8();
chars.next();
} else {
break;
}
}
segments.push(Segment::Text(&s[start..end]));
}
}
segments
}
pub fn natural_cmp(a: &str, b: &str) -> Ordering {
let segments_a = parse_segments(a);
let segments_b = parse_segments(b);
for (seg_a, seg_b) in segments_a.iter().zip(segments_b.iter()) {
match seg_a.cmp(seg_b) {
Ordering::Equal => continue,
other => return other,
}
}
segments_a.len().cmp(&segments_b.len())
}
pub fn natural_cmp_case_sensitive(a: &str, b: &str) -> Ordering {
let segments_a = parse_segments(a);
let segments_b = parse_segments(b);
for (seg_a, seg_b) in segments_a.iter().zip(segments_b.iter()) {
let cmp = match (seg_a, seg_b) {
(Segment::Text(a), Segment::Text(b)) => a.cmp(b),
(Segment::Number(a), Segment::Number(b)) => a.cmp(b),
(Segment::Number(_), Segment::Text(_)) => Ordering::Less,
(Segment::Text(_), Segment::Number(_)) => Ordering::Greater,
};
if cmp != Ordering::Equal {
return cmp;
}
}
segments_a.len().cmp(&segments_b.len())
}
pub fn natural_sort<T: AsRef<str>>(slice: &mut [T]) {
slice.sort_by(|a, b| natural_cmp(a.as_ref(), b.as_ref()));
}
pub fn natural_sort_case_sensitive<T: AsRef<str>>(slice: &mut [T]) {
slice.sort_by(|a, b| natural_cmp_case_sensitive(a.as_ref(), b.as_ref()));
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NaturalKey {
segments: Vec<NaturalKeySegment>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum NaturalKeySegment {
Text(String),
Number(u64),
}
impl PartialOrd for NaturalKeySegment {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for NaturalKeySegment {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(NaturalKeySegment::Text(a), NaturalKeySegment::Text(b)) => a.cmp(b),
(NaturalKeySegment::Number(a), NaturalKeySegment::Number(b)) => a.cmp(b),
(NaturalKeySegment::Number(_), NaturalKeySegment::Text(_)) => Ordering::Less,
(NaturalKeySegment::Text(_), NaturalKeySegment::Number(_)) => Ordering::Greater,
}
}
}
impl NaturalKey {
pub fn new(s: &str) -> Self {
let segments = parse_segments(s)
.into_iter()
.map(|seg| match seg {
Segment::Text(t) => NaturalKeySegment::Text(t.to_lowercase()),
Segment::Number(n) => NaturalKeySegment::Number(n),
})
.collect();
Self { segments }
}
pub fn new_case_sensitive(s: &str) -> Self {
let segments = parse_segments(s)
.into_iter()
.map(|seg| match seg {
Segment::Text(t) => NaturalKeySegment::Text(t.to_string()),
Segment::Number(n) => NaturalKeySegment::Number(n),
})
.collect();
Self { segments }
}
}
impl PartialOrd for NaturalKey {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for NaturalKey {
fn cmp(&self, other: &Self) -> Ordering {
for (seg_a, seg_b) in self.segments.iter().zip(other.segments.iter()) {
match seg_a.cmp(seg_b) {
Ordering::Equal => continue,
other => return other,
}
}
self.segments.len().cmp(&other.segments.len())
}
}