String.Replace ignorant la casse

214

J'ai une chaîne appelée "hello world"

J'ai besoin de remplacer le mot "monde" par "csharp"

pour cela j'utilise:

string.Replace("World", "csharp");

mais en conséquence, je ne fais pas remplacer la chaîne. La raison en est la sensibilité à la casse. La chaîne d'origine contient "world" alors que j'essaie de remplacer "World".

Existe-t-il un moyen d'éviter cette sensibilité à la casse dans la méthode string.Replace?

Sandeep
la source
5
Ici, vous trouvez un problème similaire: Y a
Michał Kuliński
1
Duplication possible de Y a
Jon Schneider

Réponses:

309

Vous pouvez utiliser un Regex et effectuer un remplacement insensible à la casse:

class Program
{
    static void Main()
    {
        string input = "hello WoRlD";
        string result = 
           Regex.Replace(input, "world", "csharp", RegexOptions.IgnoreCase);
        Console.WriteLine(result); // prints "hello csharp"
    }
}
Darin Dimitrov
la source
19
Ne fonctionne pas avec les éléments du langage Regex , ce n'est donc pas une méthode universelle. La réponse de Steve B est correcte.
AsValeO
1
Donc, vous feriez mieux de ne pas écrire hello. world?ou autre chose contenant des opérateurs regex.
Sebastian Mach
Juste au cas où quelqu'un ne serait pas enclin à lire plus loin, c'était la réponse acceptée en 2011 et a un grand nombre de votes. Cela fonctionne bien si vous ne devez remplacer que des caractères alphanumériques. Cependant, si vous devez remplacer des caractères de ponctuation, vous pouvez avoir de gros problèmes. La réponse d'Oleg Zarevennyi est supérieure, mais n'a qu'un petit nombre de votes car elle a été publiée en 2017.
Tony Pulokas
115
var search = "world";
var replacement = "csharp";
string result = Regex.Replace(
    stringToLookInto,
    Regex.Escape(search), 
    replacement.Replace("$","$$"), 
    RegexOptions.IgnoreCase
);

Le Regex.Escape est utile si vous comptez sur l' entrée d'utilisateur qui peut contenir des éléments de langage Regex

Mettre à jour

Grâce aux commentaires, vous n'avez en fait pas besoin d'échapper à la chaîne de remplacement.

Voici un petit violon qui teste le code :

using System;
using System.Text.RegularExpressions;           
public class Program
{
    public static void Main()
    {

        var tests = new[] {
            new { Input="abcdef", Search="abc", Replacement="xyz", Expected="xyzdef" },
            new { Input="ABCdef", Search="abc", Replacement="xyz", Expected="xyzdef" },
            new { Input="A*BCdef", Search="a*bc", Replacement="xyz", Expected="xyzdef" },
            new { Input="abcdef", Search="abc", Replacement="x*yz", Expected="x*yzdef" },       
            new { Input="abcdef", Search="abc", Replacement="$", Expected="$def" },
        };


        foreach(var test in tests){
            var result = ReplaceCaseInsensitive(test.Input, test.Search, test.Replacement);

            Console.WriteLine(
                "Success: {0}, Actual: {1}, {2}",
                result == test.Expected,
                result,
                test
            );

        }


    }

    private static string ReplaceCaseInsensitive(string input, string search, string replacement){
        string result = Regex.Replace(
            input,
            Regex.Escape(search), 
            replacement.Replace("$","$$"), 
            RegexOptions.IgnoreCase
        );
        return result;
    }
}

Sa sortie est:

