Menggunakan LINQ untuk menggabungkan string

345

Apa cara paling efisien untuk menulis old-school:

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

... di LINQ?

tags2k
sumber
1
Apakah Anda menemukan cara LINQ super keren lainnya dalam melakukan sesuatu?
Robert S.
3
Jawaban yang dipilih dan semua opsi lainnya tidak berfungsi di Linq to Entities.
Binoj Antony
3
@Binoj Antony, jangan membuat database Anda melakukan penggabungan string.
Amy B
6
@ Pr0fess0rX: Karena tidak bisa dan karena tidak seharusnya. Saya tidak tahu tentang database lain tetapi dalam SQL Server Anda hanya dapat concat (n) varcahr yang membatasi Anda untuk (n) varchar (max). Seharusnya tidak karena logika bisnis tidak boleh diimplementasikan dalam lapisan data.
the_drow
ada solusi akhir dengan kode sumber lengkap dan kinerja tinggi?
Kiquenet

Jawaban:

528

Jawaban ini menunjukkan penggunaan LINQ ( Aggregate) seperti yang diminta dalam pertanyaan dan tidak dimaksudkan untuk penggunaan sehari-hari. Karena ini tidak menggunakan StringBuilderitu akan memiliki kinerja yang mengerikan untuk urutan yang sangat lama. Untuk penggunaan kode biasa String.Joinseperti yang ditunjukkan pada jawaban lainnya

Gunakan kueri agregat seperti ini:

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

Output ini:

, satu dua tiga

Agregat adalah fungsi yang mengambil koleksi nilai dan mengembalikan nilai skalar. Contoh dari T-SQL termasuk min, max, dan jumlah. Baik VB dan C # memiliki dukungan untuk agregat. Baik agregat dukungan VB dan C # sebagai metode ekstensi. Menggunakan notasi titik, seseorang cukup memanggil metode pada objek IEnumerable .

Ingat bahwa permintaan agregat dieksekusi segera.

Informasi lebih lanjut - MSDN: Kueri Agregat


Jika Anda benar-benar ingin Aggregatemenggunakan varian gunakan StringBuilderdiusulkan dalam komentar oleh CodeMonkeyKing yang akan menjadi kode yang sama seperti biasa String.Jointermasuk kinerja yang baik untuk sejumlah besar objek:

 var res = words.Aggregate(
     new StringBuilder(), 
     (current, next) => current.Append(current.Length == 0? "" : ", ").Append(next))
     .ToString();
Jorge Ferreira
sumber
4
Contoh pertama tidak menampilkan "satu, dua, tiga", itu output ", satu, dua, tiga" (Perhatikan koma terkemuka).
Mort
Dalam contoh pertama Anda, sejak Anda seeded "", nilai pertama yang digunakan currentadalah string kosong. Jadi, untuk 1 elemen atau lebih, Anda akan selalu mendapatkan , di awal string.
Michael Yanni
@Mort Saya telah memperbaikinya
sergtk
358
return string.Join(", ", strings.ToArray());

Di .Net 4, ada kelebihan baru untuk string.Joinmenerima itu IEnumerable<string>. Kode tersebut kemudian akan terlihat seperti:

return string.Join(", ", strings);
Amy B
sumber
2
OK, jadi solusinya tidak menggunakan Linq, tetapi tampaknya bekerja cukup baik untuk saya
Mat Roberts
33
ToArray is linq :)
Amy B
18
Ini jawaban yang paling benar. Ini lebih cepat daripada pertanyaan dan jawaban yang diterima dan jauh lebih jelas daripada Agregat, yang membutuhkan penjelasan sepanjang paragraf setiap kali digunakan.
PRMan
@ RealPro Benar-benar salah. github.com/microsoft/referencesource/blob/master/mscorlib/… line 161
Amy B
125

Mengapa menggunakan Linq?

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

Itu bekerja dengan sempurna dan menerima IEnumerable<string>sejauh yang saya ingat. Tidak perlu Aggregateapa pun di sini yang jauh lebih lambat.

