ultimo - quitar caracter de un string c#
La forma más eficiente de eliminar caracteres especiales de la cadena (23)
Quiero eliminar todos los caracteres especiales de una cadena. Los caracteres permitidos son AZ (mayúsculas o minúsculas), números (0-9), subrayado (_) o el signo de punto (.).
Tengo lo siguiente, funciona pero sospecho (¡lo sé!) Que 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, por lo general entre 10 y 30 caracteres de longitud.
¿Por qué crees que tu método no es eficiente? En realidad, es una de las formas más eficientes de hacerlo.
Por supuesto, debe leer el carácter en una variable local o usar un enumerador para reducir el número de accesos de matriz:
public static string RemoveSpecialCharacters(this string str) {
StringBuilder sb = new StringBuilder();
foreach (char c in str) {
if ((c >= ''0'' && c <= ''9'') || (c >= ''A'' && c <= ''Z'') || (c >= ''a'' && c <= ''z'') || c == ''.'' || c == ''_'') {
sb.Append(c);
}
}
return sb.ToString();
}
Una cosa que hace que un método como este sea eficiente es que se 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 configuración de StringBuilder capacidad: 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. (Vuelvo 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 aproximadamente 13 ms.
El precio a pagar es, por supuesto, la inicialización de la enorme tabla de búsqueda y mantenerla en la memoria. Bueno, no es mucha información, pero es mucho para una función tan trivial ...
private static bool[] _lookup;
static Program() {
_lookup = new bool[65536];
for (char c = ''0''; c <= ''9''; c++) _lookup[c] = true;
for (char c = ''A''; c <= ''Z''; c++) _lookup[c] = true;
for (char c = ''a''; c <= ''z''; c++) _lookup[c] = true;
_lookup[''.''] = true;
_lookup[''_''] = true;
}
public static string RemoveSpecialCharacters(string str) {
char[] buffer = new char[str.Length];
int index = 0;
foreach (char c in str) {
if (_lookup[c]) {
buffer[index] = c;
index++;
}
}
return new string(buffer, 0, index);
}
Bueno, a menos que realmente necesite exprimir el rendimiento de su función, simplemente elija lo que sea más fácil de mantener y entender. Una expresión regular se vería así:
Para obtener un rendimiento adicional, puede precompilarlo o simplemente pedirle que compile en la primera llamada (las llamadas posteriores serán más rápidas).
public static string RemoveSpecialCharacters(string str)
{
return Regex.Replace(str, "[^a-zA-Z0-9_.]+", "", RegexOptions.Compiled);
}
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 más pequeño al arreglo):
lookup = new bool[123];
for (var c = ''0''; c <= ''9''; c++)
{
lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}
for (var c = ''A''; c <= ''Z''; c++)
{
lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}
for (var c = ''a''; c <= ''z''; c++)
{
lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}
48: 0
49: 1
50: 2
51: 3
52: 4
53: 5
54: 6
55: 7
56: 8
57: 9
65: A
66: B
67: C
68: D
69: E
70: F
71: G
72: H
73: I
74: J
75: K
76: L
77: M
78: N
79: O
80: P
81: Q
82: R
83: S
84: T
85: U
86: V
87: W
88: X
89: Y
90: Z
97: a
98: b
99: c
100: d
101: e
102: f
103: g
104: h
105: i
106: j
107: k
108: l
109: m
110: n
111: o
112: p
113: q
114: r
115: s
116: t
117: u
118: v
119: w
120: x
121: y
122: z
También puede agregar las siguientes líneas de código para admitir la configuración regional de Rusia (el tamaño del arreglo será 1104):
for (var c = ''А''; c <= ''Я''; c++)
{
lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}
for (var c = ''а''; c <= ''я''; c++)
{
lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}
Estoy de acuerdo con este ejemplo de código. La única diferencia es que lo hago en el método de extensión de tipo de cadena. Para que puedas usarlo en una línea o código muy simple:
string test = "abc@#$123";
test.RemoveSpecialCharacters();
Gracias a Guffa por tu experimento.
public static class MethodExtensionHelper
{
public static string RemoveSpecialCharacters(this string str)
{
StringBuilder sb = new StringBuilder();
foreach (char c in str)
{
if ((c >= ''0'' && c <= ''9'') || (c >= ''A'' && c <= ''Z'') || (c >= ''a'' && c <= ''z'') || c == ''_'')
{
sb.Append(c);
}
}
return sb.ToString();
}
}
HashSet es O (1)
No estoy seguro si es más rápido que la comparación existente
private static HashSet<char> ValidChars = new HashSet<char>() { ''a'', ''b'', ''c'', ''A'', ''B'', ''C'', ''1'', ''2'', ''3'', ''_'' };
public static string RemoveSpecialCharacters(string str)
{
StringBuilder sb = new StringBuilder(str.Length / 2);
foreach (char c in str)
{
if (ValidChars.Contains(c)) sb.Append(c);
}
return sb.ToString();
}
He probado y esto no es más rápido que la respuesta aceptada.
Lo dejaré como si necesitaras un conjunto configurable de caracteres, esta sería una buena solución.
Hay muchas soluciones propuestas aquí, 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:
string stringToclean = "This is a test. Do not try this at home; you might get hurt. Don''t believe it?";
var validPunctuation = new HashSet<char>(". -");
var cleanedVersion = new String(stringToclean.Where(x => (x >= ''A'' && x <= ''Z'') || (x >= ''a'' && x <= ''z'') || validPunctuation.Contains(x)).ToArray());
var cleanedLowercaseVersion = new String(stringToclean.ToLower().Where(x => (x >= ''a'' && x <= ''z'') || validPunctuation.Contains(x)).ToArray());
Me parece bien. La única mejora que haría es inicializar el StringBuilder
con la longitud de la cadena.
StringBuilder sb = new StringBuilder(str.Length);
Me pregunto si un reemplazo basado en Regex (posiblemente compilado) es más rápido. Tendría que probar que alguien ha encontrado que esto es ~ 5 veces más lento.
Aparte de eso, debe inicializar el StringBuilder con una longitud esperada, de modo que la cadena intermedia no tenga que copiarse mientras crece.
Un buen número es la longitud de la cadena original, o algo ligeramente inferior (dependiendo de la naturaleza de las entradas de las funciones).
Finalmente, puede usar una tabla de búsqueda (en el rango 0..127) para averiguar si un carácter debe ser aceptado.
No estoy convencido de que su algoritmo no sea eficiente. Es O (n) y solo mira a cada personaje una vez. No obtendrás nada mejor que eso a menos que mágicamente conozcas los valores antes de verificarlos.
Sin embargo, inicializaría la capacidad de su StringBuilder
con el tamaño inicial de la cadena. Supongo que su problema de rendimiento percibido proviene de la reasignación de memoria.
Nota al margen: verificar A
- z
no es seguro. Estás incluyendo [
, /
, ]
, ^
, _
, y `...
Nota al margen 2: Para obtener un poco más de eficiencia, ponga 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 entrada esperada, pero un ejemplo podría ser:
if (str[i] >= ''0'' && str[i] <= ''z'' &&
(str[i] >= ''a'' || str[i] <= ''9'' || (str[i] >= ''A'' && str[i] <= ''Z'') ||
str[i] == ''_'') || str[i] == ''.'')
Nota al margen 3: Si por cualquier motivo REALMENTE necesita que esto sea rápido, una instrucción de cambio puede ser más rápida. El compilador debe crear una tabla de saltos para usted, lo que resulta en una sola comparación:
switch (str[i])
{
case ''0'':
case ''1'':
.
.
.
case ''.'':
sb.Append(str[i]);
break;
}
No estoy seguro de que sea la forma más eficiente, pero funciona para mí
Public Function RemoverTildes(stIn As String) As String
Dim stFormD As String = stIn.Normalize(NormalizationForm.FormD)
Dim sb As New StringBuilder()
For ich As Integer = 0 To stFormD.Length - 1
Dim uc As UnicodeCategory = CharUnicodeInfo.GetUnicodeCategory(stFormD(ich))
If uc <> UnicodeCategory.NonSpacingMark Then
sb.Append(stFormD(ich))
End If
Next
Return (sb.ToString().Normalize(NormalizationForm.FormC))
End Function
Para S&G''s, vía linq-ified:
var original = "(*^%foo)(@)&^@#><>?:/":'';=-+_";
var valid = new char[] {
''a'', ''b'', ''c'', ''d'', ''e'', ''f'', ''g'', ''h'', ''i'', ''j'', ''k'', ''l'', ''m'', ''n'', ''o'',
''p'', ''q'', ''r'', ''s'', ''t'', ''u'', ''v'', ''w'', ''x'', ''y'', ''z'', ''A'', ''B'', ''C'', ''D'',
''E'', ''F'', ''G'', ''H'', ''I'', ''J'', ''K'', ''L'', ''M'', ''N'', ''O'', ''P'', ''Q'', ''R'', ''S'',
''T'', ''U'', ''V'', ''W'', ''X'', ''Y'', ''Z'', ''1'', ''2'', ''3'', ''4'', ''5'', ''6'', ''7'', ''8'',
''9'', ''0'', ''.'', ''_'' };
var result = string.Join("",
(from x in original.ToCharArray()
where valid.Contains(x) select x.ToString())
.ToArray());
Sin embargo, no creo que esta sea la forma más eficiente.
Puede utilizar la expresión regular de la siguiente manera:
return Regex.Replace(strIn, @"[^/w/.@-]", "", RegexOptions.None, TimeSpan.FromSeconds(1.0));
Si está utilizando una lista dinámica de caracteres, LINQ puede ofrecer una solución mucho más rápida y elegante:
public static string RemoveSpecialCharacters(string value, char[] specialCharacters)
{
return new String(value.Except(specialCharacters).ToArray());
}
Comparé este enfoque con dos de los enfoques "rápidos" anteriores (compilación de lanzamiento):
- Solución de matriz Char por LukeH - 427 ms
- Solución StringBuilder - 429 ms
- LINQ (esta respuesta) - 98 ms
Tenga en cuenta que el algoritmo está ligeramente modificado: los caracteres se pasan como una matriz en lugar de un código rígido, lo que podría estar afectando ligeramente a 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 mediante una cláusula LINQ donde, los resultados son:
- Solución de matriz Char - 7ms
- Solución StringBuilder - 22ms
- LINQ - 60 ms
Podría valer la pena ver 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.
Si te preocupa la velocidad, usa los punteros para editar la cadena existente. Podría fijar la cadena y obtener un puntero a ella, 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 no segura, y agregar el modificador "inseguro" al encabezado de su método para usar punteros.
static void Main(string[] args)
{
string str = "string!$%with^&*invalid!!characters";
Console.WriteLine( str ); //print original string
FixMyString( str, '' '' );
Console.WriteLine( str ); //print string again to verify that it has been modified
Console.ReadLine(); //pause to leave command prompt open
}
public static unsafe void FixMyString( string str, char replacement_char )
{
fixed (char* p_str = str)
{
char* c = p_str; //temp pointer, since p_str is read-only
for (int i = 0; i < str.Length; i++, c++) //loop through each character in string, advancing the character pointer as well
if (!IsValidChar(*c)) //check whether the current character is invalid
(*c) = replacement_char; //overwrite character in existing string with replacement character
}
}
public static bool IsValidChar( char c )
{
return (c >= ''0'' && c <= ''9'') || (c >= ''A'' && c <= ''Z'') || (c >= ''a'' && c <= ''z'') || (c == ''.'' || c == ''_'');
//return char.IsLetterOrDigit( c ) || c == ''.'' || c == ''_''; //this may work as well
}
Sugiero crear una tabla de búsqueda simple, que puede inicializar en el constructor estático para configurar cualquier combinación de caracteres como válida. Esto te permite hacer una comprobación rápida y única.
editar
Además, para la velocidad, querrá inicializar la capacidad de su StringBuilder a la longitud de su cadena de entrada. Esto evitará reasignaciones. Estos dos métodos juntos te darán velocidad y flexibilidad.
otra edición
Creo que el compilador podría optimizarlo, pero como cuestión de estilo y eficiencia, recomiendo foreach en lugar de for.
Tenía que hacer algo similar por trabajo, pero en mi caso tuve que filtrar todo lo que no es una letra, un número o un espacio en blanco (pero podría modificarlo según sus necesidades). El filtrado se realiza en el lado del cliente en JavaScript, pero por razones de seguridad también estoy haciendo el filtrado del lado del servidor. Ya que 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 permitió mi implementación a continuación, que debería funcionar mejor tanto para cadenas limpias como sucias.
public static string EnsureOnlyLetterDigitOrWhiteSpace(string input)
{
StringBuilder cleanedInput = null;
for (var i = 0; i < input.Length; ++i)
{
var currentChar = input[i];
var charIsValid = char.IsLetterOrDigit(currentChar) || char.IsWhiteSpace(currentChar);
if (charIsValid)
{
if(cleanedInput != null)
cleanedInput.Append(currentChar);
}
else
{
if (cleanedInput != null) continue;
cleanedInput = new StringBuilder();
if (i > 0)
cleanedInput.Append(input.Substring(0, i));
}
}
return cleanedInput == null ? input : cleanedInput.ToString();
}
Una expresión regular se verá como:
public string RemoveSpecialChars(string input)
{
return Regex.Replace(input, @"[^0-9a-zA-Z/._]", string.Empty);
}
Pero si el rendimiento es muy importante, te recomiendo que hagas algunos puntos de referencia antes de seleccionar la "ruta de expresiones regulares" ...
Utilizar:
s.erase(std::remove_if(s.begin(), s.end(), my_predicate), s.end());
bool my_predicate(char c)
{
return !(isalpha(c) || c==''_'' || c=='' ''); // depending on you definition of special characters
}
Y obtendrás una cadena limpia s
.
erase()
lo eliminará de todos los caracteres especiales y es altamente personalizable con la función my_predicate()
.
Yo usaría una cadena Reemplazar con una expresión regular en busca de "caracteres especiales", reemplazando todos los caracteres encontrados con una cadena vacía.
StringBuilder sb = new StringBuilder();
for (int i = 0; i < fName.Length; i++)
{
if (char.IsLetterOrDigit(fName[i]))
{
sb.Append(fName[i]);
}
}
public static string RemoveSpecialCharacters(string str)
{
char[] buffer = new char[str.Length];
int idx = 0;
foreach (char c in str)
{
if ((c >= ''0'' && c <= ''9'') || (c >= ''A'' && c <= ''Z'')
|| (c >= ''a'' && c <= ''z'') || (c == ''.'') || (c == ''_''))
{
buffer[idx] = c;
idx++;
}
}
return new string(buffer, 0, idx);
}
public static string RemoveSpecialCharacters(string str){
return str.replaceAll("[^A-Za-z0-9_////.]", "");
}
public string RemoveSpecial(string evalstr)
{
StringBuilder finalstr = new StringBuilder();
foreach(char c in evalstr){
int charassci = Convert.ToInt16(c);
if (!(charassci >= 33 && charassci <= 47))// special char ???
finalstr.append(c);
}
return finalstr.ToString();
}