Success: True, Actual: xyzdef, { Input = abcdef, Search = abc, Replacement = xyz, Expected = xyzdef } 
Success: True, Actual: xyzdef, { Input = ABCdef, Search = abc, Replacement = xyz, Expected = xyzdef }
Success: True, Actual: xyzdef, { Input = A*BCdef, Search = a*bc, Replacement = xyz, Expected = xyzdef } 
Success: True, Actual: x*yzdef, { Input = abcdef, Search = abc, Replacement = x*yz, Expected = x*yzdef} 
Success: True, Actual: $def, { Input = abcdef, Search = abc, Replacement = $, Expected = $def }
Steve B
la source
2
Cette méthode échoue si remplacement = "! @ # $% ^ & * ()" Vous obtenez "! @ \ # \ $% \ ^ & * ()" Remplacé à la place.
Kcoder
2
La seconde Regex.Escapeest mauvaise, elle préfixera les caractères spéciaux avec des barres obliques inverses. On dirait que la meilleure façon est .Replace ("$", "$$"), qui est un peu stupide ( stackoverflow.com/a/10078353 ).
Danny Tuppeny
1
@dannyTuppeny: vous avez raison ... J'ai mis à jour la réponse en conséquence
Steve B
54

Méthode 2.5X PLUS RAPIDE et PLUS EFFICACE que les autres méthodes d'expressions régulières:

/// <summary>
/// Returns a new string in which all occurrences of a specified string in the current instance are replaced with another 
/// specified string according the type of search to use for the specified string.
/// </summary>
/// <param name="str">The string performing the replace method.</param>
/// <param name="oldValue">The string to be replaced.</param>
/// <param name="newValue">The string replace all occurrences of <paramref name="oldValue"/>. 
/// If value is equal to <c>null</c>, than all occurrences of <paramref name="oldValue"/> will be removed from the <paramref name="str"/>.</param>
/// <param name="comparisonType">One of the enumeration values that specifies the rules for the search.</param>
/// <returns>A string that is equivalent to the current string except that all instances of <paramref name="oldValue"/> are replaced with <paramref name="newValue"/>. 
/// If <paramref name="oldValue"/> is not found in the current instance, the method returns the current instance unchanged.</returns>
[DebuggerStepThrough]
public static string Replace(this string str,
    string oldValue, string @newValue,
    StringComparison comparisonType)
{

    // Check inputs.
    if (str == null)
    {
        // Same as original .NET C# string.Replace behavior.
        throw new ArgumentNullException(nameof(str));
    }
    if (str.Length == 0)
    {
        // Same as original .NET C# string.Replace behavior.
        return str;
    }
    if (oldValue == null)
    {
        // Same as original .NET C# string.Replace behavior.
        throw new ArgumentNullException(nameof(oldValue));
    }
    if (oldValue.Length == 0)
    {
        // Same as original .NET C# string.Replace behavior.
        throw new ArgumentException("String cannot be of zero length.");
    }


    //if (oldValue.Equals(newValue, comparisonType))
    //{
    //This condition has no sense
    //It will prevent method from replacesing: "Example", "ExAmPlE", "EXAMPLE" to "example"
    //return str;
    //}



    // Prepare string builder for storing the processed string.
    // Note: StringBuilder has a better performance than String by 30-40%.
    StringBuilder resultStringBuilder = new StringBuilder(str.Length);



    // Analyze the replacement: replace or remove.
    bool isReplacementNullOrEmpty = string.IsNullOrEmpty(@newValue);



    // Replace all values.
    const int valueNotFound = -1;
    int foundAt;
    int startSearchFromIndex = 0;
    while ((foundAt = str.IndexOf(oldValue, startSearchFromIndex, comparisonType)) != valueNotFound)
    {

        // Append all characters until the found replacement.
        int @charsUntilReplacment = foundAt - startSearchFromIndex;
        bool isNothingToAppend = @charsUntilReplacment == 0;
        if (!isNothingToAppend)
        {
            resultStringBuilder.Append(str, startSearchFromIndex, @charsUntilReplacment);
        }



        // Process the replacement.
        if (!isReplacementNullOrEmpty)
        {
            resultStringBuilder.Append(@newValue);
        }


        // Prepare start index for the next search.
        // This needed to prevent infinite loop, otherwise method always start search 
        // from the start of the string. For example: if an oldValue == "EXAMPLE", newValue == "example"
        // and comparisonType == "any ignore case" will conquer to replacing:
        // "EXAMPLE" to "example" to "example" to "example" … infinite loop.
        startSearchFromIndex = foundAt + oldValue.Length;
        if (startSearchFromIndex == str.Length)
        {
            // It is end of the input string: no more space for the next search.
            // The input string ends with a value that has already been replaced. 
            // Therefore, the string builder with the result is complete and no further action is required.
            return resultStringBuilder.ToString();
        }
    }


    // Append the last part to the result.
    int @charsUntilStringEnd = str.Length - startSearchFromIndex;
    resultStringBuilder.Append(str, startSearchFromIndex, @charsUntilStringEnd);


    return resultStringBuilder.ToString();

}

