Używanie LINQ do łączenia łańcuchów

346

Jaki jest najbardziej efektywny sposób pisania starej szkoły:

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

... w LINQ?

tags2k
źródło
1
Czy odkryłeś jakieś inne fajne sposoby LINQ na robienie rzeczy?
Robert S.
3
Cóż, wybrana odpowiedź i wszystkie inne opcje nie działają w Linq to Entities.
Binoj Antony
3
@Binoj Antony, nie zmuszaj bazy danych do łączenia łańcuchów.
Amy B
6
@ Pr0fess0rX: Ponieważ nie może i dlatego, że nie powinien. Nie wiem o innych bazach danych, ale w SQL Server możesz tylko konkatować (n) varcahr, co ogranicza cię do (n) varchar (max). Nie powinno tak być, ponieważ logiki biznesowej nie należy implementować w warstwie danych.
the_drow 27.04.2011
jakieś ostateczne rozwiązanie z pełnym kodem źródłowym i wysoką wydajnością?
Kiquenet,

Odpowiedzi:

529

Ta odpowiedź pokazuje użycie LINQ ( Aggregate) zgodnie z żądaniem w pytaniu i nie jest przeznaczona do codziennego użytku. Ponieważ to nie używa StringBuilder, będzie miało straszną wydajność w przypadku bardzo długich sekwencji. Do zwykłego używania kodu, String.Joinjak pokazano w drugiej odpowiedzi

Użyj zagregowanych zapytań takich jak to:

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

To daje:

, raz Dwa Trzy

Agregacja to funkcja, która pobiera zbiór wartości i zwraca wartość skalarną. Przykłady z T-SQL obejmują min, max i sumę. Zarówno VB, jak i C # mają obsługę agregatów. Zarówno VB, jak i C # obsługują agregacje jako metody rozszerzenia. Używając notacji kropkowej, po prostu wywołujemy metodę na obiekcie IEnumerable .

Pamiętaj, że agregowane zapytania są wykonywane natychmiast.

Więcej informacji - MSDN: Zagregowane zapytania


Jeśli naprawdę chcesz Aggregateużyć wariantu użycia przy użyciu StringBuilderzaproponowanego w komentarzu CodeMonkeyKing, który byłby mniej więcej taki sam jak zwykły kod, w String.Jointym dobra wydajność dla dużej liczby obiektów:

 var res = words.Aggregate(
     new StringBuilder(), 
     (current, next) => current.Append(current.Length == 0? "" : ", ").Append(next))
     .ToString();
Jorge Ferreira
źródło
4
Pierwszy przykład nie wypisuje „jeden, dwa, trzy”, wypisuje „jeden, dwa, trzy” (zauważ przecinek wiodący).
Mort
W pierwszym przykładzie, od kiedy użyjesz nasion "", pierwszą użytą wartością currentjest pusty ciąg. Tak więc, dla 1 lub więcej elementów, zawsze będziesz , na początku łańcucha.
Michael Yanni
@Mort Naprawiłem to
sergtk,
358
return string.Join(", ", strings.ToArray());

W .NET 4, nowe przeciążenie na string.Joinktóry akceptuje IEnumerable<string>. Kod wyglądałby wtedy następująco:

return string.Join(", ", strings);
Amy B.
źródło
2
OK, więc rozwiązanie nie korzysta z Linqa, ale wydaje mi się, że działa całkiem dobrze
Mat Roberts,
33
ToArray to linq :)
Amy B
18
To jest najbardziej poprawna odpowiedź. Jest szybszy niż zarówno pytanie, jak i zaakceptowana odpowiedź, i jest znacznie jaśniejszy niż Agregat, który wymaga długiego akapitu wyjaśnienia przy każdym użyciu.
PRMan
@realPro Całkowicie fałszywe. github.com/microsoft/referencesource/blob/master/mscorlib/… linia 161
Amy B
125

Dlaczego warto korzystać z Linq?

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

