Використання LINQ для об'єднання рядків


346

Який найефективніший спосіб написання старої школи:

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

... в LINQ?


1
Ви відкрили будь-які інші надзвичайно круті LINQ способи ведення справ?
Роберт С.

3
Добре, що обрана відповідь та всі інші варіанти не працюють у Linq to Entities.
Біной Антоній

3
@Binoj Антоні, не змушуй вашу базу даних робити об'єднання рядків.
Емі Б

6
@ Pr0fess0rX: Тому що не може і тому, що не повинен. Я не знаю про інші бази даних, але в SQL Server ви можете лише стислити (n) varcahr, що обмежує вас (n) varchar (max). Це не повинно, оскільки бізнес-логіка не повинна бути реалізована в рівні даних.
the_drow

будь-яке остаточне рішення з повним вихідним кодом та високою продуктивністю?
Кікенет

Відповіді:


529

Ця відповідь показує використання LINQ ( Aggregate), як вимагається у запитанні, і не призначене для щоденного використання. Оскільки це не використовується, StringBuilderвоно матиме жахливі показники для дуже довгих послідовностей. Для регулярного використання коду, String.Joinяк показано в іншій відповіді

Використовуйте такі сукупні запити:

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

Це виводи:

, один два три

Сукупність - це функція, яка приймає набір значень і повертає скалярне значення. Приклади T-SQL включають min, max та суму. І VB, і C # підтримують агрегати. І VB, і C # підтримують агрегати як методи розширення. Використовуючи точкову нотацію, просто викликається метод на IEnumerable об'єкті .

Пам'ятайте, що сукупні запити виконуються негайно.

Додаткова інформація - MSDN: Сукупні запити


Якщо ви дійсно хочете Aggregateвикористовувати варіант використання, використовуючи StringBuilderзапропонований у коментарі CodeMonkeyKing, який буде приблизно таким же кодом, як і звичайний, String.Joinвключаючи хороші показники для великої кількості об'єктів:

 var res = words.Aggregate(
     new StringBuilder(), 
     (current, next) => current.Append(current.Length == 0? "" : ", ").Append(next))
     .ToString();

4
Перший приклад не виводить "один, два, три", він виводить ", один, два, три" (Зауважте провідну кому).
Морт

У вашому першому прикладі, оскільки ви використовуєте насіння "", перше значення, яке використовується, current- це порожній рядок. Отже, для 1 або більше елементів ви завжди отримаєте , на початку рядка.
Майкл Янні

@Mort Я це зафіксував
sergtk

358
return string.Join(", ", strings.ToArray());

У .Net 4, є нова перевантаження на string.Joinякий приймає IEnumerable<string>. Код виглядатиме так:

return string.Join(", ", strings);

2
Гаразд, так що рішення не використовує Linq, але, здається, працює на мене досить добре
Мат Робертс

33
ToArray is linq :)
Емі Б

18
Це найправильніша відповідь. Це швидше як питання, так і прийнята відповідь, і набагато чіткіше, ніж агрегатний, що вимагає пояснення в абзаці щоразу, коли воно використовується.
PRMan

@realPro Повністю помилково. github.com/microsoft/referencesource/blob/master/mscorlib/… рядок 161
Емі Б

125

Навіщо використовувати Linq?

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

Це прекрасно працює і приймає будь-яке IEnumerable<string>, наскільки я пам’ятаю. Тут не потрібно Aggregateнічого, що набагато повільніше.


19
Навчання LINQ може бути класним, і LINQ може бути милим засобом для досягнення мети, але використовувати LINQ для фактичного отримання кінцевого результату було б погано, якщо не сказати, якщо не відверто дурне
Джейсон Бантінг

9
.NET 4.0 має IEnumerable <string> та IEnumrable <T> перевантаження, що полегшить його використання
Cine

3
Як зазначає Cine, .NET 4.0 має перевантаження. Попередні версії не мають. Ви все ще можете String.Join(",", s.ToArray())в старих версіях.
Мартійн


@ Shog9 Об'єднання робить відповіді тут схожими на дублювані зусилля, а часові позначки взагалі не допомагають .. Все-таки шлях.
nawfal

77

Ви переглянули метод розширення агрегат?

var sa = (new[] { "yabba", "dabba", "doo" }).Aggregate((a,b) => a + "," + b);

