Quiero eliminar todos los caracteres especiales de una cadena. Los caracteres permitidos son AZ (mayúsculas o minúsculas), números (0-9), guión bajo (_) o el signo de punto (.).
Tengo lo siguiente, funciona pero sospecho (¡lo sé!) No es muy eficiente:
public static string RemoveSpecialCharacters(string str)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < str.Length; i++)
{
if ((str[i] >= '0' && str[i] <= '9')
|| (str[i] >= 'A' && str[i] <= 'z'
|| (str[i] == '.' || str[i] == '_')))
{
sb.Append(str[i]);
}
}
return sb.ToString();
}
¿Cuál es la forma más eficiente de hacer esto? ¿Cómo se vería una expresión regular y cómo se compara con la manipulación normal de cadenas?
Las cadenas que se limpiarán serán bastante cortas, generalmente de entre 10 y 30 caracteres de longitud.
Respuestas:
¿Por qué crees que tu método no es eficiente? En realidad, es una de las formas más eficientes en que puede hacerlo.
Por supuesto, debe leer el carácter en una variable local o usar un enumerador para reducir el número de accesos a la matriz:
Una cosa que hace que un método como este sea eficiente es que escala bien. El tiempo de ejecución será relativo a la longitud de la cadena. No hay sorpresas desagradables si lo usarías en una cadena grande.
Editar:
Hice una prueba de rendimiento rápida, ejecutando cada función un millón de veces con una cadena de 24 caracteres. Estos son los resultados:
Función original: 54,5 ms.
Mi cambio sugerido: 47.1 ms.
Mina con capacidad de StringBuilder configurada: 43.3 ms.
Expresión regular: 294,4 ms.
Edición 2: agregué la distinción entre AZ y az en el código anterior. (Volví a realizar la prueba de rendimiento y no hay una diferencia notable).
Edición 3:
probé la solución lookup + char [], y se ejecuta en unos 13 ms.
El precio a pagar es, por supuesto, la inicialización de la gran tabla de búsqueda y mantenerla en la memoria. Bueno, no son tantos datos, pero son muchos para una función tan trivial ...
fuente
char[]
búfer en lugar deStringBuilder
, tiene una ligera ventaja en este según mis pruebas. (Sin embargo, el mío es menos legible, por lo que el pequeño beneficio de rendimiento probablemente no valga la pena.)char[]
búfer funciona (ligeramente) mejor queStringBuilder
, incluso cuando se escala a cadenas que tienen decenas de miles de caracteres de longitud.Bueno, a menos que realmente necesite exprimir el rendimiento de su función, solo elija lo que sea más fácil de mantener y comprender. Una expresión regular se vería así:
Para un rendimiento adicional, puede precompilarlo o simplemente decirle que se compile en la primera llamada (las llamadas posteriores serán más rápidas).
fuente
Sugiero crear una tabla de búsqueda simple, que puede inicializar en el constructor estático para establecer cualquier combinación de caracteres como válida. Esto le permite hacer una verificación rápida y única.
editar
Además, para la velocidad, querrás inicializar la capacidad de tu StringBuilder a la longitud de tu cadena de entrada. Esto evitará reasignaciones. Estos dos métodos juntos le darán velocidad y flexibilidad.
otra edición
Creo que el compilador podría optimizarlo, pero por cuestiones de estilo y eficiencia, recomiendo foreach en lugar de por.
fuente
for
yforeach
producir código similar. Sin embargo, no sé sobre cuerdas. Dudo que el JIT sepa sobre la naturaleza de tipo cadena de String.fuente
foreach (char c in input.Where(c => char.IsLetterOrDigit(c) || allowedSpecialCharacters.Any(x => x == c))) buffer[idx++] = c;
Una expresión regular se verá así:
Pero si el rendimiento es muy importante, le recomiendo que haga algunos puntos de referencia antes de seleccionar la "ruta de expresión regular" ...
fuente
Si está utilizando una lista dinámica de caracteres, LINQ puede ofrecer una solución mucho más rápida y elegante:
Comparé este enfoque con dos de los enfoques "rápidos" anteriores (compilación de lanzamiento):
Tenga en cuenta que el algoritmo se modifica ligeramente: los caracteres se pasan como una matriz en lugar de codificados, lo que podría tener un impacto leve en las cosas (es decir, / las otras soluciones tendrían un bucle interno para verificar la matriz de caracteres).
Si cambio a una solución codificada con una cláusula where de LINQ, los resultados son:
Puede valer la pena mirar LINQ o un enfoque modificado si planea escribir una solución más genérica, en lugar de codificar la lista de caracteres. LINQ definitivamente le brinda un código conciso y altamente legible, incluso más que Regex.
fuente
No estoy convencido de que su algoritmo sea otra cosa que eficiente. Es O (n) y solo mira a cada personaje una vez. No obtendrá nada mejor que eso a menos que conozca mágicamente los valores antes de verificarlos.
Sin embargo, inicializaría la capacidad de tu
StringBuilder
al tamaño inicial de la cadena. Supongo que su problema de rendimiento percibido proviene de la reasignación de memoria.Nota al margen: Comprobación
A
:z
no es seguro. Estás incluyendo[
,\
,]
,^
,_
, y `...Nota al margen 2: Para ese bit extra de eficiencia, coloque las comparaciones en un orden para minimizar el número de comparaciones. (En el peor de los casos, estás hablando de 8 comparaciones, así que no pienses demasiado). Esto cambia con tu aporte esperado, pero un ejemplo podría ser:
Nota al margen 3: si por alguna razón REALMENTE necesita que esto sea rápido, una declaración de cambio puede ser más rápida. El compilador debe crear una tabla de salto para usted, lo que resulta en una sola comparación:
fuente
fuente
Puede usar la expresión regular de la siguiente manera:
fuente
Me parece bien La única mejora que haría es inicializar
StringBuilder
con la longitud de la cadena.fuente
Estoy de acuerdo con este ejemplo de código. Lo único diferente es que lo convierto en Método de extensión de tipo de cadena. Para que pueda usarlo en una línea o código muy simple:
Gracias a Guffa por tu experimento.
fuente
Usaría un reemplazo de cadena con una expresión regular buscando "caracteres especiales", reemplazando todos los caracteres encontrados con una cadena vacía.
fuente
Tenía que hacer algo similar para el trabajo, pero en mi caso tuve que filtrar todo lo que no sea una letra, un número o un espacio en blanco (pero podría modificarlo fácilmente según sus necesidades). El filtrado se realiza del lado del cliente en JavaScript, pero por razones de seguridad también estoy haciendo el filtrado del lado del servidor. Como puedo esperar que la mayoría de las cadenas estén limpias, me gustaría evitar copiar la cadena a menos que realmente lo necesite. Esto me permite la implementación a continuación, que debería funcionar mejor tanto para cadenas limpias como sucias.
fuente
Para S & G's, forma Linq-ified:
Sin embargo, no creo que esta sea la forma más eficiente.
fuente
fuente
Utilizar:
Y obtendrás una cuerda limpia
s
.erase()
lo despojará de todos los caracteres especiales y es altamente personalizable con lamy_predicate()
función.fuente
HashSet es O (1)
No estoy seguro si es más rápido que la comparación existente
Probé y esto no más rápido que la respuesta aceptada.
Lo dejaré como si necesitaras un conjunto de caracteres configurable, esta sería una buena solución.
fuente
Me pregunto si un reemplazo basado en Regex (posiblemente compilado) es más rápido.
Tendría que probar quealguien ha encontrado que esto es ~ 5 veces más lento.Aparte de eso, debe inicializar el StringBuilder con una longitud esperada, para que la cadena intermedia no tenga que copiarse mientras crece.
Un buen número es la longitud de la cadena original, o algo ligeramente más bajo (dependiendo de la naturaleza de las entradas de funciones).
Finalmente, puede usar una tabla de búsqueda (en el rango 0..127) para averiguar si un personaje debe ser aceptado.
fuente
El siguiente código tiene el siguiente resultado (la conclusión es que también podemos guardar algunos recursos de memoria asignando un tamaño de matriz más pequeño):
También puede agregar las siguientes líneas de código para admitir la configuración regional rusa (el tamaño de la matriz será 1104):
fuente
No estoy seguro de que sea la forma más eficiente, pero funciona para mí.
fuente
Aquí hay muchas soluciones propuestas, algunas más eficientes que otras, pero quizás no muy legibles. Aquí hay uno que puede no ser el más eficiente, pero ciertamente utilizable para la mayoría de las situaciones, y es bastante conciso y legible, aprovechando Linq:
fuente
fuente
replaceAll
que no es la función de cadena de C # sino Java o JavaScriptfuente
Si le preocupa la velocidad, use punteros para editar la cadena existente. Puede anclar la cadena y obtener un puntero, luego ejecutar un bucle for sobre cada carácter, sobrescribiendo cada carácter no válido con un carácter de reemplazo. Sería extremadamente eficiente y no requeriría asignar ninguna nueva memoria de cadena. También necesitaría compilar su módulo con la opción insegura y agregar el modificador "inseguro" al encabezado de su método para usar punteros.
fuente