Armin Ronacher
sumber
19
Mempelajari LINQ mungkin keren, dan LINQ mungkin cara yang lucu untuk mencapai tujuan, tetapi menggunakan LINQ untuk benar-benar mendapatkan hasil akhirnya akan buruk, untuk sedikitnya, jika tidak benar-benar bodoh
Jason Bunting
9
.NET 4.0 memiliki kelebihan IEnumerable <string> dan IEnumrable <T>, yang akan membuatnya lebih mudah digunakan
Cine
3
Seperti yang ditunjukkan Cine, .NET 4.0 memiliki kelebihan. Versi sebelumnya tidak. Anda masih dapat String.Join(",", s.ToArray())menggunakan versi yang lebih lama.
Martijn
1
FYI: digabung dari stackoverflow.com/questions/122670/…
Shog9
@ Shog9 Menggabungkan membuat jawaban di sini terlihat seperti upaya yang digandakan, dan stempel waktu tidak membantu sama sekali .. Masih cara untuk pergi.
nawfal
77

Sudahkah Anda melihat metode ekstensi Agregat?

var sa = (new[] { "yabba", "dabba", "doo" }).Aggregate((a,b) => a + "," + b);
Robert S.
sumber
23
Itu mungkin lebih lambat daripada String.Join (), dan lebih sulit dibaca dalam kode. Apakah menjawab pertanyaan untuk "cara LINQ", meskipun :-)
Chris Wenham
5
Ya, saya tidak ingin mencemari jawaban dengan pendapat saya. : P
Robert S.
2
Sebenarnya ini sedikit lebih lambat. Bahkan menggunakan Agregat dengan StringBuilder alih-alih penggabungan lebih lambat dari String.Join.
Joel Mueller
4
Membuat tes dengan 10.000.000 iterasi, agregat mengambil 4,3 detik dan string.join mengambil 2,3 detik. Jadi saya akan mengatakan perf diff tidak penting untuk 99% kasus penggunaan umum. Jadi, jika Anda sudah melakukan banyak linq untuk memproses data Anda, biasanya tidak perlu memecah sintaks yang bagus dan menggunakan string.join imo. gist.github.com/joeriks/5791981
joeriks
1
FYI: digabung dari stackoverflow.com/questions/122670/…
Shog9
56

Contoh nyata dari kode saya:

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

Kueri adalah objek yang memiliki properti Nama yang merupakan string, dan saya ingin nama semua kueri pada daftar yang dipilih, dipisahkan dengan koma.

Daniel Earwicker
sumber
2
Mengingat komentar tentang kinerja, saya harus menambahkan bahwa contohnya adalah dari kode yang berjalan sekali ketika dialog ditutup, dan daftar tidak mungkin memiliki lebih dari sepuluh string di atasnya!
Daniel Earwicker
Adakah petunjuk bagaimana melakukan tugas yang sama ini di Linq to Entities?
Binoj Antony
1
Contoh yang bagus. Terima kasih telah memasukkan ini ke dalam skenario dunia nyata. Saya memiliki situasi yang persis sama, dengan properti objek yang perlu disimpulkan.
Jessy Houle
1
Terpilih untuk membantu saya mengetahui bagian pertama dari memilih properti string Daftar saya <T>
Nikki9696
1
Tolong tulis tentang kinerja pendekatan ini dengan array yang lebih besar.
Giulio Caccin
31

Inilah pendekatan gabungan Bergabung / Linq yang saya selesaikan setelah melihat jawaban lain dan masalah yang dibahas dalam pertanyaan serupa (yaitu bahwa Agregat dan Gabungan gagal dengan 0 elemen).

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

atau (jika sbukan string)

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

  • Sederhana
  • mudah dibaca dan dimengerti
  • bekerja untuk elemen generik
  • memungkinkan menggunakan objek atau properti objek
  • menangani case elemen 0-length
  • dapat digunakan dengan pemfilteran Linq tambahan
  • berkinerja baik (setidaknya dalam pengalaman saya)
  • tidak memerlukan (manual) pembuatan objek tambahan (misalnya StringBuilder) untuk diimplementasikan

