2009-12-07 6 views
9

Necesito hacer muchas comparaciones de cadenas insensibles a mayúsculas y minúsculas de alto rendimiento y me di cuenta de que mi forma de hacerlo .ToLower(). Trim() era realmente estúpido debido a todas las nuevas cadenas siendo asignadoString.comparison performance (with trim)

Así que hicieron pozos alrededor de un poco y parece preferible esta manera:

String.Compare(txt1,txt2, StringComparison.OrdinalIgnoreCase) 

El único problema aquí es que quiero hacer caso omiso de espacios iniciales o finales, es decir, Trim(), pero si uso Trim I tienen el mismo problema con las asignaciones de cuerdas. Supongo que podría verificar cada cadena y ver si comienza con ("") o EndsWith ("") y solo después Trim. O eso, o averiguar el índice, la longitud de cada cuerda y pasar a string.Compare anular

public static int Compare 
(
    string strA, 
    int indexA, 
    string strB, 
    int indexB, 
    int length, 
    StringComparison comparisonType 
) 

pero que parece bastante complicado y que probablemente tenga que utilizar algunos números enteros i si no hacer una muy grande if-else declaración para cada combinación de espacios en blanco finales y anteriores en ambas cadenas ... ¿alguna idea de una solución elegante?

aquí está mi propuesta actual:

public bool IsEqual(string a, string b) 
    { 
     return (string.Compare(a, b, StringComparison.OrdinalIgnoreCase) == 0); 
    } 

    public bool IsTrimEqual(string a, string b) 
    { 
     if (Math.Abs(a.Length- b.Length) > 2) // if length differs by more than 2, cant be equal 
     { 
      return false; 
     } 
     else if (IsEqual(a,b)) 
     { 
      return true; 
     } 
     else 
     { 
      return (string.Compare(a.Trim(), b.Trim(), StringComparison.OrdinalIgnoreCase) == 0); 
     } 
    } 
+5

¿Qué le hace pensar que hay un problema? La optimización prematura es una mala idea: no hay necesidad de optimizar hasta que su aplicación sea "demasiado lenta". Mientras tanto, concéntrate en código claro sobre código rápido. –

+0

¿Puede estar seguro de que el compilador no está optimizando este caso para usted de todos modos? –

+0

También me pregunto si esto realmente requiere micro-optimización? ¿De verdad tienes un problema de rendimiento en esta área? Me imagino que hay otras áreas en las que podría obtener una mejora mucho mayor en el rendimiento – AdaTheDev

Respuesta

5

Algo así debe hacerlo:

public static int TrimCompareIgnoreCase(string a, string b) { 
    int indexA = 0; 
    int indexB = 0; 
    while (indexA < a.Length && Char.IsWhiteSpace(a[indexA])) indexA++; 
    while (indexB < b.Length && Char.IsWhiteSpace(b[indexB])) indexB++; 
    int lenA = a.Length - indexA; 
    int lenB = b.Length - indexB; 
    while (lenA > 0 && Char.IsWhiteSpace(a[indexA + lenA - 1])) lenA--; 
    while (lenB > 0 && Char.IsWhiteSpace(b[indexB + lenB - 1])) lenB--; 
    if (lenA == 0 && lenB == 0) return 0; 
    if (lenA == 0) return 1; 
    if (lenB == 0) return -1; 
    int result = String.Compare(a, indexA, b, indexB, Math.Min(lenA, lenB), true); 
    if (result == 0) { 
     if (lenA < lenB) result--; 
     if (lenA > lenB) result++; 
    } 
    return result; 
} 

Ejemplo:

string a = " asdf "; 
string b = " ASDF \t "; 

Console.WriteLine(TrimCompareIgnoreCase(a, b)); 

Salida:

0 

Usted debe perfil contra un simple ajuste y comparación con algunos datos reales, para ver si realmente hay alguna diferencia de lo que se va a utilizar para.

+0

Interesante, gracias! Haré algunas comparaciones con los diferentes métodos y veré cuál aparece en la parte superior – Homde

+3

@konrad ¿Cuáles fueron los resultados de comparar esta solución con Trim? –

2

primer lugar, asegúrese de que usted realmente necesita para optimizar el código. Tal vez la creación de copias de las cadenas no afecte notablemente su programa.

