2010-09-15 11 views
16

Comment trier une liste en fonction de la valeur entière de l'articleTri Liste <String> en C#

La liste est comme

"1" 
"5" 
"3" 
"6" 
"11" 
"9" 
"NUM1" 
"NUM0" 

Le résultat devrait être comme

"1" 
"3" 
"5" 
"6" 
"9" 
"11" 
"NUM0" 
"NUM1" 

est-il une idée faire cela en utilisant LINQ ou Lambda expression?

méthode

Merci à l'avance

+0

Est-ce que ces valeurs de chaîne représentent des nombres hexadécimaux? Ou serait-il possible que "S2" apparaisse dans la liste? –

+0

@El Ronnoco: Non hexadécimal non. Il peut être "S2" etc ... (Edité dans) –

Répondre

16

Que diriez-vous.

list.Sort((x, y) => 
    { 
     int ix, iy; 
     return int.TryParse(x, out ix) && int.TryParse(y, out iy) 
       ? ix.CompareTo(iy) : string.Compare(x, y); 
    }); 
+4

qu'en est-il des différences dans "NUM10" et "NUM2", pour nous "NUM2" est évidemment avant "NUM10" mais il ne sera pas trié de cette façon – Xander

+5

@Xander - définir "évidemment"; la question citée "valeur entière" - mais à moins que l'OP ne définisse les termes/modèles qui * devraient * être autorisés, je n'inclue pas "NUM10" comme valeur entière. –

+1

Désolé quand je voulais dire "évidemment", c'était en termes de comment un humain percevra la valeur pour signifier ... comme dans "NUM" et un nombre entier de dix "NUM10" ... C'était plus d'une tête à assurez-vous qu'un simple tri simple peut/ne peut pas être souhaitable. – Xander

-1

Je ne pense pas que vous besoin de quelque chose en plus listName.Sort() parce tri() utilise comparateur par défaut aux nœuds de tri rapide. comparateur Default fait exactement ce que vous êtes intéressé par

+2

Pas tout à fait. Vous aurez "11" juste après "1" et avant "2". – liggett78

16

Ceci est appelé un « ordre de tri naturel », et est généralement utilisé pour trier les éléments comme ceux que vous avez, comme les noms de fichiers et autres .

est ici un naïf (dans le sens où il y a probablement beaucoup de UNICODE problèmes avec elle) la mise en œuvre qui semble faire l'affaire:

Vous pouvez copier le code ci-dessous dans LINQPad pour l'exécuter et le tester.

Fondamentalement, l'algorithme de comparaison identifiera les numéros dans les chaînes, et gérer ceux de rembourrage le plus court avec des zéros non significatifs, donc par exemple les deux chaînes "Test123Abc" et "Test7X" devraient être comparés comme si elles étaient "Test123Abc" et "Test007X", qui devrait produire ce que tu veux.

Cependant, quand je dit « naïf », je veux dire que j'ai probablement des tonnes de problèmes unicode réel ici, comme la manipulation diacritiques et des caractères multi-codepoint. Si quelqu'un peut donner une meilleure mise en œuvre, j'aimerais le voir.

Notes:

  • La mise en œuvre n'a pas analyser réellement les nombres, donc arbitrairement longs nombres devrait fonctionner parfaitement
  • Comme il ne parse pas réellement les nombres comme « chiffres », les nombres à virgule flottante "123.45" contre "123.789" sera comparé à "123.045" contre "123.789", ce qui est faux.

code:

void Main() 
{ 
    List<string> input = new List<string> 
    { 
     "1", "5", "3", "6", "11", "9", "A1", "A0" 
    }; 
    var output = input.NaturalSort(); 
    output.Dump(); 
} 

public static class Extensions 
{ 
    public static IEnumerable<string> NaturalSort(
     this IEnumerable<string> collection) 
    { 
     return NaturalSort(collection, CultureInfo.CurrentCulture); 
    } 

    public static IEnumerable<string> NaturalSort(
     this IEnumerable<string> collection, CultureInfo cultureInfo) 
    { 
     return collection.OrderBy(s => s, new NaturalComparer(cultureInfo)); 
    } 

    private class NaturalComparer : IComparer<string> 
    { 
     private readonly CultureInfo _CultureInfo; 

     public NaturalComparer(CultureInfo cultureInfo) 
     { 
      _CultureInfo = cultureInfo; 
     } 

