String.Replace ignorując wielkość liter


214

Mam ciąg o nazwie „witaj świecie”

Muszę zamienić słowo „świat” na „csharp”

do tego używam:

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

ale w rezultacie nie zastępuję ciągu. Powodem jest rozróżnianie wielkości liter. Oryginalny ciąg zawiera „świat”, podczas gdy próbuję zastąpić „świat”.

Czy jest jakiś sposób na uniknięcie tego rozróżniania wielkości liter w metodzie string.Replace?



Odpowiedzi:


309

Możesz użyć Regex i wykonać zamianę bez rozróżniania wielkości liter:

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
Nie działa z elementami języka Regex , więc nie jest to metoda uniwersalna. Odpowiedź Steve'a B jest poprawna.
AsValeO

1
Więc lepiej nie pisz hello. world?ani nic innego zawierającego operatory regularne.
Sebastian Mach

Na wypadek, gdyby ktokolwiek nie był skłonny czytać dalej, była to odpowiedź zaakceptowana w 2011 roku i ma ogromną liczbę głosów. Działa to dobrze, jeśli musisz tylko zastąpić alfanumeryczny. Jeśli jednak będziesz musiał wymienić znaki interpunkcyjne, możesz mieć poważne kłopoty. Odpowiedź Olega Zarevennego jest lepsza, ale ma tylko niewielką liczbę głosów, ponieważ została opublikowana w 2017 r.
Tony Pulokas

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

Regex.Escape jest przydatna, jeśli opierają się na danych wprowadzonych przez użytkownika, które mogą zawiera elementy języka REGEX

Aktualizacja

Dzięki komentarzom tak naprawdę nie musisz uciekać przed ciągiem zastępującym.

Oto małe skrzypce, które testują kod :

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

Jego wyniki to:

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
Ta metoda kończy się niepowodzeniem, jeśli zastąpienie = =!! @ # $% ^ & * () „Dostajesz”! @ \ # \ $% \ ^ & * () ”Zamiast tego.
Kcoder,

2
Drugi Regex.Escapejest zły, poprzedzi znaki specjalne odwrotnymi ukośnikami. Wydaje się, że najlepszym sposobem jest .Replace („$”, „$$”), co jest dość głupie ( stackoverflow.com/a/10078353 ).
Danny Tuppeny,

1
@dannyTuppeny: masz rację ... Odpowiednio zaktualizowałem odpowiedź
Steve B

54

2.5X SZYBSZA i NAJBARDZIEJ SKUTECZNA niż inne metody wyrażeń regularnych:

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

}

Uwaga: ignoruj wielkość liter == StringComparison.OrdinalIgnoreCasejako parametr dla StringComparison comparisonType. Jest to najszybszy, bez rozróżniania wielkości liter sposób na zastąpienie wszystkich wartości.


Zalety tej metody:

  • Wysoka wydajność procesora i pamięci;
  • Jest to najszybsze rozwiązanie, 2,5 razy szybsze niż inne metody z wyrażeniami regularnymi (dowód na końcu);
  • Odpowiedni do usuwania części z ciągu wejściowego (ustawiony newValuena null), zoptymalizowany do tego;
  • To samo co oryginalne zachowanie .NET C # string.Replace , te same wyjątki;
  • Dobrze skomentowane, łatwe do zrozumienia;
  • Prostsze - brak wyrażeń regularnych. Wyrażenia regularne są zawsze wolniejsze ze względu na ich wszechstronność (nawet kompilację);
  • Ta metoda jest dobrze przetestowana i nie ma żadnych ukrytych wad, takich jak nieskończona pętla w innych rozwiązaniach, nawet wysoko ocenianych:

@AsValeO: Nie działa z elementami języka Regex, więc nie jest to metoda uniwersalna

@Mike Stillion: Wystąpił problem z tym kodem. Jeśli tekst w nowym jest nadzbiorem tekstu w starym, może to spowodować nieskończoną pętlę.


Benchmark-proof : to rozwiązanie jest 2,59 razy szybsze niż regex z @Steve B., kod:

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

Oryginalny pomysł - @ Darky711; dzięki @MinerR za StringBuilder.


5
Założę się, że możesz uczynić to jeszcze szybszym za pomocą StringBuilder zamiast ciągu.
MineR

1
@MineR Masz rację, pierwotnie właśnie zaktualizowałem rozwiązanie @ Darky711 bez nieskończonej pętli, więc użyłem String. Jest jednak StringBuildernaprawdę szybszy o 30-40% niż String. Zaktualizowałem rozwiązanie. Dzięki;)
Oleg Zarevennyi

2
Ciekawe podejście Prawdopodobnie lepszy (lepszy niż mój :)), gdy liczy się wydajność. Zazwyczaj metoda dodawania do wspólnej biblioteki kodów współdzielonych.
Steve B,

2
Użycie wyrażeń „nameof” sprawia, że ​​jest to poprawne tylko dla wersji C # 6.0 i nowszych. Jeśli jesteś w VS2013, możesz go użyć, po prostu usuwając operandy w wyjątkach.
LanchPad

W przypadku skomentowanego „// if (oldValue.Equals (newValue, PorównanieTyp))” zastąpić parametr porównawczy StringComparison.Ordinal?
Roger Willcocks

31

