2014-06-01 12:07:45 +00:00
|
|
|
use std::ascii::StrAsciiExt;
|
|
|
|
|
2014-06-01 10:54:31 +00:00
|
|
|
// This is an implementation of "natural sort order". See
|
|
|
|
// http://blog.codinghorror.com/sorting-for-humans-natural-sort-order/
|
|
|
|
// for more information and examples. It tries to sort "9" before
|
|
|
|
// "10", which makes sense to those regular human types.
|
|
|
|
|
|
|
|
// It works by splitting an input string into several parts, and then
|
|
|
|
// comparing based on those parts. A SortPart derives TotalOrd, so a
|
|
|
|
// Vec<SortPart> will automatically have natural sorting.
|
|
|
|
|
2014-06-02 20:02:06 +00:00
|
|
|
#[deriving(Eq, Ord, PartialEq, PartialOrd)]
|
2014-06-01 15:30:18 +00:00
|
|
|
pub enum SortPart {
|
2014-06-01 12:07:45 +00:00
|
|
|
Numeric(u64),
|
|
|
|
Stringular(String),
|
2014-06-01 10:54:31 +00:00
|
|
|
}
|
|
|
|
|
2014-06-01 15:30:18 +00:00
|
|
|
impl SortPart {
|
|
|
|
pub fn from_string(is_digit: bool, slice: &str) -> SortPart {
|
2014-06-01 10:54:31 +00:00
|
|
|
if is_digit {
|
2014-06-18 16:26:28 +00:00
|
|
|
// numbers too big for a u64 fall back into strings.
|
|
|
|
match from_str::<u64>(slice) {
|
|
|
|
Some(num) => Numeric(num),
|
|
|
|
None => Stringular(slice.to_string()),
|
|
|
|
}
|
2014-06-01 10:54:31 +00:00
|
|
|
} else {
|
2014-06-01 12:07:45 +00:00
|
|
|
Stringular(slice.to_ascii_lower())
|
2014-06-01 10:54:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// The logic here is taken from my question at
|
|
|
|
// http://stackoverflow.com/q/23969191/3484614
|
|
|
|
|
2014-06-01 15:30:18 +00:00
|
|
|
pub fn split_into_parts(input: &str) -> Vec<SortPart> {
|
2014-06-01 10:54:31 +00:00
|
|
|
let mut parts = vec![];
|
|
|
|
|
|
|
|
if input.is_empty() {
|
|
|
|
return parts
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut is_digit = input.char_at(0).is_digit();
|
|
|
|
let mut start = 0;
|
|
|
|
|
|
|
|
for (i, c) in input.char_indices() {
|
|
|
|
if is_digit != c.is_digit() {
|
|
|
|
parts.push(SortPart::from_string(is_digit, input.slice(start, i)));
|
|
|
|
is_digit = !is_digit;
|
|
|
|
start = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
parts.push(SortPart::from_string(is_digit, input.slice_from(start)));
|
|
|
|
parts
|
|
|
|
}
|
|
|
|
}
|
2014-06-18 16:26:28 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_numeric() {
|
|
|
|
let bits = SortPart::split_into_parts("123456789".as_slice());
|
|
|
|
assert!(bits == vec![ Numeric(123456789) ]);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_stringular() {
|
|
|
|
let bits = SortPart::split_into_parts("toothpaste".as_slice());
|
|
|
|
assert!(bits == vec![ Stringular("toothpaste".to_string()) ]);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_empty() {
|
|
|
|
let bits = SortPart::split_into_parts("".as_slice());
|
|
|
|
assert!(bits == vec![]);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_one() {
|
|
|
|
let bits = SortPart::split_into_parts("123abc123".as_slice());
|
|
|
|
assert!(bits == vec![ Numeric(123), Stringular("abc".to_string()), Numeric(123) ]);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_two() {
|
|
|
|
let bits = SortPart::split_into_parts("final version 3.pdf".as_slice());
|
|
|
|
assert!(bits == vec![ Stringular("final version ".to_string()), Numeric(3), Stringular(".pdf".to_string()) ]);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_huge_number() {
|
|
|
|
let bits = SortPart::split_into_parts("9999999999999999999999999999999999999999999999999999999".as_slice());
|
|
|
|
assert!(bits == vec![ Stringular("9999999999999999999999999999999999999999999999999999999".to_string()) ]);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_case() {
|
|
|
|
let bits = SortPart::split_into_parts("123ABC123".as_slice());
|
|
|
|
assert!(bits == vec![ Numeric(123), Stringular("abc".to_string()), Numeric(123) ]);
|
|
|
|
}
|