web-dev-qa-db-fra.com

Comparaison de chaînes C # en ignorant les espaces, les retours chariot ou les sauts de ligne

Comment puis-je comparer 2 chaînes en C # en ignorant la casse, les espaces et les sauts de ligne. Je dois également vérifier si les deux chaînes sont nulles, elles sont marquées comme identiques.

Merci!

38
Kim

Supprimez tous les caractères dont vous ne voulez pas, puis utilisez la méthode ToLower () pour ignorer la casse.

edit: Bien que ce qui précède fonctionne, il est préférable d'utiliser StringComparison.OrdinalIgnoreCase. Passez-le simplement comme deuxième argument à la méthode Equals.

6
helloworld922

Vous devez normaliser chaque chaîne en supprimant les caractères que vous ne souhaitez pas comparer, puis vous pouvez effectuer une String.Equals avec un StringComparison qui ignore la casse.

Quelque chose comme ça:

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

Ici Regex.Replace est utilisé en premier pour supprimer tous les espaces. Le cas spécial de nullité des deux chaînes n'est pas traité ici, mais vous pouvez facilement gérer ce cas avant d'effectuer la normalisation de chaîne.

76
João Angelo

Si vous avez besoin de performances, les solutions Regex de cette page fonctionnent trop lentement pour vous. Vous avez peut-être une grande liste de chaînes que vous souhaitez trier. (Une solution Regex est cependant plus lisible)

J'ai une classe qui examine chaque caractère individuel dans les deux chaînes et les compare tout en ignorant la casse et les espaces. Il n'alloue aucune nouvelle chaîne. Il utilise char.IsWhiteSpace(ch) pour déterminer les espaces blancs et char.ToLowerInvariant(ch) pour ne pas tenir compte de la casse (si nécessaire). Lors de mes tests, ma solution s'exécute environ 5 fois - 8 fois plus rapidement qu'une solution basée sur Regex. Ma classe implémente également la méthode GetHashCode(obj) d'IEqualityComparer en utilisant ce code dans une autre réponse SO. Cette GetHashCode(obj) ignore également les espaces blancs et éventuellement ignore la casse.

Voici ma classe:

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

Voici mon code de test (avec un exemple 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();
    }
}
5
user2023861

Remplacez d'abord tous les espaces via l'expression régulière des deux chaînes, puis utilisez le String.Compare méthode avec le paramètre 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;
4
Martin Buberl

Je commencerais probablement par supprimer les caractères que vous ne voulez pas comparer de la chaîne avant de comparer. Si les performances sont un problème, vous pouvez envisager de stocker une version de chaque chaîne avec les caractères déjà supprimés.

Alternativement, vous pouvez écrire une routine de comparaison qui ignorerait les caractères que vous souhaitez ignorer. Mais cela me semble plus de travail.

3
Jonathan Wood

Cela peut également fonctionner.

String.Compare(s1, s2, CultureInfo.CurrentCulture, CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols) == 0
2
Louis

Une autre option est la méthode LINQ SequenceEquals qui, selon mes tests, est plus de deux fois plus rapide que l'approche Regex utilisée dans d'autres réponses et très facile à lire et à maintenir.

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

Voici le code de test de performance simple que j'ai utilisé:

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
Nathan Baulch

Une approche non optimisée pour la performance, mais pour l'exhaustivité.

  • normalise null
  • normalise unicode, combinant des caractères, des signes diacritiques
  • normalise les nouvelles lignes
  • normalise l'espace blanc
  • normalise le boîtier

extrait de code:

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
dfhwze

Vous pouvez également utiliser la fonction personnalisée suivante

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

Et puis utilisez-le de la manière suivante

"Te  st".SpaceCaseInsenstiveComparision("Te st");
1
Zain Ali
  1. Je couperais la chaîne en utilisant Trim() pour supprimer tous les
    espace blanc.
  2. Utilisez StringComparison.OrdinalIgnoreCase Pour ignorer la casse, ex. stringA.Equals(stringB, StringComparison.OrdinalIgnoreCase)
0
Jayowl