Usando LINQ para concatenar seqüências de caracteres

345

Qual é a maneira mais eficiente de escrever a velha escola:

StringBuilder sb = new StringBuilder();
if (strings.Count > 0)
{
    foreach (string s in strings)
    {
        sb.Append(s + ", ");
    }
    sb.Remove(sb.Length - 2, 2);
}
return sb.ToString();

... em LINQ?

tags2k
fonte
11
Você descobriu alguma outra maneira LINQ super legal de fazer as coisas?
Robert S.
3
Bem, a resposta selecionada e todas as outras opções não funcionam no Linq to Entities.
22730 Binoj Antony
3
@Binoj Antony, não faça seu banco de dados fazer concatenação de strings.
Amy B
6
@ Pr0fess0rX: Porque não pode e porque não deveria. Não conheço outros bancos de dados, mas no SQL Server você pode apenas concat (n) varcahr, que o limita a (n) varchar (max). Não deve, porque a lógica de negócios não deve ser implementada na camada de dados.
precisa saber é o seguinte
alguma solução final com código fonte completo e alto desempenho?
Kiquenet 17/09/12

Respostas:

528

Esta resposta mostra o uso de LINQ ( Aggregate) conforme solicitado na pergunta e não se destina ao uso diário. Como isso não usa a, StringBuilderele terá um desempenho horrível por sequências muito longas. Para código regular, use String.Joincomo mostrado na outra resposta

Use consultas agregadas como esta:

string[] words = { "one", "two", "three" };
var res = words.Aggregate(
   "", // start with empty string to handle empty list case.
   (current, next) => current + ", " + next);
Console.WriteLine(res);

Isso gera:

, um dois três

Um agregado é uma função que pega uma coleção de valores e retorna um valor escalar. Exemplos do T-SQL incluem min, max e sum. O VB e o C # têm suporte para agregados. O VB e o C # suportam agregados como métodos de extensão. Usando a notação de ponto, simplesmente chama-se um método em um objeto IEnumerable .

Lembre-se de que as consultas agregadas são executadas imediatamente.

Mais informações - MSDN: consultas agregadas


Se você realmente deseja Aggregateusar a variante de uso StringBuilderproposta no comentário do CodeMonkeyKing, que seria aproximadamente o mesmo código que o normal, String.Joinincluindo bom desempenho para um grande número de objetos:

 var res = words.Aggregate(
     new StringBuilder(), 
     (current, next) => current.Append(current.Length == 0? "" : ", ").Append(next))
     .ToString();
Jorge Ferreira
fonte
4
O primeiro exemplo não gera "um, dois, três", gera ", um, dois, três" (observe a vírgula inicial).
Mort
No seu primeiro exemplo, desde que você semeia "", o primeiro valor usado currenté uma sequência vazia. Portanto, para um ou mais elementos, você sempre chegará , ao início da string.
Michael Yanni
@Mort eu reparei isso
sergtk
358
return string.Join(", ", strings.ToArray());

Na Net 4, há uma nova sobrecarga para string.Joinque aceita IEnumerable<string>. O código ficaria assim:

return string.Join(", ", strings);
Amy B
fonte
2
OK, então a solução não usa Linq, mas parece funcionar muito bem para mim
Mat Roberts
33
ToArray é linq :)
Amy B
18
Esta é a resposta mais correta. É mais rápido que a pergunta e a resposta aceita e é muito mais claro que o Agregado, o que exige uma explicação com um parágrafo cada vez que é usado.
PRMan 21/05
@realPro Completamente falso. github.com/microsoft/referencesource/blob/master/mscorlib/… line 161
Amy B
125

Por que usar o Linq?

string[] s = {"foo", "bar", "baz"};
Console.WriteLine(String.Join(", ", s));

Isso funciona perfeitamente e aceita IEnumerable<string>tanto quanto me lembro. Não precisa de Aggregatenada aqui que seja muito mais lento.

Armin Ronacher
fonte
19
Aprender LINQ pode ser legal, e LINQ pode ser um meio bonito para realizar a final, mas usando LINQ para realmente obter o resultado final seria ruim, para dizer o mínimo, se não completamente estúpido
Jason Bunting
9
.NET 4.0 tem um IEnumerable <string> e IEnumrable <T> sobrecarga, o que tornará muito mais fácil de usar
Cine
3
Como Cine aponta, o .NET 4.0 está sobrecarregado. Versões anteriores não. Você ainda pode estar String.Join(",", s.ToArray())nas versões mais antigas.
Martijn
11
FYI: mesclado de stackoverflow.com/questions/122670/…
Shog9
@ Shog9 Mesclar faz com que as respostas aqui pareçam esforços duplicados, e os carimbos de data e hora não ajudam em nada. Ainda é o caminho a percorrer.
precisa saber é
77

