{"id":3352,"date":"2020-02-05T13:56:09","date_gmt":"2020-02-05T12:56:09","guid":{"rendered":"https:\/\/solidt.eu\/site\/?p=3352"},"modified":"2024-11-21T14:36:12","modified_gmt":"2024-11-21T13:36:12","slug":"c-simple-natural-sort-with-max-digits","status":"publish","type":"post","link":"https:\/\/solidt.eu\/site\/c-simple-natural-sort-with-max-digits\/","title":{"rendered":"C# simple natural sort (with max digits)"},"content":{"rendered":"\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">void Main()\n{\n\tvar list = new List&lt;string>() {\n\t\t\"AB1235\",\n\t\t\"AB9999\",\n\t\t\"AB99\",\n\t\t\"AB999\",\n\t\t\"AB12\",\n\t\t\"AB123\",\n\t\t\"14\",\n\t\t\"12\",\n\t\t\"123\",\n\t};\n\tvar ordered = list.OrderBy(l => PrependDigits(l));\n\tforeach(var item in ordered)\n\t\tConsole.WriteLine(item);\n}\n\npublic static Regex regexPrependDigits = new Regex(@\"\\d+\", RegexOptions.Compiled);\npublic static string PrependDigits(string s) \n{\n    if (string.IsNullOrWhiteSpace(s))\n        return s;\n    return regexPrependDigits.Replace(s, m => m.Value.PadLeft(10, '0'));\n}<\/pre>\n\n\n\n<p>Output:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">12\n14\n123\nAB12\nAB99\nAB123\nAB999\nAB1235\nAB9999<\/pre>\n\n\n\n<p>Typescript prependdigits version<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"typescript\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">function prependDigits(s: string, len: number = 10): string\n{\n    return String(s).replace(\/\\d+\/, (m) => m.padStart(len, \"0\"))\n}<\/pre>\n\n\n\n<p>For a more advanced version:<br><a href=\"https:\/\/stackoverflow.com\/a\/78874525\/1052129\">https:\/\/stackoverflow.com\/a\/78874525\/1052129<\/a><\/p>\n\n\n\n<div style=\"height: 250px; position:relative; margin-bottom: 50px;\" class=\"wp-block-simple-code-block-ace\"><pre class=\"wp-block-simple-code-block-ace\" style=\"position:absolute;top:0;right:0;bottom:0;left:0\" data-mode=\"csharp\" data-theme=\"monokai\" data-fontsize=\"14\" data-lines=\"Infinity\" data-showlines=\"true\" data-copy=\"false\">using System;\nusing System.Collections.Generic;\nusing System.Globalization;\n\npublic static class SpanExtensions\n{\n    private static ReadOnlySpan&lt;char> TrimStart(this ReadOnlySpan&lt;char> span, char trimChar)\n    {\n        int start = 0;\n        while (start &lt; span.Length &amp;&amp; span[start] == trimChar)\n            start++;\n    \n        return span.Slice(start);\n    }\n}\n\npublic class NaturalSort\n{\n    public static int CompareStrings(CultureInfo culture, CompareOptions options, string x, string y)\n    {\n        if (x == null || y == null) return string.Compare(x, y, StringComparison.Ordinal);\n\n        var compareInfo = culture.CompareInfo;\n        int xPos = 0, yPos = 0;\n\n        while (xPos &lt; x.Length &amp;&amp; yPos &lt; y.Length)\n        {\n            bool xIsDigit = char.IsDigit(x[xPos]);\n            bool yIsDigit = char.IsDigit(y[yPos]);\n\n            if (xIsDigit &amp;&amp; yIsDigit)\n            {\n                int numberComparison = CompareNumbers(x, ref xPos, y, ref yPos);\n                if (numberComparison != 0) return numberComparison;\n            }\n            else\n            {\n                int charComparison = compareInfo.Compare(x, xPos, 1, y, yPos, 1, options);\n                if (charComparison != 0) return charComparison;\n                xPos++;\n                yPos++;\n            }\n        }\n\n        \/\/ Handle remaining characters\n        if (xPos &lt; x.Length) return 1;\n        if (yPos &lt; y.Length) return -1;\n\n        return 0;\n    }\n    \n    private static int CompareNumbers(string x, ref int xPos, string y, ref int yPos)\n    {\n        int xStart = xPos, yStart = yPos;\n    \n        while (xPos &lt; x.Length &amp;&amp; char.IsDigit(x[xPos])) xPos++;\n        while (yPos &lt; y.Length &amp;&amp; char.IsDigit(y[yPos])) yPos++;\n    \n        var xSpan = x.AsSpan(xStart, xPos - xStart).TrimStart('0');\n        var ySpan = y.AsSpan(yStart, yPos - yStart).TrimStart('0');\n    \n        if (xSpan.Length != ySpan.Length)\n            return xSpan.Length.CompareTo(ySpan.Length);\n    \n        for (int i = 0; i &lt; xSpan.Length; i++)\n        {\n            if (xSpan[i] != ySpan[i])\n                return xSpan[i].CompareTo(ySpan[i]);\n        }\n    \n        return 0;\n    }\n}\n\n\npublic class NaturalSortComparer : IComparer&lt;string>\n{\n    public static NaturalSortComparer Ordinal { get; } = new NaturalSortComparer(CultureInfo.InvariantCulture, CompareOptions.Ordinal);\n    public static NaturalSortComparer OrdinalIgnoreCase { get; } = new NaturalSortComparer(CultureInfo.InvariantCulture, CompareOptions.OrdinalIgnoreCase);\n    public static NaturalSortComparer CurrentCulture { get; } = new NaturalSortComparer(CultureInfo.CurrentCulture, CompareOptions.None);\n    public static NaturalSortComparer CurrentCultureIgnoreCase { get; } = new NaturalSortComparer(CultureInfo.CurrentCulture, CompareOptions.IgnoreCase);\n    public static NaturalSortComparer InvariantCulture { get; } = new NaturalSortComparer(CultureInfo.InvariantCulture, CompareOptions.None);\n    public static NaturalSortComparer InvariantCultureIgnoreCase { get; } = new NaturalSortComparer(CultureInfo.InvariantCulture, CompareOptions.IgnoreCase);\n\n    public NaturalSortComparer(CultureInfo culture, CompareOptions options)\n    {\n        Culture = culture;\n        Options = options;\n    }\n\n    public CultureInfo Culture { get; }\n    public CompareOptions Options { get; }\n\n    public int Compare(string x, string y)\n    {\n        return NaturalSort.CompareStrings(Culture, Options, x, y);\n    }\n}\n<\/pre><\/div>\n\n\n\n<p>Example usage<\/p>\n\n\n\n<div style=\"height: 250px; position:relative; margin-bottom: 50px;\" class=\"wp-block-simple-code-block-ace\"><pre class=\"wp-block-simple-code-block-ace\" style=\"position:absolute;top:0;right:0;bottom:0;left:0\" data-mode=\"csharp\" data-theme=\"monokai\" data-fontsize=\"14\" data-lines=\"Infinity\" data-showlines=\"true\" data-copy=\"false\">using System;\nusing System.Collections.Generic;\nusing System.Globalization;\nusing System.Linq;\n\nclass Program\n{\n    static void Main()\n    {\n        \/\/ Voorbeeldlijst met strings\n        var items = new List&lt;string>\n        {\n            \"file10.txt\",\n            \"file2.txt\",\n            \"file1.txt\",\n            \"file20.txt\",\n            \"file3.txt\"\n        };\n\n        Console.WriteLine(\"Oorspronkelijke lijst:\");\n        foreach (var item in items)\n        {\n            Console.WriteLine(item);\n        }\n\n        \/\/ Sorteer met NaturalSortComparer\n        Console.WriteLine(\"\\nGesorteerd (Natural Sort):\");\n        var sorted = items.OrderBy(x => x, NaturalSortComparer.InvariantCulture);\n        foreach (var item in sorted)\n        {\n            Console.WriteLine(item);\n        }\n\n        \/\/ Sorteer omgekeerd met NaturalSortComparer\n        Console.WriteLine(\"\\nOmgekeerd gesorteerd (Natural Sort):\");\n        var reverseSorted = items.OrderByDescending(x => x, NaturalSortComparer.InvariantCulture);\n        foreach (var item in reverseSorted)\n        {\n            Console.WriteLine(item);\n        }\n    }\n}\n<\/pre><\/div>\n\n\n\n<p>Typescript<\/p>\n\n\n\n<div style=\"height: 250px; position:relative; margin-bottom: 50px;\" class=\"wp-block-simple-code-block-ace\"><pre class=\"wp-block-simple-code-block-ace\" style=\"position:absolute;top:0;right:0;bottom:0;left:0\" data-mode=\"typescript\" data-theme=\"monokai\" data-fontsize=\"14\" data-lines=\"Infinity\" data-showlines=\"true\" data-copy=\"false\">function compareStrings(x: string, y: string): number {\n    if (x === null || y === null) return x === y ? 0 : x === null ? -1 : 1;\n\n    let xPos = 0, yPos = 0;\n\n    while (xPos &lt; x.length &amp;&amp; yPos &lt; y.length) {\n        const xIsDigit = isDigit(x[xPos]);\n        const yIsDigit = isDigit(y[yPos]);\n\n        if (xIsDigit &amp;&amp; yIsDigit) {\n            const numberComparison = compareNumbers(x, xPos, y, yPos);\n            if (numberComparison.result !== 0) return numberComparison.result;\n            xPos = numberComparison.xPos;\n            yPos = numberComparison.yPos;\n        } else {\n            const charComparison = x[xPos].localeCompare(y[yPos]);\n            if (charComparison !== 0) return charComparison;\n            xPos++;\n            yPos++;\n        }\n    }\n\n    \/\/ Handle remaining characters\n    if (xPos &lt; x.length) return 1;\n    if (yPos &lt; y.length) return -1;\n\n    return 0;\n}\n\nfunction compareNumbers(\n    x: string,\n    xStart: number,\n    y: string,\n    yStart: number\n): { result: number; xPos: number; yPos: number } {\n    let xPos = xStart, yPos = yStart;\n\n    \/\/ Find the end of the digit sequences\n    while (xPos &lt; x.length &amp;&amp; isDigit(x[xPos])) xPos++;\n    while (yPos &lt; y.length &amp;&amp; isDigit(y[yPos])) yPos++;\n\n    \/\/ Extract number spans\n    const xNumber = x.slice(xStart, xPos).replace(\/^0+\/, '');\n    const yNumber = y.slice(yStart, yPos).replace(\/^0+\/, '');\n\n    \/\/ Compare lengths first (natural sort behavior)\n    if (xNumber.length !== yNumber.length) {\n        return { result: xNumber.length - yNumber.length, xPos, yPos };\n    }\n\n    \/\/ Compare digit by digit\n    const result = xNumber.localeCompare(yNumber);\n    return { result, xPos, yPos };\n}\n\nfunction isDigit(char: string): boolean {\n    return \/\\d\/.test(char);\n}\n<\/pre><\/div>\n\n\n\n<p>Example usage<\/p>\n\n\n\n<div style=\"height: 250px; position:relative; margin-bottom: 50px;\" class=\"wp-block-simple-code-block-ace\"><pre class=\"wp-block-simple-code-block-ace\" style=\"position:absolute;top:0;right:0;bottom:0;left:0\" data-mode=\"typescript\" data-theme=\"monokai\" data-fontsize=\"14\" data-lines=\"Infinity\" data-showlines=\"true\" data-copy=\"false\">const items = [\"file10.txt\", \"file2.txt\", \"file1.txt\", \"file20.txt\", \"file3.txt\"];\n\nconsole.log(\"Original list:\");\nconsole.log(items);\n\n\/\/ Natural sort\nconst sorted = items.sort(compareStrings);\n\nconsole.log(\"\\nSorted list (Natural Sort):\");\nconsole.log(sorted);\n\n\/\/ Reversed sort\nconst reverseSorted = items.sort((a, b) => compareStrings(b, a));\n\nconsole.log(\"\\nReverse sorted list:\");\nconsole.log(reverseSorted);\n<\/pre><\/div>\n","protected":false},"excerpt":{"rendered":"<p>Output: Typescript prependdigits version For a more advanced version:https:\/\/stackoverflow.com\/a\/78874525\/1052129 Example usage Typescript Example usage<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"footnotes":""},"categories":[6,4,1],"tags":[],"class_list":["post-3352","post","type-post","status-publish","format-standard","hentry","category-dotnet","category-programming","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts\/3352","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/comments?post=3352"}],"version-history":[{"count":9,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts\/3352\/revisions"}],"predecessor-version":[{"id":9093,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts\/3352\/revisions\/9093"}],"wp:attachment":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/media?parent=3352"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/categories?post=3352"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/tags?post=3352"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}