/rust/registry/src/index.crates.io-1949cf8c6b5b557f/lexicmp-0.2.0/src/cmp.rs
Line | Count | Source |
1 | | use crate::iter::{iterate_lexical, iterate_lexical_only_alnum}; |
2 | | use core::cmp::Ordering; |
3 | | |
4 | | macro_rules! cmp_ascii_digits { |
5 | | (first_digits($lhs:ident, $rhs:ident), iterators($iter1:ident, $iter2:ident)) => { |
6 | | let mut n1 = ascii_to_u64($lhs); |
7 | | let mut n2 = ascii_to_u64($rhs); |
8 | | loop { |
9 | | match ( |
10 | 0 | $iter1.peek().copied().filter(|c| c.is_ascii_digit()), Unexecuted instantiation: lexicmp::cmp::natural_cmp::{closure#0}Unexecuted instantiation: lexicmp::cmp::natural_lexical_cmp::{closure#0}Unexecuted instantiation: lexicmp::cmp::natural_only_alnum_cmp::{closure#2}Unexecuted instantiation: lexicmp::cmp::natural_lexical_only_alnum_cmp::{closure#0} |
11 | 0 | $iter2.peek().copied().filter(|c| c.is_ascii_digit()), Unexecuted instantiation: lexicmp::cmp::natural_cmp::{closure#1}Unexecuted instantiation: lexicmp::cmp::natural_lexical_cmp::{closure#1}Unexecuted instantiation: lexicmp::cmp::natural_only_alnum_cmp::{closure#3}Unexecuted instantiation: lexicmp::cmp::natural_lexical_only_alnum_cmp::{closure#1} |
12 | | ) { |
13 | | (Some(lhs), Some(rhs)) => { |
14 | | n1 = n1 * 10 + ascii_to_u64(lhs); |
15 | | n2 = n2 * 10 + ascii_to_u64(rhs); |
16 | | let _ = $iter1.next(); |
17 | | let _ = $iter2.next(); |
18 | | } |
19 | | (Some(_), None) => return Ordering::Greater, |
20 | | (None, Some(_)) => return Ordering::Less, |
21 | | (None, None) => { |
22 | | if n1 != n2 { |
23 | | return n1.cmp(&n2); |
24 | | } else { |
25 | | break; |
26 | | } |
27 | | } |
28 | | } |
29 | | } |
30 | | }; |
31 | | } |
32 | | |
33 | | #[inline] |
34 | 0 | fn ascii_to_u64(c: char) -> u64 { |
35 | 0 | (c as u64) - (b'0' as u64) |
36 | 0 | } |
37 | | |
38 | | #[inline] |
39 | 0 | fn ret_ordering(lhs: char, rhs: char) -> Ordering { |
40 | 0 | let is_lhs_alnum = lhs.is_alphanumeric(); |
41 | 0 | let is_rhs_alnum = rhs.is_alphanumeric(); |
42 | | |
43 | 0 | let result = if is_lhs_alnum == is_rhs_alnum { |
44 | 0 | lhs.cmp(&rhs) |
45 | 0 | } else if is_lhs_alnum { |
46 | 0 | Ordering::Greater |
47 | | } else { |
48 | 0 | Ordering::Less |
49 | | }; |
50 | 0 | result |
51 | 0 | } |
52 | | |
53 | | /// Compares strings lexicographically |
54 | | /// |
55 | | /// For example, `"a" < "ä" < "aa"` |
56 | 0 | pub fn lexical_cmp(lhs: &str, rhs: &str) -> Ordering { |
57 | 0 | let mut iter1 = iterate_lexical(lhs); |
58 | 0 | let mut iter2 = iterate_lexical(rhs); |
59 | | |
60 | | loop { |
61 | 0 | match (iter1.next(), iter2.next()) { |
62 | 0 | (Some(lhs), Some(rhs)) => { |
63 | 0 | if lhs != rhs { |
64 | 0 | return ret_ordering(lhs, rhs); |
65 | 0 | } |
66 | | } |
67 | 0 | (Some(_), None) => return Ordering::Greater, |
68 | 0 | (None, Some(_)) => return Ordering::Less, |
69 | 0 | (None, None) => return lhs.cmp(&rhs), |
70 | | } |
71 | | } |
72 | 0 | } |
73 | | |
74 | | /// Compares strings lexicographically, skipping non-alphanumeric characters |
75 | | /// |
76 | | /// For example, `"a" < " ä" < "ä" < "aa"` |
77 | 0 | pub fn lexical_only_alnum_cmp(s1: &str, s2: &str) -> Ordering { |
78 | 0 | let mut iter1 = iterate_lexical_only_alnum(s1); |
79 | 0 | let mut iter2 = iterate_lexical_only_alnum(s2); |
80 | | |
81 | | loop { |
82 | 0 | match (iter1.next(), iter2.next()) { |
83 | 0 | (Some(lhs), Some(rhs)) => { |
84 | 0 | if lhs != rhs { |
85 | 0 | return lhs.cmp(&rhs); |
86 | 0 | } |
87 | | } |
88 | 0 | (Some(_), None) => return Ordering::Greater, |
89 | 0 | (None, Some(_)) => return Ordering::Less, |
90 | 0 | (None, None) => return s1.cmp(&s2), |
91 | | } |
92 | | } |
93 | 0 | } |
94 | | |
95 | | /// Compares strings naturally and lexicographically |
96 | | /// |
97 | | /// For example, `"a" < "ä" < "aa"`, `"50" < "100"` |
98 | 0 | pub fn natural_lexical_cmp(s1: &str, s2: &str) -> Ordering { |
99 | 0 | let mut iter1 = iterate_lexical(s1).peekable(); |
100 | 0 | let mut iter2 = iterate_lexical(s2).peekable(); |
101 | | |
102 | | loop { |
103 | 0 | match (iter1.next(), iter2.next()) { |
104 | 0 | (Some(lhs), Some(rhs)) => { |
105 | 0 | if lhs.is_ascii_digit() && rhs.is_ascii_digit() { |
106 | 0 | cmp_ascii_digits!(first_digits(lhs, rhs), iterators(iter1, iter2)); |
107 | 0 | } else if lhs != rhs { |
108 | 0 | return ret_ordering(lhs, rhs); |
109 | 0 | } |
110 | | } |
111 | 0 | (Some(_), None) => return Ordering::Greater, |
112 | 0 | (None, Some(_)) => return Ordering::Less, |
113 | 0 | (None, None) => return s1.cmp(&s2), |
114 | | } |
115 | | } |
116 | 0 | } |
117 | | |
118 | | /// Compares strings naturally and lexicographically, skipping non-alphanumeric characters |
119 | | /// |
120 | | /// For example, `"a" < " ä" < "ä" < "aa"`, `"50" < "100"` |
121 | 0 | pub fn natural_lexical_only_alnum_cmp(s1: &str, s2: &str) -> Ordering { |
122 | 0 | let mut iter1 = iterate_lexical_only_alnum(s1).peekable(); |
123 | 0 | let mut iter2 = iterate_lexical_only_alnum(s2).peekable(); |
124 | | |
125 | | loop { |
126 | 0 | match (iter1.next(), iter2.next()) { |
127 | 0 | (Some(lhs), Some(rhs)) => { |
128 | 0 | if lhs.is_ascii_digit() && rhs.is_ascii_digit() { |
129 | 0 | cmp_ascii_digits!(first_digits(lhs, rhs), iterators(iter1, iter2)); |
130 | 0 | } else if lhs != rhs { |
131 | 0 | return lhs.cmp(&rhs); |
132 | 0 | } |
133 | | } |
134 | 0 | (Some(_), None) => return Ordering::Greater, |
135 | 0 | (None, Some(_)) => return Ordering::Less, |
136 | 0 | (None, None) => return s1.cmp(&s2), |
137 | | } |
138 | | } |
139 | 0 | } |
140 | | |
141 | | /// Compares strings naturally |
142 | | /// |
143 | | /// For example, `"50" < "100"` |
144 | 0 | pub fn natural_cmp(s1: &str, s2: &str) -> Ordering { |
145 | 0 | let mut iter1 = s1.chars().peekable(); |
146 | 0 | let mut iter2 = s2.chars().peekable(); |
147 | | |
148 | | loop { |
149 | 0 | match (iter1.next(), iter2.next()) { |
150 | 0 | (Some(lhs), Some(rhs)) => { |
151 | 0 | if lhs.is_ascii_digit() && rhs.is_ascii_digit() { |
152 | 0 | cmp_ascii_digits!(first_digits(lhs, rhs), iterators(iter1, iter2)); |
153 | 0 | } else if lhs != rhs { |
154 | 0 | return lhs.cmp(&rhs); |
155 | 0 | } |
156 | | } |
157 | 0 | (Some(_), None) => return Ordering::Greater, |
158 | 0 | (None, Some(_)) => return Ordering::Less, |
159 | 0 | (None, None) => return Ordering::Equal, |
160 | | } |
161 | | } |
162 | 0 | } |
163 | | |
164 | | /// Compares strings naturally, skipping non-alphanumeric characters |
165 | | /// |
166 | | /// For example, `"a" < " b" < "b"`, `"50" < "100"` |
167 | 0 | pub fn natural_only_alnum_cmp(s1: &str, s2: &str) -> Ordering { |
168 | 0 | let mut iter1 = s1.chars().filter(|c| c.is_alphanumeric()).peekable(); |
169 | 0 | let mut iter2 = s2.chars().filter(|c| c.is_alphanumeric()).peekable(); |
170 | | |
171 | | loop { |
172 | 0 | match (iter1.next(), iter2.next()) { |
173 | 0 | (Some(lhs), Some(rhs)) => { |
174 | 0 | if lhs.is_ascii_digit() && rhs.is_ascii_digit() { |
175 | 0 | cmp_ascii_digits!(first_digits(lhs, rhs), iterators(iter1, iter2)); |
176 | 0 | } else if lhs != rhs { |
177 | 0 | return lhs.cmp(&rhs); |
178 | 0 | } |
179 | | } |
180 | 0 | (Some(_), None) => return Ordering::Greater, |
181 | 0 | (None, Some(_)) => return Ordering::Less, |
182 | 0 | (None, None) => return s1.cmp(&s2), |
183 | | } |
184 | | } |
185 | 0 | } |
186 | | |
187 | | /// Compares strings, skipping non-alphanumeric characters |
188 | | /// |
189 | | /// For example, `"a" < " b" < "b"` |
190 | 0 | pub fn only_alnum_cmp(s1: &str, s2: &str) -> Ordering { |
191 | 0 | let mut iter1 = s1.chars().filter(|c| c.is_alphanumeric()); |
192 | 0 | let mut iter2 = s2.chars().filter(|c| c.is_alphanumeric()); |
193 | | |
194 | | loop { |
195 | 0 | match (iter1.next(), iter2.next()) { |
196 | 0 | (Some(lhs), Some(rhs)) => { |
197 | 0 | if lhs != rhs { |
198 | 0 | return lhs.cmp(&rhs); |
199 | 0 | } |
200 | | } |
201 | 0 | (Some(_), None) => return Ordering::Greater, |
202 | 0 | (None, Some(_)) => return Ordering::Less, |
203 | 0 | (None, None) => return s1.cmp(&s2), |
204 | | } |
205 | | } |
206 | 0 | } |
207 | | |
208 | | /// Compares strings (not lexicographically or naturally, doesn't skip non-alphanumeric characters) |
209 | | /// |
210 | | /// For example, `"B" < "a" < "b" < "ä"` |
211 | 0 | pub fn cmp(s1: &str, s2: &str) -> Ordering { |
212 | 0 | let mut iter1 = s1.chars(); |
213 | 0 | let mut iter2 = s2.chars(); |
214 | | |
215 | | loop { |
216 | 0 | match (iter1.next(), iter2.next()) { |
217 | 0 | (Some(lhs), Some(rhs)) => { |
218 | 0 | if lhs != rhs { |
219 | 0 | return lhs.cmp(&rhs); |
220 | 0 | } |
221 | | } |
222 | 0 | (Some(_), None) => return Ordering::Greater, |
223 | 0 | (None, Some(_)) => return Ordering::Less, |
224 | 0 | (None, None) => return Ordering::Equal, |
225 | | } |
226 | | } |
227 | 0 | } |
228 | | |
229 | | #[cfg(test)] |
230 | | mod tests { |
231 | | use super::*; |
232 | | |
233 | | fn make_test(desc: &'static str, algo: impl Fn(&str, &str) -> Ordering) -> impl Fn(&str, &str) { |
234 | | move |lhs, rhs| { |
235 | | let success = algo(lhs, rhs) == Ordering::Less; |
236 | | assert!(success, "{} comparison {:?} < {:?} failed", desc, lhs, rhs); |
237 | | |
238 | | let success = algo(rhs, lhs) == Ordering::Greater; |
239 | | assert!(success, "{} comparison {:?} > {:?} failed", desc, rhs, lhs); |
240 | | } |
241 | | } |
242 | | |
243 | | #[test] |
244 | | fn test_cmp() { |
245 | | let ordered = make_test("Cmp", cmp); |
246 | | |
247 | | ordered("aaa", "aaaa"); |
248 | | ordered("aaa", "aab"); |
249 | | ordered("AAb", "aaa"); |
250 | | ordered("aab", "äáa"); |
251 | | ordered("aaa", "äáb"); |
252 | | |
253 | | ordered("T-20", "T-5"); |
254 | | ordered("T-5", "Ŧ-5"); |
255 | | } |
256 | | |
257 | | #[test] |
258 | | fn test_only_alnum() { |
259 | | let ordered = make_test("Only-alnum", only_alnum_cmp); |
260 | | |
261 | | ordered("aaa", "aaaa"); |
262 | | ordered("aaa", "aab"); |
263 | | ordered("AAb", "aaa"); |
264 | | ordered("aab", "äáa"); |
265 | | ordered("aaa", "äáb"); |
266 | | |
267 | | ordered("_ad", "_æ"); |
268 | | ordered("_ae", "_æ"); |
269 | | ordered("_ae_", "_æ"); |
270 | | ordered("_af", "_æ"); |
271 | | |
272 | | ordered("T-20", "T-5"); |
273 | | ordered("T-5", "Ŧ-5"); |
274 | | } |
275 | | |
276 | | #[test] |
277 | | fn test_lexical() { |
278 | | let ordered = make_test("Lexical", lexical_cmp); |
279 | | |
280 | | ordered("aaa", "aaaa"); |
281 | | ordered("aaa", "aab"); |
282 | | ordered("aaa", "AAb"); |
283 | | ordered("äáa", "aab"); |
284 | | ordered("aaa", "äáb"); |
285 | | |
286 | | ordered("_ad", "_æ"); |
287 | | ordered("_ae", "_æ"); |
288 | | ordered("_æ", "_ae_"); |
289 | | ordered("_æ", "_af"); |
290 | | |
291 | | ordered("T-20", "T-5"); |
292 | | ordered("T-5", "Ŧ-5"); |
293 | | } |
294 | | |
295 | | #[test] |
296 | | fn test_lexical_only_alnum() { |
297 | | let ordered = make_test("Lexical, only-alnum", lexical_only_alnum_cmp); |
298 | | |
299 | | ordered("aaa", "aaaa"); |
300 | | ordered("aaa", "aab"); |
301 | | ordered("aaa", "AAb"); |
302 | | ordered("äáa", "aab"); |
303 | | ordered("aaa", "äáb"); |
304 | | |
305 | | ordered("_ad", "_æ"); |
306 | | ordered("_ae", "_æ"); |
307 | | ordered("_ae_", "_æ"); |
308 | | ordered("_æ", "_af"); |
309 | | |
310 | | ordered("T20", "T-21"); |
311 | | ordered("T-21", "T22"); |
312 | | ordered("T-21", "T3"); |
313 | | } |
314 | | |
315 | | #[test] |
316 | | fn test_natural() { |
317 | | let ordered = make_test("Natural", natural_cmp); |
318 | | |
319 | | ordered("1", "10"); |
320 | | ordered("10", "15"); |
321 | | ordered("150", "220"); |
322 | | ordered("334", "335"); |
323 | | ordered("433", "533"); |
324 | | |
325 | | ordered("T-1", "T-5"); |
326 | | ordered("T-27", "T5"); |
327 | | ordered("T-27a", "T27b"); |
328 | | |
329 | | ordered("T-27", "Ŧ-5"); |
330 | | ordered("T-5", "Ŧ-27"); |
331 | | ordered("T-5", "Ŧ-5"); |
332 | | } |
333 | | |
334 | | #[test] |
335 | | fn test_natural_only_alnum() { |
336 | | let ordered = make_test("Natural, only-alnum", natural_only_alnum_cmp); |
337 | | |
338 | | ordered("aaa", "aaaa"); |
339 | | ordered("aaa", "aab"); |
340 | | ordered("AAb", "aaa"); |
341 | | ordered("aab", "äáa"); |
342 | | ordered("aaa", "äáb"); |
343 | | |
344 | | ordered("_ad", "_æ"); |
345 | | ordered("_ae", "_æ"); |
346 | | ordered("_ae_", "_æ"); |
347 | | ordered("_af", "_æ"); |
348 | | |
349 | | ordered("T20", "T-21"); |
350 | | ordered("T-21", "T22"); |
351 | | ordered("T3", "T-21"); |
352 | | } |
353 | | |
354 | | #[test] |
355 | | fn test_natural_lexical() { |
356 | | let ordered = make_test("Natural, lexical", natural_lexical_cmp); |
357 | | |
358 | | ordered("1", "10"); |
359 | | ordered("10", "15"); |
360 | | ordered("150", "220"); |
361 | | ordered("334", "335"); |
362 | | ordered("433", "533"); |
363 | | |
364 | | ordered("T-1", "T-5"); |
365 | | ordered("T-5", "T-27"); |
366 | | ordered("T-27a", "T-27b"); |
367 | | |
368 | | ordered("Ŧ-5", "T-27"); |
369 | | ordered("T-5", "Ŧ-27"); |
370 | | ordered("T-5", "Ŧ-5"); |
371 | | } |
372 | | |
373 | | #[test] |
374 | | fn test_natural_lexical_only_alnum() { |
375 | | let ordered = make_test( |
376 | | "Natural, lexical, only-alnum", |
377 | | natural_lexical_only_alnum_cmp, |
378 | | ); |
379 | | |
380 | | ordered("1", "10"); |
381 | | ordered("10", "15"); |
382 | | ordered("150", "220"); |
383 | | ordered("334", "335"); |
384 | | ordered("433", "533"); |
385 | | |
386 | | ordered("T-1", "T-5"); |
387 | | ordered("T5", "T-27"); |
388 | | ordered("T-27a", "T27b"); |
389 | | |
390 | | ordered("Ŧ-5", "T-27"); |
391 | | ordered("T-5", "Ŧ-27"); |
392 | | ordered("T-5", "Ŧ-5"); |
393 | | } |
394 | | } |