2009-05-05 21 views
7

Tengo un método para reemplazar todos los caracteres excepto los que especifico. Por ejemplo,Inverse String.Replace - ¿Forma más rápida de hacerlo?

ReplaceNot("test. stop; or, not", ".;/\\".ToCharArray(), '*'); 

regresarían

 
"****.*****;***,****". 

Ahora, esto no es una instancia de la optimización prematura. Llamo a este método varias veces durante una operación de red. Descubrí que en cadenas más largas, está causando cierta latencia, y eliminarlo ayudó un poco. Cualquier ayuda para acelerar esto sería apreciada.

public static string ReplaceNot(this string original, char[] pattern, char replacement) 
    {   
     int index = 0; 
     int old = -1; 

     StringBuilder sb = new StringBuilder(original.Length); 

     while ((index = original.IndexOfAny(pattern, index)) > -1) 
     { 
      sb.Append(new string(replacement, index - old - 1)); 
      sb.Append(original[index]); 
      old = index++; 
     } 

     if (original.Length - old > 1) 
     { 
      sb.Append(new string(replacement, original.Length - (old + 1))); 
     } 

     return sb.ToString(); 
    } 

Final # 's. También agregué un caso de prueba para una cadena de caracteres de 3K, ejecuté 100K veces en vez de 1M para ver qué tan bien cada una de estas escalas. La única sorpresa fue que la expresión regular 'escaló mejor' que los otros, pero no es de ayuda, ya que es muy lento para empezar:

 
User   Short * 1M Long * 100K  Scale 
John   319    2125   6.66 
Luke   360    2659   7.39 
Guffa   409    2827   6.91 
Mine   447    3372   7.54 
DirkGently  1094   9134   8.35 
Michael   1591   12785   8.04 
Peter   21106   94386   4.47 

actualización: Hice la creación de la expresión regular para la versión de Peter una variable estática, y la puso a RegexOptions.Compiled para ser justos:

 
User   Short * 1M  Long * 100K  Scale 
Peter   8997   74715   8.30 

Pastebin enlace a mi código de prueba, por favor, corríjanme si está mal: http://pastebin.com/f64f260ee

+0

Una nit: por favor cambie 'pattern.Contains (original [i]) == false? reemplazo: original [i] 'to' pattern.Contains (original [i])? original [i]: reemplazo'. No es necesario comparar una expresión de bool a verdadero/falso y generalmente se considera mala forma. –

+0

He realizado pruebas de velocidad para todas estas versiones, y su edición más reciente es en realidad la más lenta de todas, ¿cómo obtiene los resultados 4 veces más rápido? –

+0

@john, los vuelvo a verificar, con la esperanza de no estropear algo como volver a cargar una página web mientras realizo la prueba :) – esac

Respuesta

6

bien, en una cadena ~ 60 KB, esto llevará a cabo alrededor del 40% más rápido que su versión:

public static string ReplaceNot(this string original, char[] pattern, char replacement) 
{ 
    int index = 0; 

    StringBuilder sb = new StringBuilder(new string(replacement, original.Length)); 

    while ((index = original.IndexOfAny(pattern, index)) > -1) 
    { 
     sb[index] = original[index++]; 
    } 

    return sb.ToString(); 
} 

El truco es inicializar una nueva cadena con todos los caracteres de reemplazo, ya que la mayoría de ellos serán reemplazados.

+0

+1 - Nice. Sin embargo, le garantizo en este caso que dado que el resultado del índice ++ o la expresión del índice ++ no se está utilizando, habría una diferencia absolutamente nula en el rendimiento (en cuanto a cómo se incrementa el índice). Estoy seguro de que el JIT generará exactamente el mismo código. No me sorprendería si csc.exe generara exactamente la misma IL. –

+0

@Michael: parece que tienes razón, cuando lo cambié, parece funcionar de manera similar, no sé por qué fue diferente la última vez que lo probé –

+0

Para ver IL hay ildasm que viene con .NET Framework (o tal vez el SDK), o puede hacer lo que hacen todos los chicos geniales y usar el .NET Reflector gratuito: http://www.red-gate.com/products/reflector/ –

0

que va a ser O (norte). Parece que está reemplazando todos los alfabetos y espacios en blanco por *, ¿por qué no simplemente prueba si el carácter actual es un alfabeto/espacio en blanco y lo reemplaza?

8

No se puede utilizar Regex.Replace así:

Regex regex = new Regex(@"[^.;/\\]"); 
string s = regex.Replace("test. stop; or, not", "*"); 
+0

+1, ¡pásame! –

+0

