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 pub fn lexical(mut self) -> Self {
84 self.compare = Compare::Lexical;
85 self
86 }
87}
88
89pub fn indicator(order: Order, ascii: bool) -> &'static str {
91 match (order, ascii) {
92 (Order::Ascending, false) => "▲",
93 (Order::Descending, false) => "▼",
94 (Order::Ascending, true) => "^",
95 (Order::Descending, true) => "v",
96 }
97}
98
99fn numeric_str(s: &str) -> Option<f64> {
102 let s = s.trim();
103 let digits = s.trim_start_matches(['-', '+']).trim_start_matches('.');
104 if !digits.starts_with(|c: char| c.is_ascii_digit()) {
105 return None;
106 }
107 s.parse::<f64>().ok()
108}
109
110fn numeric(value: &Value) -> Option<f64> {
112 match value {
113 Value::Int(n) => Some(*n as f64),
114 Value::Float(f) => Some(*f),
115 Value::Str(s) => numeric_str(s),
116 Value::Text(t) => numeric_str(t.plain()),
117 Value::Null => None,
118 }
119}
120
121pub fn compare_values(a: &Value, b: &Value, compare: Compare) -> Ordering {
124 match (a.is_empty(), b.is_empty()) {
125 (true, true) => return Ordering::Equal,
126 (true, false) => return Ordering::Greater,
127 (false, true) => return Ordering::Less,
128 (false, false) => {}
129 }
130 match compare {
131 Compare::Lexical => a.plain().cmp(&b.plain()),
132 Compare::Natural => {
133 if let (Value::Int(x), Value::Int(y)) = (a, b) {
134 return x.cmp(y);
135 }
136 match (numeric(a), numeric(b)) {
137 (Some(x), Some(y)) => x.total_cmp(&y),
138 (Some(_), None) => Ordering::Less,
139 (None, Some(_)) => Ordering::Greater,
140 (None, None) => natural_cmp(&a.plain(), &b.plain()),
141 }
142 }
143 }
144}
145
146pub fn natural_cmp(a: &str, b: &str) -> Ordering {
157 let (mut x, mut y) = (a, b);
158 loop {
159 match (x.chars().next(), y.chars().next()) {
160 (None, None) => return a.cmp(b),
161 (None, Some(_)) => return Ordering::Less,
162 (Some(_), None) => return Ordering::Greater,
163 (Some(c), Some(d)) if c.is_ascii_digit() && d.is_ascii_digit() => {
164 let (run_x, rest_x) = split_digits(x);
165 let (run_y, rest_y) = split_digits(y);
166 let (value_x, value_y) =
167 (run_x.trim_start_matches('0'), run_y.trim_start_matches('0'));
168 let order = value_x
169 .len()
170 .cmp(&value_y.len())
171 .then_with(|| value_x.cmp(value_y));
172 if order != Ordering::Equal {
173 return order;
174 }
175 (x, y) = (rest_x, rest_y);
176 }
177 (Some(c), Some(d)) => {
178 let order = c.to_lowercase().cmp(d.to_lowercase());
179 if order != Ordering::Equal {
180 return order;
181 }
182 (x, y) = (&x[c.len_utf8()..], &y[d.len_utf8()..]);
183 }
184 }
185 }
186}
187
188fn split_digits(s: &str) -> (&str, &str) {
189 let end = s.find(|c: char| !c.is_ascii_digit()).unwrap_or(s.len());
190 s.split_at(end)
191}
192
193pub fn compare_rows(a: &[Value], b: &[Value], keys: &[SortKey]) -> Ordering {
196 const NULL: Value = Value::Null;
197 for key in keys {
198 let x = a.get(key.column).unwrap_or(&NULL);
199 let y = b.get(key.column).unwrap_or(&NULL);
200 let order = match (x.is_empty(), y.is_empty()) {
201 (false, false) => {
202 let order = compare_values(x, y, key.compare);
203 match key.order {
204 Order::Ascending => order,
205 Order::Descending => order.reverse(),
206 }
207 }
208 _ => compare_values(x, y, key.compare),
210 };
211 if order != Ordering::Equal {
212 return order;
213 }
214 }
215 Ordering::Equal
216}
217
218pub fn sort_rows<R: AsRef<[Value]>>(rows: &mut [R], keys: &[SortKey]) {
221 if keys.is_empty() {
222 return;
223 }
224 rows.sort_by(|a, b| compare_rows(a.as_ref(), b.as_ref(), keys));
225}
226
227pub fn sorted_indices<R: AsRef<[Value]>>(rows: &[R], keys: &[SortKey]) -> Vec<usize> {
230 let mut order: Vec<usize> = (0..rows.len()).collect();
231 if !keys.is_empty() {
232 order.sort_by(|&a, &b| compare_rows(rows[a].as_ref(), rows[b].as_ref(), keys));
233 }
234 order
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240
241 #[test]
242 fn natural_order_handles_digits_case_and_leading_zeros() {
243 let mut words = vec!["a10", "A2", "a2", "a02", "b", "a1b", ""];
244 words.sort_by(|a, b| natural_cmp(a, b));
245 assert_eq!(words, ["", "a1b", "A2", "a02", "a2", "a10", "b"]);
246 }
247
248 #[test]
249 fn numbers_sort_before_text_and_numeric_strings_as_numbers() {
250 let mut values = [
251 Value::from("x"),
252 Value::from("10"),
253 Value::Float(2.5),
254 Value::Int(-3),
255 Value::from("-4.5"),
256 Value::Null,
257 ];
258 values.sort_by(|a, b| compare_values(a, b, Compare::Natural));
259 let plain: Vec<String> = values.iter().map(Value::plain).collect();
260 assert_eq!(plain, ["-4.5", "-3", "2.5", "10", "x", ""]);
261 values.sort_by(|a, b| compare_values(a, b, Compare::Lexical));
262 let plain: Vec<String> = values.iter().map(Value::plain).collect();
263 assert_eq!(plain, ["-3", "-4.5", "10", "2.5", "x", ""]);
264 }
265}