Data update
This commit is contained in:
parent
72eb4943cb
commit
4d5544505c
2347 changed files with 62432 additions and 16731 deletions
|
|
@ -59,7 +59,7 @@ Note: it is not necessary to have individual control of which features ar
|
|||
|
||||
• Numeric fields as numerics. Text strings: ['foo100bar99baz0.txt',
|
||||
'foo100bar10baz0.txt',
|
||||
'foo1000bar99baz10.txt',
|
||||
sv 'foo1000bar99baz10.txt',
|
||||
'foo1000bar99baz9.txt']
|
||||
|
||||
• Title sorts. Text strings: ['The Wind in the Willows',
|
||||
|
|
|
|||
156
Task/Natural-sorting/FreeBASIC/natural-sorting.basic
Normal file
156
Task/Natural-sorting/FreeBASIC/natural-sorting.basic
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
Enum Kind
|
||||
KIND_STRING
|
||||
KIND_NUMBER
|
||||
End Enum
|
||||
|
||||
Type KeyItem
|
||||
tipo As Kind
|
||||
txt As String
|
||||
num As Integer
|
||||
End Type
|
||||
|
||||
Function NormalizeString(txt As String) As String
|
||||
Dim As String res = Trim(txt)
|
||||
Dim As Integer i, l = Len(res)
|
||||
Dim As String c, tmp = ""
|
||||
Dim As Boolean last_space = False
|
||||
|
||||
For i = 1 To l
|
||||
c = Mid(res, i, 1)
|
||||
If c = " " Or c = Chr(9) Or c = Chr(10) Or c = Chr(13) Or c = Chr(11) Or c = Chr(12) Then
|
||||
If Not last_space Then
|
||||
tmp &= " "
|
||||
last_space = True
|
||||
End If
|
||||
Else
|
||||
tmp &= Lcase(c)
|
||||
last_space = False
|
||||
End If
|
||||
Next
|
||||
res = tmp
|
||||
If Left(res, 4) = "the " Then res = Mid(res, 5)
|
||||
|
||||
Return res
|
||||
End Function
|
||||
|
||||
Sub NatOrderKey(txt As String, result() As KeyItem)
|
||||
Dim As String normalized = NormalizeString(txt)
|
||||
Dim As Integer idx = 1, l = Len(normalized)
|
||||
Dim As Integer cnt = 0
|
||||
Redim result(-1)
|
||||
|
||||
While idx <= l
|
||||
Dim As Integer e = idx
|
||||
' Process text
|
||||
While e <= l And Not (Mid(normalized, e, 1) >= "0" And Mid(normalized, e, 1) <= "9")
|
||||
e += 1
|
||||
Wend
|
||||
If e > idx Then
|
||||
Redim Preserve result(cnt)
|
||||
result(cnt).tipo = KIND_STRING
|
||||
result(cnt).txt = Mid(normalized, idx, e - idx)
|
||||
result(cnt).num = 0
|
||||
cnt += 1
|
||||
idx = e
|
||||
End If
|
||||
|
||||
' Process number
|
||||
While e <= l And (Mid(normalized, e, 1) >= "0" And Mid(normalized, e, 1) <= "9")
|
||||
e += 1
|
||||
Wend
|
||||
If e > idx Then
|
||||
Redim Preserve result(cnt)
|
||||
result(cnt).tipo = KIND_NUMBER
|
||||
result(cnt).txt = ""
|
||||
result(cnt).num = Val(Mid(normalized, idx, e - idx))
|
||||
cnt += 1
|
||||
idx = e
|
||||
End If
|
||||
Wend
|
||||
End Sub
|
||||
|
||||
Function scmp(s1 As String, s2 As String) As Integer
|
||||
If s1 < s2 Then Return -1
|
||||
If s1 > s2 Then Return 1
|
||||
Return 0
|
||||
End Function
|
||||
|
||||
Function NaturalCompare(sa As String, sb As String) As Integer
|
||||
Dim As KeyItem a(), b()
|
||||
NatOrderKey(sa, a())
|
||||
NatOrderKey(sb, b())
|
||||
|
||||
' Check for empty arrays
|
||||
Dim As Integer la = Ubound(a), lb = Ubound(b)
|
||||
|
||||
' Handle case where one or both arrays might be empty
|
||||
If la < 0 And lb < 0 Then Return 0
|
||||
If la < 0 Then Return -1
|
||||
If lb < 0 Then Return 1
|
||||
|
||||
Dim As Integer n = Iif(la < lb, la, lb)
|
||||
|
||||
For i As Integer = 0 To n
|
||||
Dim As KeyItem ai = a(i)
|
||||
Dim As KeyItem bi = b(i)
|
||||
|
||||
If ai.tipo = bi.tipo Then
|
||||
Dim As Integer result
|
||||
If ai.tipo = KIND_STRING Then
|
||||
result = scmp(ai.txt, bi.txt)
|
||||
Else
|
||||
result = Sgn(ai.num - bi.num) ' Use Sgn to get -1, 0, or 1
|
||||
End If
|
||||
If result <> 0 Then Return result
|
||||
Else
|
||||
Return Iif(ai.tipo = KIND_STRING, 1, -1)
|
||||
End If
|
||||
Next
|
||||
|
||||
If la < lb Then Return -1
|
||||
If la > lb Then Return 1
|
||||
Return 0
|
||||
End Function
|
||||
|
||||
Sub test(title As String, arr() As String)
|
||||
Print title
|
||||
Dim As Integer i, j, n = Ubound(arr)
|
||||
|
||||
Dim As String sorted(n)
|
||||
For i = 0 To n
|
||||
sorted(i) = arr(i)
|
||||
Next
|
||||
|
||||
' Bubble sort
|
||||
For i = 0 To n - 1
|
||||
For j = 0 To n - i - 1
|
||||
If NaturalCompare(sorted(j), sorted(j + 1)) > 0 Then Swap sorted(j), sorted(j + 1)
|
||||
Next
|
||||
Next
|
||||
|
||||
For i = 0 To n
|
||||
Print "'" & sorted(i) & "'"
|
||||
Next
|
||||
Print
|
||||
End Sub
|
||||
|
||||
' tests
|
||||
Dim arr1(3) As String = {"ignore leading spaces: 2-2", " ignore leading spaces: 2-1", " ignore leading spaces: 2+0", " ignore leading spaces: 2+1"}
|
||||
test("Ignoring leading spaces.", arr1())
|
||||
|
||||
Dim arr2(3) As String = {"ignore MAS spaces: 2-2", "ignore MAS spaces: 2-1", "ignore MAS spaces: 2+0", "ignore MAS spaces: 2+1"}
|
||||
test("Ignoring multiple adjacent spaces (MAS).", arr2())
|
||||
|
||||
Dim arr3(5) As String = {"Equiv. spaces: 3-3", "Equiv. " & Chr(13) & "spaces: 3-2", "Equiv. " & Chr(12) & "spaces: 3-1", "Equiv. " & Chr(11) & "spaces: 3+0", "Equiv. " & Chr(10) & "spaces: 3+1", "Equiv. " & Chr(9) & "spaces: 3+2"}
|
||||
test("Equivalent whitespace characters.", arr3())
|
||||
|
||||
Dim arr4(3) As String = {"cASE INDEPENDENT: 3-2", "caSE INDEPENDENT: 3-1", "casE INDEPENDENT: 3+0", "case INDEPENDENT: 3+1"}
|
||||
test("Case Independent sort.", arr4())
|
||||
|
||||
Dim arr5(3) As String = {"foo100bar99baz0.txt", "foo100bar10baz0.txt", "foo1000bar99baz10.txt", "foo1000bar99baz9.txt"}
|
||||
test("Numeric fields as numerics.", arr5())
|
||||
|
||||
Dim arr6(3) As String = {"The Wind in the Willows", "The 40th step more", "The 39 steps", "Wanda"}
|
||||
test("Title sorts.", arr6())
|
||||
|
||||
Sleep
|
||||
7
Task/Natural-sorting/JavaScript/natural-sorting-1.js
Normal file
7
Task/Natural-sorting/JavaScript/natural-sorting-1.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
function naturalSort(a, b) => {
|
||||
return a.trim().localeCompare(b.trim(), 'und', { numeric: true })
|
||||
}
|
||||
|
||||
const files = ['file10.txt', '\nfile9.txt', 'File11.TXT', 'file12.txt']
|
||||
console.log(files.toSorted(naturalSort))
|
||||
// ['\nfile9.txt', 'file10.txt', 'File11.TXT', 'file12.txt']
|
||||
260
Task/Natural-sorting/Rust/natural-sorting.rs
Normal file
260
Task/Natural-sorting/Rust/natural-sorting.rs
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
use regex::Regex;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
// Only covers ISO-8859-1 accented characters plus, for consistency, Ÿ
|
||||
const UC_ACCENTS: [&str; 8] = ["ÀÁÂÃÄÅ", "Ç", "ÈÉÊË", "ÌÍÎÏ", "Ñ", "ÒÓÔÕÖØ", "ÙÚÛÜ", "ÝŸ"];
|
||||
const LC_ACCENTS: [&str; 8] = ["àáâãäå", "ç", "èéêë", "ìíîï", "ñ", "òóôõöø", "ùúûü", "ýÿ"];
|
||||
const UC_UNACCENTS: [&str; 8] = ["A", "C", "E", "I", "N", "O", "U", "Y"];
|
||||
const LC_UNACCENTS: [&str; 8] = ["a", "c", "e", "i", "n", "o", "u", "y"];
|
||||
|
||||
// Only the more common ligatures
|
||||
const UC_LIGATURES: [&str; 3] = ["Æ", "IJ", "Œ"];
|
||||
const LC_LIGATURES: [&str; 3] = ["æ", "ij", "œ"];
|
||||
const UC_SEPARATES: [&str; 3] = ["AE", "IJ", "OE"];
|
||||
const LC_SEPARATES: [&str; 3] = ["ae", "ij", "oe"];
|
||||
|
||||
// Miscellaneous replacements
|
||||
const MISC_LETTERS: [&str; 3] = ["ß", "ſ", "ʒ"];
|
||||
const MISC_REPLACEMENTS: [&str; 3] = ["ss", "s", "s"];
|
||||
|
||||
// Remove leading spaces
|
||||
fn left_trim(text: &str) -> String {
|
||||
text.trim_start().to_string()
|
||||
}
|
||||
|
||||
// Replace multiple spaces with a single space
|
||||
fn replace_spaces(text: &str) -> String {
|
||||
let regex_expr = Regex::new(r" {2,}").unwrap();
|
||||
regex_expr.replace_all(text, " ").to_string()
|
||||
}
|
||||
|
||||
// Replace whitespace with a single space
|
||||
fn replace_whitespace(text: &str) -> String {
|
||||
let regex_expr = Regex::new(r"\s+").unwrap();
|
||||
regex_expr.replace_all(text, " ").to_string()
|
||||
}
|
||||
|
||||
// Display strings including whitespace as if the latter were literal characters
|
||||
fn to_display_string(text: &str) -> String {
|
||||
let whitespace_1 = ["\t", "\n", "\u{000b}", "\u{000c}", "\r"];
|
||||
let whitespace_2 = ["\\t", "\\n", "\\u000b", "\\u000c", "\\r"];
|
||||
let mut result = text.to_string();
|
||||
|
||||
for i in 0..whitespace_1.len() {
|
||||
result = result.replace(whitespace_1[i], whitespace_2[i]);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
// Transform the string into lower case
|
||||
fn to_lower_case(text: &str) -> String {
|
||||
text.to_lowercase()
|
||||
}
|
||||
|
||||
// Pad each numeric character with leading zeros to a total length of 20
|
||||
fn zero_padding(text: &str) -> String {
|
||||
let digits = Regex::new(r"-?\d+").unwrap();
|
||||
let mut result = text.to_string();
|
||||
let mut extra_index = 0;
|
||||
|
||||
for cap in digits.captures_iter(text) {
|
||||
let match_str = &cap[0];
|
||||
let start_pos = text.find(match_str).unwrap() + extra_index;
|
||||
let padding = "0".repeat(20 - match_str.len());
|
||||
|
||||
result = format!("{}{}{}",
|
||||
&result[..start_pos],
|
||||
padding,
|
||||
&result[start_pos..]);
|
||||
|
||||
extra_index += 20 - match_str.len();
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn remove_title(text: &str) -> String {
|
||||
let regex = Regex::new(r"^(The|An|A)\s+").unwrap();
|
||||
regex.replace(text, "").to_string()
|
||||
}
|
||||
|
||||
// Replace accented letters with their unaccented equivalent
|
||||
fn replace_accents(text: &str) -> String {
|
||||
let mut result = String::new();
|
||||
let chars: Vec<char> = text.chars().collect();
|
||||
|
||||
for i in 0..chars.len() {
|
||||
if (chars[i] as u32) < 128 {
|
||||
result.push(chars[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
let length = result.len();
|
||||
let letter = chars[i].to_string();
|
||||
|
||||
for j in 0..UC_ACCENTS.len() {
|
||||
if UC_ACCENTS[j].contains(&letter) {
|
||||
result.push_str(UC_UNACCENTS[j]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if length == result.len() {
|
||||
for j in 0..LC_ACCENTS.len() {
|
||||
if LC_ACCENTS[j].contains(&letter) {
|
||||
result.push_str(LC_UNACCENTS[j]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
// Replace ligatures with separated letters
|
||||
fn replace_ligatures(text: &str) -> String {
|
||||
let mut result = text.to_string();
|
||||
|
||||
for i in 0..UC_LIGATURES.len() {
|
||||
result = result.replace(UC_LIGATURES[i], UC_SEPARATES[i]);
|
||||
}
|
||||
|
||||
for i in 0..LC_LIGATURES.len() {
|
||||
result = result.replace(LC_LIGATURES[i], LC_SEPARATES[i]);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
// Replace miscellaneous letters with their equivalent replacements
|
||||
fn replace_characters(text: &str) -> String {
|
||||
let mut result = text.to_string();
|
||||
|
||||
for i in 0..MISC_LETTERS.len() {
|
||||
result = result.replace(MISC_LETTERS[i], MISC_REPLACEMENTS[i]);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("The 9 string lists, sorted 'naturally':");
|
||||
|
||||
let mut s1 = vec![
|
||||
"ignore leading spaces: 2-2".to_string(),
|
||||
" ignore leading spaces: 2-1".to_string(),
|
||||
" ignore leading spaces: 2+0".to_string(),
|
||||
" ignore leading spaces: 2+1".to_string()
|
||||
];
|
||||
|
||||
println!();
|
||||
s1.sort_by(|lhs, rhs| left_trim(lhs).cmp(&left_trim(rhs)));
|
||||
for s in &s1 {
|
||||
println!("{}", s);
|
||||
}
|
||||
|
||||
let mut s2 = vec![
|
||||
"ignore m.a.s spaces: 2-2".to_string(),
|
||||
"ignore m.a.s spaces: 2-1".to_string(),
|
||||
"ignore m.a.s spaces: 2+0".to_string(),
|
||||
"ignore m.a.s spaces: 2+1".to_string()
|
||||
];
|
||||
|
||||
println!();
|
||||
s2.sort_by(|lhs, rhs| replace_spaces(lhs).cmp(&replace_spaces(rhs)));
|
||||
for s in &s2 {
|
||||
println!("{}", s);
|
||||
}
|
||||
|
||||
let mut s3 = vec![
|
||||
"Equiv. spaces: 3-3".to_string(),
|
||||
"Equiv.\rspaces: 3-2".to_string(),
|
||||
"Equiv.\u{000c}spaces: 3-1".to_string(),
|
||||
"Equiv.\u{000b}spaces: 3+0".to_string(),
|
||||
"Equiv.\nspaces: 3+1".to_string(),
|
||||
"Equiv.\tspaces: 3+2".to_string()
|
||||
];
|
||||
|
||||
println!();
|
||||
s3.sort_by(|lhs, rhs| replace_whitespace(lhs).cmp(&replace_whitespace(rhs)));
|
||||
for s in &s3 {
|
||||
println!("{}", to_display_string(s));
|
||||
}
|
||||
|
||||
let mut s4 = vec![
|
||||
"cASE INDEPENENT: 3-2".to_string(),
|
||||
"caSE INDEPENENT: 3-1".to_string(),
|
||||
"casE INDEPENENT: 3+0".to_string(),
|
||||
"case INDEPENENT: 3+1".to_string()
|
||||
];
|
||||
|
||||
println!();
|
||||
s4.sort_by(|lhs, rhs| to_lower_case(lhs).cmp(&to_lower_case(rhs)));
|
||||
for s in &s4 {
|
||||
println!("{}", s);
|
||||
}
|
||||
|
||||
let mut s5 = vec![
|
||||
"foo100bar99baz0.txt".to_string(),
|
||||
"foo100bar10baz0.txt".to_string(),
|
||||
"foo1000bar99baz10.txt".to_string(),
|
||||
"foo1000bar99baz9.txt".to_string()
|
||||
];
|
||||
|
||||
println!();
|
||||
s5.sort_by(|lhs, rhs| zero_padding(lhs).cmp(&zero_padding(rhs)));
|
||||
for s in &s5 {
|
||||
println!("{}", s);
|
||||
}
|
||||
|
||||
let mut s6 = vec![
|
||||
"The Wind in the Willows".to_string(),
|
||||
"The 40th step more".to_string(),
|
||||
"The 39 steps".to_string(),
|
||||
"Wanda".to_string()
|
||||
];
|
||||
|
||||
println!();
|
||||
s6.sort_by(|lhs, rhs| remove_title(lhs).cmp(&remove_title(rhs)));
|
||||
for s in &s6 {
|
||||
println!("{}", s);
|
||||
}
|
||||
|
||||
let mut s7 = vec![
|
||||
"Equiv. ý accents: 2-2".to_string(),
|
||||
"Equiv. Ý accents: 2-1".to_string(),
|
||||
"Equiv. y accents: 2+0".to_string(),
|
||||
"Equiv. Y accents: 2+1".to_string()
|
||||
];
|
||||
|
||||
println!();
|
||||
s7.sort_by(|lhs, rhs| replace_accents(lhs).cmp(&replace_accents(rhs)));
|
||||
for s in &s7 {
|
||||
println!("{}", s);
|
||||
}
|
||||
|
||||
let mut s8 = vec![
|
||||
"IJ ligatured ij".to_string(),
|
||||
"no ligature".to_string()
|
||||
];
|
||||
|
||||
println!();
|
||||
s8.sort_by(|lhs, rhs| replace_ligatures(lhs).cmp(&replace_ligatures(rhs)));
|
||||
for s in &s8 {
|
||||
println!("{}", s);
|
||||
}
|
||||
|
||||
let mut s9 = vec![
|
||||
"Start with an ʒ: 2-2".to_string(),
|
||||
"Start with an ſ: 2-1".to_string(),
|
||||
"Start with an ß: 2+0".to_string(),
|
||||
"Start with an s: 2+1".to_string()
|
||||
];
|
||||
|
||||
println!();
|
||||
s9.sort_by(|lhs, rhs| replace_characters(lhs).cmp(&replace_characters(rhs)));
|
||||
for s in &s9 {
|
||||
println!("{}", s);
|
||||
}
|
||||
}
|
||||
571
Task/Natural-sorting/Zig/natural-sorting.zig
Normal file
571
Task/Natural-sorting/Zig/natural-sorting.zig
Normal file
|
|
@ -0,0 +1,571 @@
|
|||
const std = @import("std");
|
||||
const mem = std.mem;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const ArrayList = std.ArrayList;
|
||||
|
||||
// Only covers ISO-8859-1 accented characters plus, for consistency, Ÿ
|
||||
const UC_ACCENTS = [_][]const u8{ "ÀÁÂÃÄÅ", "Ç", "ÈÉÊË", "ÌÍÎÏ", "Ñ", "ÒÓÔÕÖØ", "ÙÚÛÜ", "ÝŸ" };
|
||||
const LC_ACCENTS = [_][]const u8{ "àáâãäå", "ç", "èéêë", "ìíîï", "ñ", "òóôõöø", "ùúûü", "ýÿ" };
|
||||
const UC_UNACCENTS = [_][]const u8{ "A", "C", "E", "I", "N", "O", "U", "Y" };
|
||||
const LC_UNACCENTS = [_][]const u8{ "a", "c", "e", "i", "n", "o", "u", "y" };
|
||||
|
||||
// Only the more common ligatures
|
||||
const UC_LIGATURES = [_][]const u8{ "Æ", "IJ", "Œ" };
|
||||
const LC_LIGATURES = [_][]const u8{ "æ", "ij", "œ" };
|
||||
const UC_SEPARATES = [_][]const u8{ "AE", "IJ", "OE" };
|
||||
const LC_SEPARATES = [_][]const u8{ "ae", "ij", "oe" };
|
||||
|
||||
// Miscellaneous replacements
|
||||
const MISC_LETTERS = [_][]const u8{ "ß", "ſ", "ʒ" };
|
||||
const MISC_REPLACEMENTS = [_][]const u8{ "ss", "s", "s" };
|
||||
|
||||
// Remove leading spaces
|
||||
fn leftTrim(allocator: Allocator, text: []const u8) ![]u8 {
|
||||
var i: usize = 0;
|
||||
while (i < text.len and text[i] == ' ') : (i += 1) {}
|
||||
return allocator.dupe(u8, text[i..]);
|
||||
}
|
||||
|
||||
// Replace multiple spaces with a single space
|
||||
fn replaceSpaces(allocator: Allocator, text: []const u8) ![]u8 {
|
||||
var result = ArrayList(u8).init(allocator);
|
||||
defer result.deinit();
|
||||
|
||||
var i: usize = 0;
|
||||
var inSpaces = false;
|
||||
while (i < text.len) : (i += 1) {
|
||||
if (text[i] == ' ') {
|
||||
if (!inSpaces) {
|
||||
try result.append(' ');
|
||||
inSpaces = true;
|
||||
}
|
||||
} else {
|
||||
try result.append(text[i]);
|
||||
inSpaces = false;
|
||||
}
|
||||
}
|
||||
|
||||
return result.toOwnedSlice();
|
||||
}
|
||||
|
||||
// Replace whitespace with a single space
|
||||
fn replaceWhitespace(allocator: Allocator, text: []const u8) ![]u8 {
|
||||
var result = ArrayList(u8).init(allocator);
|
||||
defer result.deinit();
|
||||
|
||||
var i: usize = 0;
|
||||
var inWhitespace = false;
|
||||
while (i < text.len) : (i += 1) {
|
||||
if (std.ascii.isWhitespace(text[i])) {
|
||||
if (!inWhitespace) {
|
||||
try result.append(' ');
|
||||
inWhitespace = true;
|
||||
}
|
||||
} else {
|
||||
try result.append(text[i]);
|
||||
inWhitespace = false;
|
||||
}
|
||||
}
|
||||
|
||||
return result.toOwnedSlice();
|
||||
}
|
||||
|
||||
// Display strings including whitespace as if the latter were literal characters
|
||||
fn toDisplayString(allocator: Allocator, text: []const u8) ![]u8 {
|
||||
const whitespace_1 = [_][]const u8{ "\t", "\n", "\x0B", "\x0C", "\r" };
|
||||
const whitespace_2 = [_][]const u8{ "\\t", "\\n", "\\u000b", "\\u000c", "\\r" };
|
||||
|
||||
var result = ArrayList(u8).init(allocator);
|
||||
defer result.deinit();
|
||||
|
||||
var i: usize = 0;
|
||||
while (i < text.len) : (i += 1) {
|
||||
var replaced = false;
|
||||
for (whitespace_1, 0..) |ws, j| {
|
||||
if (i + ws.len <= text.len and mem.eql(u8, text[i..i+ws.len], ws)) {
|
||||
try result.appendSlice(whitespace_2[j]);
|
||||
i += ws.len - 1;
|
||||
replaced = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!replaced) {
|
||||
try result.append(text[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return result.toOwnedSlice();
|
||||
}
|
||||
|
||||
// Transform the string into lower case
|
||||
fn toLowerCase(allocator: Allocator, text: []const u8) ![]u8 {
|
||||
var result = try allocator.alloc(u8, text.len);
|
||||
errdefer allocator.free(result);
|
||||
|
||||
for (text, 0..) |c, i| {
|
||||
result[i] = std.ascii.toLower(c);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Pad each numeric character with leading zeros to a total length of 20
|
||||
fn zeroPadding(allocator: Allocator, text: []const u8) ![]u8 {
|
||||
var result = ArrayList(u8).init(allocator);
|
||||
defer result.deinit();
|
||||
|
||||
var i: usize = 0;
|
||||
while (i < text.len) {
|
||||
if (std.ascii.isDigit(text[i]) or (text[i] == '-' and i + 1 < text.len and std.ascii.isDigit(text[i+1]))) {
|
||||
const start = i;
|
||||
if (text[i] == '-') {
|
||||
i += 1;
|
||||
}
|
||||
while (i < text.len and std.ascii.isDigit(text[i])) : (i += 1) {}
|
||||
|
||||
const numStr = text[start..i];
|
||||
const padding = if (numStr.len < 20) 20 - numStr.len else 0;
|
||||
|
||||
for (0..padding) |_| {
|
||||
try result.append('0');
|
||||
}
|
||||
try result.appendSlice(numStr);
|
||||
} else {
|
||||
try result.append(text[i]);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return result.toOwnedSlice();
|
||||
}
|
||||
|
||||
fn removeTitle(allocator: Allocator, text: []const u8) ![]u8 {
|
||||
if (text.len >= 4 and mem.eql(u8, text[0..4], "The ")) {
|
||||
return allocator.dupe(u8, text[4..]);
|
||||
} else if (text.len >= 3 and mem.eql(u8, text[0..3], "An ")) {
|
||||
return allocator.dupe(u8, text[3..]);
|
||||
} else if (text.len >= 2 and mem.eql(u8, text[0..2], "A ")) {
|
||||
return allocator.dupe(u8, text[2..]);
|
||||
} else {
|
||||
return allocator.dupe(u8, text);
|
||||
}
|
||||
}
|
||||
|
||||
// Replace accented letters with their unaccented equivalent
|
||||
fn replaceAccents(allocator: Allocator, text: []const u8) ![]u8 {
|
||||
var result = ArrayList(u8).init(allocator);
|
||||
defer result.deinit();
|
||||
|
||||
var i: usize = 0;
|
||||
while (i < text.len) {
|
||||
// Handle UTF-8 characters
|
||||
var char: [4]u8 = undefined;
|
||||
var char_len: usize = 0;
|
||||
|
||||
if ((text[i] & 0x80) == 0) {
|
||||
// ASCII character
|
||||
try result.append(text[i]);
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract UTF-8 character
|
||||
if ((text[i] & 0xE0) == 0xC0) {
|
||||
char_len = 2;
|
||||
} else if ((text[i] & 0xF0) == 0xE0) {
|
||||
char_len = 3;
|
||||
} else if ((text[i] & 0xF8) == 0xF0) {
|
||||
char_len = 4;
|
||||
} else {
|
||||
// Invalid UTF-8, just copy
|
||||
try result.append(text[i]);
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i + char_len > text.len) {
|
||||
// Incomplete UTF-8 sequence
|
||||
try result.append(text[i]);
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
@memcpy(char[0..char_len], text[i..i+char_len]);
|
||||
|
||||
var replaced = false;
|
||||
const charSlice = char[0..char_len];
|
||||
|
||||
for (UC_ACCENTS, 0..) |accents, j| {
|
||||
if (containsUtf8Char(accents, charSlice)) {
|
||||
try result.appendSlice(UC_UNACCENTS[j]);
|
||||
replaced = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!replaced) {
|
||||
for (LC_ACCENTS, 0..) |accents, j| {
|
||||
if (containsUtf8Char(accents, charSlice)) {
|
||||
try result.appendSlice(LC_UNACCENTS[j]);
|
||||
replaced = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!replaced) {
|
||||
try result.appendSlice(charSlice);
|
||||
}
|
||||
|
||||
i += char_len;
|
||||
}
|
||||
|
||||
return result.toOwnedSlice();
|
||||
}
|
||||
|
||||
// Helper function to check if a UTF-8 string contains a character
|
||||
fn containsUtf8Char(haystack: []const u8, needle: []const u8) bool {
|
||||
var i: usize = 0;
|
||||
while (i < haystack.len) {
|
||||
const char_len = utf8CharLen(haystack[i]);
|
||||
if (i + char_len <= haystack.len and mem.eql(u8, haystack[i..i+char_len], needle)) {
|
||||
return true;
|
||||
}
|
||||
i += char_len;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Helper function to get UTF-8 character length
|
||||
fn utf8CharLen(first_byte: u8) usize {
|
||||
if ((first_byte & 0x80) == 0) return 1;
|
||||
if ((first_byte & 0xE0) == 0xC0) return 2;
|
||||
if ((first_byte & 0xF0) == 0xE0) return 3;
|
||||
if ((first_byte & 0xF8) == 0xF0) return 4;
|
||||
return 1; // invalid UTF-8, treat as single byte
|
||||
}
|
||||
|
||||
// Replace ligatures with separated letters
|
||||
fn replaceLigatures(allocator: Allocator, text: []const u8) ![]u8 {
|
||||
var result = ArrayList(u8).init(allocator);
|
||||
defer result.deinit();
|
||||
try result.appendSlice(text);
|
||||
|
||||
for (UC_LIGATURES, 0..) |ligature, i| {
|
||||
var newResult = ArrayList(u8).init(allocator);
|
||||
defer newResult.deinit();
|
||||
|
||||
var j: usize = 0;
|
||||
while (j < result.items.len) {
|
||||
const char_len = utf8CharLen(result.items[j]);
|
||||
if (j + char_len <= result.items.len and isUtf8Char(result.items[j..j+char_len], ligature)) {
|
||||
try newResult.appendSlice(UC_SEPARATES[i]);
|
||||
j += char_len;
|
||||
} else {
|
||||
try newResult.append(result.items[j]);
|
||||
j += 1;
|
||||
}
|
||||
}
|
||||
|
||||
result.clearAndFree();
|
||||
try result.appendSlice(newResult.items);
|
||||
}
|
||||
|
||||
for (LC_LIGATURES, 0..) |ligature, i| {
|
||||
var newResult = ArrayList(u8).init(allocator);
|
||||
defer newResult.deinit();
|
||||
|
||||
var j: usize = 0;
|
||||
while (j < result.items.len) {
|
||||
const char_len = utf8CharLen(result.items[j]);
|
||||
if (j + char_len <= result.items.len and isUtf8Char(result.items[j..j+char_len], ligature)) {
|
||||
try newResult.appendSlice(LC_SEPARATES[i]);
|
||||
j += char_len;
|
||||
} else {
|
||||
try newResult.append(result.items[j]);
|
||||
j += 1;
|
||||
}
|
||||
}
|
||||
|
||||
result.clearAndFree();
|
||||
try result.appendSlice(newResult.items);
|
||||
}
|
||||
|
||||
return result.toOwnedSlice();
|
||||
}
|
||||
|
||||
// Helper function to compare UTF-8 characters
|
||||
fn isUtf8Char(a: []const u8, b: []const u8) bool {
|
||||
return mem.eql(u8, a, b);
|
||||
}
|
||||
|
||||
// Replace miscellaneous letters with their equivalent replacements
|
||||
fn replaceCharacters(allocator: Allocator, text: []const u8) ![]u8 {
|
||||
var result = ArrayList(u8).init(allocator);
|
||||
defer result.deinit();
|
||||
try result.appendSlice(text);
|
||||
|
||||
for (MISC_LETTERS, 0..) |letter, i| {
|
||||
var newResult = ArrayList(u8).init(allocator);
|
||||
defer newResult.deinit();
|
||||
|
||||
var j: usize = 0;
|
||||
while (j < result.items.len) {
|
||||
const char_len = utf8CharLen(result.items[j]);
|
||||
if (j + char_len <= result.items.len and isUtf8Char(result.items[j..j+char_len], letter)) {
|
||||
try newResult.appendSlice(MISC_REPLACEMENTS[i]);
|
||||
j += char_len;
|
||||
} else {
|
||||
try newResult.append(result.items[j]);
|
||||
j += 1;
|
||||
}
|
||||
}
|
||||
|
||||
result.clearAndFree();
|
||||
try result.appendSlice(newResult.items);
|
||||
}
|
||||
|
||||
return result.toOwnedSlice();
|
||||
}
|
||||
|
||||
// Custom context for sort comparators
|
||||
const SortContext = struct {
|
||||
allocator: Allocator,
|
||||
};
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer _ = gpa.deinit();
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
const stdout = std.io.getStdOut().writer();
|
||||
try stdout.print("The 9 string lists, sorted 'naturally':\n", .{});
|
||||
|
||||
var s1 = ArrayList([]const u8).init(allocator);
|
||||
defer s1.deinit();
|
||||
try s1.append("ignore leading spaces: 2-2");
|
||||
try s1.append(" ignore leading spaces: 2-1");
|
||||
try s1.append(" ignore leading spaces: 2+0");
|
||||
try s1.append(" ignore leading spaces: 2+1");
|
||||
|
||||
try stdout.print("\n", .{});
|
||||
|
||||
// Sort using leftTrim
|
||||
const ctx1 = SortContext{ .allocator = allocator };
|
||||
std.sort.insertion([]const u8, s1.items, ctx1, struct {
|
||||
fn lessThan(ctx: SortContext, lhs: []const u8, rhs: []const u8) bool {
|
||||
const l = leftTrim(ctx.allocator, lhs) catch return false;
|
||||
defer ctx.allocator.free(l);
|
||||
const r = leftTrim(ctx.allocator, rhs) catch return false;
|
||||
defer ctx.allocator.free(r);
|
||||
return mem.lessThan(u8, l, r);
|
||||
}
|
||||
}.lessThan);
|
||||
|
||||
for (s1.items) |s| {
|
||||
try stdout.print("{s}\n", .{s});
|
||||
}
|
||||
|
||||
var s2 = ArrayList([]const u8).init(allocator);
|
||||
defer s2.deinit();
|
||||
try s2.append("ignore m.a.s spaces: 2-2");
|
||||
try s2.append("ignore m.a.s spaces: 2-1");
|
||||
try s2.append("ignore m.a.s spaces: 2+0");
|
||||
try s2.append("ignore m.a.s spaces: 2+1");
|
||||
|
||||
try stdout.print("\n", .{});
|
||||
|
||||
// Sort using replaceSpaces
|
||||
const ctx2 = SortContext{ .allocator = allocator };
|
||||
std.sort.insertion([]const u8, s2.items, ctx2, struct {
|
||||
fn lessThan(ctx: SortContext, lhs: []const u8, rhs: []const u8) bool {
|
||||
const l = replaceSpaces(ctx.allocator, lhs) catch return false;
|
||||
defer ctx.allocator.free(l);
|
||||
const r = replaceSpaces(ctx.allocator, rhs) catch return false;
|
||||
defer ctx.allocator.free(r);
|
||||
return mem.lessThan(u8, l, r);
|
||||
}
|
||||
}.lessThan);
|
||||
|
||||
for (s2.items) |s| {
|
||||
try stdout.print("{s}\n", .{s});
|
||||
}
|
||||
|
||||
var s3 = ArrayList([]const u8).init(allocator);
|
||||
defer s3.deinit();
|
||||
try s3.append("Equiv. spaces: 3-3");
|
||||
try s3.append("Equiv.\rspaces: 3-2");
|
||||
try s3.append("Equiv.\x0Cspaces: 3-1");
|
||||
try s3.append("Equiv.\x0Bspaces: 3+0");
|
||||
try s3.append("Equiv.\nspaces: 3+1");
|
||||
try s3.append("Equiv.\tspaces: 3+2");
|
||||
|
||||
try stdout.print("\n", .{});
|
||||
|
||||
// Sort using replaceWhitespace
|
||||
const ctx3 = SortContext{ .allocator = allocator };
|
||||
std.sort.insertion([]const u8, s3.items, ctx3, struct {
|
||||
fn lessThan(ctx: SortContext, lhs: []const u8, rhs: []const u8) bool {
|
||||
const l = replaceWhitespace(ctx.allocator, lhs) catch return false;
|
||||
defer ctx.allocator.free(l);
|
||||
const r = replaceWhitespace(ctx.allocator, rhs) catch return false;
|
||||
defer ctx.allocator.free(r);
|
||||
return mem.lessThan(u8, l, r);
|
||||
}
|
||||
}.lessThan);
|
||||
|
||||
for (s3.items) |s| {
|
||||
const displayStr = try toDisplayString(allocator, s);
|
||||
defer allocator.free(displayStr);
|
||||
try stdout.print("{s}\n", .{displayStr});
|
||||
}
|
||||
|
||||
var s4 = ArrayList([]const u8).init(allocator);
|
||||
defer s4.deinit();
|
||||
try s4.append("cASE INDEPENENT: 3-2");
|
||||
try s4.append("caSE INDEPENENT: 3-1");
|
||||
try s4.append("casE INDEPENENT: 3+0");
|
||||
try s4.append("case INDEPENENT: 3+1");
|
||||
|
||||
try stdout.print("\n", .{});
|
||||
|
||||
// Sort using toLowerCase
|
||||
const ctx4 = SortContext{ .allocator = allocator };
|
||||
std.sort.insertion([]const u8, s4.items, ctx4, struct {
|
||||
fn lessThan(ctx: SortContext, lhs: []const u8, rhs: []const u8) bool {
|
||||
const l = toLowerCase(ctx.allocator, lhs) catch return false;
|
||||
defer ctx.allocator.free(l);
|
||||
const r = toLowerCase(ctx.allocator, rhs) catch return false;
|
||||
defer ctx.allocator.free(r);
|
||||
return mem.lessThan(u8, l, r);
|
||||
}
|
||||
}.lessThan);
|
||||
|
||||
for (s4.items) |s| {
|
||||
try stdout.print("{s}\n", .{s});
|
||||
}
|
||||
|
||||
var s5 = ArrayList([]const u8).init(allocator);
|
||||
defer s5.deinit();
|
||||
try s5.append("foo100bar99baz0.txt");
|
||||
try s5.append("foo100bar10baz0.txt");
|
||||
try s5.append("foo1000bar99baz10.txt");
|
||||
try s5.append("foo1000bar99baz9.txt");
|
||||
|
||||
try stdout.print("\n", .{});
|
||||
|
||||
// Sort using zeroPadding
|
||||
const ctx5 = SortContext{ .allocator = allocator };
|
||||
std.sort.insertion([]const u8, s5.items, ctx5, struct {
|
||||
fn lessThan(ctx: SortContext, lhs: []const u8, rhs: []const u8) bool {
|
||||
const l = zeroPadding(ctx.allocator, lhs) catch return false;
|
||||
defer ctx.allocator.free(l);
|
||||
const r = zeroPadding(ctx.allocator, rhs) catch return false;
|
||||
defer ctx.allocator.free(r);
|
||||
return mem.lessThan(u8, l, r);
|
||||
}
|
||||
}.lessThan);
|
||||
|
||||
for (s5.items) |s| {
|
||||
try stdout.print("{s}\n", .{s});
|
||||
}
|
||||
|
||||
var s6 = ArrayList([]const u8).init(allocator);
|
||||
defer s6.deinit();
|
||||
try s6.append("The Wind in the Willows");
|
||||
try s6.append("The 40th step more");
|
||||
try s6.append("The 39 steps");
|
||||
try s6.append("Wanda");
|
||||
|
||||
try stdout.print("\n", .{});
|
||||
|
||||
// Sort using removeTitle
|
||||
const ctx6 = SortContext{ .allocator = allocator };
|
||||
std.sort.insertion([]const u8, s6.items, ctx6, struct {
|
||||
fn lessThan(ctx: SortContext, lhs: []const u8, rhs: []const u8) bool {
|
||||
const l = removeTitle(ctx.allocator, lhs) catch return false;
|
||||
defer ctx.allocator.free(l);
|
||||
const r = removeTitle(ctx.allocator, rhs) catch return false;
|
||||
defer ctx.allocator.free(r);
|
||||
return mem.lessThan(u8, l, r);
|
||||
}
|
||||
}.lessThan);
|
||||
|
||||
for (s6.items) |s| {
|
||||
try stdout.print("{s}\n", .{s});
|
||||
}
|
||||
|
||||
var s7 = ArrayList([]const u8).init(allocator);
|
||||
defer s7.deinit();
|
||||
try s7.append("Equiv. ý accents: 2-2");
|
||||
try s7.append("Equiv. Ý accents: 2-1");
|
||||
try s7.append("Equiv. y accents: 2+0");
|
||||
try s7.append("Equiv. Y accents: 2+1");
|
||||
|
||||
try stdout.print("\n", .{});
|
||||
|
||||
// Sort using replaceAccents
|
||||
const ctx7 = SortContext{ .allocator = allocator };
|
||||
std.sort.insertion([]const u8, s7.items, ctx7, struct {
|
||||
fn lessThan(ctx: SortContext, lhs: []const u8, rhs: []const u8) bool {
|
||||
const l = replaceAccents(ctx.allocator, lhs) catch return false;
|
||||
defer ctx.allocator.free(l);
|
||||
const r = replaceAccents(ctx.allocator, rhs) catch return false;
|
||||
defer ctx.allocator.free(r);
|
||||
return mem.lessThan(u8, l, r);
|
||||
}
|
||||
}.lessThan);
|
||||
|
||||
for (s7.items) |s| {
|
||||
try stdout.print("{s}\n", .{s});
|
||||
}
|
||||
|
||||
var s8 = ArrayList([]const u8).init(allocator);
|
||||
defer s8.deinit();
|
||||
try s8.append("IJ ligatured ij");
|
||||
try s8.append("no ligature");
|
||||
|
||||
try stdout.print("\n", .{});
|
||||
|
||||
// Sort using replaceLigatures
|
||||
const ctx8 = SortContext{ .allocator = allocator };
|
||||
std.sort.insertion([]const u8, s8.items, ctx8, struct {
|
||||
fn lessThan(ctx: SortContext, lhs: []const u8, rhs: []const u8) bool {
|
||||
const l = replaceLigatures(ctx.allocator, lhs) catch return false;
|
||||
defer ctx.allocator.free(l);
|
||||
const r = replaceLigatures(ctx.allocator, rhs) catch return false;
|
||||
defer ctx.allocator.free(r);
|
||||
return mem.lessThan(u8, l, r);
|
||||
}
|
||||
}.lessThan);
|
||||
|
||||
for (s8.items) |s| {
|
||||
try stdout.print("{s}\n", .{s});
|
||||
}
|
||||
|
||||
var s9 = ArrayList([]const u8).init(allocator);
|
||||
defer s9.deinit();
|
||||
try s9.append("Start with an ʒ: 2-2");
|
||||
try s9.append("Start with an ſ: 2-1");
|
||||
try s9.append("Start with an ß: 2+0");
|
||||
try s9.append("Start with an s: 2+1");
|
||||
|
||||
try stdout.print("\n", .{});
|
||||
|
||||
// Sort using replaceCharacters
|
||||
const ctx9 = SortContext{ .allocator = allocator };
|
||||
std.sort.insertion([]const u8, s9.items, ctx9, struct {
|
||||
fn lessThan(ctx: SortContext, lhs: []const u8, rhs: []const u8) bool {
|
||||
const l = replaceCharacters(ctx.allocator, lhs) catch return false;
|
||||
defer ctx.allocator.free(l);
|
||||
const r = replaceCharacters(ctx.allocator, rhs) catch return false;
|
||||
defer ctx.allocator.free(r);
|
||||
return mem.lessThan(u8, l, r);
|
||||
}
|
||||
}.lessThan);
|
||||
|
||||
for (s9.items) |s| {
|
||||
try stdout.print("{s}\n", .{s});
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue