use std::cmp::Ordering;
use std::iter::Peekable;
use std::str::Chars;
pub fn apply_ordering<T: Clone + PartialEq>(items: &mut Vec<T>, order: &[T]) {
if order.is_empty() || items.is_empty() {
return;
}
let mut leading: Vec<T> = Vec::new();
let mut runs: Vec<Vec<T>> = Vec::new();
for item in items.drain(..) {
if order.contains(&item) {
runs.push(vec![item]);
} else if let Some(run) = runs.last_mut() {
run.push(item);
} else {
leading.push(item);
}
}
let mut result = leading;
for name in order {
if let Some(pos) = runs.iter().position(|run| run.first() == Some(name)) {
result.extend(runs.remove(pos));
}
}
for run in runs {
result.extend(run);
}
*items = result;
}
pub fn element_cmp(a: &str, b: &str) -> Ordering {
let mut a = a.chars().peekable();
let mut b = b.chars().peekable();
let mut tiebreak = Ordering::Equal;
loop {
match (a.peek().copied(), b.peek().copied()) {
(None, None) => return tiebreak,
(None, Some(_)) => return Ordering::Less,
(Some(_), None) => return Ordering::Greater,
(Some(ca), Some(cb)) if ca.is_ascii_digit() && cb.is_ascii_digit() => {
let (za, zb) = (consume_zeros(&mut a), consume_zeros(&mut b));
match cmp_digit_runs(&mut a, &mut b) {
Ordering::Equal => {
if tiebreak == Ordering::Equal {
tiebreak = za.cmp(&zb);
}
}
ord => return ord,
}
}
(Some(ca), Some(cb)) => {
let (ra, rb) = (category_rank(ca), category_rank(cb));
if ra != rb {
return ra.cmp(&rb);
}
let (fa, fb) = (ca.to_ascii_lowercase(), cb.to_ascii_lowercase());
if fa != fb {
return (fa as u32).cmp(&(fb as u32));
}
if tiebreak == Ordering::Equal && ca != cb {
tiebreak = ca.is_ascii_lowercase().cmp(&cb.is_ascii_lowercase());
}
a.next();
b.next();
}
}
}
}
fn category_rank(c: char) -> u8 {
if c == '_' {
0
} else if c.is_ascii_digit() {
1
} else if c.is_ascii_alphabetic() {
2
} else {
3
}
}
fn consume_zeros(it: &mut Peekable<Chars>) -> usize {
let mut zeros = 0;
while it.next_if_eq(&'0').is_some() {
zeros += 1;
}
zeros
}
fn cmp_digit_runs(a: &mut Peekable<Chars>, b: &mut Peekable<Chars>) -> Ordering {
let mut result = Ordering::Equal;
loop {
let da = a.next_if(char::is_ascii_digit);
let db = b.next_if(char::is_ascii_digit);
match (da, db) {
(Some(x), Some(y)) if result == Ordering::Equal && x != y => result = x.cmp(&y),
(Some(_), Some(_)) => {}
(Some(_), None) => return Ordering::Greater,
(None, Some(_)) => return Ordering::Less,
(None, None) => return result,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn s(items: &[&str]) -> Vec<String> {
items.iter().map(|s| (*s).to_owned()).collect()
}
#[test]
fn ordered_names_carry_trailing_run() {
let mut children = s(&["a", "b", "c", "d", "e"]);
apply_ordering(&mut children, &s(&["c", "a"]));
assert_eq!(children, s(&["c", "d", "e", "a", "b"]));
}
#[test]
fn full_reverse_works() {
let mut children = s(&["a", "b", "c"]);
apply_ordering(&mut children, &s(&["c", "b", "a"]));
assert_eq!(children, s(&["c", "b", "a"]));
}
#[test]
fn duplicate_items_are_preserved() {
let mut items = s(&["a", "b", "a", "c"]);
apply_ordering(&mut items, &s(&["a"]));
assert_eq!(items.len(), 4);
assert_eq!(items, s(&["a", "b", "a", "c"]));
}
#[test]
fn unknown_names_in_order_are_ignored() {
let mut children = s(&["a", "b", "c"]);
apply_ordering(&mut children, &s(&["b", "missing", "a"]));
assert_eq!(children, s(&["b", "c", "a"]));
}
#[test]
fn leading_block_stays_in_front() {
let mut children = s(&["p", "q", "b", "a"]);
apply_ordering(&mut children, &s(&["a", "b"]));
assert_eq!(children, s(&["p", "q", "a", "b"]));
}
#[test]
fn empty_order_is_noop() {
let mut children = s(&["a", "b", "c"]);
apply_ordering(&mut children, &[]);
assert_eq!(children, s(&["a", "b", "c"]));
}
#[test]
fn no_overlap_is_noop() {
let mut children = s(&["a", "b", "c"]);
apply_ordering(&mut children, &s(&["x", "y", "z"]));
assert_eq!(children, s(&["a", "b", "c"]));
}
#[test]
fn empty_children_is_noop() {
let mut children: Vec<String> = Vec::new();
apply_ordering(&mut children, &s(&["a", "b"]));
assert!(children.is_empty());
}
#[test]
fn element_cmp_spec_example() {
let mut names = s(&[
"foobar",
"Foobar",
"_foobar",
"foo_bar",
"foo001bar001abc",
"foo001bar002abc",
"foo0001bar0002xyz",
"foo00001bar",
"a0",
"a℗",
"ab",
]);
names.sort_by(|x, y| element_cmp(x, y));
assert_eq!(
names,
s(&[
"_foobar",
"a0",
"ab",
"a℗",
"foo_bar",
"foo00001bar",
"foo001bar001abc",
"foo001bar002abc",
"foo0001bar0002xyz",
"Foobar",
"foobar",
])
);
}
#[test]
fn element_cmp_tiebreaks() {
assert_eq!(element_cmp("Bar", "bar"), Ordering::Less);
assert_eq!(element_cmp("a1", "a01"), Ordering::Less);
assert_eq!(element_cmp("a9", "a10"), Ordering::Less);
assert_eq!(element_cmp("abc", "abc"), Ordering::Equal);
}
}