To działa idealnie i akceptuje wszystko, IEnumerable<string>o ile pamiętam. Nie potrzeba Aggregatetu nic, co jest o wiele wolniejsze.

Armin Ronacher
źródło
19
Nauka LINQ może być fajna, a LINQ może być uroczym sposobem na osiągnięcie celu, ale użycie LINQ do uzyskania wyniku końcowego byłoby co najmniej złe, jeśli nie wręcz wręcz głupie
Jason Bunting
9
.NET 4.0 ma przeciążenie IEnumerable <string> i IEnumrable <T>, co znacznie ułatwi korzystanie
Cine
3
Jak podkreśla Cine, .NET 4.0 ma przeciążenie. Poprzednie wersje nie. Nadal możesz jednak korzystać String.Join(",", s.ToArray())ze starszych wersji.
Martijn
1
FYI: scalono ze stackoverflow.com/questions/122670/…
Shog9
@ Shog9 Scalanie powoduje, że odpowiedzi tutaj wyglądają jak powielone wysiłki, a znaczniki czasu wcale nie pomagają. Nadal jest droga.
nawfal
77

Czy spojrzałeś na metodę rozszerzenia Aggregate?

var sa = (new[] { "yabba", "dabba", "doo" }).Aggregate((a,b) => a + "," + b);
Robert S.
źródło
23
Jest to prawdopodobnie wolniejsze niż String.Join () i trudniejsze do odczytania w kodzie. Odpowiada jednak na pytanie „metodą LINQ” :-)
Chris Wenham
5
Tak, nie chciałem skazywać odpowiedzi moimi opiniami. : P
Robert S.
2
W rzeczywistości jest to zdecydowanie wolniejsze. Nawet użycie Aggregate z StringBuilder zamiast konkatenacji jest wolniejsze niż String.Join.
Joel Mueller
4
Wykonano test z 10 000 000 iteracji, agregacja zajęła 4,3 sekundy, a string.join zajęło 2,3 sekundy. Powiedziałbym więc, że perf diff nie ma znaczenia dla 99% typowych przypadków użycia. Więc jeśli już robisz dużo linqa do przetwarzania danych, zwykle nie musisz łamać tej ładnej składni i używać string.join imo. gist.github.com/joeriks/5791981
joeriks
1
Do Twojej wiadomości: scalono ze stackoverflow.com/questions/122670/…
Shog9
56

Prawdziwy przykład z mojego kodu:

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

Zapytanie to obiekt, który ma właściwość Name, która jest łańcuchem, i chcę, aby nazwy wszystkich zapytań na wybranej liście były oddzielone przecinkami.

Daniel Earwicker
źródło
2
Biorąc pod uwagę komentarze na temat wydajności, powinienem dodać, że przykład pochodzi z kodu, który jest uruchamiany raz po zamknięciu okna dialogowego, a na liście prawdopodobnie nie będzie więcej niż dziesięć ciągów!
Daniel Earwicker
Jakiś pomysł, jak wykonać to samo zadanie w Linq wobec podmiotów?
Binoj Antony
1
Doskonały przykład. Dziękujemy za umieszczenie tego w prawdziwym świecie. Miałem tę samą dokładną sytuację z właściwością obiektu, który wymagał konkatowania.
Jessy Houle,
1
Głosowałem za pomoc w ustaleniu pierwszej części wyboru właściwości ciągu z mojej Listy <T>
Nikki9696,
1
Napisz o wydajności tego podejścia z większą tablicą.
Giulio Caccin
31

Oto połączone podejście Join / Linq, na którym zdecydowałem się po zapoznaniu się z innymi odpowiedziami i problemami poruszonymi w podobnym pytaniu (mianowicie, że agregacja i konkatenat zawodzą z 0 elementami).

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

lub (jeśli snie jest łańcuchem)

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

  • Prosty
  • łatwy do odczytania i zrozumienia
  • działa dla elementów ogólnych
  • pozwala na używanie obiektów lub właściwości obiektu
  • obsługuje przypadek elementów o długości 0
  • może być stosowany z dodatkowym filtrowaniem Linq
  • działa dobrze (przynajmniej z mojego doświadczenia)
  • nie wymaga (ręcznego) utworzenia dodatkowego obiektu (np. StringBuilder) do wdrożenia

