Quero remover todos os caracteres especiais de uma string. Os caracteres permitidos são AZ (maiúsculas ou minúsculas), números (0-9), sublinhado (_) ou o sinal de ponto (.).
Eu tenho o seguinte, funciona, mas desconfio (eu sei!) Que não é muito 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();
}
Qual é a maneira mais eficiente de fazer isso? Como seria uma expressão regular e como ela se compara à manipulação normal de strings?
As strings que serão limpas serão bastante curtas, geralmente entre 10 e 30 caracteres.
Respostas:
Por que você acha que seu método não é eficiente? Na verdade, é uma das maneiras mais eficientes de fazer isso.
Obviamente, você deve ler o caractere em uma variável local ou usar um enumerador para reduzir o número de acessos à matriz:
Uma coisa que torna um método como esse eficiente é que ele se adapta bem. O tempo de execução será relativo ao comprimento da string. Não há surpresas desagradáveis se você a usar em uma corda grande.
Edit:
Fiz um teste rápido de desempenho, executando cada função um milhão de vezes com uma sequência de 24 caracteres. Estes são os resultados:
Função original: 54,5 ms.
Minha alteração sugerida: 47,1 ms.
Mina com configuração da capacidade do StringBuilder: 43,3 ms.
Expressão regular: 294,4 ms.
Edit 2: eu adicionei a distinção entre AZ e az no código acima. (Eu refiz o teste de desempenho e não há diferença perceptível.)
Edit 3:
Eu testei a solução lookup + char [], e ela é executada em cerca de 13 ms.
O preço a pagar é, obviamente, a inicialização da enorme tabela de pesquisa e a memória. Bem, não há muitos dados, mas é uma função tão trivial ...
fonte
char[]
buffer em vez deStringBuilder
, tem uma ligeira vantagem neste, de acordo com meus testes. (Embora menos legível do meu, então a pequena vantagem de desempenho provavelmente não vale a pena.)char[]
buffer tem um desempenho (ligeiramente) melhor queStringBuilder
, mesmo ao escalar para seqüências de caracteres com dezenas de milhares de caracteres.Bem, a menos que você realmente precise reduzir o desempenho de sua função, basta seguir o que é mais fácil de manter e entender. Uma expressão regular ficaria assim:
Para um desempenho adicional, você pode pré-compilar ou apenas pedir para compilar na primeira chamada (as chamadas subseqüentes serão mais rápidas).
fonte
Sugiro criar uma tabela de pesquisa simples, que você pode inicializar no construtor estático para definir qualquer combinação de caracteres como válida. Isso permite que você faça uma verificação rápida e única.
editar
Além disso, para velocidade, você desejará inicializar a capacidade do seu StringBuilder para o comprimento da sua string de entrada. Isso evitará realocações. Esses dois métodos juntos oferecem velocidade e flexibilidade.
outra edição
Eu acho que o compilador pode otimizá-lo, mas por uma questão de estilo e eficiência, eu recomendo foreach em vez de for.
fonte
for
eforeach
produza código semelhante. Eu não sei sobre cordas embora. Duvido que o JIT conheça a natureza de String do tipo array.fonte
foreach (char c in input.Where(c => char.IsLetterOrDigit(c) || allowedSpecialCharacters.Any(x => x == c))) buffer[idx++] = c;
Uma expressão regular será semelhante a:
Mas se o desempenho for muito importante, recomendo que você faça alguns benchmarks antes de selecionar o "caminho da expressão regular" ...
fonte
Se você estiver usando uma lista dinâmica de caracteres, o LINQ poderá oferecer uma solução muito mais rápida e elegante:
Comparei essa abordagem com duas das abordagens "rápidas" anteriores (compilação de versão):
Observe que o algoritmo é ligeiramente modificado - os caracteres são passados como uma matriz em vez de codificados, o que pode impactar um pouco as coisas (ou seja, as outras soluções teriam um loop interno para verificar a matriz de caracteres).
Se eu mudar para uma solução codificada usando uma cláusula LINQ where, os resultados serão:
Pode valer a pena olhar para o LINQ ou uma abordagem modificada se você estiver planejando escrever uma solução mais genérica, em vez de codificar a lista de caracteres. Definitivamente, o LINQ fornece um código conciso e altamente legível - ainda mais que o Regex.
fonte
Não estou convencido de que seu algoritmo seja tudo menos eficiente. É O (n) e só olha para cada personagem uma vez. Você não vai ficar melhor do que isso, a menos que saiba magicamente os valores antes de verificá-los.
No entanto, eu inicializaria a capacidade do seu
StringBuilder
para o tamanho inicial da string. Suponho que seu problema de desempenho percebido vem da realocação de memória.Nota lateral: Verificação
A
-z
não é seguro. Você está incluindo[
,\
,]
,^
,_
, e `...Nota lateral 2: Para obter um pouco mais de eficiência, faça as comparações em ordem para minimizar o número de comparações. (Na pior das hipóteses, você está falando 8 comparações, não pense muito.) Isso muda com a entrada esperada, mas um exemplo pode ser:
Nota lateral 3: Se, por qualquer motivo, você REALMENTE precisar que isso seja rápido, uma declaração de chave poderá ser mais rápida. O compilador deve criar uma tabela de salto para você, resultando em apenas uma única comparação:
fonte
fonte
Você pode usar expressões regulares da seguinte maneira:
fonte
Parece bom para mim. A única melhoria que eu faria seria inicializar o
StringBuilder
com o comprimento da string.fonte
Eu concordo com este exemplo de código. O único diferente que eu faço no método de extensão do tipo string. Para que você possa usá-lo em uma linha ou código muito simples:
Agradeço a Guffa por sua experiência.
fonte
Eu usaria uma String Substituir por uma Expressão Regular procurando "caracteres especiais", substituindo todos os caracteres encontrados por uma string vazia.
fonte
Eu tive que fazer algo semelhante para o trabalho, mas no meu caso eu tive que filtrar tudo o que não é uma letra, número ou espaço em branco (mas você pode modificá-lo facilmente para suas necessidades). A filtragem é feita no lado do cliente em JavaScript, mas por motivos de segurança, também estou fazendo a filtragem no lado do servidor. Como posso esperar que a maioria das seqüências de caracteres esteja limpa, gostaria de evitar copiar a sequência, a menos que realmente precise. Isso permitiu a implementação abaixo, que deve ter um desempenho melhor para as strings limpas e sujas.
fonte
Para S&G, Linq-ified:
Eu não acho que essa seja a maneira mais eficiente, no entanto.
fonte
fonte
Usar:
E você terá uma string limpa
s
.erase()
tira todos os caracteres especiais e é altamente personalizável com amy_predicate()
funçãofonte
HashSet é O (1)
Não tenho certeza se é mais rápido que a comparação existente
Eu testei e isso não é mais rápido que a resposta aceita.
Vou deixar como se você precisasse de um conjunto configurável de caracteres, seria uma boa solução.
fonte
Gostaria de saber se uma substituição baseada em Regex (possivelmente compilada) é mais rápida.
Teria que testar issoalguém descobriu que isso era ~ 5 vezes mais lento.Fora isso, você deve inicializar o StringBuilder com um comprimento esperado, para que a cadeia intermediária não precise ser copiada enquanto cresce.
Um bom número é o comprimento da string original, ou algo um pouco menor (dependendo da natureza das entradas das funções).
Por fim, você pode usar uma tabela de pesquisa (no intervalo de 0 a 127) para descobrir se um caractere deve ser aceito.
fonte
O código a seguir tem a seguinte saída (conclusão é que também podemos economizar alguns recursos de memória alocando tamanho menor da matriz):
Você também pode adicionar as seguintes linhas de código para dar suporte à localidade russa (o tamanho da matriz será 1104):
fonte
Não sei se é a maneira mais eficiente, mas funciona para mim
fonte
Existem muitas soluções propostas aqui, algumas mais eficientes que outras, mas talvez não sejam muito legíveis. Aqui está um que pode não ser o mais eficiente, mas certamente utilizável na maioria das situações, e é bastante conciso e legível, aproveitando o Linq:
fonte
fonte
replaceAll
não é função C # Cordas mas Java ou JavaScriptfonte
Se você está preocupado com a velocidade, use ponteiros para editar a string existente. Você pode fixar a sequência e obter um ponteiro para ela e, em seguida, executar um loop for sobre cada caractere, substituindo cada caractere inválido por um caractere de substituição. Seria extremamente eficiente e não exigiria a alocação de nenhuma nova memória de string. Você também precisaria compilar seu módulo com a opção não segura e adicionar o modificador "não seguro" ao cabeçalho do método para usar ponteiros.
fonte