String.Replace ignorando el caso

214

Tengo una cadena llamada "hola mundo"

Necesito reemplazar la palabra "mundo" por "csharp"

para esto uso:

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

pero como resultado, no consigo reemplazar la cadena. La razón es la sensibilidad a mayúsculas y minúsculas. La cadena original contiene "mundo", mientras que estoy tratando de reemplazar "Mundo".

¿Hay alguna manera de evitar esta distinción entre mayúsculas y minúsculas en string.

Sandeep
fuente

Respuestas:

309

Podría usar un Regex y realizar un reemplazo sin distinción entre mayúsculas y 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
fuente
19
No funciona con elementos del lenguaje Regex , por lo que no es un método universal. La respuesta de Steve B es correcta.
AsValeO
1
Por lo tanto, es mejor que no escriba hello. world?ni nada que contenga operadores de expresiones regulares.
Sebastian Mach
En caso de que alguien no estuviera dispuesto a seguir leyendo, esta fue la respuesta aceptada en 2011 y tiene una gran cantidad de votos. Esto funciona bien si solo tiene que reemplazar alfanumérico. Sin embargo, si tiene que reemplazar los caracteres de puntuación, puede meterse en grandes problemas. La respuesta de Oleg Zarevennyi es superior, pero solo tiene un pequeño número de votos porque se publicó en 2017.
Tony Pulokas
115
var search = "world";
var replacement = "csharp";
string result = Regex.Replace(
    stringToLookInto,
    Regex.Escape(search), 
    replacement.Replace("$","$$"), 
    RegexOptions.IgnoreCase
);

El Regex.Escape es útil si se basan en la entrada del usuario, que puede contener elementos del lenguaje de expresiones regulares

Actualizar

Gracias a los comentarios, en realidad no tienes que escapar de la cadena de reemplazo.

Aquí hay un pequeño violín que prueba el 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;
    }
}

Su salida es:

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
fuente
2
Este método falla si reemplazo = "! @ # $% ^ & * ()" Obtendrá "! @ \ # \ $% \ ^ & * ()" Reemplazado en su lugar.
Kcoder
2
El segundo Regex.Escapees malo, prefijará caracteres especiales con barras invertidas. Parece que la mejor manera es .Replace ("$", "$$"), que es un poco tonto ( stackoverflow.com/a/10078353 ).
Danny Tuppeny
1
@dannyTuppeny: tienes razón ... Actualicé la respuesta en consecuencia
Steve B
54

Método 2.5X MÁS RÁPIDO y MÁS EFECTIVO que los métodos de expresiones regulares de otros:

/// <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 caso == StringComparison.OrdinalIgnoreCasecomo parámetro para StringComparison comparisonType. Es la forma más rápida y sin distinción entre mayúsculas y minúsculas para reemplazar todos los valores.


Ventajas de este método:

  • Alta eficiencia de CPU y MEMORIA;
  • Es la solución más rápida, 2.5 veces más rápido que los métodos de otros con expresiones regulares (prueba al final);
  • Adecuado para eliminar partes de la cadena de entrada (establecido newValueen null), optimizado para esto;
  • Igual que el string.Replace comportamiento original de .NET C # , las mismas excepciones;
  • Bien comentado, fácil de entender;
  • Más simple: no hay expresiones regulares. Las expresiones regulares siempre son más lentas debido a su versatilidad (incluso compiladas);
  • Este método está bien probado y no hay fallas ocultas como el bucle infinito en las soluciones de otros, incluso altamente calificadas:

@AsValeO: no funciona con elementos de lenguaje Regex, por lo que no es un método universal

@ Mike Stillion: Hay un problema con este código. Si el texto en nuevo es un superconjunto del texto en antiguo, esto puede producir un bucle sin fin.


Prueba de referencia : esta solución es 2,59 veces más rápida que la expresión regular 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();

Idea original - @ Darky711; gracias @MinerR por StringBuilder.

Oleg Zarevennyi
fuente
55
Apuesto a que puedes hacer esto aún más rápido usando un StringBuilder en lugar de una cadena.
MineR
1
@MineR Tienes razón, originalmente acabo de actualizar la solución @ Darky711 sin bucle infinito, así que usé el String. Sin embargo, el StringBuilderes realmente más rápido en un 30-40% que el String. He actualizado la solución. Gracias;)
Oleg Zarevennyi
2
Enfoque interesante Probablemente el mejor (mejor que el mío :)) cuando el rendimiento es importante. Normalmente, un método para agregar a una biblioteca de código compartido común.
Steve B
2
El uso de expresiones 'nameof' hace que esto sea válido solo para C # 6.0 y más allá. Si está en VS2013, puede usarlo simplemente eliminando los operandos en las excepciones.
LanchPad
Para el comentario "// if (oldValue.Equals (newValue, compareType))", ¿reemplaza compareType con StringComparison.Ordinal?
Roger Willcocks
31

