按分隔符划分列表
Partition list by delimiter
是否有一种优雅的方式(例如,使用 LINQ)通过分隔符将列表划分为子列表列表?按 delim
划分 { 1, 2, delim, delim, 3, delim, 4, 5 }
可能会产生 { { 1, 2 }, { 3 }, { 4, 5 } }
...
直接在Linq中,我觉得会很难,但是你可以创建一个自定义运算符。也许是这样的:
List<String> test = new List<String>() { "1", "8", ";", "2", "7", "42", ";", "3" };
var restul = test.StrangePartition(";");
与 :
public static class Helper
{
public static IEnumerable<IEnumerable<T>> StrangePartition<T>(this IEnumerable<T> source, T partitionKey)
{
List<List<T>> partitions = new List<List<T>>();
List<T> partition = new List<T>();
foreach (T item in source)
{
if (item.Equals(partitionKey))
{
partitions.Add(partition);
partition = new List<T>();
}
else
{
partition.Add(item);
}
}
partitions.Add(partition);
return partitions;
}
}
我认为没有使用默认 LINQ 方法的简单而优雅的方法。但是您可以创建自己的扩展方法:
public static class MyExtensions
{
public static IEnumerable<IEnumerable<TElement>> SplitBy<TElement>(
this IEnumerable<TElement> source,
TElement split,
bool skipEmptyGroups = true)
where TElement : IEquatable<TElement>
{
var group = new List<TElement>();
foreach (var item in source)
{
if (split.Equals(item))
{
if (group.Count > 0 || !skipEmptyGroups)
{
yield return group;
group = new List<TElement>();
}
}
else
{
group.Add(item);
}
}
if (group.Count > 0 || !skipEmptyGroups)
yield return group;
}
}
用法很简单:
var source = new List<int> { 1, 2, 3, 3, 4, 3, 5, 3, 6, 7, 8 };
var result = source.SplitBy(3);
如果你想 return 空组你可以传递额外的 bool
参数:
var resultWithEmptyGroups = source.SplitBy(3, false);
不,没有任何类似的东西,但实现起来很容易:
public static class LinqEx
{
public static IEnumerable<List<TSource>> Split<TSource>(this IEnumerable<TSource> enu, TSource delimiter, IEqualityComparer<TSource> comparer = null)
{
// list == null handles the case where enu is empty
List<TSource> list = null;
if (comparer == null)
{
// Note how the equality comparer is "selected".
// This is how LINQ methods do it
// (see Enumerable.SequenceEqual<TSource>)
comparer = EqualityComparer<TSource>.Default;
}
foreach (TSource el in enu)
{
if (comparer.Equals(el, delimiter))
{
if (list == null)
{
list = new List<TSource>();
}
yield return list;
// Note that we have to recreate the list every time!
// We can't simply do a list.Clear()
list = new List<TSource>();
continue;
}
if (list == null)
{
list = new List<TSource>();
}
list.Add(el);
}
if (list != null)
{
yield return list;
}
}
public static IEnumerable<List<TSource>> SplitRemoveEmpty<TSource>(this IEnumerable<TSource> enu, TSource delimiter, IEqualityComparer<TSource> comparer = null)
{
var list = new List<TSource>();
if (comparer == null)
{
// Note how the equality comparer is "selected".
// This is how LINQ methods do it
// (see Enumerable.SequenceEqual<TSource>)
comparer = EqualityComparer<TSource>.Default;
}
foreach (TSource el in enu)
{
if (comparer.Equals(el, delimiter))
{
if (list.Count != 0)
{
yield return list;
// Note that we have to recreate the list every time!
// We can't simply do a list.Clear()
list = new List<TSource>();
}
continue;
}
list.Add(el);
}
if (list.Count != 0)
{
yield return list;
}
}
}
有两种变体:第一种 (Split
) 将 return 甚至空组,第二种将去除空组。
例如:
{ delim, 1, 2, delim, delim, 3, delim, 4, 5, delim }
Split
您将获得
{ { }, {1, 2}, { }, {3}, {4, 5}, { } }
同时 SplitRemoveEmpty
{ {1, 2}, {3}, {4, 5} }
像这样使用它们:
var res = new[] { 1, 2, 0, 0, 3, 4, 0, 5 }.SplitRemoveEmpty(0);
foreach (List<int> group in res)
{
// Do something
}
请记住,这些拆分方法是基于Equals
方法的正确性!但是有一个可选的最后一个参数,您可以在其中提供方法将使用的 IEqualityComparer<TSource>
而不是默认参数。
虽然其他人提到使用命令式方法会更容易,但它在 LINQ 中当然可行。 Aggregate
运算符有很多用途,但通常可读性较差。
var testCase = new List<String> { "1", "8", ";", "2", "7", "42", ";", "3" };
var result = testCase.Aggregate(new List<List<String>>() { new List<String>() }, (l, s) => {
if (s == ";") {
l.Add(new List<String>());
} else {
l[l.Count - 1].Add(s);
}
return l;
}).ToList();
它会产生所需的输出,但它不仅难以理解,而且还会在查询内产生副作用(不如在查询外产生副作用那么糟糕,但仍然不明智)。
如果您愿意,可以将其放入您自己的扩展方法中:
public static IEnumerable<IEnumerable<T>> PartitionBy(this IEnumerable<T> source, T delimiter) {
return source.Aggregate(new List<List<T>>() { new List<T>() }, (l, elem) =>
if(elem.Equals(delimiter)) {
l.Add(new List<T>());
} else {
l[l.Count - 1].Add(elem);
}
return l;
});
}
同样,考虑这个答案只是为了 LINQ 答案的完整性。
是否有一种优雅的方式(例如,使用 LINQ)通过分隔符将列表划分为子列表列表?按 delim
划分 { 1, 2, delim, delim, 3, delim, 4, 5 }
可能会产生 { { 1, 2 }, { 3 }, { 4, 5 } }
...
直接在Linq中,我觉得会很难,但是你可以创建一个自定义运算符。也许是这样的:
List<String> test = new List<String>() { "1", "8", ";", "2", "7", "42", ";", "3" };
var restul = test.StrangePartition(";");
与 :
public static class Helper
{
public static IEnumerable<IEnumerable<T>> StrangePartition<T>(this IEnumerable<T> source, T partitionKey)
{
List<List<T>> partitions = new List<List<T>>();
List<T> partition = new List<T>();
foreach (T item in source)
{
if (item.Equals(partitionKey))
{
partitions.Add(partition);
partition = new List<T>();
}
else
{
partition.Add(item);
}
}
partitions.Add(partition);
return partitions;
}
}
我认为没有使用默认 LINQ 方法的简单而优雅的方法。但是您可以创建自己的扩展方法:
public static class MyExtensions
{
public static IEnumerable<IEnumerable<TElement>> SplitBy<TElement>(
this IEnumerable<TElement> source,
TElement split,
bool skipEmptyGroups = true)
where TElement : IEquatable<TElement>
{
var group = new List<TElement>();
foreach (var item in source)
{
if (split.Equals(item))
{
if (group.Count > 0 || !skipEmptyGroups)
{
yield return group;
group = new List<TElement>();
}
}
else
{
group.Add(item);
}
}
if (group.Count > 0 || !skipEmptyGroups)
yield return group;
}
}
用法很简单:
var source = new List<int> { 1, 2, 3, 3, 4, 3, 5, 3, 6, 7, 8 };
var result = source.SplitBy(3);
如果你想 return 空组你可以传递额外的 bool
参数:
var resultWithEmptyGroups = source.SplitBy(3, false);
不,没有任何类似的东西,但实现起来很容易:
public static class LinqEx
{
public static IEnumerable<List<TSource>> Split<TSource>(this IEnumerable<TSource> enu, TSource delimiter, IEqualityComparer<TSource> comparer = null)
{
// list == null handles the case where enu is empty
List<TSource> list = null;
if (comparer == null)
{
// Note how the equality comparer is "selected".
// This is how LINQ methods do it
// (see Enumerable.SequenceEqual<TSource>)
comparer = EqualityComparer<TSource>.Default;
}
foreach (TSource el in enu)
{
if (comparer.Equals(el, delimiter))
{
if (list == null)
{
list = new List<TSource>();
}
yield return list;
// Note that we have to recreate the list every time!
// We can't simply do a list.Clear()
list = new List<TSource>();
continue;
}
if (list == null)
{
list = new List<TSource>();
}
list.Add(el);
}
if (list != null)
{
yield return list;
}
}
public static IEnumerable<List<TSource>> SplitRemoveEmpty<TSource>(this IEnumerable<TSource> enu, TSource delimiter, IEqualityComparer<TSource> comparer = null)
{
var list = new List<TSource>();
if (comparer == null)
{
// Note how the equality comparer is "selected".
// This is how LINQ methods do it
// (see Enumerable.SequenceEqual<TSource>)
comparer = EqualityComparer<TSource>.Default;
}
foreach (TSource el in enu)
{
if (comparer.Equals(el, delimiter))
{
if (list.Count != 0)
{
yield return list;
// Note that we have to recreate the list every time!
// We can't simply do a list.Clear()
list = new List<TSource>();
}
continue;
}
list.Add(el);
}
if (list.Count != 0)
{
yield return list;
}
}
}
有两种变体:第一种 (Split
) 将 return 甚至空组,第二种将去除空组。
例如:
{ delim, 1, 2, delim, delim, 3, delim, 4, 5, delim }
Split
您将获得
{ { }, {1, 2}, { }, {3}, {4, 5}, { } }
同时 SplitRemoveEmpty
{ {1, 2}, {3}, {4, 5} }
像这样使用它们:
var res = new[] { 1, 2, 0, 0, 3, 4, 0, 5 }.SplitRemoveEmpty(0);
foreach (List<int> group in res)
{
// Do something
}
请记住,这些拆分方法是基于Equals
方法的正确性!但是有一个可选的最后一个参数,您可以在其中提供方法将使用的 IEqualityComparer<TSource>
而不是默认参数。
虽然其他人提到使用命令式方法会更容易,但它在 LINQ 中当然可行。 Aggregate
运算符有很多用途,但通常可读性较差。
var testCase = new List<String> { "1", "8", ";", "2", "7", "42", ";", "3" };
var result = testCase.Aggregate(new List<List<String>>() { new List<String>() }, (l, s) => {
if (s == ";") {
l.Add(new List<String>());
} else {
l[l.Count - 1].Add(s);
}
return l;
}).ToList();
它会产生所需的输出,但它不仅难以理解,而且还会在查询内产生副作用(不如在查询外产生副作用那么糟糕,但仍然不明智)。
如果您愿意,可以将其放入您自己的扩展方法中:
public static IEnumerable<IEnumerable<T>> PartitionBy(this IEnumerable<T> source, T delimiter) {
return source.Aggregate(new List<List<T>>() { new List<T>() }, (l, elem) =>
if(elem.Equals(delimiter)) {
l.Add(new List<T>());
} else {
l[l.Count - 1].Add(elem);
}
return l;
});
}
同样,考虑这个答案只是为了 LINQ 答案的完整性。