Ordre de sorting naturel en C #

Quelqu’un at-il une bonne ressource ou fournit-il un exemple de sorting d’ordre naturel en C # pour un tableau FileInfo ? IComparer interface IComparer dans mes sortes.

Le plus simple est de faire appel à la fonction intégrée de Windows et de l’utiliser comme fonction de comparaison dans votre IComparer :

 [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)] private static extern int StrCmpLogicalW(ssortingng psz1, ssortingng psz2); 

Michael Kaplan a quelques exemples de la façon dont cette fonction fonctionne ici et des modifications apscopes pour que Vista fonctionne de manière plus intuitive. L’avantage de cette fonction est qu’elle aura le même comportement que la version de Windows sur laquelle elle s’exécute, mais cela signifie qu’elle diffère d’une version à l’autre de Windows. Vous devez donc déterminer si cela vous pose un problème.

Donc, une implémentation complète serait quelque chose comme:

 [SuppressUnmanagedCodeSecurity] internal static class SafeNativeMethods { [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)] public static extern int StrCmpLogicalW(ssortingng psz1, ssortingng psz2); } public sealed class NaturalSsortingngComparer : IComparer { public int Compare(ssortingng a, ssortingng b) { return SafeNativeMethods.StrCmpLogicalW(a, b); } } public sealed class NaturalFileInfoNameComparer : IComparer { public int Compare(FileInfo a, FileInfo b) { return SafeNativeMethods.StrCmpLogicalW(a.Name, b.Name); } } 

Je pensais juste append à cela (avec la solution la plus concise que je puisse trouver):

 public static IOrderedEnumerable OrderByAlphaNumeric(this IEnumerable source, Func selector) { int max = source .SelectMany(i => Regex.Matches(selector(i), @"\d+").Cast().Select(m => (int?)m.Value.Length)) .Max() ?? 0; return source.OrderBy(i => Regex.Replace(selector(i), @"\d+", m => m.Value.PadLeft(max, '0'))); } 

Ce qui précède compresse les nombres de la chaîne à la longueur maximale de tous les nombres de toutes les chaînes et utilise la chaîne résultante pour sortinger.

Le cast à ( int? ) Est destiné à permettre des collections de chaînes sans aucun nombre ( .Max() sur un .Max() vide renvoie une InvalidOperationException ).

