1use std::cmp::Ordering;
29
30use super::Value;
31
32#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
34pub enum Order {
35 #[default]
37 Ascending,
38 Descending,
40}
41
42#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
44pub enum Compare {
45 #[default]
48 Natural,
49 Lexical,
51}
52
53#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
55pub struct SortKey {
56 pub column: usize,
58 pub order: Order,
60 pub compare: Compare,
62}
63
64impl SortKey {
65 pub fn asc(column: usize) -> Self {
67 SortKey {
68 column,
69 order: Order::Ascending,
70 compare: Compare::Natural,
71 }
72 }
73
74 pub fn desc(column: usize) -> Self {
76 SortKey {
77 order: Order::Descending,
78 ..SortKey::asc(column)
79 }
80 }
81
82 #[must_use]
84 pub fn lexical(mut self) -> Self {
85 self.compare = Compare::Lexical;
86 self
87 }
88}
89
90pub fn indicator(order: Order, ascii: bool) -> &'static str {
92 match (order, ascii) {
93 (Order::Ascending, false) => "▲",
94 (Order::Descending, false) => "▼",
95 (Order::Ascending, true) => "^",
96 (Order::Descending, true) => "v",
97 }
98}
99
100fn numeric_str(s: &str) -> Option<f64> {
103 let s = s.trim();
104 let digits = s.trim_start_matches(['-', '+']).trim_start_matches('.');
105 if !digits.starts_with(|c: char| c.is_ascii_digit()) {
106 return None;
107 }
108 s.parse::<f64>().ok()
109}
110
111fn numeric(value: &Value) -> Option<f64> {
113 match value {
114 Value::Int(n) => Some(*n as f64),
115 Value::Float(f) => Some(*f),
116 Value::Str(s) => numeric_str(s),
117 Value::Text(t) => numeric_str(t.plain()),
118 Value::Null => None,
119 }
120}
121
122pub fn compare_values(a: &Value, b: &Value, compare: Compare) -> Ordering {
125 match (a.is_empty(), b.is_empty()) {
126 (true, true) => return Ordering::Equal,
127 (true, false) => return Ordering::Greater,
128 (false, true) => return Ordering::Less,
129 (false, false) => {}
130 }
131 match compare {
132 Compare::Lexical => a.plain().cmp(&b.plain()),
133 Compare::Natural => {
134 if let (Value::Int(x), Value::Int(y)) = (a, b) {
135 return x.cmp(y);
136 }
137 match (numeric(a), numeric(b)) {
138 (Some(x), Some(y)) => x.total_cmp(&y),
139 (Some(_), None) => Ordering::Less,
140 (None, Some(_)) => Ordering::Greater,
141 (None, None) => natural_cmp(&a.plain(), &b.plain()),
142 }
143 }
144 }
145}
146
147pub fn natural_cmp(a: &str, b: &str) -> Ordering {
158 let (mut x, mut y) = (a, b);
159 loop {
160 match (x.chars().next(), y.chars().next()) {
161 (None, None) => return a.cmp(b),
162 (None, Some(_)) => return Ordering::Less,
163 (Some(_), None) => return Ordering::Greater,
164 (Some(c), Some(d)) if c.is_ascii_digit() && d.is_ascii_digit() => {
165 let (run_x, rest_x) = split_digits(x);
166 let (run_y, rest_y) = split_digits(y);
167 let (value_x, value_y) =
168 (run_x.trim_start_matches('0'), run_y.trim_start_matches('0'));
169 let order = value_x
170 .len()
171 .cmp(&value_y.len())
172 .then_with(|| value_x.cmp(value_y));
173 if order != Ordering::Equal {
174 return order;
175 }
176 (x, y) = (rest_x, rest_y);
177 }
178 (Some(c), Some(d)) => {
179 let order = c.to_lowercase().cmp(d.to_lowercase());
180 if order != Ordering::Equal {
181 return order;
182 }
183 (x, y) = (&x[c.len_utf8()..], &y[d.len_utf8()..]);
184 }
185 }
186 }
187}
188
189fn split_digits(s: &str) -> (&str, &str) {
190 let end = s.find(|c: char| !c.is_ascii_digit()).unwrap_or(s.len());
191 s.split_at(end)
192}
193
194pub fn compare_rows(a: &[Value], b: &[Value], keys: &[SortKey]) -> Ordering {
197 const NULL: Value = Value::Null;
198 for key in keys {
199 let x = a.get(key.column).unwrap_or(&NULL);
200 let y = b.get(key.column).unwrap_or(&NULL);
201 let order = match (x.is_empty(), y.is_empty()) {
202 (false, false) => {
203 let order = compare_values(x, y, key.compare);
204 match key.order {
205 Order::Ascending => order,
206 Order::Descending => order.reverse(),
207 }
208 }
209 _ => compare_values(x, y, key.compare),
211 };
212 if order != Ordering::Equal {
213 return order;
214 }
215 }
216 Ordering::Equal
217}
218
219pub fn sort_rows<R: AsRef<[Value]>>(rows: &mut [R], keys: &[SortKey]) {
222 if keys.is_empty() {
223 return;
224 }
225 rows.sort_by(|a, b| compare_rows(a.as_ref(), b.as_ref(), keys));
226}
227
228pub fn sorted_indices<R: AsRef<[Value]>>(rows: &[R], keys: &[SortKey]) -> Vec<usize> {
231 let mut order: Vec<usize> = (0..rows.len()).collect();
232 if !keys.is_empty() {
233 order.sort_by(|&a, &b| compare_rows(rows[a].as_ref(), rows[b].as_ref(), keys));
234 }
235 order
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241
242 #[test]
243 fn natural_order_handles_digits_case_and_leading_zeros() {
244 let mut words = vec!["a10", "A2", "a2", "a02", "b", "a1b", ""];
245 words.sort_by(|a, b| natural_cmp(a, b));
246 assert_eq!(words, ["", "a1b", "A2", "a02", "a2", "a10", "b"]);
247 }
248
249 #[test]
250 fn numbers_sort_before_text_and_numeric_strings_as_numbers() {
251 let mut values = [
252 Value::from("x"),
253 Value::from("10"),
254 Value::Float(2.5),
255 Value::Int(-3),
256 Value::from("-4.5"),
257 Value::Null,
258 ];
259 values.sort_by(|a, b| compare_values(a, b, Compare::Natural));
260 let plain: Vec<String> = values.iter().map(Value::plain).collect();
261 assert_eq!(plain, ["-4.5", "-3", "2.5", "10", "x", ""]);
262 values.sort_by(|a, b| compare_values(a, b, Compare::Lexical));
263 let plain: Vec<String> = values.iter().map(Value::plain).collect();
264 assert_eq!(plain, ["-3", "-4.5", "10", "2.5", "x", ""]);
265 }
266}