trailcache_core/models/
sorting.rs1use super::{RankRequirement, MeritBadgeRequirement};
7
8pub fn req_number_sort_key(num: &str) -> (u32, String) {
11 let trimmed = num.trim_matches(|c: char| c == '(' || c == ')' || c.is_whitespace());
12 let numeric_end = trimmed.find(|c: char| !c.is_ascii_digit()).unwrap_or(trimmed.len());
13 if numeric_end > 0 {
14 let n: u32 = trimmed[..numeric_end].parse().unwrap_or(u32::MAX);
15 let suffix = trimmed[numeric_end..].to_lowercase();
16 (n, suffix)
17 } else {
18 (0, trimmed.to_lowercase())
20 }
21}
22
23pub fn sorted_indices_by_number(numbers: &[String]) -> Vec<usize> {
26 let keys: Vec<(u32, String)> = numbers.iter().map(|n| req_number_sort_key(n)).collect();
27
28 let mut parent_map: Vec<u32> = Vec::with_capacity(keys.len());
30 let mut last_parent: u32 = 0;
31 for key in &keys {
32 if key.0 > 0 {
33 last_parent = key.0;
34 parent_map.push(key.0);
35 } else {
36 parent_map.push(last_parent);
37 }
38 }
39
40 let final_keys: Vec<(u32, String)> = keys
41 .iter()
42 .enumerate()
43 .map(|(i, key)| {
44 if key.0 == 0 {
45 (parent_map[i], key.1.clone())
46 } else {
47 key.clone()
48 }
49 })
50 .collect();
51
52 let mut indices: Vec<usize> = (0..numbers.len()).collect();
53 indices.sort_by(|&a, &b| final_keys[a].cmp(&final_keys[b]));
54 indices
55}
56
57pub trait HasRequirementNumber {
59 fn requirement_number_str(&self) -> String;
60}
61
62impl HasRequirementNumber for RankRequirement {
63 fn requirement_number_str(&self) -> String {
64 self.number()
65 }
66}
67
68impl HasRequirementNumber for MeritBadgeRequirement {
69 fn requirement_number_str(&self) -> String {
70 self.number()
71 }
72}
73
74pub fn sort_requirements<T: HasRequirementNumber + Clone>(reqs: &mut [T]) {
77 let numbers: Vec<String> = reqs.iter().map(|r| r.requirement_number_str()).collect();
78 let order = sorted_indices_by_number(&numbers);
79 let orig = reqs.to_vec();
80 for (i, &idx) in order.iter().enumerate() {
81 reqs[i] = orig[idx].clone();
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn test_req_number_sort_key() {
91 assert_eq!(req_number_sort_key("1"), (1, String::new()));
92 assert_eq!(req_number_sort_key("3a"), (3, "a".to_string()));
93 assert_eq!(req_number_sort_key("10"), (10, String::new()));
94 assert_eq!(req_number_sort_key("a"), (0, "a".to_string()));
95 assert_eq!(req_number_sort_key("(b)"), (0, "b".to_string()));
96 }
97
98 #[test]
99 fn test_sorted_indices_numeric() {
100 let nums: Vec<String> = vec!["3", "1", "2", "10"]
101 .into_iter()
102 .map(String::from)
103 .collect();
104 let order = sorted_indices_by_number(&nums);
105 let sorted: Vec<&str> = order.iter().map(|&i| nums[i].as_str()).collect();
106 assert_eq!(sorted, vec!["1", "2", "3", "10"]);
107 }
108
109 #[test]
110 fn test_sorted_indices_with_subreqs() {
111 let nums: Vec<String> = vec!["1", "a", "b", "2", "a", "3"]
112 .into_iter()
113 .map(String::from)
114 .collect();
115 let order = sorted_indices_by_number(&nums);
116 let sorted: Vec<&str> = order.iter().map(|&i| nums[i].as_str()).collect();
117 assert_eq!(sorted, vec!["1", "a", "b", "2", "a", "3"]);
118 }
119
120 #[test]
121 fn test_sorted_indices_mixed() {
122 let nums: Vec<String> = vec!["2", "1", "1a", "1b", "3", "2a"]
123 .into_iter()
124 .map(String::from)
125 .collect();
126 let order = sorted_indices_by_number(&nums);
127 let sorted: Vec<&str> = order.iter().map(|&i| nums[i].as_str()).collect();
128 assert_eq!(sorted, vec!["1", "1a", "1b", "2", "2a", "3"]);
129 }
130}