I oczywiście Join zajmuje się nieprzyjemnym końcowym przecinkiem, który czasem wymyka się innym podejściom ( for, foreach), dlatego przede wszystkim szukałem rozwiązania Linq.

brichiny
źródło
1
źle dopasowany nawias.
ctrl-alt-delor
1
Do Twojej wiadomości: scalono ze stackoverflow.com/questions/122670/…
Shog9
3
Podoba mi się ta odpowiedź, ponieważ użycie .Select()tego typu zapewnia łatwe miejsce do modyfikacji każdego elementu podczas tej operacji. Na przykład zawijanie każdego przedmiotu taką postaciąstring Result = String.Join(",", split.Select(s => "'" + s + "'"));
Sam Storie,
29

Możesz użyć StringBuilderw 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());

( SelectJest tam tylko po to, aby pokazać, że możesz zrobić więcej rzeczy LINQ.)

jonathan.s
źródło
2
+1 fajnie. Jednak IMO lepiej unikać dodawania dodatkowego „,” niż kasować go później. Coś w stylunew[] {"one", "two", "three"}.Aggregate(new StringBuilder(), (sb, s) =>{if (sb.Length > 0) sb.Append(", ");sb.Append(s);return sb;}).ToString();
dss539
5
Zaoszczędziłbyś cenne cykle zegara, nie sprawdzając if (length > 0)linq i wyjmując go.
Binoj Antony
1
Zgadzam się z dss539. Moja wersja jest new[] {"", "one", "two", "three"}.Aggregate(new StringBuilder(), (sb, s) => (String.IsNullOrEmpty(sb.ToString())) ? sb.Append(s) : sb.Append(", ").Append(s)).ToString();
zgodna
22

szybkie dane wydajności dla przypadku StringBuilder vs Select & Aggregate dla ponad 3000 elementów:

Test jednostkowy - czas trwania (sekundy)
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);
    }
użytkownik337754
źródło
Pomocny w podjęciu decyzji o skorzystaniu z trasy innej niż LINQ
crabCRUSHERclamCOLLECTOR
4
Różnica czasu to prawdopodobnie StringBuilder vs String Concatination przy użyciu +. Nie ma to nic wspólnego z LINQ ani agregacją. Umieść StringBuilder w LINQ Aggregate (mnóstwo przykładów na SO), a powinno być tak samo szybko.
skrzynka kontrolna
16

Zawsze używam metody rozszerzenia:

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
źródło
5
string.Joinw .net 4 może już wziąć IEnumerable<T>dowolny dowolny T.
rekurencyjne
1
FYI: scalono ze stackoverflow.com/questions/122670/…
Shog9
12

Mówiąc „ super fajny sposób LINQ ”, możesz mówić o tym, jak LINQ sprawia, że ​​programowanie funkcjonalne jest o wiele smaczniejsze przy użyciu metod rozszerzenia. Mam na myśli cukier syntaktyczny, który pozwala na łączenie funkcji w wizualnie liniowy sposób (jeden po drugim) zamiast zagnieżdżania (jeden wewnątrz drugiego). Na przykład:

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

można napisać w ten sposób:

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

Możesz zobaczyć, jak drugi przykład jest łatwiejszy do odczytania. Możesz także zobaczyć, jak można dodać więcej funkcji przy mniejszej liczbie problemów z wcięciem lub Lispy zamykających pojawiających się na końcu wyrażenia.

Wiele innych odpowiedzi String.Joinmówi, że jest to najlepsza droga, ponieważ jest najszybsza lub najprostsza do odczytania. Ale jeśli weźmiesz moją interpretację „ super fajnego sposobu LINQ ”, odpowiedzią jest użycie, String.Joinale zapakuj ją w metodę rozszerzenia w stylu LINQ, która pozwoli ci połączyć twoje funkcje w przyjemny wizualnie sposób. Więc jeśli chcesz pisać, sa.Concatenate(", ")musisz po prostu stworzyć coś takiego:

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

