2010-09-15 24 views
16

Cómo ordenar una lista basada en valor entero del elementoClasificación Lista <String> en C#

La lista es como

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

El resultado debe ser como

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

es que hay alguna idea hacer esto usando la expresión LINQ o Lambda?

Gracias de antemano

+0

¿Estos valores de cadena representan números hexadecimales? ¿O sería posible que "S2" aparezca en la lista decir? –

+0

@El Ronnoco: No hexadecimal no. Puede ser "S2", etc ... (Editado) –

Respuesta

16

¿Qué tal:.

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é pasa con las diferencias en "NUM10" y "NUM2", para nosotros "NUM2" es obviamente antes de "NUM10" pero no se ordenará de esa manera – Xander

+5

@Xander - definir "obviamente"; la pregunta citó "valor entero" - pero a menos que el OP defina los términos/patrones que * deberían * permitirse, entonces no incluyo "NUM10" como un valor entero. –

+1

Lo siento cuando quise decir "obviamente", fue en términos de cómo un ser humano percibirá el valor que significa ... como en "NUM" y un número entero de diez "NUM10" ... Fue más como un aviso asegúrese de que una clasificación simple y simple pueda/no sea deseable. – Xander

-1

no creo que necesite nada, además de listName.Sort() porque sort() método utiliza comparador predeterminado para ordenar los nodos rápidos. comparador predeterminado hace exactamente lo que usted está interesado en

+2

No del todo. Obtendrá "11" justo después de "1" y antes de "2". – liggett78

16

Esto se llama un "orden natural", y por lo general se emplea para ordenar los elementos como los que usted tiene, al igual que los nombres de archivo y tal .

Aquí hay una ingenua (en el sentido de que es probable que haya un montón de Unicode problemas con él) aplicación que parece hacer el truco:

Puede copiar el siguiente código en LINQPad para ejecutarlo y probarlo.

Básicamente, el algoritmo de comparación identificará los números dentro de las cuerdas, y manejar los impregnando la más corta con ceros a la izquierda, por lo que por ejemplo, las dos cadenas "Test123Abc" y "Test7X" deben compararse como si fueran "Test123Abc" y "Test007X", que debe producir Lo que quieras.

Sin embargo, cuando dije "ingenuo", quiero decir que probablemente tenga un montón de problemas de Unicode reales aquí, como el manejo de signos diacríticos y caracteres de código múltiple. Si alguien puede dar una mejor implementación, me encantaría verlo.

Notas:

  • La aplicación en realidad no analizar los números, por lo que de manera arbitraria números largos debería funcionar bien
  • Ya que en realidad no analizar los números como "números", los números de punto flotante no se maneje correctamente, "123.45" contra "123.789" se comparará como "123.045" contra "123.789", lo cual es incorrecto.

Código:

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

He abierto una pregunta para descubrir una mejor manera de manejar los signos diacríticos y los caracteres con múltiples puntos de código, aquí: http://stackoverflow.com/questions/3717132/writing-a-better-natural-sort –

2

Jeff Atwood tiene una clasificación sobre blog post natural donde se une a algunas implementaciones disponibles del algoritmo deseado.

Uno de Jeffs enlaces a puntos Dave Koelle cómo tiene un 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

intentar escribir una pequeña clase de ayuda para analizar y representar sus fichas.Por ejemplo, y sin demasiados controles:

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; } 
} 

Ahora se convierte en fácil escribir un comparador o bien ordenar manualmente:

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); 

da el resultado:

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

+0

Como nota al pie , el código solo considera el número cerca del final de la cadena - 'a123b12' ->' Nombre: a123b', 'Número: 12' – Kobi

0

Este es el algoritmo más rápido - me llevó 2 mili para ordenar 5 0 productos ~

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

Aquí es una solución C# 7 (suponiendo que la lista tiene el nombre de una):

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); 
Cuestiones relacionadas