23
Це, мабуть, повільніше, ніж String.Join (), і важче читати в коді. Відповідає на питання "LINQ way", хоча :-)
Chris Wenham

5
Так, я не хотів заплямовувати відповідь своєю думкою. : P
Роберт С.

2
Це, безперечно, зовсім трохи повільніше. Навіть використання Aggregate з StringBuilder замість конкатенації повільніше, ніж String.Join.
Джоель Мюллер

4
Зробив тест з 10 000 000 ітерацій, агрегат зайняв 4,3 сек, а string.join зайняв 2,3 сек. Тому я б сказав, що перфектне розходження є неважливим для 99% випадків загального використання. Отже, якщо ви вже робите багато посилань для обробки своїх даних, зазвичай не потрібно порушувати цей приємний синтаксис і використовувати string.join imo. gist.github.com/joeriks/5791981
joeriks


56

Реальний приклад з мого коду:

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

Запит - це об'єкт, який має властивість Name, що є рядком, і я хочу, щоб імена всіх запитів у вибраному списку були розділені комами.


2
З огляду на коментарі щодо продуктивності, я повинен додати, що приклад - з коду, який запускається один раз, коли діалогове вікно закривається, і список навряд чи колись матиме більше десяти рядків!
Даніель Ервікер

Будь-яка підказка, як виконати цю саму задачу в Linq to Entities?
Біной Антоній

1
Відмінний приклад. Дякуємо, що ввели це в реальний сценарій. У мене була така ж точна ситуація, що має властивість предмета, який потребував лаконічності.
Джессі Хоул

1
Запропоновано допомогти мені з'ясувати, що перша частина вибору рядкового властивості мого списку <T>
Nikki9696

1
Напишіть, будь ласка, про ефективність цього підходу з більшим масивом.
Джуліо Каччін

31

Ось комбінований підхід Join / Linq, на якому я вирішив, переглянувши інші відповіді та питання, вирішені в аналогічному питанні (а саме те, що Aggregate і Concatenate виходять з 0 елементів).

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

або (якщо sце не рядок)

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

  • Простий
  • легко читати і розуміти
  • працює на родові елементи
  • дозволяє використовувати об’єкти або властивості об'єкта
  • обробляє корпус елементів довжиною 0
  • може використовуватися з додатковою фільтрацією Linq
  • працює добре (принаймні, з мого досвіду)
  • не вимагає (вручну) створення додаткового об'єкта (наприклад StringBuilder) для реалізації

І звичайно, Join піклується про прикрі фінальні коми, які іноді підкрадаються до інших підходів ( for, foreach), саме тому я шукав рішення Linq в першу чергу.


1
круглі дужки.
ctrl-alt-delor


3
Мені подобається ця відповідь, тому що використання .Select()цього способу забезпечує просте місце для зміни кожного елемента під час цієї операції. Наприклад, загортаючи кожен предмет у такий персонаж, як такstring Result = String.Join(",", split.Select(s => "'" + s + "'"));
Сем Сторі

29

Ви можете використовувати StringBuilderв 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());

( SelectЄ там просто для того, щоб показати, що ви можете робити більше матеріалів LINQ.)


2
+1 приємно. Однак IMO краще уникати додавання зайвих "," ніж потім стирати його. Щось на кшталтnew[] {"one", "two", "three"}.Aggregate(new StringBuilder(), (sb, s) =>{if (sb.Length > 0) sb.Append(", ");sb.Append(s);return sb;}).ToString();
dss539,

5
Ви б економили дорогоцінні цикли годин, не перевіряючи if (length > 0)лінк і не виймаючи його.
Біной Антоній

1
Я згоден з dss539. Моя версія - за програмоюnew[] {"", "one", "two", "three"}.Aggregate(new StringBuilder(), (sb, s) => (String.IsNullOrEmpty(sb.ToString())) ? sb.Append(s) : sb.Append(", ").Append(s)).ToString();
ProfNimrod

22

швидкі дані про продукти для корпусу StringBuilder vs Select & Aggregate понад 3000 елементів:

Тест одиниці - Тривалість (секунди)
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);
    }

Корисний при вирішенні цього маршруту не LINQ
crabCRUSHERclamCOLLECTOR

