Reemplazar varios elementos de cadena en C #

86

¿Hay una mejor manera de hacer esto ...

MyString.Trim().Replace("&", "and").Replace(",", "").Replace("  ", " ")
         .Replace(" ", "-").Replace("'", "").Replace("/", "").ToLower();

He ampliado la clase de cadena para mantenerla en un solo trabajo, pero ¿hay una forma más rápida?

public static class StringExtension
{
    public static string clean(this string s)
    {
        return s.Replace("&", "and").Replace(",", "").Replace("  ", " ")
                .Replace(" ", "-").Replace("'", "").Replace(".", "")
                .Replace("eacute;", "é").ToLower();
    }
}

Solo por diversión (y para detener los argumentos en los comentarios), hice una evaluación comparativa de los diversos ejemplos a continuación.

https://gist.github.com/ChrisMcKee/5937656

La opción de expresiones regulares puntúa terriblemente; la opción de diccionario es la más rápida; la versión larga de la sustitución de stringbuilder es ligeramente más rápida que la mano corta.

Chris McKee
fuente
1
Según lo que tiene en sus puntos de referencia, parece que la versión del diccionario no está haciendo todos los reemplazos que sospecho que es lo que lo está haciendo más rápido que las soluciones StringBuilder.
sapo
1
@toad Hola de 2009; Agregué un comentario a continuación en abril sobre ese error evidente. La esencia se actualiza aunque me salté D. La versión del diccionario es aún más rápida.
Chris McKee
1
@TotZam al menos verifica las fechas antes de marcar las cosas; esto es de 2009 eso es de 2012
Chris McKee
Dado que muchas respuestas aquí parecen preocupadas por el rendimiento, creo que debería señalarse que la respuesta de Andrej Adamanko probablemente sea la más rápida para muchos reemplazos; ciertamente más rápido que encadenar .Replace () especialmente en una cadena de entrada grande como se indica en su respuesta.
persona27 de

Respuestas:

123

Más rápido, no. Más efectivo, sí, si usa la StringBuilderclase. Con su implementación, cada operación genera una copia de una cadena que, bajo circunstancias, puede afectar el rendimiento. Las cadenas son objetos inmutables , por lo que cada operación solo devuelve una copia modificada.

Si espera que este método sea llamado de forma activa en múltiples Stringsde longitud significativa, sería mejor "migrar" su implementación a la StringBuilderclase. Con él, cualquier modificación se realiza directamente en esa instancia, por lo que ahorra operaciones de copia innecesarias.

public static class StringExtention
{
    public static string clean(this string s)
    {
        StringBuilder sb = new StringBuilder (s);

        sb.Replace("&", "and");
        sb.Replace(",", "");
        sb.Replace("  ", " ");
        sb.Replace(" ", "-");
        sb.Replace("'", "");
        sb.Replace(".", "");
        sb.Replace("eacute;", "é");

        return sb.ToString().ToLower();
    }
}
BC2
fuente
2
Para mayor claridad, la respuesta del diccionario es la más rápida stackoverflow.com/a/1321366/52912
Chris McKee
3
En su punto de referencia en gist.github.com/ChrisMcKee/5937656, la prueba del diccionario no está completa: no hace todos los reemplazos y "" reemplaza "", no "". No hacer todos los reemplazos podría ser la razón por la que es más rápido en el punto de referencia. El reemplazo de expresiones regulares tampoco está completo. Pero lo más importante es que su cadena TestData es muy corta. Como dice la respuesta aceptada, la cadena debe tener una longitud significativa para que StringBuilder sea una ventaja. ¿Podría repetir el punto de referencia con cadenas de 10 kB, 100 kB y 1 MB?
Leif
Es un buen punto; tal como está, se estaba utilizando para la limpieza de URL, por lo que las pruebas a 100kb - 1mb no habrían sido realistas. Actualizaré el punto de referencia para que esté usando todo, eso fue un error.
Chris McKee
Para obtener el mejor rendimiento, recorra los personajes y reemplácelos usted mismo. Sin embargo, eso puede ser tedioso si tiene más de cadenas de caracteres individuales (encontrarlas le obliga a comparar varios caracteres a la vez, mientras que reemplazarlos requiere asignar más memoria y mover el resto de la cadena).
Chayim Friedman
13

esto será más eficiente:

public static class StringExtension
{
    public static string clean(this string s)
    {
        return new StringBuilder(s)
              .Replace("&", "and")
              .Replace(",", "")
              .Replace("  ", " ")
              .Replace(" ", "-")
              .Replace("'", "")
              .Replace(".", "")
              .Replace("eacute;", "é")
              .ToString()
              .ToLower();
    }
}
TheVillageIdiot
fuente
Realmente difícil de leer. Estoy seguro de que sabe lo que hace, pero un desarrollador junior se rascará la cabeza ante lo que realmente sucede. Estoy de acuerdo, también busco siempre la letra corta de escribir algo, pero fue solo para mi propia satisfacción. Otras personas se estaban volviendo locas con el montón de desorden.
Piotr Kula
3
En realidad, esto es más lento. BenchmarkOverhead ... 13ms StringClean-user151323 ... 2843ms StringClean-TheVillageIdiot ... 2921ms Varía según las reposiciones, pero la respuesta gana gist.github.com/anonymous/5937596
Chris McKee
12

