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.