Dan tentu saja Bergabunglah mengurus koma akhir sial yang kadang-kadang menyelinap ke pendekatan lain ( for, foreach), itulah sebabnya saya mencari solusi Linq di tempat pertama.

brichins
sumber
1
kurung tidak cocok.
ctrl-alt-delor
1
FYI: digabung dari stackoverflow.com/questions/122670/…
Shog9
3
Saya suka jawaban ini karena menggunakan .Select()seperti ini menyediakan tempat yang mudah untuk memodifikasi setiap elemen selama operasi ini. Misalnya, membungkus setiap item dalam beberapa karakter sepertistring Result = String.Join(",", split.Select(s => "'" + s + "'"));
Sam Storie
29

Anda dapat menggunakan StringBuilderdi 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());

(Ada Selectdi sana hanya untuk menunjukkan Anda dapat melakukan lebih banyak hal LINQ.)

jonathan.s
sumber
2
+1 bagus. Namun, IMO lebih baik untuk menghindari menambahkan ekstra "," daripada menghapusnya sesudahnya. Sesuatu sepertinew[] {"one", "two", "three"}.Aggregate(new StringBuilder(), (sb, s) =>{if (sb.Length > 0) sb.Append(", ");sb.Append(s);return sb;}).ToString();
dss539
5
Anda akan menghemat siklus jam yang berharga dengan tidak memeriksa if (length > 0)linq dan dengan mengeluarkannya.
Binoj Antony
1
Saya setuju dengan DSS539. Versi saya ada di sepanjang barisnew[] {"", "one", "two", "three"}.Aggregate(new StringBuilder(), (sb, s) => (String.IsNullOrEmpty(sb.ToString())) ? sb.Append(s) : sb.Append(", ").Append(s)).ToString();
ProfNimrod
22

data kinerja cepat untuk kasus StringBuilder vs Select & Aggregate lebih dari 3000 elemen:

Uji unit - Durasi (detik)
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);
    }
pengguna337754
sumber
Bermanfaat dalam memutuskan untuk memilih rute non LINQ untuk ini
crabCRUSHERclamCOLLECTOR
4
Perbedaan waktu mungkin StringBuilder vs String Concatination menggunakan +. Tidak ada hubungannya dengan LINQ atau Agregat. Masukkan StringBuilder ke dalam LINQ Aggregate (banyak contoh di SO), dan harus sama cepatnya.
controlbox
16

Saya selalu menggunakan metode ekstensi:

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
sumber
5
string.Joindi .net 4 sudah bisa mengambil IEnumerable<T>untuk sembarang T.
Rekursif
1
FYI: digabung dari stackoverflow.com/questions/122670/…
Shog9
12

Dengan ' cara LINQ super keren ' Anda mungkin berbicara tentang cara LINQ membuat pemrograman fungsional jauh lebih cocok dengan penggunaan metode ekstensi. Maksud saya, gula sintaksis yang memungkinkan fungsi dirantai dalam cara yang linier secara visual (satu demi satu) alih-alih bersarang (satu di dalam yang lain). Sebagai contoh:

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

dapat ditulis seperti ini:

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

Anda dapat melihat bagaimana contoh kedua lebih mudah dibaca. Anda juga dapat melihat bagaimana lebih banyak fungsi dapat ditambahkan dengan lebih sedikit masalah indentasi atau Lispy parens penutup muncul di akhir ekspresi.

Banyak jawaban lain menyatakan bahwa itu String.Joinadalah cara untuk pergi karena ini adalah yang tercepat atau paling sederhana untuk dibaca. Tetapi jika Anda mengambil interpretasi saya tentang ' cara LINQ yang sangat keren ' maka jawabannya adalah menggunakan String.Jointetapi membungkusnya dengan metode ekstensi gaya LINQ yang akan memungkinkan Anda untuk menghubungkan fungsi Anda dengan cara yang menyenangkan secara visual. Jadi, jika Anda ingin menulis, sa.Concatenate(", ")Anda hanya perlu membuat sesuatu seperti ini:

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

