2011-01-17 15 views
29

Jak mogę porównać 2 ciągi w C# ignorując wielkość liter, spacje i ewentualne podziały wierszy. Muszę również sprawdzić, czy oba łańcuchy mają wartość NULL, a następnie są oznaczone jako takie same.Porównywanie ciągów znaków w C# ignorowanie spacji, powrót karetki lub podziały wierszy

Dzięki!

+1

zobaczyć również http://stackoverflow.com/questions/6859255/how-do-i-make-my-string-compare- nieczułe na ignorowanie-górników-różnice-w-wh/6859344 # 6859344 –

+0

To inne pytanie SO odnotowuje CompareOptions.IgnoreSymbols na String.Compare - które odpowiada temu wymaganiu – MrTelly

Odpowiedz

3

Usuń wszystkie znaki, których nie chcesz, a następnie użyj metody ToLower(), aby zignorować wielkość liter.

edytuj: Chociaż powyższe działa, lepiej jest użyć StringComparison.OrdinalIgnoreCase. Wystarczy przekazać go jako drugi argument metody Equals.

+22

-1: Należy użyć StringComparison.OrdinalIgnoreCase, a nie ToLower(). – zimdanen

+1

Nie używałbym OrdininalIgnoreCase ignorować aktualną kulturę, więc działa na (Na przykład) turecki wielkie litery i problem? –

+2

Metoda z 'StringComparison.OrdinalIgnoreCase' nie rozwiąże problemu z ignorowaniem nowych linii. – cederlof

4

Najprawdopodobniej zacznę od usunięcia znaków, których nie chcę porównywać, przed porównaniem. Jeśli wydajność jest problemem, możesz przyjrzeć się przechowywaniu wersji każdego ciągu znaków z już usuniętymi znakami.

Alternatywnie można napisać procedurę porównania, która pomijałaby znaki, które mają zostać zignorowane. Ale to po prostu wydaje mi się większą pracą.

+0

+1 http://msdn.microsoft.com /en-us/library/aa904305 (VS.71).aspx, następnie http://msdn.microsoft.com/en-us/library/system.string.join.aspx i porównaj. – kenny

59

Powinieneś znormalizować każdy ciąg, usuwając znaki, których nie chcesz porównywać, a następnie możesz wykonać z StringComparison, który ignoruje wielkość liter.

coś takiego:

string s1 = "HeLLo wOrld!"; 
string s2 = "Hello\n WORLd!"; 

string normalized1 = Regex.Replace(s1, @"\s", ""); 
string normalized2 = Regex.Replace(s2, @"\s", ""); 

bool stringEquals = String.Equals(
    normalized1, 
    normalized2, 
    StringComparison.OrdinalIgnoreCase); 

Console.WriteLine(stringEquals); 

Tutaj Regex.Replace służy najpierw usunąć wszystkie białe znaki. Specjalny przypadek obu łańcuchów o wartości null nie jest tutaj traktowany, ale można łatwo obsłużyć tę sprawę przed wykonaniem normalizacji ciągu znaków.

+0

Czy wystąpiłby jakikolwiek wpływ wydajności na użycie 'Regex.Replace' na dwóch ciągach tutaj? – JDandChips

+0

To nie jest łatwo odpowiedzieć. Oczywiście istnieją lepsze rozwiązania pod względem wydajności, na przykład rozwiązanie, które nie wymaga tworzenia dwóch nowych łańcuchów, ale jeśli nie udowodnisz, że wyrażenie regularne jest wąskim gardłem w twoim konkretnym scenariuszu, nie zawracałbym sobie tym głowy. –

3

Najpierw zastąp wszystkie białe znaki za pomocą wyrażenia regularnego z obu ciągów, a następnie użyj metody String.Compare z parametrem ignoreCase = true.

string a = System.Text.RegularExpressions.Regex.Replace("void foo", @"\s", ""); 
string b = System.Text.RegularExpressions.Regex.Replace("voidFoo", @"\s", ""); 
bool isTheSame = String.Compare(a, b, true) == 0; 
-2

Spróbuj użyć zamiast tego, jego znacznie czystsze i zabiera go z systemu operacyjnego, który używasz:

Environment.NewLine

+0

Za co, a zamiast czego? –

0

Można także skorzystać z następującej funkcji niestandardowej

