String. Sostituisci il caso ignorato


214

Ho una stringa chiamata "ciao mondo"

Devo sostituire la parola "mondo" a "csharp"

per questo utilizzo:

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

ma di conseguenza, non riesco a sostituire la stringa. Il motivo è la sensibilità al caso. La stringa originale contiene "world" mentre sto cercando di sostituire "World".

Esiste un modo per evitare la sensibilità di questo caso nella stringa. Sostituire metodo?



Risposte:


309

È possibile utilizzare un Regex ed eseguire una sostituzione senza distinzione tra maiuscole e minuscole:

class Program
{
    static void Main()
    {
        string input = "hello WoRlD";
        string result = 
           Regex.Replace(input, "world", "csharp", RegexOptions.IgnoreCase);
        Console.WriteLine(result); // prints "hello csharp"
    }
}

19
Non funziona con gli elementi del linguaggio Regex , quindi non è un metodo universale. La risposta di Steve B è corretta.
AsValeO,

1
Quindi è meglio non scrivere hello. world?o nient'altro contenente operatori regex.
Sebastian Mach,

Nel caso in cui qualcuno non fosse propenso a leggere oltre, questa è stata la risposta accettata nel 2011 e ha un numero enorme di voti. Funziona bene se devi solo sostituire alfanumerico. Tuttavia, se devi sostituire i caratteri di punteggiatura, potresti avere grossi problemi. La risposta di Oleg Zarevennyi è superiore, ma ha solo un piccolo numero di voti perché è stata pubblicata nel 2017.
Tony Pulokas,

115
var search = "world";
var replacement = "csharp";
string result = Regex.Replace(
    stringToLookInto,
    Regex.Escape(search), 
    replacement.Replace("$","$$"), 
    RegexOptions.IgnoreCase
);

Il Regex.Escape è utile se si basano su input dell'utente, che può contenere elementi di linguaggio Regex

Aggiornare

Grazie ai commenti, in realtà non è necessario uscire dalla stringa di sostituzione.

Ecco un piccolo violino che verifica il codice :

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

Il suo output è:

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 }

2
Questo metodo ha esito negativo se si sostituisce la sostituzione = "! @ # $% ^ & * ()" Ottieni "! @ \ # \ $% \ ^ & * ()".
Kcoder,

2
Il secondo Regex.Escapeè negativo, prefigura i caratteri speciali con barre rovesciate. Sembra che il modo migliore sia. Sostituisci ("$", "$$"), che è un po 'stupido ( stackoverflow.com/a/10078353 ).
Danny Tuppeny,

1
@dannyTuppeny: hai ragione ... ho aggiornato la risposta di conseguenza
Steve B,

54

Metodo 2,5X PIÙ VELOCE e PIÙ EFFICACE rispetto ai metodi di altre espressioni regolari:

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

}

Nota: ignora case == StringComparison.OrdinalIgnoreCasecome parametro per StringComparison comparisonType. È il modo più veloce e senza distinzione tra maiuscole e minuscole per sostituire tutti i valori.


Vantaggi di questo metodo:

  • Alta efficienza CPU e MEMORIA;
  • È la soluzione più veloce, 2,5 volte più veloce dei metodi di altri con espressioni regolari (prova alla fine);
  • Adatto per rimuovere parti dalla stringa di input (impostato newValuesu null), ottimizzato per questo;
  • Stesso comportamento .NET C # originale string.Replace, stesse eccezioni;
  • Ben commentato, facile da capire;
  • Più semplice: nessuna espressione regolare. Le espressioni regolari sono sempre più lente a causa della loro versatilità (anche compilata);
  • Questo metodo è ben testato e non ci sono difetti nascosti come il loop infinito nelle soluzioni di altri, anche molto apprezzati:

@AsValeO: non funziona con gli elementi del linguaggio Regex, quindi non è un metodo universale

@Mike Stillion: si è verificato un problema con questo codice. Se il testo in new è un superset del testo in old, questo può produrre un ciclo infinito.


A prova di benchmark : questa soluzione è 2,59 volte più veloce di regex di @Steve B., codice:

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

Idea originale - @ Darky711; grazie @MinerR per StringBuilder.


5
Scommetto che puoi renderlo ancora più veloce usando StringBuilder piuttosto che una stringa.
MineR

1
@MineR Hai ragione, inizialmente avevo appena aggiornato la soluzione @ Darky711 senza loop infinito, quindi ho usato il String. Tuttavia, StringBuilderè molto più veloce del 30-40% rispetto a String. Ho aggiornato la soluzione. Grazie;)
Oleg Zarevennyi

2
Approccio interessante Probabilmente il migliore (migliore del mio :)) quando le prestazioni contano. In genere un metodo da aggiungere a una libreria di codice condivisa comune.
Steve B,

2
L'uso delle espressioni 'nameof' lo rende valido solo per C # 6.0 e versioni successive. Se ti trovi in ​​VS2013, puoi usarlo semplicemente cancellando gli operandi nelle eccezioni.
LanchPad,

Per il commento "// if (oldValue.Equals (newValue, confrontoType))" sostituire il confrontoType con StringComparison.Ordinal?
Roger Willcocks,