Zapewni to kod, który jest tak samo wydajny jak bezpośrednie wywołanie (przynajmniej pod względem złożoności algorytmu), aw niektórych przypadkach może sprawić, że kod będzie bardziej czytelny (w zależności od kontekstu), szczególnie jeśli inny kod w bloku używa stylu funkcji łańcuchowej .

tpower
źródło
1
Liczba literówek w tym wątku jest szalona: seperator => separator, konkatynian => konkatenat
SilverSideDown
1
FYI: scalono ze stackoverflow.com/questions/122670/…
Shog9
5

Istnieją różne alternatywne odpowiedzi na to poprzednie pytanie - które wprawdzie było ukierunkowane na tablicę liczb całkowitych jako źródło, ale otrzymało uogólnione odpowiedzi.

Jon Skeet
źródło
5

Tutaj używa czystego LINQ jako pojedynczego wyrażenia:

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

I to jest cholernie szybkie!

cdiggins
źródło
3

Oszukam trochę i wyrzucę na to nową odpowiedź, która wydaje się podsumowywać to, co najlepsze tutaj, zamiast umieszczać to w komentarzu.

Możesz więc wstawić jedną linię:

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

Edycja: albo najpierw będziesz chciał sprawdzić, czy nie ma pustego wyliczenia, albo dodaj.Replace("\a",string.Empty); na końcu wyrażenia. Chyba starałem się być trochę zbyt mądry.

Odpowiedź od @ a.friend może być nieco bardziej wydajna, nie jestem pewien, co robi Replace pod maską w porównaniu do Remove. Jedynym innym zastrzeżeniem jest to, że z jakiegoś powodu chciałbyś połączyć łańcuchy zakończone ... straciłbyś separatory ... Uważam to za mało prawdopodobne. W takim przypadku masz do wyboru inne fantazyjne postacie .

Chris Marisic
źródło
2

Możesz połączyć LINQ i string.join()całkiem skutecznie. Tutaj usuwam element z łańcucha. Są też lepsze sposoby na zrobienie tego, ale oto:

filterset = String.Join(",",
                        filterset.Split(',')
                                 .Where(f => mycomplicatedMatch(f,paramToMatch))
                       );
Andii
źródło
1
FYI: scalono ze stackoverflow.com/questions/122670/…
Shog9
1

Tutaj jest duży wybór. Możesz użyć LINQ i StringBuilder, aby uzyskać wyższą wydajność:

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
źródło
Szybciej byłoby nie sprawdzać builder.Length > 0ForEach i usuwając pierwszy przecinek po ForEach
Binoj Antony
1

Zrobiłem następujące szybkie i brudne podczas analizowania pliku dziennika IIS przy użyciu linq, działał on całkiem dobrze 1 milion linii (15 sekund), chociaż wystąpił błąd braku pamięci przy próbie 2 milionów linii.

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

    }

Prawdziwym powodem, dla którego użyłem linq, był Distinct (), którego potrzebowałem wcześniej:

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.
źródło
1
FYI: scalono ze stackoverflow.com/questions/122670/…
Shog9
0

Blogowałem o tym jakiś czas temu, to, co zrobiłem, wydaje się być dokładnie tym, czego szukasz:

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

W poście na blogu opisz, jak zaimplementować metody rozszerzeń, które działają na IEnumerable i noszą nazwy Concatenate, pozwoli to napisać takie rzeczy jak:

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

Lub bardziej skomplikowane rzeczy, takie jak:

var methodNames = typeof(IFoo).GetMethods().Select(x => x.Name);
string result = methodNames.Concatenate(", ");
Patrik Hägne
źródło
1
FYI: scalono ze stackoverflow.com/questions/122670/…
Shog9
Czy możesz tutaj połączyć kod, aby odpowiedź była łatwiejsza do zrozumienia?
Giulio Caccin