Aucune des implémentations existantes ne semblait géniale, alors j’ai écrit la mienne. Les résultats sont presque identiques au sorting utilisé par les versions modernes de Windows Explorer (Windows 7/8). Les seules différences que j’ai vues sont 1) bien que Windows utilisé pour (par exemple, XP) manipule des nombres de toute longueur, il est maintenant limité à 19 chiffres – le mien est illimité, 2) Windows donne des résultats incohérents avec certains ensembles de chiffres Unicode bien (bien qu’il ne compare pas numériquement les chiffres des paires de substitution; Windows ne le fait pas non plus) et 3) le mien ne peut pas distinguer différents types de poids de sorting non primaires s’ils se trouvent dans des sections différentes (par exemple “e-1é” vs) é1e- “- les sections avant et après le nombre ont des différences de poids diacritiques et de ponctuation).

 public static int CompareNatural(ssortingng strA, ssortingng strB) { return CompareNatural(strA, strB, CultureInfo.CurrentCulture, CompareOptions.IgnoreCase); } public static int CompareNatural(ssortingng strA, ssortingng strB, CultureInfo culture, CompareOptions options) { CompareInfo cmp = culture.CompareInfo; int iA = 0; int iB = 0; int softResult = 0; int softResultWeight = 0; while (iA < strA.Length && iB < strB.Length) { bool isDigitA = Char.IsDigit(strA[iA]); bool isDigitB = Char.IsDigit(strB[iB]); if (isDigitA != isDigitB) { return cmp.Compare(strA, iA, strB, iB, options); } else if (!isDigitA && !isDigitB) { int jA = iA + 1; int jB = iB + 1; while (jA < strA.Length && !Char.IsDigit(strA[jA])) jA++; while (jB < strB.Length && !Char.IsDigit(strB[jB])) jB++; int cmpResult = cmp.Compare(strA, iA, jA - iA, strB, iB, jB - iB, options); if (cmpResult != 0) { // Certain strings may be considered different due to "soft" differences that are // ignored if more significant differences follow, eg a hyphen only affects the // comparison if no other differences follow string sectionA = strA.Substring(iA, jA - iA); string sectionB = strB.Substring(iB, jB - iB); if (cmp.Compare(sectionA + "1", sectionB + "2", options) == cmp.Compare(sectionA + "2", sectionB + "1", options)) { return cmp.Compare(strA, iA, strB, iB, options); } else if (softResultWeight < 1) { softResult = cmpResult; softResultWeight = 1; } } iA = jA; iB = jB; } else { char zeroA = (char)(strA[iA] - (int)Char.GetNumericValue(strA[iA])); char zeroB = (char)(strB[iB] - (int)Char.GetNumericValue(strB[iB])); int jA = iA; int jB = iB; while (jA < strA.Length && strA[jA] == zeroA) jA++; while (jB < strB.Length && strB[jB] == zeroB) jB++; int resultIfSameLength = 0; do { isDigitA = jA < strA.Length && Char.IsDigit(strA[jA]); isDigitB = jB < strB.Length && Char.IsDigit(strB[jB]); int numA = isDigitA ? (int)Char.GetNumericValue(strA[jA]) : 0; int numB = isDigitB ? (int)Char.GetNumericValue(strB[jB]) : 0; if (isDigitA && (char)(strA[jA] - numA) != zeroA) isDigitA = false; if (isDigitB && (char)(strB[jB] - numB) != zeroB) isDigitB = false; if (isDigitA && isDigitB) { if (numA != numB && resultIfSameLength == 0) { resultIfSameLength = numA < numB ? -1 : 1; } jA++; jB++; } } while (isDigitA && isDigitB); if (isDigitA != isDigitB) { // One number has more digits than the other (ignoring leading zeros) - the longer // number must be larger return isDigitA ? 1 : -1; } else if (resultIfSameLength != 0) { // Both numbers are the same length (ignoring leading zeros) and at least one of // the digits differed - the first difference determines the result return resultIfSameLength; } int lA = jA - iA; int lB = jB - iB; if (lA != lB) { // Both numbers are equivalent but one has more leading zeros return lA > lB ? -1 : 1; } else if (zeroA != zeroB && softResultWeight < 2) { softResult = cmp.Compare(strA, iA, 1, strB, iB, 1, options); softResultWeight = 2; } iA = jA; iB = jB; } } if (iA < strA.Length || iB < strB.Length) { return iA < strA.Length ? 1 : -1; } else if (softResult != 0) { return softResult; } return 0; } 

La signature correspond au délégué Comparison :

 ssortingng[] files = Directory.GetFiles(@"C:\"); Array.Sort(files, CompareNatural); 

Voici une classe d'encapsuleur à utiliser comme IComparer :

 public class CustomComparer : IComparer { private Comparison _comparison; public CustomComparer(Comparison comparison) { _comparison = comparison; } public int Compare(T x, T y) { return _comparison(x, y); } } 

Exemple:

 ssortingng[] files = Directory.EnumerateFiles(@"C:\") .OrderBy(f => f, new CustomComparer(CompareNatural)) .ToArray(); 

Voici un bon jeu de noms de fichiers que j'utilise pour tester:

 Func expand = (s) => { int o; while ((o = s.IndexOf('\\')) != -1) { int p = o + 1; int z = 1; while (s[p] == '0') { z++; p++; } int c = Int32.Parse(s.Subssortingng(p, z)); s = s.Subssortingng(0, o) + new ssortingng(s[o - 1], c) + s.Subssortingng(p + z); } return s; }; ssortingng encodedFileNames = "KDEqLW4xMiotbjEzKjAwMDFcMDY2KjAwMlwwMTcqMDA5XDAxNyowMlwwMTcqMDlcMDE3KjEhKjEtISox" + "LWEqMS4yNT8xLjI1KjEuNT8xLjUqMSoxXDAxNyoxXDAxOCoxXDAxOSoxXDA2NioxXDA2NyoxYSoyXDAx" + "NyoyXDAxOCo5XDAxNyo5XDAxOCo5XDA2Nio9MSphMDAxdGVzdDAxKmEwMDF0ZXN0aW5nYTBcMzEqYTAw" + "Mj9hMDAyIGE/YTAwMiBhKmEwMDIqYTAwMmE/YTAwMmEqYTAxdGVzdGluZ2EwMDEqYTAxdnNmcyphMSph" + "MWEqYTF6KmEyKmIwMDAzcTYqYjAwM3E0KmIwM3E1KmMtZSpjZCpjZipmIDEqZipnP2cgMT9oLW4qaG8t" + "bipJKmljZS1jcmVhbT9pY2VjcmVhbT9pY2VjcmVhbS0/ajBcNDE/ajAwMWE/ajAxP2shKmsnKmstKmsx" + "KmthKmxpc3QqbTAwMDNhMDA1YSptMDAzYTAwMDVhKm0wMDNhMDA1Km0wMDNhMDA1YSpuMTIqbjEzKm8t" + "bjAxMypvLW4xMipvLW40P28tbjQhP28tbjR6P28tbjlhLWI1Km8tbjlhYjUqb24wMTMqb24xMipvbjQ/" + "b240IT9vbjR6P29uOWEtYjUqb245YWI1Km/CrW4wMTMqb8KtbjEyKnAwMCpwMDEqcDAxwr0hKnAwMcK9" + "KnAwMcK9YSpwMDHCvcK+KnAwMipwMMK9KnEtbjAxMypxLW4xMipxbjAxMypxbjEyKnItMDAhKnItMDAh" + "NSpyLTAwIe+8lSpyLTAwYSpyLe+8kFwxIS01KnIt77yQXDEhLe+8lSpyLe+8kFwxISpyLe+8kFwxITUq" + "ci3vvJBcMSHvvJUqci3vvJBcMWEqci3vvJBcMyE1KnIwMCEqcjAwLTUqcjAwLjUqcjAwNSpyMDBhKnIw" + "NSpyMDYqcjQqcjUqctmg2aYqctmkKnLZpSpy27Dbtipy27Qqctu1KnLfgN+GKnLfhCpy34UqcuClpuCl" + "rCpy4KWqKnLgpasqcuCnpuCnrCpy4KeqKnLgp6sqcuCppuCprCpy4KmqKnLgqasqcuCrpuCrrCpy4Kuq" + "KnLgq6sqcuCtpuCtrCpy4K2qKnLgrasqcuCvpuCvrCpy4K+qKnLgr6sqcuCxpuCxrCpy4LGqKnLgsasq" + "cuCzpuCzrCpy4LOqKnLgs6sqcuC1puC1rCpy4LWqKnLgtasqcuC5kOC5lipy4LmUKnLguZUqcuC7kOC7" + "lipy4LuUKnLgu5UqcuC8oOC8pipy4LykKnLgvKUqcuGBgOGBhipy4YGEKnLhgYUqcuGCkOGClipy4YKU" + "KnLhgpUqcuGfoOGfpipy4Z+kKnLhn6UqcuGgkOGglipy4aCUKnLhoJUqcuGlhuGljCpy4aWKKnLhpYsq" + "cuGnkOGnlipy4aeUKnLhp5UqcuGtkOGtlipy4a2UKnLhrZUqcuGusOGutipy4a60KnLhrrUqcuGxgOGx" + "hipy4bGEKnLhsYUqcuGxkOGxlipy4bGUKnLhsZUqcuqYoFwx6pilKnLqmKDqmKUqcuqYoOqYpipy6pik" + "KnLqmKUqcuqjkOqjlipy6qOUKnLqo5UqcuqkgOqkhipy6qSEKnLqpIUqcuqpkOqplipy6qmUKnLqqZUq" + "cvCQkqAqcvCQkqUqcvCdn5gqcvCdn50qcu+8kFwxISpy77yQXDEt77yVKnLvvJBcMS7vvJUqcu+8kFwx" + "YSpy77yQXDHqmKUqcu+8kFwx77yO77yVKnLvvJBcMe+8lSpy77yQ77yVKnLvvJDvvJYqcu+8lCpy77yV" + "KnNpKnPEsSp0ZXN02aIqdGVzdNmi2aAqdGVzdNmjKnVBZS0qdWFlKnViZS0qdUJlKnVjZS0xw6kqdWNl" + "McOpLSp1Y2Uxw6kqdWPDqS0xZSp1Y8OpMWUtKnVjw6kxZSp3ZWlhMSp3ZWlhMip3ZWlzczEqd2Vpc3My" + "KndlaXoxKndlaXoyKndlacOfMSp3ZWnDnzIqeSBhMyp5IGE0KnknYTMqeSdhNCp5K2EzKnkrYTQqeS1h" + "Myp5LWE0KnlhMyp5YTQqej96IDA1MD96IDIxP3ohMjE/ejIwP3oyMj96YTIxP3rCqTIxP1sxKl8xKsKt" + "bjEyKsKtbjEzKsSwKg=="; ssortingng[] fileNames = Encoding.UTF8.GetSsortingng(Convert.FromBase64Ssortingng(encodedFileNames)) .Replace("*", ".txt?").Split(new[] { "?" }, SsortingngSplitOptions.RemoveEmptyEnsortinges) .Select(n => expand(n)).ToArray(); 

Solution C # pure pour linq orderby:

http://zootfroot.blogspot.com/2009/09/natural-sort-compare-with-linq-orderby.html

 public class NaturalSortComparer : IComparer, IDisposable { private bool isAscending; public NaturalSortComparer(bool inAscendingOrder = true) { this.isAscending = inAscendingOrder; } #region IComparer Members public int Compare(ssortingng x, ssortingng y) { throw new NotImplementedException(); } #endregion #region IComparer Members int IComparer.Compare(ssortingng x, ssortingng y) { if (x == y) return 0; ssortingng[] x1, y1; if (!table.TryGetValue(x, out x1)) { x1 = Regex.Split(x.Replace(" ", ""), "([0-9]+)"); table.Add(x, x1); } if (!table.TryGetValue(y, out y1)) { y1 = Regex.Split(y.Replace(" ", ""), "([0-9]+)"); table.Add(y, y1); } int returnVal; for (int i = 0; i < x1.Length && i < y1.Length; i++) { if (x1[i] != y1[i]) { returnVal = PartCompare(x1[i], y1[i]); return isAscending ? returnVal : -returnVal; } } if (y1.Length > x1.Length) { returnVal = 1; } else if (x1.Length > y1.Length) { returnVal = -1; } else { returnVal = 0; } return isAscending ? returnVal : -returnVal; } private static int PartCompare(ssortingng left, ssortingng right) { int x, y; if (!int.TryParse(left, out x)) return left.CompareTo(right); if (!int.TryParse(right, out y)) return left.CompareTo(right); return x.CompareTo(y); } #endregion private Dictionary table = new Dictionary(); public void Dispose() { table.Clear(); table = null; } } 

Ma solution:

 void Main() { new[] {"a4","a3","a2","a10","b5","b4","b400","1","C1d","c1d2"}.OrderBy(x => x, new NaturalSsortingngComparer()).Dump(); } public class NaturalSsortingngComparer : IComparer { private static readonly Regex _re = new Regex(@"(?<=\D)(?=\d)|(?<=\d)(?=\D)", RegexOptions.Compiled); public int Compare(string x, string y) { x = x.ToLower(); y = y.ToLower(); if(string.Compare(x, 0, y, 0, Math.Min(x.Length, y.Length)) == 0) { if(x.Length == y.Length) return 0; return x.Length < y.Length ? -1 : 1; } var a = _re.Split(x); var b = _re.Split(y); int i = 0; while(true) { int r = PartCompare(a[i], b[i]); if(r != 0) return r; ++i; } } private static int PartCompare(string x, string y) { int a, b; if(int.TryParse(x, out a) && int.TryParse(y, out b)) return a.CompareTo(b); return x.CompareTo(y); } } 

Résultats:

 1 a2 a3 a4 a10 b4 b5 b400 C1d c1d2 

Matthews Horsleys answer est la méthode la plus rapide qui ne change pas le comportement en fonction de la version de Windows utilisée par votre programme. Cependant, il peut être encore plus rapide en créant la regex une fois et en utilisant RegexOptions.Comstackd. J’ai également ajouté la possibilité d’insérer un comparateur de chaînes pour pouvoir ignorer les casse si nécessaire et améliorer un peu la lisibilité.

  public static IEnumerable OrderByNatural(this IEnumerable items, Func selector, SsortingngComparer ssortingngComparer = null) { var regex = new Regex(@"\d+", RegexOptions.Comstackd); int maxDigits = items .SelectMany(i => regex.Matches(selector(i)).Cast().Select(digitChunk => (int?)digitChunk.Value.Length)) .Max() ?? 0; return items.OrderBy(i => regex.Replace(selector(i), match => match.Value.PadLeft(maxDigits, '0')), ssortingngComparer ?? SsortingngComparer.CurrentCulture); } 

Utiliser par

 var sortedEmployees = employees.OrderByNatural(emp => emp.Name); 

Cela prend 450 ms pour sortinger 100 000 chaînes par rapport à 300 ms pour la comparaison de chaînes par défaut .net assez rapide!

Vous devez faire attention – je me rappelle vaguement avoir lu que StrCmpLogicalW, ou quelque chose du genre, n’était pas ssortingctement transitif, et j’ai observé que les méthodes de sorting de .NET étaient parfois bloquées dans des boucles infinies si la fonction de comparaison enfreignait cette règle.

Une comparaison transitive indiquera toujours que a

Ajoutant à la réponse de Greg Beech (parce que je viens de le chercher), si vous voulez utiliser ceci depuis Linq, vous pouvez utiliser OrderBy qui prend un IComparer . Par exemple:

 var items = new List(); // fill items var sorted = items.OrderBy(item => item.Name, new NaturalSsortingngComparer()); 

Ceci est mon code pour sortinger une chaîne ayant à la fois des caractères alphanumériques.

Tout d’abord, cette méthode d’extension:

 public static IEnumerable AlphanumericSort(this IEnumerable me) { return me.OrderBy(x => Regex.Replace(x, @"\d+", m => m.Value.PadLeft(50, '0'))); } 

Ensuite, utilisez-le simplement n’importe où dans votre code comme ceci:

 List test = new List() { "The 1st", "The 12th", "The 2nd" }; test = test.AlphanumericSort(); 

Comment ça fonctionne ? En remplaçant par des zéros:

  Original | Regex Replace | The | Returned List | Apply PadLeft | Sorting | List | | | "The 1st" | "The 001st" | "The 001st" | "The 1st" "The 12th" | "The 012th" | "The 002nd" | "The 2nd" "The 2nd" | "The 002nd" | "The 012th" | "The 12th" 

Fonctionne avec des nombres multiples:

  Alphabetical Sorting | Alphanumeric Sorting | "Page 21, Line 42" | "Page 3, Line 7" "Page 21, Line 5" | "Page 3, Line 32" "Page 3, Line 32" | "Page 21, Line 5" "Page 3, Line 7" | "Page 21, Line 42" 

J’espère que ça va aider.

Voici un exemple relativement simple qui n’utilise pas P / Invoke et évite toute allocation pendant l’exécution.

 internal sealed class NumericSsortingngComparer : IComparer { public static NumericSsortingngComparer Instance { get; } = new NumericSsortingngComparer(); public int Compare(ssortingng x, ssortingng y) { // sort nulls to the start if (x == null) return y == null ? 0 : -1; if (y == null) return 1; var ix = 0; var iy = 0; while (true) { // sort shorter ssortingngs to the start if (ix >= x.Length) return iy >= y.Length ? 0 : -1; if (iy >= y.Length) return 1; var cx = x[ix]; var cy = y[iy]; int result; if (char.IsDigit(cx) && char.IsDigit(cy)) result = CompareInteger(x, y, ref ix, ref iy); else result = cx.CompareTo(y[iy]); if (result != 0) return result; ix++; iy++; } } private static int CompareInteger(ssortingng x, ssortingng y, ref int ix, ref int iy) { var lx = GetNumLength(x, ix); var ly = GetNumLength(y, iy); // shorter number first (note, doesn't handle leading zeroes) if (lx != ly) return lx.CompareTo(ly); for (var i = 0; i < lx; i++) { var result = x[ix++].CompareTo(y[iy++]); if (result != 0) return result; } return 0; } private static int GetNumLength(string s, int i) { var length = 0; while (i < s.Length && char.IsDigit(s[i++])) length++; return length; } } 

Il n'ignore pas les zéros en tête, donc 01 vient après 2 .

Test unitaire correspondant:

 public class NumericSsortingngComparerTests { [Fact] public void OrdersCorrectly() { AssertEqual("", ""); AssertEqual(null, null); AssertEqual("Hello", "Hello"); AssertEqual("Hello123", "Hello123"); AssertEqual("123", "123"); AssertEqual("123Hello", "123Hello"); AssertOrdered("", "Hello"); AssertOrdered(null, "Hello"); AssertOrdered("Hello", "Hello1"); AssertOrdered("Hello123", "Hello124"); AssertOrdered("Hello123", "Hello133"); AssertOrdered("Hello123", "Hello223"); AssertOrdered("123", "124"); AssertOrdered("123", "133"); AssertOrdered("123", "223"); AssertOrdered("123", "1234"); AssertOrdered("123", "2345"); AssertOrdered("0", "1"); AssertOrdered("123Hello", "124Hello"); AssertOrdered("123Hello", "133Hello"); AssertOrdered("123Hello", "223Hello"); AssertOrdered("123Hello", "1234Hello"); } private static void AssertEqual(ssortingng x, ssortingng y) { Assert.Equal(0, NumericSsortingngComparer.Instance.Compare(x, y)); Assert.Equal(0, NumericSsortingngComparer.Instance.Compare(y, x)); } private static void AssertOrdered(ssortingng x, ssortingng y) { Assert.Equal(-1, NumericSsortingngComparer.Instance.Compare(x, y)); Assert.Equal( 1, NumericSsortingngComparer.Instance.Compare(y, x)); } } 

Je l’ai effectivement implémenté en tant que méthode d’extension sur le SsortingngComparer pour que vous puissiez par exemple:

  • SsortingngComparer.CurrentCulture.WithNaturalSort() ou
  • SsortingngComparer.OrdinalIgnoreCase.WithNaturalSort() .

IComparer peut être utilisé dans tous les endroits comme OrderBy , OrderBy , OrderByDescending , ThenBy , ThenByDescending SortedSet , etc. Et vous pouvez toujours modifier facilement la sensibilité à la casse, la culture, etc.

L’implémentation est assez sortingviale et devrait fonctionner très bien même sur de grandes séquences.


Je l’ai également publié comme un tout petit paquet NuGet , vous pouvez donc faire ce qui suit :

 Install-Package NaturalSort.Extension 

Le code incluant les commentaires de la documentation XML et la suite de tests est disponible dans le référentiel GitHub de NaturalSort.Extension .


Le code en entier est le suivant (si vous ne pouvez pas encore utiliser C # 7, installez simplement le package NuGet):

 public static class SsortingngComparerNaturalSortExtension { public static IComparer WithNaturalSort(this SsortingngComparer ssortingngComparer) => new NaturalSortComparer(ssortingngComparer); private class NaturalSortComparer : IComparer { public NaturalSortComparer(SsortingngComparer ssortingngComparer) { _ssortingngComparer = ssortingngComparer; } private readonly SsortingngComparer _ssortingngComparer; private static readonly Regex NumberSequenceRegex = new Regex(@"(\d+)", RegexOptions.Comstackd | RegexOptions.CultureInvariant); private static ssortingng[] Tokenize(ssortingng s) => s == null ? new ssortingng[] { } : NumberSequenceRegex.Split(s); private static ulong ParseNumberOrZero(ssortingng s) => ulong.TryParse(s, NumberStyles.None, CultureInfo.InvariantCulture, out var result) ? result : 0; public int Compare(ssortingng s1, ssortingng s2) { var tokens1 = Tokenize(s1); var tokens2 = Tokenize(s2); var zipCompare = tokens1.Zip(tokens2, TokenCompare).FirstOrDefault(x => x != 0); if (zipCompare != 0) return zipCompare; var lengthCompare = tokens1.Length.CompareTo(tokens2.Length); return lengthCompare; } private int TokenCompare(ssortingng token1, ssortingng token2) { var number1 = ParseNumberOrZero(token1); var number2 = ParseNumberOrZero(token2); var numberCompare = number1.CompareTo(number2); if (numberCompare != 0) return numberCompare; var ssortingngCompare = _ssortingngComparer.Compare(token1, token2); return ssortingngCompare; } } } 

En développant quelques réponses précédentes et en utilisant des méthodes d’extension, je suis arrivé avec les éléments suivants qui ne préviennent pas les énumérations multiples énumérables, ou les problèmes de performances liés à l’utilisation de plusieurs objects regex, ou à l’appel inutile de regex, Cela dit, il utilise ToList (), ce qui peut annuler les avantages des grandes collections.

Le sélecteur prend en charge la saisie générique pour permettre l’atsortingbution de tout délégué, les éléments de la collection source sont mutés par le sélecteur, puis convertis en chaînes avec ToSsortingng ().

  private static readonly Regex _NaturalOrderExpr = new Regex(@"\d+", RegexOptions.Comstackd); public static IEnumerable OrderByNatural( this IEnumerable source, Func selector) { int max = 0; var selection = source.Select( o => { var v = selector(o); var s = v != null ? v.ToSsortingng() : Ssortingng.Empty; if (!Ssortingng.IsNullOrWhiteSpace(s)) { var mc = _NaturalOrderExpr.Matches(s); if (mc.Count > 0) { max = Math.Max(max, mc.Cast().Max(m => m.Value.Length)); } } return new { Key = o, Value = s }; }).ToList(); return selection.OrderBy( o => Ssortingng.IsNullOrWhiteSpace(o.Value) ? o.Value : _NaturalOrderExpr.Replace(o.Value, m => m.Value.PadLeft(max, '0'))) .Select(o => o.Key); } public static IEnumerable OrderByDescendingNatural( this IEnumerable source, Func selector) { int max = 0; var selection = source.Select( o => { var v = selector(o); var s = v != null ? v.ToSsortingng() : Ssortingng.Empty; if (!Ssortingng.IsNullOrWhiteSpace(s)) { var mc = _NaturalOrderExpr.Matches(s); if (mc.Count > 0) { max = Math.Max(max, mc.Cast().Max(m => m.Value.Length)); } } return new { Key = o, Value = s }; }).ToList(); return selection.OrderByDescending( o => Ssortingng.IsNullOrWhiteSpace(o.Value) ? o.Value : _NaturalOrderExpr.Replace(o.Value, m => m.Value.PadLeft(max, '0'))) .Select(o => o.Key); } 

Nous avions besoin d’un type naturel pour traiter le texte avec le modèle suivant:

 "Test 1-1-1 something" "Test 1-2-3 something" ... 

Pour une raison quelconque, lorsque j’ai regardé SO, je n’ai pas trouvé ce post et implémenté le nôtre. Comparé à certaines des solutions présentées ici, même si son concept est similaire, il pourrait être plus simple et plus facile à comprendre. Cependant, bien que j’aie essayé de regarder les goulots d’étranglement des performances, l’implémentation est encore beaucoup plus lente que OrderBy() par défaut.

Voici la méthode d’extension que j’implémente:

 public static class EnumerableExtensions { // set up the regex parser once and for all private static readonly Regex Regex = new Regex(@"\d+|\D+", RegexOptions.Comstackd | RegexOptions.Singleline); // stateless comparer can be built once private static readonly AggregateComparer Comparer = new AggregateComparer(); public static IEnumerable OrderByNatural(this IEnumerable source, Func selector) { // first extract ssortingng from object using selector // then extract digit and non-digit groups Func> splitter = s => Regex.Matches(selector(s)) .Cast() .Select(m => Char.IsDigit(m.Value[0]) ? (IComparable) int.Parse(m.Value) : m.Value); return source.OrderBy(splitter, Comparer); } ///  /// This comparer will compare two lists of objects against each other ///  /// Objects in each list are compare to their corresponding elements in the other /// list until a difference is found. private class AggregateComparer : IComparer> { public int Compare(IEnumerable x, IEnumerable y) { return x.Zip(y, (a, b) => new {a, b}) // walk both lists .Select(pair => pair.a.CompareTo(pair.b)) // compare each object .FirstOrDefault(result => result != 0); // until a difference is found } } } 

L’idée est de diviser les chaînes d’origine en blocs de chiffres et de non-chiffres ( "\d+|\D+" ). Comme il s’agit d’une tâche potentiellement coûteuse, elle n’est effectuée qu’une fois par entrée. Nous utilisons ensuite un comparateur d’objects comparables (désolé, je ne trouve pas de manière plus appropriée de le dire). Il compare chaque bloc à son bloc correspondant dans l’autre chaîne.

J’aimerais avoir des commentaires sur la façon dont cela pourrait être amélioré et quelles sont les principales lacunes. Notez que la maintenabilité est importante pour nous à ce stade et nous ne l’utilisons pas actuellement dans des ensembles de données extrêmement volumineux.

Si votre code de fin est pour le Web (ASP.NET, etc.), le sorting naturel peut être obtenu en utilisant la fonction JavaScript localeCampare

 '10'.localeCompare('2', undefined, {numeric: true, sensitivity: 'base'}) 

https://stackoverflow.com/a/38641281/952018

Voici une façon LINQ naïve d’une ligne sans regex (empruntée à python):

 List alphaSsortingngs = new List() { "10","2","3","4","50","11","100","a12","b12" }; alphaSsortingngs.OrderBy(g => new Tuple(g.ToCharArray().All(char.IsDigit)? int.Parse(g) : int.MaxValue, g)).Dump(); // Order Now: ["2","3","4","10","11","50","100","a12","b12"]