Las extensiones nos hacen la vida más fácil:

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
fuente
10
Y escapar hace que nuestras vidas tengan menos errores :-) return Regex.Replace (input, Regex.Escape (search), replace.Replace ("$", "$$"), RegexOptions.IgnoreCase);
Vman
29

Muchas sugerencias usando Regex. ¿Qué tal este método de extensión sin él?

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
fuente
Tenga en cuenta que el argumento de comparación no se está utilizando para hacer el reemplazo real (siempre distingue entre mayúsculas y minúsculas)
Bolo
2
Hay un problema con este código. Si el texto en nuevo es un superconjunto del texto en antiguo , esto puede producir un bucle sin fin. Una vez que se inserta nuevo en FoundAt , el valor de FoundAt debe avanzarse por la longitud del nuevo .
Mike Stillion
comparisonEl parámetro debe usarse en IndexOflugar deStringComparison.CurrentCultureIgnoreCase
Maxence
@Bolo Lo he editado para usar el argumento de comparación (puede tomar un poco para ser revisado por pares).
bradlis7
2
También separaría esta condición para devolver la nueva cadena: if(old.Equals(@new, comparison)) return @new;ya que la nueva cadena puede diferir en mayúsculas / minúsculas.
sɐunıɔ ןɐ qɐp
13

Puede usar el espacio de nombres Microsoft.VisualBasic para encontrar esta función auxiliar:

Replace(sourceString, "replacethis", "withthis", , , CompareMethod.Text)
usuario2991288
fuente
Estaba orgulloso de mi respuesta hasta que vi una respuesta mejor porque está integrada. Ej: Strings.Replace ("TeStInG123", "t", "z", 1, -1, CompareMethod.Text) devuelve " zeSzInG123 "
Bolo
Advertencia, Strings.Replace devuelve nulo si la cadena que se está buscando es una cadena vacía.
Mafu Josh
1
En .Net 4.7.2, debe agregar una referencia a Microsoft.VisualBasic para que esto funcione. En .Net Core, la clase Microsoft.VisualBasic.Strings (en la versión 10.3.0 de todos modos) no parece implementar la función Reemplazar. Esto también funciona en Powershell si agrega primero Class -AssemblyName Microsoft.VisualBasic.
Prof Von Lemongargle
6

( Editado: no estaba al tanto del problema del 'enlace desnudo', lo siento)

Tomado de aquí :

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

Parece que no eres el primero en quejarte de la falta de una cadena que no distinga entre mayúsculas y minúsculas.

Mella
fuente
5

Se modificó la respuesta de @ Darky711 para usar el tipo de comparación aprobada y coincidir con el marco, reemplazar los nombres y los comentarios xml lo más cerca posible.

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

He escrito el método de extensión:

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

Utilizo dos métodos de extensión adicionales para el método de extensión anterior:

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

    public static bool IsNot(this bool val)
    {
        return val == false;
    }
Georgy Batalov
fuente
2
Votado Pero se IsNotestá tomando las extensiones demasiado en serio :)
nawfal
Decepcionante, esto no funciona en todas las situaciones. Estaba pasando un nombre distinguido y se agrega hasta que la cadena tiene un millón de caracteres y luego se queda sin memoria
Bbb
Alternativa ofrecida a continuación que solucionó mi problema
Bbb
Realmente me gusta.IsNot
ttugates
1

Extender la respuesta de Petrucio con Regex.Escapela cadena de búsqueda y escapar del grupo coincidente como se sugiere en la respuesta de Steve B (y algunos cambios menores a mi gusto):

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

Lo que producirá los siguientes 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

Sin embargo, sin realizar los escapes, obtendría lo siguiente, que no es un comportamiento esperado de un String.Replacecaso que no distingue entre mayúsculas y 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
fuente
1

Esto no funciona: no puedo imaginar que otra cosa sea mucho más rápida o 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
fuente
No sé si es más rápido pero es conciso, no usa regex sobrecarga y problemas potenciales y usa la comparación de cadenas incorporada.
fvlinden
0

La siguiente función es eliminar todas las palabras de coincidencia como (this) del conjunto de cadenas. 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
fuente
Esta función es reemplazar todas las cadenas del conjunto de cadenas ... por Ravikant Sonare,
Ravikant Sonare
0

Usando la solución @Georgy Batalov tuve un problema al usar el siguiente ejemplo

string original = "bla, DC = bleh, DC = blih, DC = bloh, DC = com"; cadena reemplazada = original.ReplaceIgnoreCase (", DC =", ".")

A continuación se muestra cómo reescribí su extensión

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

a continuación se muestra la alternativa para reemplazar la cadena ignorando el caso de 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
fuente
0

También puedes probar la Regexclase.

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

Hiren Patel
fuente
-3

Prefiero esto: "Hello World". ToLower (). Reemplazar ("world", "csharp");

Harshal
fuente
1
Esto minúscula todo, incluso las palabras que no se suponía que fueran reemplazadas.
JJJ
Obviamente, puede usar esto solo si no le preocupa el caso.
Harshal