31

Le estensioni ci semplificano la vita:

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

10
E la fuga rende le nostre vite meno buggy :-) restituisce Regex.Replace (input, Regex.Escape (ricerca), sostituzione.Replace ("$", "$$"), RegexOptions.IgnoreCase);
Vman,

29

Molti suggerimenti con Regex. Che ne dici di questo metodo di estensione senza di esso:

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

Si noti che l'argomento di confronto non viene utilizzato per eseguire la sostituzione effettiva (è sempre senza distinzione tra maiuscole e minuscole)
Bolo

2
Si è verificato un problema con questo codice. Se il testo in new è un superset del testo in old , questo può produrre un ciclo infinito. Una volta inserito il nuovo in FoundAt , il valore di FoundAt deve essere aumentato della lunghezza del nuovo .
Mike Stillion,

comparisonil parametro deve essere utilizzato in IndexOf, anzichéStringComparison.CurrentCultureIgnoreCase
Maxence

@Bolo L'ho modificato per utilizzare l'argomento di confronto (potrebbe richiedere un po 'di tempo per essere rivisto da pari).
Bradrad

2
Separerei anche questa condizione per restituire la nuova stringa:, if(old.Equals(@new, comparison)) return @new;poiché la nuova stringa potrebbe differire in maiuscolo / minuscolo.
sɐunıɔ ןɐ qɐp,

13

È possibile utilizzare lo spazio dei nomi Microsoft.VisualBasic per trovare questa funzione di supporto:

Replace(sourceString, "replacethis", "withthis", , , CompareMethod.Text)

Ero orgoglioso della mia risposta fino a quando non ho visto questa che è una risposta migliore perché è integrata. Ex: Strings.Replace ("TeStInG123", "t", "z", 1, -1, CompareMethod.Text) restituisce " zeSzInG123 "
Bolo

Attenzione, Strings.Replace restituisce null se la stringa in fase di ricerca è una stringa vuota.
Mafu Josh,

1
In .Net 4.7.2, è necessario aggiungere un riferimento a Microsoft.VisualBasic per farlo funzionare. In .Net Core, la classe Microsoft.VisualBasic.Strings (nella versione 10.3.0 comunque) non sembra implementare la funzione Sostituisci. Funziona anche su Powershell se aggiungi prima Classe -AssemblyName Microsoft.VisualBasic.
Prof. Von Lemongargle,

6

( Modificato: non ero a conoscenza del problema del "collegamento nudo", mi dispiace per quello)

Tratto da qui :

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

Sembra che non sei il primo a lamentarti della mancanza di stringhe insensibili al maiuscolo / minuscolo.


5

Modificata la risposta di @ Darky711 per utilizzare il tipo di confronto passato e abbinare il framework per sostituire il più possibile i commenti di denominazione e xml.

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

2

Ho scritto il metodo di estensione:

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

Uso due metodi di estensione aggiuntivi per il metodo di estensione precedente:

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

    public static bool IsNot(this bool val)
    {
        return val == false;
    }

2
Upvoted. Ma IsNotsta prendendo troppo sul serio le estensioni :)
nawfal l'

Deludente, questo non funziona in tutte le situazioni. Stavo passando un nome distinto e si aggiunge fino a quando la stringa è lunga un milione di caratteri e quindi esaurisce la memoria
Bbb

L'alternativa offerta di seguito ha risolto il mio problema
Bbb

Mi piace molto.IsNot
ttugates il

1

Estendere la risposta di Petrucio con Regex.Escapesulla stringa di ricerca e sfuggire al gruppo corrispondente come suggerito nella risposta di Steve B (e alcune piccole modifiche ai miei gusti):

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

Che produrrà i seguenti risultati previsti:

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

Tuttavia, senza eseguire le escape si otterrebbe quanto segue, che non è un comportamento previsto da un String.Replacecaso che non fa distinzione tra maiuscole e minuscole:

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

1

Non funziona: non posso immaginare nient'altro che sia molto più veloce o più 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;
    }
}

Non so se è più veloce ma è conciso, non utilizza regex overhead e potenziali problemi e utilizza StringComparison integrato.
fvlinden,

0

Di seguito la funzione è quella di rimuovere tutte le parole corrispondenti come (questa) dal set di stringhe. Di 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);
}

Questa funzione sostituisce tutte le stringhe del set di stringhe ... di Ravikant Sonare,
Ravikant Sonare

0

Utilizzando la soluzione @Georgy Batalov ho riscontrato un problema durante l'utilizzo del seguente esempio

string original = "blah, DC = bleh, DC = blih, DC = bloh, DC = com"; stringa sostituita = original.ReplaceIgnoreCase (", DC =", ".")

Di seguito è come ho riscritto la sua estensione

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

0

di seguito è l'alternativa per sostituire la stringa ignorando il caso di carattere

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

0

Puoi anche provare la Regexlezione.

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


-3

Preferisco questo: "Hello World" .ToLower (). Sostituisci ("world", "csharp");


1
Questo minuscola tutto, anche le parole che non avrebbero dovuto essere sostituite.
JJJ,

Ovviamente, puoi usarlo solo se non ti preoccupi del caso.
Harshal,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.