Remarque: ignorez la casse == StringComparison.OrdinalIgnoreCasecomme paramètre pour StringComparison comparisonType. C'est le moyen le plus rapide et insensible à la casse de remplacer toutes les valeurs.


Avantages de cette méthode:

  • Haute efficacité CPU et MEMORY;
  • C'est la solution la plus rapide, 2,5 fois plus rapide que les autres méthodes avec des expressions régulières (preuve à la fin);
  • Convient pour supprimer des parties de la chaîne d'entrée (définie newValuesur null), optimisé pour cela;
  • Identique au comportement d' origine de .NET C # string.Replace , mêmes exceptions;
  • Bien commenté, facile à comprendre;
  • Plus simple - pas d'expressions régulières. Les expressions régulières sont toujours plus lentes en raison de leur polyvalence (même compilées);
  • Cette méthode est bien testée et il n'y a pas de défauts cachés comme la boucle infinie dans les autres solutions, même très appréciées:

@AsValeO: ne fonctionne pas avec les éléments du langage Regex, donc ce n'est pas une méthode universelle

@Mike Stillion: Il y a un problème avec ce code. Si le texte dans new est un sur-ensemble du texte dans old, cela peut produire une boucle sans fin.


Benchmark-proof : cette solution est 2,59 fois plus rapide que regex de @Steve B., code:

// Results:
// 1/2. Regular expression solution: 4486 milliseconds
// 2/2. Current solution: 1727 milliseconds — 2.59X times FASTER! than regex!

// Notes: the test was started 5 times, the result is an average; release build.

const int benchmarkIterations = 1000000;
const string sourceString = "aaaaddsdsdsdsdsd";
const string oldValue = "D";
const string newValue = "Fod";
long totalLenght = 0;

Stopwatch regexStopwatch = Stopwatch.StartNew();
string tempString1;
for (int i = 0; i < benchmarkIterations; i++)
{
    tempString1 = sourceString;
    tempString1 = ReplaceCaseInsensitive(tempString1, oldValue, newValue);

    totalLenght = totalLenght + tempString1.Length;
}
regexStopwatch.Stop();



Stopwatch currentSolutionStopwatch = Stopwatch.StartNew();
string tempString2;
for (int i = 0; i < benchmarkIterations; i++)
{
    tempString2 = sourceString;
    tempString2 = tempString2.Replace(oldValue, newValue,
        StringComparison.OrdinalIgnoreCase);

    totalLenght = totalLenght + tempString2.Length;
}
currentSolutionStopwatch.Stop();

Idée originale - @ Darky711; merci @MinerR pour StringBuilder.

Oleg Zarevennyi
la source
5
Je parie que vous pouvez rendre cela encore plus rapide en utilisant un StringBuilder plutôt qu'une chaîne.
MineR
1
@MineR Vous avez raison, à l'origine, je viens de mettre à jour la solution @ Darky711 sans boucle infinie, j'ai donc utilisé le String. Cependant, le StringBuilderest vraiment plus rapide de 30 à 40% que le String. J'ai mis à jour la solution. Merci;)
Oleg Zarevennyi
2
Approche intéressante. Probablement le meilleur (meilleur que le mien :)) lorsque les performances comptent. Il s'agit généralement d'une méthode à ajouter à une bibliothèque de code partagée commune.
Steve B
2
L'utilisation d'expressions 'nameof' rend cela valable uniquement pour C # 6.0 et au-delà. Si vous êtes dans VS2013, vous pouvez l'utiliser en supprimant simplement les opérandes dans les exceptions.
LanchPad
Pour le commentaire "// if (oldValue.Equals (newValue, comparaisonType))", remplacez comparaisonType par StringComparison.Ordinal?
Roger Willcocks
31

