Substituir caso de ignição

214

Eu tenho uma string chamada "olá mundo"

Preciso substituir a palavra "mundo" por "csharp"

para isso eu uso:

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

mas, como resultado, não substituo a string. A razão é a distinção entre maiúsculas e minúsculas. A string original contém "world", enquanto eu estou tentando substituir "World".

Existe alguma maneira de evitar essa distinção entre maiúsculas e minúsculas no método string.Replace?

Sandeep
fonte

Respostas:

309

Você pode usar um Regex e executar uma substituição que não diferencia maiúsculas de minúsculas:

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
fonte
19
Não funciona com elementos de linguagem Regex , portanto, não é um método universal. A resposta de Steve B está correta.
AsValeO
1
Então é melhor você não escrever hello. world?ou qualquer outra coisa que contenha operadores de expressões regulares.
Sebastian Mach
Caso alguém não tenha vontade de ler mais, esta foi a resposta aceita em 2011 e tem um grande número de votos. Isso funciona bem se você precisar substituir apenas alfanuméricos. No entanto, se você precisar substituir qualquer caractere de pontuação, poderá ter grandes problemas. A resposta de Oleg Zarevennyi é superior, mas tem apenas um pequeno número de votos, porque foi publicada em 2017.
Tony Pulokas
115
var search = "world";
var replacement = "csharp";
string result = Regex.Replace(
    stringToLookInto,
    Regex.Escape(search), 
    replacement.Replace("$","$$"), 
    RegexOptions.IgnoreCase
);

O Regex.Escape é útil se você confiar na entrada do usuário que pode conter elementos de idioma do Regex

Atualizar

Graças aos comentários, você realmente não precisa escapar da string de substituição.

Aqui está um pequeno violino que testa o código :

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

Sua saída é:

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
fonte
2
Este método falha se a substituição = "! @ # $% ^ & * ()" Você obtém "! @ \ # \ $% \ ^ & * ()" Substituída.
Kcoder
2
O segundo Regex.Escapeé ruim, prefixará caracteres especiais com barras invertidas. Parece que a melhor maneira é .Replace ("$", "$$"), que é meio burro ( stackoverflow.com/a/10078353 ).
Danny Tuppeny
1
@dannyTuppeny: você está certo ... Eu atualizei a resposta em conformidade #
Steve B
54

Método 2.5X MAIS RÁPIDO e MAIS EFICAZ do que os métodos de expressões regulares de outros:

/// <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: ignore case == StringComparison.OrdinalIgnoreCasecomo parâmetro para StringComparison comparisonType. É a maneira mais rápida, sem distinção entre maiúsculas e minúsculas, de substituir todos os valores.


Vantagens deste método:

  • Alta eficiência de CPU e MEMÓRIA;
  • É a solução mais rápida, 2,5 vezes mais rápida que os métodos de outros com expressões regulares (prova no final);
  • Adequado para remover peças da sequência de entrada (definida newValuecomo null), otimizada para isso;
  • Igual ao comportamento original do .NET C # string.Replace , mesmas exceções;
  • Bem comentado, fácil de entender;
  • Mais simples - sem expressões regulares. Expressões regulares são sempre mais lentas devido à sua versatilidade (até compiladas);
  • Esse método é bem testado e não há falhas ocultas, como loop infinito, nas soluções de outros, mesmo com alta classificação:

@AsValeO: Não funciona com elementos da linguagem Regex, portanto, não é um método universal

@ Mike Stillion: Há um problema com este código. Se o texto em novo for um superconjunto do texto em antigo, isso poderá produzir um loop infinito.


À prova de benchmark : esta solução é 2,59 vezes mais rápida que o regex de @Steve B., código:

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

Ideia original - @ Darky711; obrigado @MinerR por StringBuilder.

Oleg Zarevennyi
fonte
5
Aposto que você pode tornar isso ainda mais rápido usando um StringBuilder em vez de uma string.
Miner
1
@MineR Você está certo, eu originalmente atualizei a solução @ Darky711 sem loop infinito, então usei o String. No entanto, o StringBuilderé realmente mais rápido em 30-40% do que o String. Eu atualizei a solução. Obrigado;)
Oleg Zarevennyi
2
Abordagem interessante. Provavelmente o melhor (melhor que o meu :)) quando o desempenho é importante. Normalmente, um método para adicionar a uma biblioteca de códigos compartilhada comum.
Steve B
2
O uso de expressões 'nameof' torna isso válido apenas para C # 6.0 e além. Se você estiver no VS2013, poderá usá-lo simplesmente excluindo os operandos nas exceções.
21419 LanchPad
Para o comentado "// if (oldValue.Equals (newValue, ComparationType))" substitua o compareType por StringComparison.Ordinal?
Roger Willcocks
31