Rozszerzenia ułatwiają nam życie:

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
A ucieczka sprawia, że ​​nasze życie jest mniej wadliwe :-) zwraca Regex.Replace (wejście, Regex.Escape (wyszukiwanie), replace.Replace („$”, „$$”), RegexOptions.IgnoreCase);
Vman

29

Wiele sugestii przy użyciu Regex. Co powiesz na tę metodę rozszerzenia bez niej:

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

Zauważ, że argument porównania nie jest używany do faktycznego zastąpienia (zawsze nie jest rozróżniana wielkość liter)
Bolo

2
Wystąpił problem z tym kodem. Jeśli tekst w nowym jest nadzbiorem tekstu w starym , może to spowodować nieskończoną pętlę. Po wstawieniu nowego w FoundAt wartość FoundAt musi zostać zwiększona o długość new .
Mike Stillion,

comparisonparametr należy użyć IndexOfzamiastStringComparison.CurrentCultureIgnoreCase
Maxence

@ Bolo Zredagowałem go, aby użyć argumentu porównania (może zająć trochę czasu, aby zostać sprawdzonym).
bradlis7

2
Oddzielę również ten warunek zwrotu nowego ciągu: if(old.Equals(@new, comparison)) return @new;ponieważ nowy ciąg może różnić się wielkimi / małymi literami.
sɐunıɔ ןɐ qɐp

13

Możesz użyć przestrzeni nazw Microsoft.VisualBasic, aby znaleźć tę funkcję pomocnika:

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

Byłem dumny z mojej odpowiedzi, dopóki nie zobaczyłem tej, która jest lepszą odpowiedzią, ponieważ jest wbudowana. Np .: Strings.Replace („TeStInG123”, „t”, „z”, 1, -1, CompareMethod.Text) zwraca „ zeSzInG123 "
Bolo,

Ostrzeżenie: Strings.Replace zwraca null, jeśli szukany ciąg jest pustym ciągiem.
Mafu Josh

1
W .Net 4.7.2 musisz dodać odwołanie do Microsoft.VisualBasic, aby to działało. W .Net Core klasa Microsoft.VisualBasic.Strings (w każdym razie w wersji 10.3.0) nie wydaje się implementować funkcji Zamień. Działa to również w Powershell, jeśli najpierw dodasz klasę -AssemblyName Microsoft.VisualBasic.
Prof. von Lemongargle

6

( Edytowane: nie zdawałem sobie sprawy z problemu „odsyłacza”, przepraszam za to)

Zaczerpnięte stąd :

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

Wygląda na to, że nie jesteś pierwszym, który narzeka na brak łańcucha bez rozróżniania wielkości liter.


5

Zmodyfikowano odpowiedź @ Darky711, aby używać przekazanego w porównaniu typu i dopasować ramę, zastępując nazewnictwo i komentarze xml tak dokładnie, jak to możliwe.

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

Napisałem metodę rozszerzenia:

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

Używam dwóch dodatkowych metod rozszerzenia dla poprzedniej metody rozszerzenia:

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

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

2
Pozytywne. Ale IsNotzbyt poważnie traktuje rozszerzenia :)
nawfal

Rozczarowujące, nie działa to we wszystkich sytuacjach. Podawałem wyróżniającą się nazwę, która dołącza się, dopóki łańcuch nie będzie miał miliona znaków, a potem zabraknie pamięci
Bbb

Poniżej podano alternatywę, która rozwiązała mój problem
Bbb

Naprawdę lubię.IsNot
ttugates

1

Rozszerzając odpowiedź Petrucio za Regex.Escapepomocą ciągu wyszukiwania i ucieczkę od dopasowanej grupy, jak sugerowano w odpowiedzi Steve'a B (i kilka drobnych zmian w moim guście):

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

Które przyniosą następujące oczekiwane wyniki:

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

Jednak bez wykonywania String.Replaceznaków ucieczki uzyskasz następujące wyniki, co nie jest oczekiwanym zachowaniem po uwzględnieniu wielkości liter:

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

Czy to nie działa: nie mogę zrobić niczego szybciej lub łatwiej.

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

Nie wiem, czy jest szybszy, ale jest zwięzły, nie używa narzutu wyrażenia regularnego i potencjalnych problemów i używa wbudowanego StringComparison.
fvlinden

0

Poniższa funkcja polega na usunięciu wszystkich pasujących słów typu (this) z zestawu ciągów. Autorstwa 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);
}

Ta funkcja zastępuje cały ciąg z zestawu ciągów ... autor: Ravikant Sonare,
Ravikant Sonare

0

Korzystając z rozwiązania @Georgy Batalov, miałem problem podczas korzystania z następującego przykładu

ciąg oryginalny = "bla, DC = bleh, DC = blih, DC = bloh, DC = com"; ciąg zastąpiony = oryginalny.ReplaceIgnoreCase (", DC =", ".")

Poniżej napisałem, jak przepisałem jego rozszerzenie

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

poniżej znajduje się alternatywa dla zamiany ciągu ignorującego wielkość liter

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

Możesz także wypróbować Regexklasę.

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


-3

Wolę to - „Hello World” .ToLower (). Zamień („world”, „csharp”);


1
Spowoduje to zapisanie małymi literami wszystkiego, nawet słów, których nie powinno się zastępować.
JJJ

Oczywiście możesz tego użyć tylko wtedy, gdy nie przejmujesz się sprawą.
Harshal
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.