Reemplazar varios caracteres en una cadena de C #

178

¿Hay una mejor manera de reemplazar cadenas?

Me sorprende que Reemplazar no tome una matriz de caracteres o una matriz de cadenas. Supongo que podría escribir mi propia extensión, pero tenía curiosidad por saber si hay una mejor forma de hacer lo siguiente. Observe que el último reemplazo es una cadena, no un carácter.

myString.Replace(';', '\n').Replace(',', '\n').Replace('\r', '\n').Replace('\t', '\n').Replace(' ', '\n').Replace("\n\n", "\n");
zgirod
fuente

Respuestas:

206

Puede usar una expresión regular de reemplazo.

s/[;,\t\r ]|[\n]{2}/\n/g
  • s/ al principio significa una búsqueda
  • Los caracteres entre [y ]son los caracteres a buscar (en cualquier orden)
  • El segundo /delimita el texto de búsqueda y el texto de reemplazo.

En inglés, esto lee:

"Buscar ;o ,o \to \ro (espacio) o exactamente dos secuencial \ny sustituirla por \n"

En C #, puede hacer lo siguiente: (después de importar System.Text.RegularExpressions)

Regex pattern = new Regex("[;,\t\r ]|[\n]{2}");
pattern.Replace(myString, "\n");
johnluetke
fuente
2
\ty \restán incluidos en \s. Entonces tu expresión regular es equivalente a [;,\s].
NullUserException
3
Y en \srealidad es equivalente a, [ \f\n\r\t\v]por lo que incluye algunas cosas que no estaban en la pregunta original. Además, la pregunta original pregunta por Replace("\n\n", "\n")qué su expresión regular no maneja.
NullUserException
11
Tenga en cuenta que para las operaciones de reemplazo simples que no son configurables por un usuario, el uso de expresiones regulares no es óptimo, ya que es muy lento en comparación con las operaciones de cadena normales, de acuerdo con un primer artículo de referencia que encontré al buscar "reemplazo de rendimiento de c # regex", es aproximadamente 13 veces más lento
también
Ah regex, los jeroglíficos del poder! El único problema que puedo ver aquí es la legibilidad humana de las expresiones regulares; muchos se niegan a entenderlos. Recientemente he agregado una solución a continuación para aquellos que buscan una alternativa menos compleja.
sɐunıɔ ןɐ qɐp
Entonces, ¿cómo escribimos si queremos reemplazar varios caracteres con varios caracteres?
Habip Oğuz
114

Si te sientes particularmente inteligente y no quieres usar Regex:

char[] separators = new char[]{' ',';',',','\r','\t','\n'};

string s = "this;is,\ra\t\n\n\ntest";
string[] temp = s.Split(separators, StringSplitOptions.RemoveEmptyEntries);
s = String.Join("\n", temp);

También podría envolver esto en un método de extensión con poco esfuerzo.

Editar: O simplemente espera 2 minutos y terminaré escribiéndolo de todos modos :)

public static class ExtensionMethods
{
   public static string Replace(this string s, char[] separators, string newVal)
   {
       string[] temp;

       temp = s.Split(separators, StringSplitOptions.RemoveEmptyEntries);
       return String.Join( newVal, temp );
   }
}

Y voilá...

char[] separators = new char[]{' ',';',',','\r','\t','\n'};
string s = "this;is,\ra\t\n\n\ntest";

s = s.Replace(separators, "\n");
Paul Walls
fuente
Muy ineficiente de memoria, especialmente para cadenas más grandes.
MarcinJuraszek
@MarcinJuraszek Lol ... Esa es probablemente la primera vez que escucho a alguien afirmar que los métodos de cadena incorporados son menos eficientes en memoria que las expresiones regulares.
Paul Walls
10
Tienes razón. Debería haber medido antes de publicar eso. Ejecuto benchmark y Regex.Replacees más de 8 veces más lento que varias string.Replacellamadas seguidas. y 4 veces más lento que Split+ Join. Ver gist.github.com/MarcinJuraszek/c1437d925548561ba210a1c6ed144452
MarcinJuraszek
1
Buena solución! Solo un pequeño complemento. Desafortunadamente, esto no funcionará si desea que los primeros caracteres también sean reemplazados. Digamos que desea reemplazar el carácter 't' en la cadena de ejemplo. El método Split simplemente eliminará esa 't' de la primera palabra 'this' porque es una entrada vacía. Si usa StringSplitOptions.None en lugar de RemoveEmptyEntries, Split dejará la entrada y el método Join agregará el carácter separador. Espero que esto ayude
Pierre
58

Puede usar la función Agregado de Linq:

string s = "the\nquick\tbrown\rdog,jumped;over the lazy fox.";
char[] chars = new char[] { ' ', ';', ',', '\r', '\t', '\n' };
string snew = chars.Aggregate(s, (c1, c2) => c1.Replace(c2, '\n'));

Aquí está el método de extensión:

public static string ReplaceAll(this string seed, char[] chars, char replacementCharacter)
{
    return chars.Aggregate(seed, (str, cItem) => str.Replace(cItem, replacementCharacter));
}

Ejemplo de uso del método de extensión:

string snew = s.ReplaceAll(chars, '\n');
dodgy_coder
fuente
21

Este es el camino más corto:

myString = Regex.Replace(myString, @"[;,\t\r ]|[\n]{2}", "\n");
ParPar
fuente
1
Este forro también ayuda cuando lo necesitas en los inicializadores.
Guney Ozsan
8

Ohhh, el horror de rendimiento! La respuesta está un poco desactualizada, pero aún así ...