public static string ExceptChars(this string str, IEnumerable<char> toExclude) 
     { 
      StringBuilder sb = new StringBuilder(); 
      for (int i = 0; i < str.Length; i++) 
      { 
       char c = str[i]; 
       if (!toExclude.Contains(c)) 
        sb.Append(c); 
      } 
      return sb.ToString(); 
     } 

     public static bool SpaceCaseInsenstiveComparision(this string stringa, string stringb) 
     { 
      return (stringa==null&&stringb==null)||stringa.ToLower().ExceptChars(new[] { ' ', '\t', '\n', '\r' }).Equals(stringb.ToLower().ExceptChars(new[] { ' ', '\t', '\n', '\r' })); 
     } 

A następnie użyj go w następujący sposób:

"Te st".SpaceCaseInsenstiveComparision("Te st"); 
+1

Chciałbym uniknąć wywołań '.ToLower()' (ponieważ tworzą kolejny ciąg) i użyć 'StringComparison.OrdinalIgnoreCase' (który jest również szybszy). –

3

Jeśli potrzebujesz wydajności, rozwiązania Regex na tej stronie działają zbyt wolno. Może masz dużą listę ciągów, które chcesz posortować. (Rozwiązanie Regex jest jednak bardziej czytelne)

Mam klasę, która analizuje każdy znak w obu łańcuchach i porównuje je, ignorując przypadek i białe znaki. Nie przydziela nowych ciągów. Używa ona wartości char.IsWhiteSpace(ch) do określenia białych znaków i do rozróżniania dużych i małych liter (jeśli jest to wymagane). W moich testach moje rozwiązanie działa około 5x - 8x szybciej niż rozwiązanie oparte na Regex. Moja klasa implementuje również metodę IEqualityComparer pod nazwą GetHashCode(obj) przy użyciu this code w innej odpowiedzi SO. Ten GetHashCode(obj) również ignoruje białe znaki i opcjonalnie ignoruje wielkość liter.

Oto moja klasa:

private class StringCompIgnoreWhiteSpace : IEqualityComparer<string> 
{ 
    public bool Equals(string strx, string stry) 
    { 
     if (strx == null) //stry may contain only whitespace 
      return string.IsNullOrWhiteSpace(stry); 

     else if (stry == null) //strx may contain only whitespace 
      return string.IsNullOrWhiteSpace(strx); 

     int ix = 0, iy = 0; 
     for (; ix < strx.Length && iy < stry.Length; ix++, iy++) 
     { 
      char chx = strx[ix]; 
      char chy = stry[iy]; 

      //ignore whitespace in strx 
      while (char.IsWhiteSpace(chx) && ix < strx.Length) 
      { 
       ix++; 
       chx = strx[ix]; 
      } 

      //ignore whitespace in stry 
      while (char.IsWhiteSpace(chy) && iy < stry.Length) 
      { 
       iy++; 
       chy = stry[iy]; 
      } 

      if (ix == strx.Length && iy != stry.Length) 
      { //end of strx, so check if the rest of stry is whitespace 
       for (int iiy = iy + 1; iiy < stry.Length; iiy++) 
       { 
        if (!char.IsWhiteSpace(stry[iiy])) 
         return false; 
       } 
       return true; 
      } 

      if (ix != strx.Length && iy == stry.Length) 
      { //end of stry, so check if the rest of strx is whitespace 
       for (int iix = ix + 1; iix < strx.Length; iix++) 
       { 
        if (!char.IsWhiteSpace(strx[iix])) 
         return false; 
       } 
       return true; 
      } 

      //The current chars are not whitespace, so check that they're equal (case-insensitive) 
      //Remove the following two lines to make the comparison case-sensitive. 
      chx = char.ToLowerInvariant(chx); 
      chy = char.ToLowerInvariant(chy); 

      if (chx != chy) 
       return false; 
     } 

     //If strx has more chars than stry 
     for (; ix < strx.Length; ix++) 
     { 
      if (!char.IsWhiteSpace(strx[ix])) 
       return false; 
     } 

     //If stry has more chars than strx 
     for (; iy < stry.Length; iy++) 
     { 
      if (!char.IsWhiteSpace(stry[iy])) 
       return false; 
     } 

     return true; 
    } 

    public int GetHashCode(string obj) 
    { 
     if (obj == null) 
      return 0; 

     int hash = 17; 
     unchecked // Overflow is fine, just wrap 
     { 
      for (int i = 0; i < obj.Length; i++) 
      { 
       char ch = obj[i]; 
       if(!char.IsWhiteSpace(ch)) 
        //use this line for case-insensitivity 
        hash = hash * 23 + char.ToLowerInvariant(ch).GetHashCode(); 

        //use this line for case-sensitivity 
        //hash = hash * 23 + ch.GetHashCode(); 
      } 
     } 
     return hash; 
    } 
} 

