Substitua vários elementos de string em C #


86

Existe um jeito melhor de fazer isso...

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

Eu estendi a classe string para mantê-la em um único trabalho, mas existe uma maneira mais 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();
    }
}

Apenas por diversão (e para interromper os argumentos nos comentários), eu apresentei uma ideia básica comparando os vários exemplos abaixo.

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

A opção regex pontua terrivelmente; a opção de dicionário surge mais rápido; a versão longa do stringbuilder replace é ligeiramente mais rápida do que a mão curta.


1
Com base no que você tem em seus benchmarks, parece que a versão do dicionário não está fazendo todas as substituições, o que eu suspeito que está tornando-a mais rápida do que as soluções StringBuilder.
sapo

1
@toad Hi de 2009; Adicionei um comentário abaixo em abril sobre esse erro gritante. A essência foi atualizada embora eu tenha pulado D. A versão do dicionário é ainda mais rápida.
Chris McKee de


1
@TotZam pelo menos verifique as datas antes de sinalizar coisas; isso é de 2009, isso é de 2012
Chris McKee

Visto que muitas respostas aqui parecem estar relacionadas com o desempenho, acredito que deva ser apontado que a resposta de Andrej Adamanko é provavelmente a mais rápida para muitas substituições; certamente mais rápido do que encadear .Replace (), especialmente em uma string de entrada grande, conforme declarado em sua resposta.
pessoa

Respostas:


123

Mais rápido - não. Mais eficaz - sim, se você usar a StringBuilderclasse. Com sua implementação, cada operação gera uma cópia de uma string que, em certas circunstâncias, pode prejudicar o desempenho. Strings são objetos imutáveis , então cada operação retorna apenas uma cópia modificada.

Se você espera que esse método seja ativado ativamente em múltiplos Stringsde comprimento significativo, pode ser melhor "migrar" sua implementação para a StringBuilderclasse. Com ele, qualquer modificação é realizada diretamente nessa instância, evitando operações de cópia desnecessárias.

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();
    }
}

2
Para maior clareza, a resposta do dicionário é a mais rápida stackoverflow.com/a/1321366/52912
Chris McKee

3
Em seu benchmark em gist.github.com/ChrisMcKee/5937656 o teste de dicionário não está completo: ele não faz todas as substituições e "" substitui "", não "". Não fazer todas as substituições pode ser o motivo pelo qual é mais rápido no benchmark. A substituição do regex também não está completa. Mas o mais importante, sua string TestData é muito curta. Como os estados de resposta aceitos, a string deve ter um comprimento significativo para que o StringBuilder seja vantajoso. Você poderia repetir o benchmark com strings de 10kB, 100kB e 1 MB?
Leif

É um bom ponto; do jeito que está, estava sendo usado para limpeza de urls, então os testes a 100kb - 1 MB não seriam realistas. Vou atualizar o benchmark, então está usando a coisa toda, no entanto, isso foi um erro.
Chris McKee,

Para melhor desempenho, faça um loop sobre os personagens e substitua-os você mesmo. No entanto, isso pode ser entediante se você tiver mais de uma sequência de caracteres (localizá-los obriga você a comparar vários caracteres de uma vez, ao passo que substituí-los requer alocação de mais memória e movimentação do resto da sequência).
Chayim Friedman

13

isso será mais 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();
    }
}

Muito difícil de ler. Tenho certeza de que você sabe o que isso faz, mas um Junior Dev coçará a cabeça com o que realmente acontece. Eu concordo- também procuro sempre a mão curta de escrever algo- Mas foi apenas para minha própria satisfação. Outras pessoas estavam pirando com a pilha de bagunça.
Piotr Kula

3
Na verdade, isso é mais lento. BenchmarkOverhead ... 13ms StringClean-user151323 ... 2843ms StringClean-TheVillageIdiot ... 2921ms Varia nas repetições, mas a resposta ganha gist.github.com/anonymous/5937596
Chris McKee

12

Se você está simplesmente atrás de uma solução bonita e não precisa economizar alguns nanossegundos, que tal um pouco de açú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));

Semelhante ao exemplo C na Síntese (se você olhar acima, a declaração linq mais feia está no comentário)
Chris McKee

1
É interessante que você defina uma declaração funcional como "mais feia" do que uma procedimental.
TimS de

não vou discutir sobre isso; sua mera preferência. Como você disse, linq é simplesmente açúcar sintático; e como eu disse eu já colocaria o equivalente acima do código :)
Chris McKee

11

Talvez um pouco mais legível?

    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;
        }
    }

Adicione também a sugestão do New In Town sobre StringBuilder ...


5
Seria mais legível assim:private static Dictionary<string, string> _replacements = new Dictionary<string, string>() { {"&", "and"}, {",", ""}, {" ", " "} /* etc */ };
ANeves pensa que SE é o mal

2
ou é claro ... private static readonly Dictionary <string, string> Replacements = new Dictionary <string, string> () {{"&", "and"}, {",", ""}, {"", ""} / * etc * /}; public static string Clean (esta string s) {return Replacements.Keys.Aggregate (s, (current, toReplace) => current.Replace (toReplace, Replacements [toReplace])); }
Chris McKee

2
-1: Usar um dicionário não faz qualquer sentido aqui. Basta usar um List<Tuple<string,string>>. Isso também muda a ordem em que as substituições são feitas E não é tão rápido quanto, por exemplo s.Replace("a").Replace("b").Replace("c"). Não use isso!
Thomas

6

Existe uma coisa que pode ser otimizada nas soluções sugeridas. Ter muitas chamadas para Replace()faz com que o código faça várias passagens na mesma string. Com strings muito longas, as soluções podem ser lentas devido à perda de capacidade do cache da CPU. Pode ser que se deva considerar a substituição de várias strings em uma única passagem .


1
Muitas respostas parecem preocupadas com o desempenho, caso em que esta é a melhor. E é simples porque é apenas uma sobrecarga documentada de String.Replace onde você retorna um valor esperado com base na correspondência, neste exemplo, usando um dicionário para combiná-los. Deve ser simples de entender.
pessoa

4

Outra opção usando o linq é

[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);
}

Você pode declarar var removeList = new List<string> { /*...*/ };então basta chamar removeList.ForEach( /*...*/ );e simplificar seu código. Observe também que ele não responde totalmente à pergunta porque todas as strings encontradas são substituídas por String.Empty.
Tok

2

Estou fazendo algo semelhante, mas no meu caso estou fazendo a serialização / desserialização, então preciso poder ir nas duas direções. Eu descobri que usar uma string [] [] funciona quase de forma idêntica ao dicionário, incluindo a inicialização, mas você pode ir na outra direção também, retornando os substitutos aos seus valores originais, algo que o dicionário realmente não está configurado para fazer.

Editar: você pode usar Dictionary<Key,List<Values>>para obter o mesmo resultado que string [] []


-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]);
}

2
Você deve considerar adicionar contexto às suas respostas. Como uma breve explicação do que está fazendo e, se for relevante, por que você escreveu dessa maneira.
Neil
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.