Você já olhou para o método de extensão Agregado?

var sa = (new[] { "yabba", "dabba", "doo" }).Aggregate((a,b) => a + "," + b);
Robert S.
fonte
23
Provavelmente é mais lento que String.Join () e mais difícil de ler no código. Será que responder à pergunta de uma "maneira LINQ", embora :-)
Chris Wenham
5
Sim, eu não queria macular a resposta com minhas opiniões. : P
Robert S.
2
É inquestionavelmente um pouco mais lento, na verdade. Mesmo usando Aggregate com um StringBuilder em vez de concatenação é mais lento que String.Join.
Joel Mueller
4
Fez um teste com 10.000.000 de iterações, o agregado levou 4,3 segundos e a string.join levou 2,3 ​​segundos. Então, eu diria que o diff diff não é importante para 99% dos casos de uso comuns. Portanto, se você já está fazendo muito linq para processar seus dados, geralmente não há necessidade de quebrar essa sintaxe agradável e usar string.join imo. gist.github.com/joeriks/5791981
joeriks
11
FYI: mesclado de stackoverflow.com/questions/122670/…
Shog9
56

Exemplo real do meu código:

return selected.Select(query => query.Name).Aggregate((a, b) => a + ", " + b);

Uma consulta é um objeto que possui uma propriedade Name, que é uma string, e eu quero os nomes de todas as consultas na lista selecionada, separados por vírgulas.

Daniel Earwicker
fonte
2
Dados os comentários sobre desempenho, devo acrescentar que o exemplo é do código que é executado uma vez quando uma caixa de diálogo é fechada, e é improvável que a lista tenha mais de dez strings nela!
Daniel Earwicker
Alguma pista de como fazer essa mesma tarefa no Linq to Entities?
22730 Binoj Antony
11
Excelente exemplo. Obrigado por colocar isso em um cenário do mundo real. Eu tive a mesma situação exata, com a propriedade de um objeto que precisava ser concedida.
Jessy Houle
11
Upvoted por me ajudar a descobrir que a primeira parte de selecionar a propriedade seqüência de minha List <T>
Nikki9696
11
Por favor, escreva sobre o desempenho desta abordagem com uma matriz maior.
Giulio Caccin
31

Aqui está a abordagem combinada do Join / Linq em que eu decidi depois de examinar as outras respostas e os problemas abordados em uma pergunta semelhante (a saber, que Agregado e Concatenado falham com 0 elementos).

string Result = String.Join(",", split.Select(s => s.Name));

ou (se snão for uma string)

string Result = String.Join(",", split.Select(s => s.ToString()));

  • Simples
  • Fácil de ler e compreender
  • funciona para elementos genéricos
  • permite usar objetos ou propriedades de objetos
  • lida com o caso de elementos de comprimento 0
  • poderia ser usado com filtragem Linq adicional
  • executa bem (pelo menos na minha experiência)
  • não requer a criação (manual) de um objeto adicional (por exemplo StringBuilder) para implementar

E é claro que o Join cuida da vírgula final traquina que às vezes se infiltra em outras abordagens ( for, foreach), e é por isso que eu estava procurando uma solução Linq em primeiro lugar.

brichins
fonte
11
parênteses errados.
Ctrl-alt-delor
11
FYI: mesclado de stackoverflow.com/questions/122670/…
Shog9
3
Gosto desta resposta, porque usar .Select()dessa maneira fornece um local fácil para modificar cada elemento durante esta operação. Por exemplo, agrupando cada item em algum personagem como essestring Result = String.Join(",", split.Select(s => "'" + s + "'"));
Sam Storie
29

Você pode usar StringBuilderem Aggregate:

  List<string> strings = new List<string>() { "one", "two", "three" };

  StringBuilder sb = strings
    .Select(s => s)
    .Aggregate(new StringBuilder(), (ag, n) => ag.Append(n).Append(", "));

  if (sb.Length > 0) { sb.Remove(sb.Length - 2, 2); }

  Console.WriteLine(sb.ToString());

(O Selectestá lá apenas para mostrar que você pode fazer mais coisas sobre o LINQ.)

jonathan.s
fonte
2
+1 legal. No entanto, na IMO, é melhor evitar adicionar o "" extra do que apagá-lo posteriormente. Algo comonew[] {"one", "two", "three"}.Aggregate(new StringBuilder(), (sb, s) =>{if (sb.Length > 0) sb.Append(", ");sb.Append(s);return sb;}).ToString();
dss539
5
Você economizaria preciosos ciclos de relógio não verificando if (length > 0)o linq e removendo-o.
Binoj Antony
11
Eu concordo com o dss539. Minha versão está na linha denew[] {"", "one", "two", "three"}.Aggregate(new StringBuilder(), (sb, s) => (String.IsNullOrEmpty(sb.ToString())) ? sb.Append(s) : sb.Append(", ").Append(s)).ToString();
ProfNimrod
22