Les extensions nous facilitent la vie:

static public class StringExtensions
{
    static public string ReplaceInsensitive(this string str, string from, string to)
    {
        str = Regex.Replace(str, from, to, RegexOptions.IgnoreCase);
        return str;
    }
}
Petrucio
la source
10
Et s'échapper rend notre vie moins boguée :-) return Regex.Replace (input, Regex.Escape (search), replacement.Replace ("$", "$$"), RegexOptions.IgnoreCase);
Vman
29

Beaucoup de suggestions en utilisant Regex. Que diriez-vous de cette méthode d'extension sans elle:

public static string Replace(this string str, string old, string @new, StringComparison comparison)
{
    @new = @new ?? "";
    if (string.IsNullOrEmpty(str) || string.IsNullOrEmpty(old) || old.Equals(@new, comparison))
        return str;
    int foundAt = 0;
    while ((foundAt = str.IndexOf(old, foundAt, comparison)) != -1)
    {
        str = str.Remove(foundAt, old.Length).Insert(foundAt, @new);
        foundAt += @new.Length;
    }
    return str;
}
Darky711
la source
Notez que l'argument de comparaison n'est pas utilisé pour effectuer le remplacement réel (il est toujours insensible à la casse)
Bolo
2
Il y a un problème avec ce code. Si le texte dans new est un sur-ensemble du texte dans old , cela peut produire une boucle sans fin. Une fois que new est inséré à FoundAt , la valeur de FoundAt doit être augmentée de la longueur de new .
Mike Stillion
comparisondoit être utilisé dans IndexOf, au lieu deStringComparison.CurrentCultureIgnoreCase
Maxence
@Bolo Je l'ai édité pour utiliser l'argument de comparaison (cela peut prendre un peu de temps à être évalué par les pairs).
bradlis7
2
Je séparerais également cette condition pour renvoyer la nouvelle chaîne:, if(old.Equals(@new, comparison)) return @new;car la nouvelle chaîne peut différer en majuscules / minuscules.
sɐunıɔ ןɐ qɐp
13

Vous pouvez utiliser l' espace de noms Microsoft.VisualBasic pour trouver cette fonction d'assistance:

Replace(sourceString, "replacethis", "withthis", , , CompareMethod.Text)
user2991288
la source
J'étais fier de ma réponse jusqu'à ce que je voie cela, ce qui est une meilleure réponse car il est intégré. Ex: Strings.Replace ("TeStInG123", "t", "z", 1, -1, CompareMethod.Text) renvoie " zeSzInG123 "
Bolo
Attention, Strings.Replace renvoie null si la chaîne recherchée est une chaîne vide.
Mafu Josh
1
Dans .Net 4.7.2, vous devez ajouter une référence à Microsoft.VisualBasic pour que cela fonctionne. Dans .Net Core, la classe Microsoft.VisualBasic.Strings (dans la version 10.3.0 de toute façon) ne semble pas implémenter la fonction Replace. Cela fonctionne également dans Powershell si vous ajoutez d'abord Add-Class -AssemblyName Microsoft.VisualBasic.
Prof Von Lemongargle
6

