Сравнение строк в C # без учета пробелов, возврата каретки или переносов строк - PullRequest
39 голосов
/ 18 января 2011

Как я могу сравнить 2 строки в C #, игнорируя регистр, пробелы и любые переносы строк.Мне также нужно проверить, являются ли обе строки нулевыми, тогда они помечены как одинаковые.

Спасибо!

Ответы [ 10 ]

75 голосов
/ 18 января 2011

Вы должны нормализовать каждую строку, удалив символы, которые вы не хотите сравнивать, а затем вы можете выполнить String.Equals с StringComparison, который игнорирует регистр.

Примерно так:

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

Здесь Regex.Replace используется первым, чтобы удалить все пробельные символы. Особый случай, когда обе строки имеют значение null, здесь не рассматривается, но вы можете легко обработать этот случай перед выполнением нормализации строки.

6 голосов
/ 18 января 2011

Удалите все символы, которые вам не нужны, и затем используйте метод ToLower (), чтобы игнорировать регистр.

edit: Хотя вышеприведенное работает, лучше использовать StringComparison.OrdinalIgnoreCase.Просто передайте его как второй аргумент методу Equals.

5 голосов
/ 18 сентября 2013

Если вам нужна производительность, решения Regex на этой странице работают слишком медленно для вас.Возможно, у вас есть большой список строк, которые вы хотите отсортировать.(Однако решение Regex более читабельно)

У меня есть класс, который просматривает каждый отдельный символ в обеих строках и сравнивает их, игнорируя регистр и пробелы.Он не выделяет никаких новых строк.Он использует char.IsWhiteSpace(ch) для определения пробела и char.ToLowerInvariant(ch) для учета регистра (если требуется).В моем тестировании мое решение работает примерно в 5-8 раз быстрее, чем решение на основе Regex.Мой класс также реализует метод GetHashCode(obj) IEqualityComparer, используя этот код в другом ответе SO.Это GetHashCode(obj) также игнорирует пробелы и, возможно, игнорирует регистр.

Вот мой класс:

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

Вот мой тестовый код (с примером Regex):

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();
    }
}
4 голосов
/ 18 января 2011

Сначала замените все пробелы регулярным выражением из обеих строк, а затем используйте метод String.Compare с параметром 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;
3 голосов
/ 18 января 2011

Я бы, вероятно, начал с удаления символов, которые вы не хотите сравнивать, из строки перед сравнением. Если производительность является проблемой, вы можете посмотреть на сохранение версии каждой строки с уже удаленными символами.

Кроме того, вы можете написать процедуру сравнения, которая пропустит символы, которые вы хотите игнорировать. Но мне кажется, что это больше работы.

1 голос
/ 06 июля 2019

Подход не оптимизирован для производительности, но для полноты.

  • нормализует null
  • нормализует юникод, объединяет символы, диакритические знаки
  • нормализует новые строки
  • нормализует пустое пространство
  • нормализует корпус

фрагмент кода:

public static class StringHelper
{
    public static bool AreEquivalent(string source, string target)
    {
        if (source == null) return target == null;
        if (target == null) return false;
        var normForm1 = Normalize(source);
        var normForm2 = Normalize(target);
        return string.Equals(normForm1, normForm2);
    }

    private static string Normalize(string value)
    {
        Debug.Assert(value != null);
        // normalize unicode, combining characters, diacritics
        value = value.Normalize(NormalizationForm.FormC);
        // normalize new lines to white space
        value = value.Replace("\r\n", "\n").Replace("\r", "\n");
        // normalize white space
        value = Regex.Replace(value, @"\s", string.Empty);
        // normalize casing
        return value.ToLowerInvariant();
    }
}
1 голос
/ 21 мая 2019

Это тоже может сработать.

String.Compare(s1, s2, CultureInfo.CurrentCulture, CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols) == 0
1 голос
/ 16 июля 2015

Другим вариантом является метод LINQ SequenceEquals, который, согласно моим тестам, более чем в два раза быстрее, чем подход Regex, используемый в других ответах, и очень прост для чтения и обслуживания.

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

Вот простой код теста производительности, который я использовал:

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
1 голос
/ 13 марта 2013

Вы также можете использовать следующую пользовательскую функцию

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

И затем использовать ее следующим образом

"Te  st".SpaceCaseInsenstiveComparision("Te st");
0 голосов
/ 03 мая 2019
  1. Я бы обрезал строку, используя Trim(), чтобы удалить все
    пробельные.
  2. Используйте StringComparison.OrdinalIgnoreCase, чтобы игнорировать чувствительность к регистру, напр. stringA.Equals(stringB, StringComparison.OrdinalIgnoreCase)
...