2011-02-23 27 views
24

Tengo un string[] en el que cada elemento termina con algún valor numérico.Clasificación alfanumérica con LINQ

string[] partNumbers = new string[] 
{ 
    "ABC10", "ABC1","ABC2", "ABC11","ABC10", "AB1", "AB2", "Ab11" 
}; 

estoy tratando de ordenar la matriz anterior como sigue usando LINQ pero no estoy obteniendo el resultado esperado.

var result = partNumbers.OrderBy(x => x); 

Resultado real:

AB1
Ab11
AB2
ABC1
ABC10
ABC10 ABC11

ABC2

Resultado esperado

AB1 AB2

AB11
..

+0

a [artículo útil] (http://www.dotnetperls.com/alphanumeric-sorting) sobre la clasificación alfanumérica (resultado esperado) en comparación con la clasificación ASCII (resultado real) – mcdon

Respuesta

24

Eso es debido a que el orden predeterminado para la cadena es alfanumérico estándar ordenamiento de diccionario (lexicográfico), y ABC11 vendrá antes de ABC2 porque el orden siempre procede de izquierda a derecha.

para conseguir lo que quiere, lo que necesita para rellenar la parte numérica de su orden por cláusula, algo así como:

var result = partNumbers.OrderBy(x => PadNumbers(x)); 

donde PadNumbers se podría definir como:

public static string PadNumbers(string input) 
{ 
    return Regex.Replace(input, "[0-9]+", match => match.Value.PadLeft(10, '0')); 
} 

Este pastillas de ceros para cualquier número (o números) que aparecen en la cadena de entrada por lo que ve OrderBy:

ABC0000000010 
ABC0000000001 
... 
AB0000000011 

El relleno solo ocurre en la clave utilizada para la comparación. Las cadenas originales (sin relleno) se conservan en el resultado.

Tenga en cuenta que este enfoque asume un número máximo de dígitos para los números en la entrada.

+3

@geek: no hay una función predefinida por ese nombre. Estaba sugiriendo que implemente una función con el comportamiento que describí, usando expresiones regulares o algún tipo de enfoque. El nombre de la función fue para fines ilustrativos. – Nathan

+1

Seguí adelante y agregué una función simple para hacer el relleno. –

1

Bueno parece que está haciendo un pedido lexicográfico con independencia de caracteres pequeños o de capital.

Puede intentar usar alguna expresión personalizada en esa lambda para hacer eso.

-2

No sé cómo hacer eso en LINQ, pero tal vez te gusta esta manera de:

Array.Sort(partNumbers, new AlphanumComparatorFast()); 

// Muestra los resultados

foreach (string h in partNumbers) 
{ 
Console.WriteLine(h); 
} 
3
public class AlphanumComparatorFast : IComparer 
{ 
    List<string> GetList(string s1) 
    { 
     List<string> SB1 = new List<string>(); 
     string st1, st2, st3; 
     st1 = ""; 
     bool flag = char.IsDigit(s1[0]); 
     foreach (char c in s1) 
     { 
      if (flag != char.IsDigit(c) || c=='\'') 
      { 
       if(st1!="") 
       SB1.Add(st1); 
       st1 = ""; 
       flag = char.IsDigit(c); 
      } 
      if (char.IsDigit(c)) 
      { 
       st1 += c; 
      } 
      if (char.IsLetter(c)) 
      { 
       st1 += c; 
      } 


     } 
     SB1.Add(st1); 
     return SB1; 
    } 



    public int Compare(object x, object y) 
    { 
     string s1 = x as string; 
     if (s1 == null) 
     { 
      return 0; 
     } 
     string s2 = y as string; 
     if (s2 == null) 
     { 
      return 0; 
     } 
     if (s1 == s2) 
     { 
      return 0; 
     } 
     int len1 = s1.Length; 
     int len2 = s2.Length; 
     int marker1 = 0; 
     int marker2 = 0; 

     // Walk through two the strings with two markers. 
     List<string> str1 = GetList(s1); 
     List<string> str2 = GetList(s2); 
     while (str1.Count != str2.Count) 
     { 
      if (str1.Count < str2.Count) 
      { 
       str1.Add(""); 
      } 
      else 
      { 
       str2.Add(""); 
      } 
     } 
     int x1 = 0; int res = 0; int x2 = 0; string y2 = ""; 
     bool status = false; 
     string y1 = ""; bool s1Status = false; bool s2Status = false; 
     //s1status ==false then string ele int; 
     //s2status ==false then string ele int; 
     int result = 0; 
     for (int i = 0; i < str1.Count && i < str2.Count; i++) 
     { 
      status = int.TryParse(str1[i].ToString(), out res); 
      if (res == 0) 
      { 
       y1 = str1[i].ToString(); 
       s1Status = false; 
      } 
      else 
      { 
       x1 = Convert.ToInt32(str1[i].ToString()); 
       s1Status = true; 
      } 

      status = int.TryParse(str2[i].ToString(), out res); 
      if (res == 0) 
      { 
       y2 = str2[i].ToString(); 
       s2Status = false; 
      } 
      else 
      { 
       x2 = Convert.ToInt32(str2[i].ToString()); 
       s2Status = true; 
      } 
      //checking --the data comparision 
      if(!s2Status && !s1Status) //both are strings 
      { 
       result = str1[i].CompareTo(str2[i]); 
      } 
      else if (s2Status && s1Status) //both are intergers 
      { 
       if (x1 == x2) 
       { 
        if (str1[i].ToString().Length < str2[i].ToString().Length) 
        { 
         result = 1; 
        } 
        else if (str1[i].ToString().Length > str2[i].ToString().Length) 
         result = -1; 
        else 
         result = 0; 
       } 
       else 
       { 
        int st1ZeroCount=str1[i].ToString().Trim().Length- str1[i].ToString().TrimStart(new char[]{'0'}).Length; 
        int st2ZeroCount = str2[i].ToString().Trim().Length - str2[i].ToString().TrimStart(new char[] { '0' }).Length; 
        if (st1ZeroCount > st2ZeroCount) 
         result = -1; 
        else if (st1ZeroCount < st2ZeroCount) 
         result = 1; 
        else 
        result = x1.CompareTo(x2); 

       } 
      } 
      else 
      { 
       result = str1[i].CompareTo(str2[i]); 
      } 
      if (result == 0) 
      { 
       continue; 
      } 
      else 
       break; 

     } 
     return result; 
    } 
} 

uso de esta clase:

List<string> marks = new List<string>(); 
       marks.Add("M'00Z1"); 
       marks.Add("M'0A27"); 
       marks.Add("M'00Z0"); 
marks.Add("0000A27"); 
       marks.Add("100Z0"); 

    string[] Markings = marks.ToArray(); 

       Array.Sort(Markings, new AlphanumComparatorFast()); 
3

Usted puede utilizar PInvoke para obtener un resultado rápido y bueno:

class AlphanumericComparer : IComparer<string> 
{ 
    [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)] 
    static extern int StrCmpLogicalW(string s1, string s2); 

    public int Compare(string x, string y) => StrCmpLogicalW(x, y); 
} 

Usted puede usarlo como AlphanumComparatorFast de la respuesta anterior.

3

Si desea ordenar una lista de objetos por una propiedad específica utilizando LINQ y un comparador personalizado como el de Dave Koelle que haría algo como esto:

... 

items = items.OrderBy(x => x.property, new AlphanumComparator()).ToList(); 

... 

También hay que alterar la clase de Dave a heredar de System.Collections.Generic.IComparer<object> en lugar de la IComparer básica por lo que la firma se convierte en clase:

... 

public class AlphanumComparator : System.Collections.Generic.IComparer<object> 
{ 

    ... 

Personalmente, prefiero la aplicación por James McCormack ya que implementa IDisposable, TH Aunque mi evaluación comparativa muestra que es un poco más lento.

Cuestiones relacionadas