2012-04-03 23 views
7

Duplicar posible:
Natural Sort Order in C#ordenar cadena números

Tengo una lista con una gran cantidad de números en ella. Pero se guardan como cadenas debido a algunas letras adicionales.

Mi lista es como la siguiente:

1 
10 
11 
11a 
11b 
12 
2 
20 
21a 
21c 
A1 
A2 
... 

pero debería tener este aspecto

1 
2 
10 
11a 
11b 
... 
A1 
A2 
... 

¿Cómo puedo ordenar mi lista para obtener este resultado?

+0

Romper el 'número' en sus componentes, a continuación, ordenar por eso. – leppie

+1

sí, el tipo natural es lo que ur después. este es un duplicado según lo indicado por Jon. Un buen artículo sobre esto en http://zootfroot.blogspot.com.au/2009/09/natural-sort-compare-with-linq-orderby.html –

+0

[Ordenamiento natural en C#] (http: //www.interact -sw.co.uk/iangblog/2007/12/13/natural-sorting) –

Respuesta

12

A juzgar por los comentarios anteriores, que también se implementarían una clase personalizada IComparer<T> . Según lo que puedo deducir, la estructura de los artículos es un número, una combinación de un número seguido de una (s) letra (s). Si este es el caso, la siguiente implementación IComparer<T> debería funcionar.

public class CustomComparer : IComparer<string> 
{ 
    public int Compare(string x, string y) 
    { 
     var regex = new Regex("^(d+)"); 

     // run the regex on both strings 
     var xRegexResult = regex.Match(x); 
     var yRegexResult = regex.Match(y); 

     // check if they are both numbers 
     if (xRegexResult.Success && yRegexResult.Success) 
     { 
      return int.Parse(xRegexResult.Groups[1].Value).CompareTo(int.Parse(yRegexResult.Groups[1].Value)); 
     } 

     // otherwise return as string comparison 
     return x.CompareTo(y); 
    } 
} 

Con esta IComparer<T>, usted será capaz de ordenar la lista de string haciendo

var myComparer = new CustomComparer(); 
myListOfStrings.Sort(myComparer); 

Esto ha sido probado con los siguientes elementos:

2, 1, 4d, 4e, 4c, 4a, 4b, A1, 20, B2, A2, a3, 5, 6, 4f, 1a

y da el resultado:

1, 1a, 2, 20, 4a, 4b, 4c, 4d, 4e, 4f, 5, 6, A1, A2, a3, B2

+0

Esto es genial, pero no maneja correctamente cosas como números de versión ... 1.5.2, 1.9.9, 1.10.17 Extrañamente, el Explorador de Windows ordena esto correctamente ... pero aparentemente, el comparador no está disponible para ningún otro código C# para reproducir el mismo orden de nombre de archivo que el Explorador de archivos mostrará al ordenar por nombre de archivo. Frustrante. – pmbAustin

+0

Creo que Regex debería ser '^ (\\ d +)'. Aún mejor, la Regex podría ser simplemente '(\\ d +)' para manejar 'Región 1, Región 10, Región 2' así como' 1, 10, 2'. – stack247

2

Bueno, necesita extraer el número de cada cadena y luego ordenar la lista de cadenas basadas en la lista de números como claves. Haz esto en dos pasos.

Para extraer el número de cada cadena, la forma más simple que creo es utilizar una expresión regular: busque una coincidencia para (\d+) (si tiene números negativos o decimales, tendrá que usar una expresión regular diferente) . Digamos que hizo que en una función llamada ExtractNumber

Ahora usted puede utilizar un poco de LINQ creativa para ordenar, de esta manera:

strings.Select(s=>new { key=ExtractNumber(s), value=s }) // Create a key-value pair 
     .OrderBy(p=>p.key)        // Sort by key 
     .Select(p=>p.Value);        // Extract the values 
+0

Parece una solución elegante con LINQ, pero ¿qué pasaría cuando la iteración llegara a los elementos 'A1' y' A2'? – Richard

+0

int.Parse ((nueva Regex (@ "(? <= Pdf _) \ d *? (? = _ \. Bmp $)")). Coincidencia (archivo) .Valor); // coincide con pdf_123456890_.bmp –

0

Soy bastante nuevo en C#, pero aquí es una solución que aprecio en Java: debe avanzar en 2 pasos, primero defina un IComparer personalizado y, segundo, utilícelo al llamar al método de clasificación. Por lo que debe ser capaz de hacer algo como:

public class MyListSorter : IComparer<MyObject> 
{ 
    public int Compare(MyObject obj1, MyObject obj2) 
    { 
    if (!Char.IsNumber(obj1) && Char.IsNumber(obj2)) 
    { 
     return 0; 
    } 
    else if (Char.IsNumber(obj1) && !Char.IsNumber(obj2)) 
    { 
     return 1; 
    } 
    else 
    { 
     return obj2.CompareTo(obj1); 
    } 
    } 
} 

y luego

myObjectList.Sort(new MyListSorter()); 

Más informaciones sobre IComparer: http://support.microsoft.com/kb/320727

1

Dado que esto incluye muchas operaciones de cadena, expresiones regulares, etc., no creo que sea un algoritmo eficiente, pero parece funcionar.

List<string> list1 = new List<string>() { "11c22", "1", "10", "11", "11a", "11b", "12", "2", "20", "21a", "21c", "A1", "A2" }; 
List<string> list2 = new List<string>() { "File (5).txt", "File (1).txt", "File (10).txt", "File (100).txt", "File (2).txt" }; 
var sortedList1 = NaturalSort(list1).ToArray(); 
var sortedList2 = NaturalSort(list2).ToArray(); 

public static IEnumerable<string> NaturalSort(IEnumerable<string> list) 
{ 
    int maxLen = list.Select(s => s.Length).Max(); 
    Func<string, char> PaddingChar = s => char.IsDigit(s[0]) ? ' ' : char.MaxValue; 

    return list 
      .Select(s => 
       new 
       { 
        OrgStr = s, 
        SortStr = Regex.Replace(s, @"(\d+)|(\D+)", m => m.Value.PadLeft(maxLen, PaddingChar(m.Value))) 
       }) 
      .OrderBy(x => x.SortStr) 
      .Select(x => x.OrgStr); 
} 
+0

Me encantaría ver una versión que realmente maneja números de versión ... que tienen varios números para mantener en orden: 2.5.7, 10.3.2, 2.18.3, etc. – pmbAustin

+1

¡esto funciona genial! ¡gracias! –

Cuestiones relacionadas