c# - son - qué es un alfanumérico de 6 dígitos
Clasificación alfanumérica con LINQ (10)
Tengo una string[]
en la que cada elemento termina con algún valor numérico.
string[] partNumbers = new string[]
{
"ABC10", "ABC1","ABC2", "ABC11","ABC10", "AB1", "AB2", "Ab11"
};
Estoy tratando de ordenar la matriz anterior de la siguiente manera utilizando LINQ
pero no estoy obteniendo el resultado esperado.
var result = partNumbers.OrderBy(x => x);
Resultado actual:
AB1
Ab11
AB2
ABC1
ABC10
ABC10
ABC11
ABC2
Resultado Esperado
AB1
AB2
AB11
..
Como el número de caracteres al principio es variable, una expresión regular ayudaría:
var re = new Regex(@"/d+$"); // finds the consecutive digits at the end of the string
var result = partNumbers.OrderBy(x => int.Parse(re.Match(x).Value));
Si hubiera un número fijo de caracteres de prefijo, entonces podría usar el método de Substring
para extraer a partir de los caracteres relevantes:
// parses the string as a number starting from the 5th character
var result = partNumbers.OrderBy(x => int.Parse(x.Substring(4)));
Si los números pueden contener un separador decimal o un separador de miles, entonces la expresión regular también debe permitir esos caracteres:
var re = new Regex(@"[/d,]*/.?/d+$");
var result = partNumbers.OrderBy(x => double.Parse(x.Substring(4)));
Si la cadena devuelta por la expresión regular o Substring
puede ser inutilizable por int.Parse
/ double.Parse
, entonces use la variante TryParse
relevante:
var re = new Regex(@"/d+$"); // finds the consecutive digits at the end of the string
var result = partNumbers.OrderBy(x => {
int? parsed = null;
if (int.TryParse(re.Match(x).Value, out var temp)) {
parsed = temp;
}
return parsed;
});
Esto se debe a que el orden predeterminado para la cadena es el orden alfabético estándar del diccionario (lexicográfico), y ABC11 vendrá antes que ABC2 porque el orden siempre procede de izquierda a derecha.
Para obtener lo que desea, debe rellenar la parte numérica en su orden por cláusula, algo así como:
var result = partNumbers.OrderBy(x => PadNumbers(x));
donde PadNumbers
podría definirse como:
public static string PadNumbers(string input)
{
return Regex.Replace(input, "[0-9]+", match => match.Value.PadLeft(10, ''0''));
}
Esto almohadilla los ceros para cualquier número (o números) que aparezca en la cadena de entrada para que OrderBy
vea:
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 supone una cantidad máxima de dígitos para los números en la entrada.
No hay una forma natural de hacerlo en .NET, pero eche un vistazo a esta publicación de blog sobre clasificación natural
Puede poner esto en un método de extensión y usarlo en lugar de OrderBy
No sé cómo hacer eso en LINQ, pero tal vez te guste esta forma de:
Array.Sort(partNumbers, new AlphanumComparatorFast());
// Mostrar los resultados
foreach (string h in partNumbers )
{
Console.WriteLine(h);
}
Parece que está haciendo un pedido lexicográfico, independientemente de los caracteres pequeños o mayúsculas.
Puedes intentar usar alguna expresión personalizada en esa lambda para hacer eso.
Puede invocar a StrCmpLogicalW
(la función de Windows) para hacer esto. Vea aquí: Orden de clasificación natural en C #
Puede encontrar una implementación adecuada de un método de clasificación alfanumérico que ''simplemente funciona'' en el sitio de Dave Koelle . La versión de C # está aquí .
Puede usar 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);
}
Puede usarlo como AlphanumComparatorFast
desde la respuesta anterior.
Si desea ordenar una lista de objetos por una propiedad específica utilizando LINQ y un comparador personalizado como el de Dave Koelle , haría algo como esto:
...
items = items.OrderBy(x => x.property, new AlphanumComparator()).ToList();
...
También debe alterar la clase de Dave para heredar de System.Collections.Generic.IComparer<object>
lugar del IComparer
básico para que la firma de clase se convierta en:
...
public class AlphanumComparator : System.Collections.Generic.IComparer<object>
{
...
Personalmente, prefiero la implementación de James McCormack porque implementa IDisposable, aunque mi evaluación comparativa muestra que es un poco más lenta.
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());