As extensões facilitam nossa vida:

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
fonte
10
E fugir faz com que nossas vidas sejam menos complicadas :-) retorne Regex.Replace (input, Regex.Escape (pesquisa), replace.Replace ("$", "$$"), RegexOptions.IgnoreCase);
Vman
29

Muitas sugestões usando o Regex. Que tal esse método de extensão sem ele:

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
fonte
Note-se que o argumento de comparação não está sendo usado para fazer a substituição real (é sempre maiúsculas e minúsculas)
Bolo
2
Há um problema com este código. Se o texto em novo for um superconjunto do texto em antigo , isso poderá produzir um loop infinito. Depois que new é inserido em FoundAt , o valor de FoundAt precisa ser avançado pela duração de new .
Mike Stillion
comparisonparâmetro deve ser usado em IndexOf, em vez deStringComparison.CurrentCultureIgnoreCase
Maxence
@ Bolo Eu o editei para usar o argumento de comparação (pode demorar um pouco para ser revisado por pares).
bradlis7
2
Eu também separaria essa condição para retornar a nova string:, if(old.Equals(@new, comparison)) return @new;uma vez que a nova string pode diferir em maiúsculas / minúsculas.
sɐunıɔ ןɐ qɐp
13

Você pode usar o espaço para nome Microsoft.VisualBasic para encontrar esta função auxiliar:

Replace(sourceString, "replacethis", "withthis", , , CompareMethod.Text)
user2991288
fonte
Fiquei orgulhoso da minha resposta até que vi uma resposta melhor porque ela foi incorporada. Ex: Strings.Replace ("TeStInG123", "t", "z", 1, -1, CompareMethod.Text) retorna " zeSzInG123 "
Bolo
Aviso, Strings.Replace retornará null se a string que está sendo pesquisada for uma string vazia.
Mafu Josh
1
No .net 4.7.2, você precisa adicionar uma referência ao Microsoft.VisualBasic para que isso funcione. No .Net Core, a classe Microsoft.VisualBasic.Strings (na versão 10.3.0 de qualquer maneira) não parece implementar a função Replace. Isso funciona no Powershell também se você adicionar primeiro a classe -AssemblyName Microsoft.VisualBasic.
Prof Von Lemongargle 29/11
6

( Editado: não estava ciente do problema do `link nu ', desculpe por isso)

Retirado daqui :

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

Parece que você não é o primeiro a reclamar da falta de sequência que não diferencia maiúsculas de minúsculas.

usuario
fonte
5

A resposta do @ Darky711 modificada para usar o tipo de comparação passado e corresponder à estrutura substitui os comentários de nomenclatura e xml o mais próximo possível.

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

Eu escrevi o método de extensão:

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

Eu uso dois métodos de extensão adicionais para o método de extensão anterior:

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

    public static bool IsNot(this bool val)
    {
        return val == false;
    }
Georgy Batalov
fonte
2
Votado. Mas IsNotestá tomando extensões muito a sério :)
Nawfal
Desapontante, isso não funciona em todas as situações. Eu estava passando um nome distinto e ele acrescenta até que a corda é um milhão de caracteres e, em seguida, ficar sem memória
Bbb
Alternativa oferecida abaixo que fixa o meu problema
Bbb
Eu realmente gosto.IsNot
ttugates
1

Estendendo a resposta de Petrucio com Regex.Escapena cadeia de busca e escapando do grupo correspondente, conforme sugerido na resposta de Steve B (e algumas pequenas alterações no meu gosto):

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

O que produzirá os seguintes resultados esperados:

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

No entanto, sem executar as fugas, você obteria o seguinte, que não é um comportamento esperado de um String.Replaceque não diferencia maiúsculas de minúsculas:

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
fonte
1

Isso não funciona: não consigo imaginar algo mais rápido ou fácil.

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
fonte
Não sei se é mais rápido, mas é conciso, não usa sobrecarga de regex e possíveis problemas e usa o StringComparison interno.
fvlinden 14/06
0

A função abaixo é remover todas as palavras correspondentes (como esta) do conjunto de cadeias. Por 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
fonte
Esta função é substituir todos corda do conjunto a corda ... por Ravikant Sonare,
Ravikant Sonare
0

Usando a solução @Georgy Batalov, tive um problema ao usar o exemplo a seguir

string original = "blá, CD = bleh, CD = blih, CD = bloh, DC = com"; sequência substituída = original.ReplaceIgnoreCase (", DC =", ".")

Abaixo está como eu reescrevi sua extensão

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
fonte
0

abaixo é a alternativa para substituir a seqüência de caracteres ignorando caracteres

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
fonte
0

Você também pode tentar a Regexaula.

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

Hiren Patel
fonte
-3

Eu prefiro isso - "Olá Mundo" .ToLower (). Substitua ("mundo", "csharp");

Harshal
fonte
1
Isso vai minúscula tudo, até palavras que não deveriam ser substituídas.
JJJ
Obviamente, você pode usar isso apenas se não estiver preocupado com o caso.
Harshal 21/01/19