( Modifié: n'était pas au courant du problème du "lien nu", désolé)

Extrait d' ici :

string myString = "find Me and replace ME";
string strReplace = "me";
myString = Regex.Replace(myString, "me", strReplace, RegexOptions.IgnoreCase);

Semble que vous n'êtes pas le premier à vous plaindre de l'absence de chaîne insensible à la casse.

pseudo
la source
5

Modification de la réponse de @ Darky711 pour utiliser le type de comparaison passé et faire correspondre le cadre remplacer les noms et les commentaires xml aussi étroitement que possible.

/// <summary>
/// Returns a new string in which all occurrences of a specified string in the current instance are replaced with another specified string.
/// </summary>
/// <param name="str">The string performing the replace method.</param>
/// <param name="oldValue">The string to be replaced.</param>
/// <param name="newValue">The string replace all occurrances of oldValue.</param>
/// <param name="comparisonType">Type of the comparison.</param>
/// <returns></returns>
public static string Replace(this string str, string oldValue, string @newValue, StringComparison comparisonType)
{
    @newValue = @newValue ?? string.Empty;
    if (string.IsNullOrEmpty(str) || string.IsNullOrEmpty(oldValue) || oldValue.Equals(@newValue, comparisonType))
    {
        return str;
    }
    int foundAt;
    while ((foundAt = str.IndexOf(oldValue, 0, comparisonType)) != -1)
    {
        str = str.Remove(foundAt, oldValue.Length).Insert(foundAt, @newValue);
    }
    return str;
}
Bolo
la source
2

J'ai écrit la méthode d'extension:

public static string ReplaceIgnoreCase(this string source, string oldVale, string newVale)
    {
        if (source.IsNullOrEmpty() || oldVale.IsNullOrEmpty())
            return source;

        var stringBuilder = new StringBuilder();
        string result = source;

        int index = result.IndexOf(oldVale, StringComparison.InvariantCultureIgnoreCase);

        while (index >= 0)
        {
            if (index > 0)
                stringBuilder.Append(result.Substring(0, index));

            if (newVale.IsNullOrEmpty().IsNot())
                stringBuilder.Append(newVale);

            stringBuilder.Append(result.Substring(index + oldVale.Length));

            result = stringBuilder.ToString();

            index = result.IndexOf(oldVale, StringComparison.InvariantCultureIgnoreCase);
        }

        return result;
    }

J'utilise deux méthodes d'extension supplémentaires pour la méthode d'extension précédente:

    public static bool IsNullOrEmpty(this string value)
    {
        return string.IsNullOrEmpty(value);
    }

    public static bool IsNot(this bool val)
    {
        return val == false;
    }
Georgy Batalov
la source
2
A voté. Mais IsNotprend les extensions trop au sérieux :)
nawfal
Décevant, cela ne fonctionne pas dans toutes les situations. Je passais un nom distinctif et il s'ajoute jusqu'à ce que la chaîne soit longue d'un million de caractères, puis manque de mémoire
Bbb
Alternative proposée ci-dessous qui a résolu mon problème
Bbb
J'aime vraiment.IsNot
ttugates
1

Étendre la réponse de Petrucio avec Regex.Escapesur la chaîne de recherche et échapper au groupe correspondant comme suggéré dans la réponse de Steve B (et quelques changements mineurs à mon goût):

public static class StringExtensions
{
    public static string ReplaceIgnoreCase(this string str, string from, string to)
    {
        return Regex.Replace(str, Regex.Escape(from), to.Replace("$", "$$"), RegexOptions.IgnoreCase);
    }
}

Ce qui produira les résultats attendus suivants:

Console.WriteLine("(heLLo) wOrld".ReplaceIgnoreCase("(hello) world", "Hi $1 Universe")); // Hi $1 Universe
Console.WriteLine("heLLo wOrld".ReplaceIgnoreCase("(hello) world", "Hi $1 Universe"));   // heLLo wOrld

Cependant, sans effectuer les échappements, vous obtiendriez ce qui suit, ce qui n'est pas un comportement attendu d'un String.Replacequi est juste insensible à la casse:

Console.WriteLine("(heLLo) wOrld".ReplaceIgnoreCase_NoEscaping("(hello) world", "Hi $1 Universe")); // (heLLo) wOrld
Console.WriteLine("heLLo wOrld".ReplaceIgnoreCase_NoEscaping("(hello) world", "Hi $1 Universe"));   // Hi heLLo Universe
Sina Iravanian
la source
1

