2009-12-07 12 views
9

Ho bisogno di fare un sacco di confronti tra stringhe insensibili alle maiuscole e alte prestazioni e ho capito che il mio modo di farlo. ToLower(). Trim() era davvero stupido a causa di tutte le nuove stringhe essere allocatoString.comparison performance (con trim)

Così ho fatta una buca in giro un po 'e in questo modo sembra preferibile:

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

L'unico problema qui è che voglio ignorare spazi iniziali o finali, vale a dire Trim(), ma se uso Trim I hanno lo stesso problema con le allocazioni di stringhe. Immagino di poter controllare ogni stringa e vedere se StartsWith ("") o EndsWith ("") e solo poi Trim. Uno che o capire il dell'indice, di lunghezza per ogni stringa e passare al string.Compare ignorare

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

ma che sembra piuttosto disordinato e probabilmente devo usare per alcuni interi se io non fare davvero un grande if-else dichiarazione per ogni combinazione di trailing e lead blanks su entrambe le stringhe ... quindi qualsiasi idea di una soluzione elegante?

Ecco la mia proposta:

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

Cosa ti fa pensare che c'è un problema? L'ottimizzazione prematura è una cattiva idea: non è necessario ottimizzare finché la tua applicazione non diventa "troppo lenta". Nel frattempo, concentrati sul codice chiaro su un codice veloce. –

+0

Sei sicuro che il compilatore non stia comunque ottimizzando un caso del genere? –

+0

Mi piacerebbe anche chiedermi se questo richiede veramente micro-ottimizzazione? Hai davvero un problema di prestazioni in questo settore? Immagino che ci siano altre aree in cui potresti ottenere molto più miglioramenti nelle prestazioni – AdaTheDev

risposta

5

Qualcosa del genere dovrebbe farlo:

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

Esempio:

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

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

uscita:

0 

Si dovrebbe profilo it contro una semplice cornice e confronta con alcuni dati reali, per vedere se c'è davvero alcuna differenza per quello che avete intenzione di usarlo per.

+0

Interessante, grazie! Farò alcuni confronti con i diversi metodi e vedremo quale si presenta in cima – Homde

+3

@konrad Quali sono stati i risultati del confronto di questa soluzione con Trim? –

2

innanzitutto assicurarsi che si ha realmente bisogno per ottimizzare questo codice. Forse la creazione di copie delle stringhe non influenzerà sensibilmente il tuo programma.

Se è davvero necessario ottimizzare, è possibile provare a elaborare le stringhe quando si memorizzano prima anziché quando le si confronta (presumendo che ciò avvenga in fasi diverse del programma). Ad esempio, memorizza le versioni tagliate e minuscole delle stringhe, in modo tale che quando le confronti si possa usare semplicemente controllare l'equivalenza.

+0

Beh, non c'è niente di sbagliato nell'usare un metodo più efficiente in questo caso. L'uso di String.Compare non è un trucco "intelligente", ma è un modo integrato per confrontare le stringhe che è anche più efficiente di chiamare ToUpper(). ToLower(). È anche più chiaro nelle intenzioni, quindi non credo che tu possa fare un caso valido di "ottimizzazione prematura" in questa istanza/ –

+0

Prendo ciò che intendevi per Trim(). ToLower() –

+0

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

2

Non puoi semplicemente tagliare (e possibilmente renderlo in minuscolo) ogni stringa esattamente una volta (quando lo ottieni)? Fare di più suona come l'ottimizzazione prematura ....

+0

Ovviamente in alcuni casi potrei farlo, solo interessato a vedere se si riuscisse a trovare un metodo ottimizzato per lo scopo generale per fare questo – Homde

3

vorrei utilizzare il codice che avete

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

e aggiungere qualsiasi .Trim() chiamate quando ne hai bisogno. Ciò farà risparmiare la vostra opzione iniziale 4 stringhe maggior parte del tempo (.ToLower().Trim() e due stringhe per tutto il tempo (.ToLower()).

Se si soffre problemi di prestazioni dopo questo, allora la vostra opzione "disordinato" è probabilmente la soluzione migliore .

+0

Questo è interessante. Mattias: se la maggior parte delle stringhe non ha bisogno del trim(), quindi puoi generalmente farlo in questo modo, e se le stringhe non corrispondono, fallback e prova con una chiamata a trim(), quindi "veramente" restituisce che non sono corrispondenti. –

+0

Hmm , in questo caso, suppongo che dovrei eseguire alcuni test per vedere se l'IsPrefix()/IsSuffix() necessario (quattro di essi) richiede prestazioni più o meno del semplice Trim – Homde

+0

Ah! prima fai il confronto, poi esegui assetto comparabile (o metehod disordinato) se non 0, nice – Homde

0
  1. Il fatto è che, se ha bisogno di essere fatto, deve essere fatto. non credo che nessuna delle vostre diverse soluzioni farà la differenza. In ogni caso ci deve essere un numero di confronti per trovare lo spazio bianco o rimuoverlo

    A quanto pare, la rimozione di spazi vuoti fa parte del problema, quindi non dovresti preoccuparti di questo.

  2. E una stringa in minuscolo prima del confronto, è un bug se si lavora con caratteri unicode e possibilmente più lento della copia di una stringa.

0

Gli avvisi sono circa ottimizzazione prematura sono corrette, ma darò per scontato che hai testato questo e ha scoperto che un sacco di tempo viene sprecata stringhe di copia.In quel caso avrei prova i seguenti:

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 è una funzione che trova l'indice iniziale e lunghezza della "tagliata" stringa (questo non è testato, ma dovrebbe dare l'idea generale):

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

È possibile implementare il proprio StringComparer. Ecco un'implementazione di base:

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

Osservazione: bug

  • non è ampiamente testato e potrebbe contenere
  • prestazioni è ancora da valutare (ma probabilmente è meglio che chiamare Trim e ToLower comunque)
  • il metodo GetHashCode non è implementata, quindi non usarlo come una chiave in un dizionario
0

Ho notato che il tuo primo suggerimento si confronta solo per l'uguaglianza piuttosto che per l'ordinamento, che consente ulteriori risparmi di efficienza.

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

Naturalmente, quello che è un ispettore uguaglianza senza un produttore corrispondente codice 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; 
}