Si simplemente busca una solución bonita y no necesita ahorrar unos nanosegundos, ¿qué tal un poco de azúcar LINQ?

var input = "test1test2test3";
var replacements = new Dictionary<string, string> { { "1", "*" }, { "2", "_" }, { "3", "&" } };

var output = replacements.Aggregate(input, (current, replacement) => current.Replace(replacement.Key, replacement.Value));
TimS
fuente
Similar al ejemplo C en la esencia (si miras arriba, la declaración de linq más fea está en el comentario)
Chris McKee
1
Es interesante que defina una declaración funcional como "más fea" que una de procedimiento.
TimS
no voy a discutir sobre eso; su mera preferencia. Como dices, linq es simplemente azúcar sintáctico; y como dije, ya puse el equivalente encima del código :)
Chris McKee
11

¿Quizás un poco más legible?

    public static class StringExtension {

        private static Dictionary<string, string> _replacements = new Dictionary<string, string>();

        static StringExtension() {
            _replacements["&"] = "and";
            _replacements[","] = "";
            _replacements["  "] = " ";
            // etc...
        }

        public static string clean(this string s) {
            foreach (string to_replace in _replacements.Keys) {
                s = s.Replace(to_replace, _replacements[to_replace]);
            }
            return s;
        }
    }

También agregue la sugerencia de New In Town sobre StringBuilder ...

Paolo Tedesco
fuente
5
Sería más legible así:private static Dictionary<string, string> _replacements = new Dictionary<string, string>() { {"&", "and"}, {",", ""}, {" ", " "} /* etc */ };
ANeves piensa que SE es malvado
2
o por supuesto ... Diccionario privado estático de solo lectura <cadena, cadena> Reemplazos = nuevo Diccionario <cadena, cadena> () {{"&", "y"}, {",", ""}, {"", ""} / * etc * /}; cadena estática pública Limpiar (esta cadena s) {return Replacements.Keys.Aggregate (s, (actual, toReplace) => current.Replace (toReplace, Replacements [toReplace])); }
Chris McKee
2
-1: Usar un diccionario no tiene ningún sentido aquí. Solo usa un List<Tuple<string,string>>. Esto también cambia el orden de los reemplazos y no es tan rápido como, por ejemplo s.Replace("a").Replace("b").Replace("c"). ¡No uses esto!
Thomas
6

Hay una cosa que se puede optimizar en las soluciones sugeridas. Tener muchas llamadas a Replace()hace que el código realice múltiples pasadas sobre la misma cadena. Con cadenas muy largas, las soluciones pueden ser lentas debido a fallas de capacidad de caché de la CPU. Puede ser que uno deba considerar reemplazar varias cadenas en una sola pasada .

Andrej Adamenko
fuente
1
Muchas respuestas parecen preocupadas por el rendimiento, en cuyo caso esta es la mejor. Y es simple porque es solo una sobrecarga documentada de String. Reemplace donde devuelve un valor esperado basado en la coincidencia, en este ejemplo, usando un diccionario para emparejarlos. Debe ser simple de entender.
27 de
4

Otra opción que usa linq es

[TestMethod]
public void Test()
{
  var input = "it's worth a lot of money, if you can find a buyer.";
  var expected = "its worth a lot of money if you can find a buyer";
  var removeList = new string[] { ".", ",", "'" };
  var result = input;

  removeList.ToList().ForEach(o => result = result.Replace(o, string.Empty));

  Assert.AreEqual(expected, result);
}
Luiz Felipe
fuente
Puede declarar y var removeList = new List<string> { /*...*/ };luego simplemente llamar removeList.ForEach( /*...*/ );y simplificar su código. Tenga en cuenta también que no responde completamente a la pregunta porque todas las cadenas encontradas se reemplazan con String.Empty.
Tok
2

Estoy haciendo algo similar, pero en mi caso estoy haciendo serialización / deserialización, así que necesito poder ir en ambas direcciones. Encuentro que usar una cadena [] [] funciona casi de manera idéntica al diccionario, incluida la inicialización, pero también puede ir en la otra dirección, devolviendo los sustitutos a sus valores originales, algo para lo que el diccionario realmente no está configurado.

Editar: puede utilizar Dictionary<Key,List<Values>>para obtener el mismo resultado que la cadena [] []

sidDemure
fuente
-1
string input = "it's worth a lot of money, if you can find a buyer.";
for (dynamic i = 0, repl = new string[,] { { "'", "''" }, { "money", "$" }, { "find", "locate" } }; i < repl.Length / 2; i++) {
    input = input.Replace(repl[i, 0], repl[i, 1]);
}
usuario7718176
fuente
2
Debería considerar agregar contexto a sus respuestas. Como una breve explicación de lo que hace y, si es relevante, por qué lo escribió de la forma en que lo hizo.
Neil