public static class StringUtils
{
    #region Private members

    [ThreadStatic]
    private static StringBuilder m_ReplaceSB;

    private static StringBuilder GetReplaceSB(int capacity)
    {
        var result = m_ReplaceSB;

        if (null == result)
        {
            result = new StringBuilder(capacity);
            m_ReplaceSB = result;
        }
        else
        {
            result.Clear();
            result.EnsureCapacity(capacity);
        }

        return result;
    }


    public static string ReplaceAny(this string s, char replaceWith, params char[] chars)
    {
        if (null == chars)
            return s;

        if (null == s)
            return null;

        StringBuilder sb = null;

        for (int i = 0, count = s.Length; i < count; i++)
        {
            var temp = s[i];
            var replace = false;

            for (int j = 0, cc = chars.Length; j < cc; j++)
                if (temp == chars[j])
                {
                    if (null == sb)
                    {
                        sb = GetReplaceSB(count);
                        if (i > 0)
                            sb.Append(s, 0, i);
                    }

                    replace = true;
                    break;
                }

            if (replace)
                sb.Append(replaceWith);
            else
                if (null != sb)
                    sb.Append(temp);
        }

        return null == sb ? s : sb.ToString();
    }
}
John Whiter
fuente
7

Las cadenas son simplemente matrices de caracteres inmutables

Solo necesitas hacerlo mutable:

  • ya sea usando StringBuilder
  • ir al unsafemundo y jugar con punteros (aunque peligroso)

e intente iterar a través de la matriz de caracteres la menor cantidad de veces. Tenga en cuenta el HashSetaquí, ya que evita atravesar la secuencia de caracteres dentro del bucle. Si necesita una búsqueda aún más rápida, puede reemplazarla HashSetpor una búsqueda optimizada para char(basada en un array[256]).

Ejemplo con StringBuilder

public static void MultiReplace(this StringBuilder builder, 
    char[] toReplace, 
    char replacement)
{
    HashSet<char> set = new HashSet<char>(toReplace);
    for (int i = 0; i < builder.Length; ++i)
    {
        var currentCharacter = builder[i];
        if (set.Contains(currentCharacter))
        {
            builder[i] = replacement;
        }
    }
}

Editar - Versión optimizada

public static void MultiReplace(this StringBuilder builder, 
    char[] toReplace,
    char replacement)
{
    var set = new bool[256];
    foreach (var charToReplace in toReplace)
    {
        set[charToReplace] = true;
    }
    for (int i = 0; i < builder.Length; ++i)
    {
        var currentCharacter = builder[i];
        if (set[currentCharacter])
        {
            builder[i] = replacement;
        }
    }
}

Entonces solo lo usas así:

var builder = new StringBuilder("my bad,url&slugs");
builder.MultiReplace(new []{' ', '&', ','}, '-');
var result = builder.ToString();
Fabuloso
fuente
Recuerde que las cadenas están wchar_ten .net, está reemplazando solo un subconjunto de todos los caracteres posibles (y necesitará 65536 bools para optimizar eso ...)
gog
3

También puede simplemente escribir estos métodos de extensión de cadena y colocarlos en algún lugar de su solución:

using System.Text;

public static class StringExtensions
{
    public static string ReplaceAll(this string original, string toBeReplaced, string newValue)
    {
        if (string.IsNullOrEmpty(original) || string.IsNullOrEmpty(toBeReplaced)) return original;
        if (newValue == null) newValue = string.Empty;
        StringBuilder sb = new StringBuilder();
        foreach (char ch in original)
        {
            if (toBeReplaced.IndexOf(ch) < 0) sb.Append(ch);
            else sb.Append(newValue);
        }
        return sb.ToString();
    }

    public static string ReplaceAll(this string original, string[] toBeReplaced, string newValue)
    {
        if (string.IsNullOrEmpty(original) || toBeReplaced == null || toBeReplaced.Length <= 0) return original;
        if (newValue == null) newValue = string.Empty;
        foreach (string str in toBeReplaced)
            if (!string.IsNullOrEmpty(str))
                original = original.Replace(str, newValue);
        return original;
    }
}


Llámalos así:

"ABCDE".ReplaceAll("ACE", "xy");

xyBxyDxy


Y esto:

"ABCDEF".ReplaceAll(new string[] { "AB", "DE", "EF" }, "xy");

xyCxyF

sɐunıɔ ןɐ qɐp
fuente
2

Use RegEx.Replace, algo como esto:

  string input = "This is   text with   far  too   much   " + 
                 "whitespace.";
  string pattern = "[;,]";
  string replacement = "\n";
  Regex rgx = new Regex(pattern);
  string result = rgx.Replace(input, replacement);

Aquí hay más información sobre esta documentación de MSDN para RegEx.

Dmitry Samuylov
fuente
1

Rendimiento sabio, probablemente esta no sea la mejor solución, pero funciona.

var str = "filename:with&bad$separators.txt";
char[] charArray = new char[] { '#', '%', '&', '{', '}', '\\', '<', '>', '*', '?', '/', ' ', '$', '!', '\'', '"', ':', '@' };
foreach (var singleChar in charArray)
{
   str = str.Replace(singleChar, '_');
}
Daniel Székely
fuente
1
string ToBeReplaceCharacters = @"~()@#$%&amp;+,'&quot;&lt;&gt;|;\/*?";
string fileName = "filename;with<bad:separators?";

foreach (var RepChar in ToBeReplaceCharacters)
{
    fileName = fileName.Replace(RepChar.ToString(), "");
}
Jignesh Bhayani
fuente