human_sort/lib.rs
1//! Utilities to sort and compare strings with numeric symbols in human-friendly order.
2//!
3//! It built over iterators and compare string slices char by char (except for numerals)
4//! until the first difference found without creating Strings or another structures with whole
5//! data from provided &str, so doesn't require lots of memory.
6//!
7//! # Examples
8//!
9//! ```
10//! use human_sort::sort;
11//!
12//! let mut arr = ["file10.txt", "file2.txt", "file1.txt"];
13//! sort(&mut arr);
14//!
15//! assert_eq!(arr, ["file1.txt", "file2.txt", "file10.txt"]);
16//! ```
17//!
18//! ```
19//! use std::cmp::Ordering;
20//! use human_sort::compare;
21//!
22//! assert_eq!(compare("item200", "item3"), Ordering::Greater);
23//! ```
24
25mod iter_pair;
26
27use iter_pair::IterPair;
28use std::{cmp::Ordering, iter::Peekable, str::Chars};
29
30/// Sorts [&str] in human-friendly order
31///
32/// # Example
33///
34/// ```
35/// use human_sort::sort;
36///
37/// let mut arr = ["file10.txt", "file2.txt", "file1.txt"];
38/// sort(&mut arr);
39///
40/// assert_eq!(arr, ["file1.txt", "file2.txt", "file10.txt"]);
41/// ```
42///
43pub fn sort(arr: &mut [&str]) {
44 arr.sort_by(|a, b| compare(a, b));
45}
46
47/// Compares string slices
48///
49/// # Example
50///
51/// ```
52/// use std::cmp::Ordering;
53/// use human_sort::compare;
54///
55/// assert_eq!(compare("item200", "item3"), Ordering::Greater);
56/// ```
57///
58pub fn compare(s1: &str, s2: &str) -> Ordering {
59 compare_chars_iters(s1.chars(), s2.chars()).unwrap_or(s1.cmp(s2))
60}
61
62///
63/// ```
64/// use std::cmp::Ordering;
65/// use human_sort::compare_chars_iters;
66/// assert_eq!(compare_chars_iters("aaa".chars(), "bbb".chars()), Ok(Ordering::Less));
67/// ```
68///
69pub fn compare_chars_iters<'a>(c1: Chars<'a>, c2: Chars<'a>) -> Result<Ordering, ()> {
70 let mut iters = IterPair::from(c1, c2);
71
72 while let [Some(x), Some(y)] = iters.peek() {
73 if x == y {
74 iters.next();
75 } else if x.is_numeric() && y.is_numeric() {
76 match take_numeric(&mut iters.fst).cmp(&take_numeric(&mut iters.lst)) {
77 Ordering::Equal => iters.next(),
78 ref a => return Ok(*a),
79 };
80 } else {
81 return Ok(x.cmp(y));
82 }
83 }
84
85 Err(())
86}
87
88fn take_numeric(iter: &mut Peekable<Chars>) -> u32 {
89 let mut sum = 0;
90
91 while let Some(p) = iter.peek() {
92 match p.to_string().parse::<u32>() {
93 Ok(n) => {
94 sum = sum * 10 + n;
95 iter.next();
96 }
97 _ => break,
98 }
99 }
100
101 sum
102}