dados de desempenho rápido para o caso StringBuilder vs Select & Aggregate, com mais de 3000 elementos:

Teste de unidade - Duração (segundos)
LINQ_StringBuilder - 0.0036644
LINQ_Select.Aggregate - 1.8012535

    [TestMethod()]
    public void LINQ_StringBuilder()
    {
        IList<int> ints = new List<int>();
        for (int i = 0; i < 3000;i++ )
        {
            ints.Add(i);
        }
        StringBuilder idString = new StringBuilder();
        foreach (int id in ints)
        {
            idString.Append(id + ", ");
        }
    }
    [TestMethod()]
    public void LINQ_SELECT()
    {
        IList<int> ints = new List<int>();
        for (int i = 0; i < 3000; i++)
        {
            ints.Add(i);
        }
        string ids = ints.Select(query => query.ToString())
                         .Aggregate((a, b) => a + ", " + b);
    }
user337754
fonte
Útil para decidir ir a rota LINQ não para este
crabCRUSHERclamCOLLECTOR
4
A diferença horária é provavelmente StringBuilder vs Concatinação de String usando +. Nada a ver com LINQ ou Agregado. Coloque StringBuilder no LINQ Aggregate (muitos exemplos no SO), e deve ser igualmente rápido.
controlbox
16

Eu sempre uso o método de extensão:

public static string JoinAsString<T>(this IEnumerable<T> input, string seperator)
{
    var ar = input.Select(i => i.ToString()).ToArray();
    return string.Join(seperator, ar);
}
Kieran Benton
fonte
5
string.Joinno .net 4 já pode levar um IEnumerable<T>para qualquer arbitrário T.
recursivo
11
FYI: mesclado de stackoverflow.com/questions/122670/…
Shog9
12

Por 'maneira super bacana do LINQ ', você pode estar falando sobre o modo como o LINQ torna a programação funcional muito mais agradável com o uso de métodos de extensão. Quero dizer, o açúcar sintático que permite que as funções sejam encadeadas de maneira visualmente linear (uma após a outra) em vez de aninhar (uma dentro da outra). Por exemplo:

int totalEven = Enumerable.Sum(Enumerable.Where(myInts, i => i % 2 == 0));

pode ser escrito assim:

int totalEven = myInts.Where(i => i % 2 == 0).Sum();

Você pode ver como o segundo exemplo é mais fácil de ler. Você também pode ver como mais funções podem ser adicionadas com menos problemas de recuo ou os pontos de fechamento do Lispy aparecendo no final da expressão.

Muitas das outras respostas afirmam que esse String.Joiné o caminho a seguir, porque é a mais rápida ou mais simples de ler. Mas se você entender minha interpretação de ' maneira LINQ super bacana ', a resposta é usá-la, String.Joinmas envolva-a em um método de extensão no estilo LINQ que permitirá que você encadeie suas funções de uma maneira visualmente agradável. Então, se você quiser escrever, sa.Concatenate(", ")basta criar algo como isto:

public static class EnumerableStringExtensions
{
   public static string Concatenate(this IEnumerable<string> strings, string separator)
   {
      return String.Join(separator, strings);
   }
}

Isso fornecerá um código com desempenho tão alto quanto a chamada direta (pelo menos em termos de complexidade do algoritmo) e, em alguns casos, poderá tornar o código mais legível (dependendo do contexto), especialmente se outro código do bloco estiver usando o estilo de função em cadeia .

tpower
fonte
11
O número de erros de digitação neste tópico é uma loucura: seperator => separador, concatenar => Concatenate
SilverSideDown
11
FYI: mesclado de stackoverflow.com/questions/122670/…
Shog9
5

Existem várias respostas alternativas nesta pergunta anterior - que reconhecidamente estava mirando uma matriz inteira como a fonte, mas recebeu respostas generalizadas.

Jon Skeet
fonte
5

Aqui está usando LINQ puro como uma única expressão:

static string StringJoin(string sep, IEnumerable<string> strings) {
  return strings
    .Skip(1)
    .Aggregate(
       new StringBuilder().Append(strings.FirstOrDefault() ?? ""), 
       (sb, x) => sb.Append(sep).Append(x));
}

E é muito rápido!

cdiggins
fonte
3

Vou trapacear um pouco e dar uma nova resposta a isso que parece resumir o melhor de tudo aqui em vez de colocá-lo dentro de um comentário.