Si realmente necesita optimizar, puede intentar procesar las cadenas la primera vez que las almacene en lugar de compararlas (suponiendo que ocurra en diferentes etapas del programa). Por ejemplo, almacene versiones recortadas y minúsculas de las cadenas, para que cuando las compare pueda usar simplemente verificar la equivalencia.

+0

Bueno, no hay nada de malo en usar un método más eficiente en este caso. Usar String.Compare no es un truco "inteligente", es una forma integrada de comparar cadenas que también es más eficiente que llamar a ToUpper(). ToLower(). También es más claro en su intención, así que no creo que pueda hacer un caso válido de "optimización prematura" en este caso/ –

+0

Supongo que se refería a Trim(). ToLower() –

+0

Errr, sí :-) [15chars] –

2

¿No se puede recortar (y posiblemente hacerlo en minúscula) cada cadena exactamente una vez (al obtenerla)? Hacer más sonidos como la optimización prematura ....

+0

Por supuesto, en algunos casos podría hacerlo, solo estoy interesado en ver si se puede encontrar un método de propósito general optimizado para hacer esto – Homde

3

me gustaría utilizar el código que tiene

String.Compare(txt1,txt2, StringComparison.OrdinalIgnoreCase) 

y agregar cualquier .Trim() llamadas a medida que los necesite. Esto ahorrará su opción inicial de 4 cuerdas la mayoría de las veces (.ToLower().Trim(), y dos cadenas de todo el tiempo (.ToLower()).

Si usted está sufriendo problemas de rendimiento después de esto, entonces la opción de "sucio" es probable que la mejor apuesta .

+0

Esto es interesante. Mattias: si la mayoría de sus cadenas no necesitan el llamada trim(), entonces generalmente podría hacerlo de esta manera, y si las cadenas no coinciden, repliegue e intente con una llamada trim() siguiente, entonces "realmente" devolverá que no coinciden. –

+0

Hmm , en ese caso, supongo que debería ejecutar algunas pruebas para ver si el IsPrefix()/IsSuffix() (cuatro) necesarios toma más o menos rendimiento que simplemente hacer Trim – Homde

+0

¡Ah, por supuesto! Primero haga la comparación, luego haga el comparar trim (o método sucio) si no es 0, nice – Homde

0
  1. La cosa es que, si hay que hacer, lo que hay que hacer. no creo que ninguna de sus diferentes soluciones hará una diferencia. En cada caso es necesario que haya un número de comparaciones para encontrar el espacio en blanco o eliminarlo.

    Aparentemente, la eliminación del espacio en blanco es parte del problema, entonces no debes preocuparte por eso.

  2. Y minicar una cadena antes de comparar, es un error si se trabaja con caracteres Unicode y posiblemente más lento que copiar una cadena.

0

Las advertencias son acerca de la optimización prematura son correctos, pero voy a suponer que haya probado esto y encontraron que una gran cantidad de tiempo se está desperdiciando cadenas de copiado.En ese caso, me gustaría probar lo siguiente:

int startIndex1, length1, startIndex2, length2; 
FindStartAndLength(txt1, out startIndex1, out length1); 
FindStartAndLength(txt2, out startIndex2, out length2); 

int compareLength = Math.Max(length1, length2); 
int result = string.Compare(txt1, startIndex1, txt2, startIndex2, compareLength); 

FindStartAndLength es una función que encuentra el índice de inicio y la duración de la "recortado" cadena (esto no se ha probado, pero debe dar la idea general):

static void FindStartAndLength(string text, out int startIndex, out int length) 
{ 
    startIndex = 0; 
    while(char.IsWhiteSpace(text[startIndex]) && startIndex < text.Length) 
     startIndex++; 

    length = text.Length - startIndex; 
    while(char.IsWhiteSpace(text[startIndex + length - 1]) && length > 0) 
     length--; 
} 
0

Puede implementar su propio StringComparer. He aquí una implementación básica:

public class TrimmingStringComparer : StringComparer 
{ 
    private StringComparison _comparisonType; 

    public TrimmingStringComparer() 
     : this(StringComparison.CurrentCulture) 
    { 
    } 

    public TrimmingStringComparer(StringComparison comparisonType) 
    { 
     _comparisonType = comparisonType; 
    } 

    public override int Compare(string x, string y) 
    { 
     int indexX; 
     int indexY; 
     int lengthX = TrimString(x, out indexX); 
     int lengthY = TrimString(y, out indexY); 

     if (lengthX <= 0 && lengthY <= 0) 
      return 0; // both strings contain only white space 

     if (lengthX <= 0) 
      return -1; // x contains only white space, y doesn't 

     if (lengthY <= 0) 
      return 1; // y contains only white space, x doesn't 

     if (lengthX < lengthY) 
      return -1; // x is shorter than y 

     if (lengthY < lengthX) 
      return 1; // y is shorter than x 

     return String.Compare(x, indexX, y, indexY, lengthX, _comparisonType); 
    } 

    public override bool Equals(string x, string y) 
    { 
     return Compare(x, y) == 0; 
    } 

    public override int GetHashCode(string obj) 
    { 
     throw new NotImplementedException(); 
    } 

    private int TrimString(string s, out int index) 
    { 
     index = 0; 
     while (index < s.Length && Char.IsWhiteSpace(s, index)) index++; 
     int last = s.Length - 1; 
     while (last >= 0 && Char.IsWhiteSpace(s, last)) last--; 
     return last - index + 1; 
    } 
} 

Observaciones: insectos

  • no está ampliamente probado y podría contener
  • rendimiento aún no se ha evaluado (pero probablemente es mejor que llamar Trim y ToLower de todos modos)
  • el método GetHashCode no está implementado, por lo tanto, no lo use como una clave en un diccionario
0

Observo que su primera sugerencia solo se compara por igualdad en lugar de ordenar, lo que permite un mayor ahorro de eficiencia.

public static bool TrimmedOrdinalIgnoreCaseEquals(string x, string y) 
{ 
    //Always check for identity (same reference) first for 
    //any comparison (equality or otherwise) that could take some time. 
    //Identity always entails equality, and equality always entails 
    //equivalence. 
    if(ReferenceEquals(x, y)) 
     return true; 
    //We already know they aren't both null as ReferenceEquals(null, null) 
    //returns true. 
    if(x == null || y == null) 
     return false; 
    int startX = 0; 
    //note we keep this one further than the last char we care about. 
    int endX = x.Length; 
    int startY = 0; 
    //likewise, one further than we care about. 
    int endY = y.Length; 
    while(startX != endX && char.IsWhiteSpace(x[startX])) 
     ++startX; 
    while(startY != endY && char.IsWhiteSpace(y[startY])) 
     ++startY; 
    if(startX == endX)  //Empty when trimmed. 
     return startY == endY; 
    if(startY == endY) 
     return false; 
    //lack of bounds checking is safe as we would have returned 
    //already in cases where endX and endY can fall below zero. 
    while(char.IsWhiteSpace(x[endX - 1])) 
     --endX; 
    while(char.IsWhiteSpace(y[endY - 1])) 
     --endY; 
    //From this point on I am assuming you do not care about 
    //the complications of case-folding, based on your example 
    //referencing the ordinal version of string comparison 
    if(endX - startX != endY - startY) 
     return false; 
    while(startX != endX) 
    { 
     //trade-off: with some data a case-sensitive 
     //comparison first 
     //could be more efficient. 
     if(
      char.ToLowerInvariant(x[startX++]) 
      != char.ToLowerInvariant(y[startY++]) 
     ) 
      return false; 
    } 
    return true; 
} 

Por supuesto, lo que es un verificador de igualdad sin un productor coincidente código hash:

public static int TrimmedOrdinalIgnoreCaseHashCode(string str) 
{ 
    //Higher CMP_NUM (or get rid of it altogether) gives 
    //better hash, at cost of taking longer to compute. 
    const int CMP_NUM = 12; 
    if(str == null) 
     return 0; 
    int start = 0; 
    int end = str.Length; 
    while(start != end && char.IsWhiteSpace(str[start])) 
     ++start; 
    if(start != end) 
     while(char.IsWhiteSpace(str[end - 1])) 
      --end; 

    int skipOn = (end - start)/CMP_NUM + 1; 
    int ret = 757602046; // no harm matching native .NET with empty string. 
    while(start < end) 
    { 
      //prime numbers are our friends. 
     ret = unchecked(ret * 251 + (int)(char.ToLowerInvariant(str[start]))); 
     start += skipOn; 
    } 
    return ret; 
}