Esto me grita la expresión regular también. Tan pronto como tenga más de un par de llamadas a los métodos .IndexOf * o .Substring (o la nueva cadena posiblemente menos eficiente (?) "(Replacement, index - old - 1)"), probablemente pueda lograr lo que sea que esté tratando de hacer con una expresión regular relativamente simple, en un par de líneas de código. – gregmac

+0

Y regex será más rápido que ejecutar la cadena una vez? ¡Siempre pensé que las expresiones regulares no eran buenas cuando el rendimiento era un problema! – dirkgently

4

No sé si esto va a ser más rápido, pero evita Newing hasta cuerdas sólo para que puedan ser adjuntados a la constructor de cadena, que pueden ayudar:

public static string ReplaceNot(this string original, char[] pattern, char replacement) 
    { 
     StringBuilder sb = new StringBuilder(original.Length); 

     foreach (char ch in original) { 
      if (Array.IndexOf(pattern, ch) >= 0) { 
       sb.Append(ch); 
      } 
      else { 
       sb.Append(replacement); 
      } 
     } 

     return sb.ToString(); 
    } 

Si el número de caracteres en pattern será de cualquier tamaño (que supongo que generalmente no), podría ser útil ordenarlo y realizar un Array.BinarySearch() en lugar del Array.indexOf().

Para una transformación tan simple, apostaría a que no tendrá problema en ser más rápido que una expresión regular, también.

Además, dado que el conjunto de caracteres en pattern es probable que lleguen por lo general de una cadena de todos modos (al menos esa ha sido mi experiencia en general con este tipo de API), ¿por qué no tienen la firma del método es:

public static string ReplaceNot(this string original, string pattern, char replacement) 

o mejor aún, tener una sobrecarga en pattern puede ser un char[] o string?

+0

Bueno, después de ver los resultados de sincronización yo ' Estoy un poco sorprendido de lo mal que esta tarifa se compara con tu original ... Supongo que usar String.IndexOfAny() es mucho, mucho mejor que llamar Array.IndexOf repetidamente en cada personaje. –

2

StringBuilder tiene una sobrecarga que toma un carácter y un recuento, por lo que no tiene que crear cadenas intermedias para agregar a StringBuilder. Consigo una mejora de aproximadamente 20% mediante la sustitución de esto:

sb.Append(new string(replacement, index - old - 1)); 

con:

sb.Append(replacement, index - old - 1); 

y esto:

sb.Append(new string(replacement, original.Length - (old + 1))); 

con:

sb.Append(replacement, original.Length - (old + 1)); 

(He probado el código que dijiste era aproximadamente cuatro veces más rápido, y lo encontré aproximadamente 15 veces más lento ...)

+0

Así que lo que creo que es la pregunta interesante ahora es: ¿es esto más rápido que la rutina de John Rasch que optimísticamente carga el StringBuilder con el chas de reemplazo o Rasch más rápido? Pude ver que iba en cualquier dirección (y sospecho que depende de los datos). –

+0

@Michael: números actualizados en unos pocos. – esac

+0

@esac - gracias ... Estoy genuinamente interesado, y me alegro de que haya alguien menos perezoso que yo por aquí para descubrirlo. –

4

Aquí hay otra versión para usted. Mis pruebas sugieren que su rendimiento es bastante bueno.

public static string ReplaceNot(
    this string original, char[] pattern, char replacement) 
{ 
    char[] buffer = new char[original.Length]; 

    for (int i = 0; i < buffer.Length; i++) 
    { 
     bool replace = true; 

     for (int j = 0; j < pattern.Length; j++) 
     { 
      if (original[i] == pattern[j]) 
      { 
       replace = false; 
       break; 
      } 
     } 

     buffer[i] = replace ? replacement : original[i]; 
    } 

    return new string(buffer); 
} 
+0

muy agradable, aunque su escalamiento ha disminuido un poco, está muy cerca de superar la mejor presentación :) he actualizado los horarios principales. – esac

+0

Interesante: Algoritmo, esto es similar a mi publicación. Las diferencias principales son un bucle explícito en lugar de llamar a Array.IndexOf() y usar un char [] para generar el resultado en lugar de un StringBuffer(). Pero esta implementación es 4-5 veces más rápida ... –

+0

@esac, parece que los puntos de referencia varían significativamente según la máquina/el marco en el que se ejecutan: en mi PC anterior (P4 2.6GHz, 1.5GB, XP32) mi versión es dos veces más rápido que el de John; En mi PC más nueva (Core2Duo 2.66GHz, 4GB, Vista64) ¡la versión de John es dos veces más rápida que la mía! – LukeH

Cuestiones relacionadas