how split the List of strings into another Lists if particular string matches condition using linq?
i have List contains{a,b,c,at,h,c,bt}
i like split into List<List<string>>{{a,b,c},{at,h,c},{bt}};
if particular strings contains "t"
i n开发者_StackOverflow中文版eed break that line how can i do this in linq?
Well, there's a horrible way of doing it:
int tCounter = 0;
var groups = sequence.GroupBy(x => x.Contains("t") ? ++tCounter : tCounter)
.Select(group => group.ToList())
.ToList();
or equivalently (but without the call to Select):
int tCounter = 0;
var groups = sequence.GroupBy(x => x.Contains("t") ? ++tCounter : tCounter,
(count, group) => group.ToList())
.ToList();
That relies on a side-effect within the GroupBy
clause - which is a really bad idea. LINQ is designed around functional ideals, where queries shouldn't have side effects. You put side effects in the code which uses the query, not in the query itself. This will work, but I wouldn't advise it.
Here's a short but complete demonstration, just to prove it does actually work:
using System;
using System.Collections.Generic;
using System.Linq;
public class Test
{
static void Main(string[] args)
{
var input = new List<string>{"a","b","c","at","h","c","bt"};
int tCounter = 0;
var groups = input.GroupBy(x => x.Contains("t") ? ++tCounter : tCounter)
.Select(group => group.ToList())
.ToList();
foreach (var list in groups)
{
Console.WriteLine(string.Join(", ", list));
}
}
}
Output:
a, b, c
at, h, c
bt
What we really need is a "Scan" (aka foldl, I believe - not sure) operator - like Aggregate, but providing a running aggregation. Then the scan could keep track of the current number of Ts as well as the current value, and the GroupBy
could work on that.
It's not hard to write such an operator, and IIRC the Reactive Extensions System.Interactive assembly already contains one. You may want to use that instead of my horribly grotty hack. At that point you could actually write it reasonably elegantly in LINQ.
There is a built-in extension method Aggregate which is exactly what you need.
var source = new List<string> { "a", "b", "c", "at", "h", "c", "bt" };
var result = source.Aggregate(new List<List<string>>(), (list, s) =>
{
if (list.Count == 0 || s.Contains('t')) list.Add(new List<string>());
list.Last().Add(s);
return list;
});
The result
is List<List<string>>
.
I don't think it can be done with the built-in Linq methods (actually, it can... see other answers), but you can easily create your own extension method for this purpose:
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, Func<T, bool> isSeparator)
{
List<T> list = new List<T>();
foreach(T item in source)
{
if (isSeparator(item))
{
if (list.Count > 0)
{
yield return list;
list = new List<T>();
}
}
list.Add(item);
}
if (list.Count > 0)
{
yield return list;
}
}
And use it as follows:
var list = new[] { "a", "b", "c", "at", "h", "c", "bt" };
var result = list.Split(s => s.Contains("t"));
This problem doesn't scream LINQ to me. If you're asking for a LINQ answer as a mental exercise, that's something else, but here's how I'd solve it (using a plain old loop):
List<List<string>> list = new List<List<string>>();
List<string> sublist = new List<string>();
foreach (string element in originalList)
{
if (element.Contains("t"))
{
list.Add(sublist);
sublist = new List<string>();
}
sublist.Add(element);
}
list.Add(sublist);
Don't get me wrong, I abuse LINQ more than anyone. :)
精彩评论