From aad97d03fd54e2fb2b9a97a5fe803ce2fc34a469 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 28 Feb 2024 19:54:50 -0500 Subject: [PATCH] Port NaturalSort classes from SabreTools --- NaturalComparer.cs | 103 +++++++++++++++++++++++++++++++++++++ NaturalComparerUtil.cs | 78 ++++++++++++++++++++++++++++ NaturalReversedComparer.cs | 103 +++++++++++++++++++++++++++++++++++++ 3 files changed, 284 insertions(+) create mode 100644 NaturalComparer.cs create mode 100644 NaturalComparerUtil.cs create mode 100644 NaturalReversedComparer.cs diff --git a/NaturalComparer.cs b/NaturalComparer.cs new file mode 100644 index 0000000..eff784f --- /dev/null +++ b/NaturalComparer.cs @@ -0,0 +1,103 @@ +/* + * + * Links for info and original source code: + * + * https://blog.codinghorror.com/sorting-for-humans-natural-sort-order/ + * http://www.codeproject.com/Articles/22517/Natural-Sort-Comparer + * + * Exact code implementation used with permission, originally by motoschifo + * + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace SabreTools.Matching +{ + public class NaturalComparer : Comparer, IDisposable + { + private readonly Dictionary table; + + public NaturalComparer() + { + table = []; + } + + public void Dispose() + { + table.Clear(); + } + + public override int Compare(string? x, string? y) + { + if (x == null || y == null) + { + if (x == null && y != null) + return -1; + else if (x != null && y == null) + return 1; + else + return 0; + } + if (x.ToLowerInvariant() == y.ToLowerInvariant()) + { + return x.CompareTo(y); + } + if (!table.TryGetValue(x, out string[]? x1)) + { + //x1 = Regex.Split(x.Replace(" ", string.Empty), "([0-9]+)"); + x1 = Regex.Split(x.ToLowerInvariant(), "([0-9]+)").Where(s => !string.IsNullOrEmpty(s)).ToArray(); + table.Add(x, x1); + } + if (!table.TryGetValue(y, out string[]? y1)) + { + //y1 = Regex.Split(y.Replace(" ", string.Empty), "([0-9]+)"); + y1 = Regex.Split(y.ToLowerInvariant(), "([0-9]+)").Where(s => !string.IsNullOrEmpty(s)).ToArray(); + table.Add(y, y1); + } + + for (int i = 0; i < x1.Length && i < y1.Length; i++) + { + if (x1[i] != y1[i]) + { + return PartCompare(x1[i], y1[i]); + } + } + if (y1.Length > x1.Length) + { + return 1; + } + else if (x1.Length > y1.Length) + { + return -1; + } + else + { + return x.CompareTo(y); + } + } + + private static int PartCompare(string left, string right) + { + if (!long.TryParse(left, out long x)) + { + return NaturalComparerUtil.CompareNumeric(left, right); + } + + if (!long.TryParse(right, out long y)) + { + return NaturalComparerUtil.CompareNumeric(left, right); + } + + // If we have an equal part, then make sure that "longer" ones are taken into account + if (x.CompareTo(y) == 0) + { + return left.Length - right.Length; + } + + return x.CompareTo(y); + } + } +} diff --git a/NaturalComparerUtil.cs b/NaturalComparerUtil.cs new file mode 100644 index 0000000..90779bc --- /dev/null +++ b/NaturalComparerUtil.cs @@ -0,0 +1,78 @@ +using System.IO; + +namespace SabreTools.Matching +{ + public static class NaturalComparerUtil + { + public static int CompareNumeric(string s1, string s2) + { + // Save the orginal strings, for later comparison + string s1orig = s1; + string s2orig = s2; + + // We want to normalize the strings, so we set both to lower case + s1 = s1.ToLowerInvariant(); + s2 = s2.ToLowerInvariant(); + + // If the strings are the same exactly, return + if (s1 == s2) + return s1orig.CompareTo(s2orig); + + // If one is null, then say that's less than + if (s1 == null) + return -1; + if (s2 == null) + return 1; + + // Now split into path parts after converting AltDirSeparator to DirSeparator + s1 = s1.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + s2 = s2.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + string[] s1parts = s1.Split(Path.DirectorySeparatorChar); + string[] s2parts = s2.Split(Path.DirectorySeparatorChar); + + // Then compare each part in turn + for (int j = 0; j < s1parts.Length && j < s2parts.Length; j++) + { + int compared = CompareNumericPart(s1parts[j], s2parts[j]); + if (compared != 0) + return compared; + } + + // If we got out here, then it looped through at least one of the strings + if (s1parts.Length > s2parts.Length) + return 1; + if (s1parts.Length < s2parts.Length) + return -1; + + return s1orig.CompareTo(s2orig); + } + + private static int CompareNumericPart(string s1, string s2) + { + // Otherwise, loop through until we have an answer + for (int i = 0; i < s1.Length && i < s2.Length; i++) + { + int s1c = s1[i]; + int s2c = s2[i]; + + // If the characters are the same, continue + if (s1c == s2c) + continue; + + // If they're different, check which one was larger + if (s1c > s2c) + return 1; + if (s1c < s2c) + return -1; + } + + // If we got out here, then it looped through at least one of the strings + if (s1.Length > s2.Length) + return 1; + if (s1.Length < s2.Length) + return -1; + + return 0; + } + } +} diff --git a/NaturalReversedComparer.cs b/NaturalReversedComparer.cs new file mode 100644 index 0000000..63febbb --- /dev/null +++ b/NaturalReversedComparer.cs @@ -0,0 +1,103 @@ +/* + * + * Links for info and original source code: + * + * https://blog.codinghorror.com/sorting-for-humans-natural-sort-order/ + * http://www.codeproject.com/Articles/22517/Natural-Sort-Comparer + * + * Exact code implementation used with permission, originally by motoschifo + * + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace SabreTools.Matching +{ + public class NaturalReversedComparer : Comparer, IDisposable + { + private readonly Dictionary table; + + public NaturalReversedComparer() + { + table = []; + } + + public void Dispose() + { + table.Clear(); + } + + public override int Compare(string? x, string? y) + { + if (x == null || y == null) + { + if (x == null && y != null) + return -1; + else if (x != null && y == null) + return 1; + else + return 0; + } + if (y.ToLowerInvariant() == x.ToLowerInvariant()) + { + return y.CompareTo(x); + } + if (!table.TryGetValue(x, out string[]? x1)) + { + //x1 = Regex.Split(x.Replace(" ", string.Empty), "([0-9]+)"); + x1 = Regex.Split(x.ToLowerInvariant(), "([0-9]+)").Where(s => !string.IsNullOrEmpty(s)).ToArray(); + table.Add(x, x1); + } + if (!table.TryGetValue(y, out string[]? y1)) + { + //y1 = Regex.Split(y.Replace(" ", string.Empty), "([0-9]+)"); + y1 = Regex.Split(y.ToLowerInvariant(), "([0-9]+)").Where(s => !string.IsNullOrEmpty(s)).ToArray(); + table.Add(y, y1); + } + + for (int i = 0; i < x1.Length && i < y1.Length; i++) + { + if (x1[i] != y1[i]) + { + return PartCompare(x1[i], y1[i]); + } + } + if (y1.Length > x1.Length) + { + return 1; + } + else if (x1.Length > y1.Length) + { + return -1; + } + else + { + return y.CompareTo(x); + } + } + + private static int PartCompare(string left, string right) + { + if (!long.TryParse(left, out long x)) + { + return NaturalComparerUtil.CompareNumeric(right, left); + } + + if (!long.TryParse(right, out long y)) + { + return NaturalComparerUtil.CompareNumeric(right, left); + } + + // If we have an equal part, then make sure that "longer" ones are taken into account + if (y.CompareTo(x) == 0) + { + return right.Length - left.Length; + } + + return y.CompareTo(x); + } + } +}