Então você pode uma linha isso:

List<string> strings = new List<string>() { "one", "two", "three" };

string concat = strings        
    .Aggregate(new StringBuilder("\a"), 
                    (current, next) => current.Append(", ").Append(next))
    .ToString()
    .Replace("\a, ",string.Empty); 

Editar: você deseja verificar primeiro um enumerável vazio ou adicionar um.Replace("\a",string.Empty); no final da expressão. Acho que eu poderia estar tentando ficar um pouco inteligente demais.

A resposta de @ a.friend pode ser um pouco mais eficiente, não sei o que o Replace faz sob o capô em comparação com o Remove. A única outra ressalva, se por algum motivo você quisesse concatenar strings que terminassem em \ a's, você perderia seus separadores ... Acho isso improvável. Se for esse o caso, você tem outros personagens sofisticados para escolher.

Chris Marisic
fonte
2

Você pode combinar o LINQ e com string.join()bastante eficiência. Aqui estou removendo um item de uma string. Também existem maneiras melhores de fazer isso, mas aqui está:

filterset = String.Join(",",
                        filterset.Split(',')
                                 .Where(f => mycomplicatedMatch(f,paramToMatch))
                       );
Andiih
fonte
11
FYI: mesclado de stackoverflow.com/questions/122670/…
Shog9
1

Muitas opções aqui. Você pode usar o LINQ e um StringBuilder para obter o desempenho da seguinte forma:

StringBuilder builder = new StringBuilder();
List<string> MyList = new List<string>() {"one","two","three"};

MyList.ForEach(w => builder.Append(builder.Length > 0 ? ", " + w : w));
return builder.ToString();
Kelly
fonte
Seria mais rápido para não verificar o builder.Length > 0na ForEach e removendo a primeira vírgula após o ForEach
Binoj Antony
1

Fiz o seguinte rápido e sujo ao analisar um arquivo de log do IIS usando linq, funcionou muito bem a 1 milhão de linhas (15 segundos), embora tenha ocorrido um erro de falta de memória ao tentar 2 milhões de linhas.

    static void Main(string[] args)
    {

        Debug.WriteLine(DateTime.Now.ToString() + " entering main");

        // USED THIS DOS COMMAND TO GET ALL THE DAILY FILES INTO A SINGLE FILE: copy *.log target.log 
        string[] lines = File.ReadAllLines(@"C:\Log File Analysis\12-8 E5.log");

        Debug.WriteLine(lines.Count().ToString());

        string[] a = lines.Where(x => !x.StartsWith("#Software:") &&
                                      !x.StartsWith("#Version:") &&
                                      !x.StartsWith("#Date:") &&
                                      !x.StartsWith("#Fields:") &&
                                      !x.Contains("_vti_") &&
                                      !x.Contains("/c$") &&
                                      !x.Contains("/favicon.ico") &&
                                      !x.Contains("/ - 80")
                                 ).ToArray();

        Debug.WriteLine(a.Count().ToString());

        string[] b = a
                    .Select(l => l.Split(' '))
                    .Select(words => string.Join(",", words))
                    .ToArray()
                    ;

        System.IO.File.WriteAllLines(@"C:\Log File Analysis\12-8 E5.csv", b);

        Debug.WriteLine(DateTime.Now.ToString() + " leaving main");

    }

A verdadeira razão pela qual usei o linq foi para um Distinct () que eu precisava anteriormente:

string[] b = a
    .Select(l => l.Split(' '))
    .Where(l => l.Length > 11)
    .Select(words => string.Format("{0},{1}",
        words[6].ToUpper(), // virtual dir / service
        words[10]) // client ip
    ).Distinct().ToArray()
    ;
Andy S.
fonte
11
FYI: mesclado de stackoverflow.com/questions/122670/…
Shog9
0

Eu escrevi sobre isso há um tempo atrás, o que eu fiz para ser exatamente o que você está procurando:

http://ondevelopment.blogspot.com/2009/02/string-concatenation-made-easy.html

Na postagem do blog, descreva como implementar métodos de extensão que funcionam no IEnumerable e são nomeados Concatenar, permitindo escrever coisas como:

var sequence = new string[] { "foo", "bar" };
string result = sequence.Concatenate();

Ou coisas mais elaboradas, como:

var methodNames = typeof(IFoo).GetMethods().Select(x => x.Name);
string result = methodNames.Concatenate(", ");
Patrik Hägne
fonte
11
FYI: mesclado de stackoverflow.com/questions/122670/…
Shog9
Você pode concatenar o código aqui para que a resposta seja mais fácil de entender?
Giulio Caccin