     public int Compare(string x, string y) 
     { 
      // simple cases 
      if (x == y) // also handles null 
       return 0; 
      if (x == null) 
       return -1; 
      if (y == null) 
       return +1; 

      int ix = 0; 
      int iy = 0; 
      while (ix < x.Length && iy < y.Length) 
      { 
       if (Char.IsDigit(x[ix]) && Char.IsDigit(y[iy])) 
       { 
        // We found numbers, so grab both numbers 
        int ix1 = ix++; 
        int iy1 = iy++; 
        while (ix < x.Length && Char.IsDigit(x[ix])) 
         ix++; 
        while (iy < y.Length && Char.IsDigit(y[iy])) 
         iy++; 
        string numberFromX = x.Substring(ix1, ix - ix1); 
        string numberFromY = y.Substring(iy1, iy - iy1); 

        // Pad them with 0's to have the same length 
        int maxLength = Math.Max(
         numberFromX.Length, 
         numberFromY.Length); 
        numberFromX = numberFromX.PadLeft(maxLength, '0'); 
        numberFromY = numberFromY.PadLeft(maxLength, '0'); 

        int comparison = _CultureInfo 
         .CompareInfo.Compare(numberFromX, numberFromY); 
        if (comparison != 0) 
         return comparison; 
       } 
       else 
       { 
        int comparison = _CultureInfo 
         .CompareInfo.Compare(x, ix, 1, y, iy, 1); 
        if (comparison != 0) 
         return comparison; 
        ix++; 
        iy++; 
       } 
      } 

      // we should not be here with no parts left, they're equal 
      Debug.Assert(ix < x.Length || iy < y.Length); 

      // we still got parts of x left, y comes first 
      if (ix < x.Length) 
       return +1; 

      // we still got parts of y left, x comes first 
      return -1; 
     } 
    } 
} 
+0

J'ai ouvert une question pour trouver une meilleure façon de gérer les caractères diacritiques et multi-points de code, ici: http://stackoverflow.com/questions/3717132/writing-a-better-natural-sort –

2

Jeff Atwood a un blog post sur le tri naturel où il lie à certaines implémentations disponibles de l'algorithme souhaité.

L'un des liens Jeffs points Dave Koelle comment a C# implementation:

/* 
* The Alphanum Algorithm is an improved sorting algorithm for strings 
* containing numbers. Instead of sorting numbers in ASCII order like 
* a standard sort, this algorithm sorts numbers in numeric order. 
* 
* The Alphanum Algorithm is discussed at http://www.DaveKoelle.com 
* 
* Based on the Java implementation of Dave Koelle's Alphanum algorithm. 
* Contributed by Jonathan Ruckwood <[email protected]> 
* 
* Adapted by Dominik Hurnaus <[email protected]> to 
* - correctly sort words where one word starts with another word 
* - have slightly better performance 
* 
* Released under the MIT License - https://opensource.org/licenses/MIT 
* 
* Permission is hereby granted, free of charge, to any person obtaining 
* a copy of this software and associated documentation files (the "Software"), 
* to deal in the Software without restriction, including without limitation 
* the rights to use, copy, modify, merge, publish, distribute, sublicense, 
* and/or sell copies of the Software, and to permit persons to whom the 
* Software is furnished to do so, subject to the following conditions: 
* 
* The above copyright notice and this permission notice shall be included 
* in all copies or substantial portions of the Software. 
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 
* USE OR OTHER DEALINGS IN THE SOFTWARE. 
* 
*/ 
using System; 
using System.Collections; 
using System.Text; 

/* 
* Please compare against the latest Java version at http://www.DaveKoelle.com 
* to see the most recent modifications 
*/ 
namespace AlphanumComparator 
{ 
    public class AlphanumComparator : IComparer 
    { 
     private enum ChunkType {Alphanumeric, Numeric}; 
     private bool InChunk(char ch, char otherCh) 
     { 
      ChunkType type = ChunkType.Alphanumeric; 

      if (char.IsDigit(otherCh)) 
      { 
       type = ChunkType.Numeric; 
      } 

      if ((type == ChunkType.Alphanumeric && char.IsDigit(ch)) 
       || (type == ChunkType.Numeric && !char.IsDigit(ch))) 
      { 
       return false; 
      } 

      return true; 
     } 