Cela ne fonctionne-t-il pas: je ne peux pas imaginer que quoi que ce soit d'autre soit beaucoup plus rapide ou plus facile.

public static class ExtensionMethodsString
{
    public static string Replace(this String thisString, string oldValue, string newValue, StringComparison stringComparison)
    {
        string working = thisString;
        int index = working.IndexOf(oldValue, stringComparison);
        while (index != -1)
        {
            working = working.Remove(index, oldValue.Length);
            working = working.Insert(index, newValue);
            index = index + newValue.Length;
            index = working.IndexOf(oldValue, index, stringComparison);
        }
        return working;
    }
}
Tom Robson
la source
Je ne sais pas si c'est plus rapide mais c'est concis, n'utilise pas la surcharge regex et les problèmes potentiels et utilise le StringComparison intégré.
fvlinden
0

La fonction ci-dessous consiste à supprimer tous les mots de correspondance comme (this) de l'ensemble de chaînes. Par Ravikant Sonare.

private static void myfun()
{
    string mystring = "thiTHISThiss This THIS THis tThishiThiss. Box";
    var regex = new Regex("this", RegexOptions.IgnoreCase);
    mystring = regex.Replace(mystring, "");
    string[] str = mystring.Split(' ');
    for (int i = 0; i < str.Length; i++)
    {
        if (regex.IsMatch(str[i].ToString()))
        {
            mystring = mystring.Replace(str[i].ToString(), string.Empty);

        }
    }
    Console.WriteLine(mystring);
}
Ravikant Sonare
la source
Cette fonction remplace toutes les cordes du jeu de cordes ... par Ravikant Sonare,
Ravikant Sonare
0

En utilisant la solution @Georgy Batalov, j'ai eu un problème lors de l'utilisation de l'exemple suivant

chaîne d'origine = "blah, DC = bleh, DC = blih, DC = bloh, DC = com"; chaîne remplacée = original.ReplaceIgnoreCase (", DC =", ".")

Voici comment j'ai réécrit son extension

public static string ReplaceIgnoreCase(this string source, string oldVale, 
string newVale)
    {
        if (source.IsNullOrEmpty() || oldVale.IsNullOrEmpty())
            return source;

        var stringBuilder = new StringBuilder();
        string result = source;

        int index = result.IndexOf(oldVale, StringComparison.InvariantCultureIgnoreCase);
        bool initialRun = true;

        while (index >= 0)
        {
            string substr = result.Substring(0, index);
            substr = substr + newVale;
            result = result.Remove(0, index);
            result = result.Remove(0, oldVale.Length);

            stringBuilder.Append(substr);

            index = result.IndexOf(oldVale, StringComparison.InvariantCultureIgnoreCase);
        }

        if (result.Length > 0)
        {
            stringBuilder.Append(result);
        }

        return stringBuilder.ToString();
    }
Bbb
la source
0

ci-dessous est l'alternative pour remplacer la chaîne en ignorant la casse des caractères

String thisString = "hello world"; 
String replaceString = "World";

//thisString.Replace("World", "csharp"); 
//below is the alternative to replace string ignoring character case

int start = StringUtils.indexOfIgnoreCase(thisString,replaceString);
String searchKey = thisString.substring(start, start+replaceString.length());
thisString= thisString.replaceAll(searchKey ,replaceString );
System.out.println(thisString);

//prints hello World
sjsj15
la source
0

Vous pouvez également essayer la Regexclasse.

var regex = new Regex( "camel", RegexOptions.IgnoreCase ); var newSentence = regex.Replace( sentence, "horse" );

Hiren Patel
la source
-3

Je préfère ceci - "Hello World" .ToLower (). Replace ("world", "csharp");

Harshal
la source
1
Cela mettra tout en minuscule, même les mots qui n'étaient pas censés être remplacés.
JJJ
De toute évidence, vous ne pouvez l'utiliser que si vous n'êtes pas gêné par l'affaire.
Harshal