private static void TestComp() 
{ 
    var comp = new StringCompIgnoreWhiteSpace(); 

    Console.WriteLine(comp.Equals("abcd", "abcd")); //true 
    Console.WriteLine(comp.Equals("abCd", "Abcd")); //true 
    Console.WriteLine(comp.Equals("ab Cd", "Ab\n\r\tcd ")); //true 
    Console.WriteLine(comp.Equals(" ab Cd", " A b" + Environment.NewLine + "cd ")); //true 
    Console.WriteLine(comp.Equals(null, " \t\n\r ")); //true 
    Console.WriteLine(comp.Equals(" \t\n\r ", null)); //true 
    Console.WriteLine(comp.Equals("abcd", "abcd h")); //false 

    Console.WriteLine(comp.GetHashCode(" a b c d")); //-699568861 


    //This is -699568861 if you #define StringCompIgnoreWhiteSpace_CASE_INSENSITIVE 
    // Otherwise it's -1555613149 
    Console.WriteLine(comp.GetHashCode("A B c  \t  d")); 
} 

Oto mój kod badania (z regex przykład):

private static void SpeedTest() 
{ 
    const int loop = 100000; 
    string first = "a bc d"; 
    string second = "ABC D"; 

    var compChar = new StringCompIgnoreWhiteSpace(); 
    Stopwatch sw1 = Stopwatch.StartNew(); 
    for (int i = 0; i < loop; i++) 
    { 
     bool equals = compChar.Equals(first, second); 
    } 
    sw1.Stop(); 
    Console.WriteLine(string.Format("char time = {0}", sw1.Elapsed)); //char time = 00:00:00.0361159 

    var compRegex = new StringCompIgnoreWhiteSpaceRegex(); 
    Stopwatch sw2 = Stopwatch.StartNew(); 
    for (int i = 0; i < loop; i++) 
    { 
     bool equals = compRegex.Equals(first, second); 
    } 
    sw2.Stop(); 
    Console.WriteLine(string.Format("regex time = {0}", sw2.Elapsed)); //regex time = 00:00:00.2773072 
} 

private class StringCompIgnoreWhiteSpaceRegex : IEqualityComparer<string> 
{ 
    public bool Equals(string strx, string stry) 
    { 
     if (strx == null) 
      return string.IsNullOrWhiteSpace(stry); 
     else if (stry == null) 
      return string.IsNullOrWhiteSpace(strx); 

     string a = System.Text.RegularExpressions.Regex.Replace(strx, @"\s", ""); 
     string b = System.Text.RegularExpressions.Regex.Replace(stry, @"\s", ""); 
     return String.Compare(a, b, true) == 0; 
    } 

    public int GetHashCode(string obj) 
    { 
     if (obj == null) 
      return 0; 

     string a = System.Text.RegularExpressions.Regex.Replace(obj, @"\s", ""); 
     return a.GetHashCode(); 
    } 
} 
0

Inną opcją jest LINQ SequenceEquals metoda według moich testów jest ponad dwukrotnie szybszy niż t on Regex podejście stosowane w innych odpowiedziach i bardzo łatwe do odczytania i utrzymania.

public static bool Equals_Linq(string s1, string s2) 
{ 
    return Enumerable.SequenceEqual(
     s1.Where(c => !char.IsWhiteSpace(c)).Select(char.ToUpperInvariant), 
     s2.Where(c => !char.IsWhiteSpace(c)).Select(char.ToUpperInvariant)); 
} 

public static bool Equals_Regex(string s1, string s2) 
{ 
    return string.Equals(
     Regex.Replace(s1, @"\s", ""), 
     Regex.Replace(s2, @"\s", ""), 
     StringComparison.OrdinalIgnoreCase); 
} 

Oto prosty kod testowy wydajność użyłem:

var s1 = "HeLLo wOrld!"; 
var s2 = "Hello\n WORLd!"; 
var watch = Stopwatch.StartNew(); 
for (var i = 0; i < 1000000; i++) 
{ 
    Equals_Linq(s1, s2); 
} 
Console.WriteLine(watch.Elapsed); // ~1.7 seconds 
watch = Stopwatch.StartNew(); 
for (var i = 0; i < 1000000; i++) 
{ 
    Equals_Regex(s1, s2); 
} 
Console.WriteLine(watch.Elapsed); // ~4.6 seconds 
Powiązane problemy