Ini akan memberikan kode yang sama berkinerja dengan panggilan langsung (setidaknya dalam hal kompleksitas algoritma) dan dalam beberapa kasus dapat membuat kode lebih mudah dibaca (tergantung pada konteksnya) terutama jika kode lain dalam blok menggunakan gaya fungsi berantai .

kekuatan
sumber
1
Jumlah kesalahan pengetikan di utas ini gila: seperator => separator, Concatinate => Concatenate
SilverSideDown
1
FYI: digabung dari stackoverflow.com/questions/122670/…
Shog9
5

Ada berbagai alternatif jawaban pada pertanyaan sebelumnya - yang memang menargetkan penargetan bilangan bulat sebagai sumber, tetapi menerima jawaban umum.

Jon Skeet
sumber
5

Ini dia menggunakan LINQ murni sebagai ekspresi tunggal:

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

Dan itu sangat cepat!

cdiggins
sumber
3

Saya akan menipu sedikit dan membuang jawaban baru untuk ini yang tampaknya meringkas yang terbaik dari semua yang ada di sini alih-alih menempelkannya di dalam komentar.

Jadi Anda dapat satu baris ini:

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

Sunting: Anda akan ingin memeriksa enumerable kosong terlebih dahulu atau menambahkan.Replace("\a",string.Empty); ke akhir ekspresi. Kurasa aku mungkin mencoba menjadi sedikit terlalu pintar.

Jawaban dari @ a.friend mungkin sedikit lebih berkinerja, saya tidak yakin apa yang Replace lakukan di bawah tenda dibandingkan dengan Hapus. Satu-satunya peringatan lain jika beberapa alasan Anda ingin menyelesaikan string yang berakhir dengan Anda akan kehilangan pemisah Anda ... Saya menemukan itu tidak mungkin. Jika itu masalahnya Anda memiliki karakter mewah lain untuk dipilih.

Chris Marisic
sumber
2

Anda dapat menggabungkan LINQ dan string.join()cukup efektif. Di sini saya menghapus item dari string. Ada juga cara yang lebih baik untuk melakukan ini, tetapi ini dia:

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

Banyak pilihan di sini. Anda dapat menggunakan LINQ dan StringBuilder sehingga Anda mendapatkan kinerja juga seperti itu:

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
sumber
Akan lebih cepat untuk tidak memeriksa builder.Length > 0ForEach dan dengan menghapus koma pertama setelah
ForEach
1

Saya melakukan hal berikut dengan cepat dan kotor ketika mem-parsing file log IIS menggunakan LINQ, itu berhasil @ 1 juta baris cukup baik (15 detik), meskipun mendapat kesalahan memori ketika mencoba 2 juta baris.

    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");

    }

Alasan sebenarnya saya menggunakan LINQ adalah untuk Distinct () yang saya butuhkan sebelumnya:

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.
sumber
1
FYI: digabung dari stackoverflow.com/questions/122670/…
Shog9
0

Saya membuat blog tentang ini beberapa waktu yang lalu, apa yang saya lakukan sesuai dengan apa yang Anda cari:

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

Dalam posting blog menjelaskan cara menerapkan metode ekstensi yang berfungsi di IEnumerable dan diberi nama Concatenate, ini akan memungkinkan Anda menulis hal-hal seperti:

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

Atau lebih banyak hal rumit seperti:

var methodNames = typeof(IFoo).GetMethods().Select(x => x.Name);
string result = methodNames.Concatenate(", ");
Patrik Hägne
sumber
1
FYI: digabung dari stackoverflow.com/questions/122670/…
Shog9
Bisakah Anda menggabungkan kode di sini sehingga jawabannya lebih mudah dipahami?
Giulio Caccin