     public int Compare(object x, object y) 
     { 
      String s1 = x as string; 
      String s2 = y as string; 
      if (s1 == null || s2 == null) 
      { 
       return 0; 
      } 

      int thisMarker = 0, thisNumericChunk = 0; 
      int thatMarker = 0, thatNumericChunk = 0; 

      while ((thisMarker < s1.Length) || (thatMarker < s2.Length)) 
      { 
       if (thisMarker >= s1.Length) 
       { 
        return -1; 
       } 
       else if (thatMarker >= s2.Length) 
       { 
        return 1; 
       } 
       char thisCh = s1[thisMarker]; 
       char thatCh = s2[thatMarker]; 

       StringBuilder thisChunk = new StringBuilder(); 
       StringBuilder thatChunk = new StringBuilder(); 

       while ((thisMarker < s1.Length) && (thisChunk.Length==0 ||InChunk(thisCh, thisChunk[0]))) 
       { 
        thisChunk.Append(thisCh); 
        thisMarker++; 

        if (thisMarker < s1.Length) 
        { 
         thisCh = s1[thisMarker]; 
        } 
       } 

       while ((thatMarker < s2.Length) && (thatChunk.Length==0 ||InChunk(thatCh, thatChunk[0]))) 
       { 
        thatChunk.Append(thatCh); 
        thatMarker++; 

        if (thatMarker < s2.Length) 
        { 
         thatCh = s2[thatMarker]; 
        } 
       } 

       int result = 0; 
       // If both chunks contain numeric characters, sort them numerically 
       if (char.IsDigit(thisChunk[0]) && char.IsDigit(thatChunk[0])) 
       { 
        thisNumericChunk = Convert.ToInt32(thisChunk.ToString()); 
        thatNumericChunk = Convert.ToInt32(thatChunk.ToString()); 

        if (thisNumericChunk < thatNumericChunk) 
        { 
         result = -1; 
        } 

        if (thisNumericChunk > thatNumericChunk) 
        { 
         result = 1; 
        } 
       } 
       else 
       { 
        result = thisChunk.ToString().CompareTo(thatChunk.ToString()); 
       } 

       if (result != 0) 
       { 
        return result; 
       } 
      } 

      return 0; 
     } 
    } 
} 
2

Essayez d'écrire une petite classe d'aide pour analyser et représenter vos jetons.Par exemple, sans trop de contrôles:

public class NameAndNumber 
{ 
    public NameAndNumber(string s) 
    { 
     OriginalString = s; 
     Match match = Regex.Match(s,@"^(.*?)(\d*)$"); 
     Name = match.Groups[1].Value; 
     int number; 
     int.TryParse(match.Groups[2].Value, out number); 
     Number = number; //will get default value when blank 
    } 

    public string OriginalString { get; private set; } 
    public string Name { get; private set; } 
    public int Number { get; private set; } 
} 

Maintenant, il devient facile d'écrire un comparateur ou trier manuellement:

var list = new List<string> { "ABC", "1", "5", "NUM44", "3", 
           "6", "11", "9", "NUM1", "NUM0" }; 

var sorted = list.Select(str => new NameAndNumber(str)) 
    .OrderBy(n => n.Name) 
    .ThenBy(n => n.Number); 

donne le résultat:

1, 3, 5, 6, 9, 11, ABC, NUM0, NUM1, NUM44

+0

Comme une note de côté , le code ne concerne que le chiffre proche de la fin de la chaîne - 'a123b12' ->' Name: a123b', 'Number: 12' – Kobi

0

C'est le plus rapide algorithme - m'a pris 2 Mili pour trier 5 0 articles ~

static void Sort() 
{ 
    string[] partNumbers = new string[] {"A1", "A2", "A10", "A111"}; 
    string[] result = partNumbers.OrderBy(x => PadNumbers(x)).ToArray(); 
} 


public static string PadNumbers(string input) 
{ 
     const int MAX_NUMBER_LEN = 10; 

     string newInput = ""; 
     string currentNumber = ""; 
     foreach (char a in input) 
     { 
      if (!char.IsNumber(a)) 
      { 
       if (currentNumber == "") 
       { 
        newInput += a; 
        continue; 
       } 
       newInput += "0000000000000".Substring(0, MAX_NUMBER_LEN - currentNumber.Length) + currentNumber; 
       currentNumber = ""; 
      } 
      currentNumber += a; 
     } 
     if (currentNumber != "") 
     { 
      newInput += "0000000000000".Substring(0, MAX_NUMBER_LEN - currentNumber.Length) + currentNumber; 
     } 

     return newInput; 
    } 

~

-1

Voici une solution C# 7 (en supposant que la liste a le nom a):

var numericList = a.Where(i => int.TryParse(i, out _)).OrderBy(j => int.Parse(j)).ToList(); 
    var nonNumericList = a.Where(i => !int.TryParse(i, out _)).OrderBy(j => j).ToList(); 
    a.Clear(); 
    a.AddRange(numericList); 
    a.AddRange(nonNumericList);