4
Різниця в часі, ймовірно, StringBuilder проти String Concatination з використанням +. Нічого спільного з LINQ або Aggregate. Покладіть StringBuilder в LINQ Aggregate (велика кількість прикладів на SO), і це має бути так само швидко.
контрольний

16

Я завжди використовую метод розширення:

public static string JoinAsString<T>(this IEnumerable<T> input, string seperator)
{
    var ar = input.Select(i => i.ToString()).ToArray();
    return string.Join(seperator, ar);
}

5
string.Joinв .net 4 вже може приймати IEnumerable<T>для будь-якого довільного T.
рекурсивна


12

" Супер крутим способом LINQ " ви можете говорити про те, як LINQ робить функціональне програмування набагато приємнішим із застосуванням методів розширення. Я маю на увазі синтаксичний цукор, який дозволяє функціонувати візуально лінійним способом (одна за одною) замість гніздування (одна всередині іншої). Наприклад:

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

можна записати так:

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

Ви можете бачити, як другий приклад легше читати. Ви також можете бачити, як можна додавати більше функцій при меншій кількості проблем з відступом або Lispy паролями Lispy, що з’являються в кінці виразу.

Багато інших відповідей стверджують, що String.Joinце шлях, оскільки це найшвидший чи найпростіший для читання. Але якщо ви сприймете мою інтерпретацію " супер-крутого способу LINQ ", то відповідь полягає в тому, щоб використовувати його, String.Joinале не загорніть його в метод розширення стилю LINQ, який дозволить вам візуально приємно ланцюговувати свої функції. Тож якщо ви хочете написати, sa.Concatenate(", ")вам просто потрібно створити щось подібне:

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

Це забезпечить код, який є настільки ж ефективним, як і прямий виклик (принаймні, з точки зору складності алгоритму), а в деяких випадках може зробити код більш читабельним (залежно від контексту), особливо якщо інший код у блоці використовує стиль ланцюгової функції .


1
Кількість помилок у цій темі божевільна: seperator => separator, Concatinate => Concatenate
SilverSideDown



5

Тут використовується чистий LINQ як єдиний вираз:

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

І це досить чортово швидко!


3

Я збираюся трохи обдурити і викину нову відповідь на це, що, здається, підбиває найкраще з усього тут, а не приклеювати його всередині коментаря.

Отже, ви можете один рядок цього:

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

Редагувати: Ви або хочете спочатку перевірити чи порожню кількість чи додати.Replace("\a",string.Empty); в кінці". Гадаю, я, можливо, намагався стати занадто розумним.

Відповідь від @ a.friend може бути трохи більш ефективною, я не впевнений, що замінить під кришкою порівняно з «Видалити». Єдине інше застереження, якби з якоїсь причини ви хотіли стиснути рядки, які закінчилися в \ а, ви втратите свої роздільники ... Я вважаю це малоймовірним. Якщо це так, у вас є на вибір інші модні персонажі .


2

Можна комбінувати LINQ і string.join()досить ефективно. Тут я виймаю елемент із рядка. Існують і кращі способи зробити це, але ось це:

filterset = String.Join(",",
                        filterset.Split(',')
                                 .Where(f => mycomplicatedMatch(f,paramToMatch))
                       );


1

Тут багато варіантів. Ви можете використовувати LINQ та StringBuilder, щоб ви отримували ефективність так:

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

Було б швидше не перевіряти builder.Length > 0в ForEach і видалити першу кому після ForEach
Binoj Antony

1

Я зробив наступні швидко і брудно, коли розбирав файл журналу IIS за допомогою linq, він спрацював @ 1 мільйон рядків досить добре (15 секунд), хоча вийшов з помилки пам'яті при спробі 2 мільйонів рядків.

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

    }

Справжня причина, по якій я використовував linq, була для Distinct (), яка мені потрібна раніше:

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


0

Я про це блогував деякий час тому, про що я робив шви, щоб бути саме тим, що ви шукаєте:

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

У публікації блогу опишіть, як реалізувати методи розширення, які працюють на IEnumerable і названі Concatenate, це дозволить вам написати такі речі, як:

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

Або більш складні речі, такі як:

var methodNames = typeof(IFoo).GetMethods().Select(x => x.Name);
string result = methodNames.Concatenate(", ");


Чи можете ви тут об'єднати код, щоб відповідь було легше зрозуміти?
Джуліо Каччін
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.