将TakeWhile和SkipWhile结合起来进行分区收集

时间:2016-06-30 20:25:26

标签: c# linq

我想在项目上对collection进行分区,这与特定条件相匹配。我可以使用TakeWhileSkipWhile来做到这一点,这很容易理解:

public static bool IsNotSeparator(int value) => value != 3;

var collection = new [] { 1, 2, 3, 4, 5 };
var part1 = collection.TakeWhile(IsNotSeparator);
var part2 = collection.SkipWhile(IsNotSeparator);

但这会从collection开始两次迭代,如果IsNotSeparator需要很长时间,则可能是性能问题。

更快的方法是使用类似的东西:

var part1 = new List<int>();
var index = 0;
for (var max = collection.Length; index < max; ++index) {
    if (IsNotSeparator(collection[i]))
        part1.Add(collection[i]);
    else
        break;
}
var part2 = collection.Skip(index);

但这比第一个例子的可读性更低。

所以我的问题是:在特定元素上对collection进行分区的最佳解决方案是什么?

我将上述两者结合起来的是:

var collection = new [] { 1, 2, 3, 4, 5 };
var part1 = collection.TakeWhile(IsNotSeparator).ToList();
var part2 = collection.Skip(part1.Count);

2 个答案:

答案 0 :(得分:0)

这是一个快速示例,说明如何在不使用LINQ的情况下执行更通用的方法(多个拆分,如注释中所述)(可以将其转换为LINQ,但我不确定它是否会更多可读,我现在稍微匆忙):

public static IEnumerable<IEnumerable<T>> Split<T>(this IList<T> list, Predicate<T> match)
{
    if (list.Count == 0)
        yield break;

    var chunkStart = 0;
    for (int i = 1; i < list.Count; i++)
    {
        if (match(list[i]))
        {
            yield return new ListSegment<T>(list, chunkStart, i - 1);
            chunkStart = i;
        }
    }

    yield return new ListSegment<T>(list, chunkStart, list.Count - 1);
}

代码假定一个名为ListSegment<T> : IEnumerable<T>的类,它只是在原始列表上从from迭代到to(没有复制,类似于ArraySegment<T>的工作方式(但不幸的是限于数组)。

因此代码将返回与匹配项一样多的块,即此代码:

var collection = new[] { "A", "B", "-", "C", "D", "-", "E" };
foreach (var chunk in collection.Split(i => i == "-"))
    Console.WriteLine(string.Join(", ", chunk));

会打印:

A, B
-, C, D
-, E

答案 1 :(得分:0)

如何使用Array Copy方法:

var separator = 3;
var collection = new [] { 1, 2, 3, 4, 5 };

var i = Array.IndexOf(collection,separator);

int[] part1 = new int[i];
int[] part2 = new int[collection.Length - i];
Array.Copy(collection, 0, part1, 0, i ); 
Array.Copy(collection, i, part2, 0, collection.Length - i ); 

或者更有效地使用ArraySegment:

var i = Array.IndexOf(collection,separator);
var part1 = new ArraySegment<int>( collection, 0, i );
var part2 = new ArraySegment<int>( collection, i, collection.Length - i );

ArraySegment是一个数组的包装器,用于分隔该数组中的一系列元素。多个ArraySegment实例可以引用相同的原始数组并且可以重叠。

  

编辑 - 添加原始问题与ArraySegment的组合,以便不再迭代集合两次。

public static bool IsNotSeparator(int value) => value != 3;
var collection = new [] { 1, 2, 3, 4, 5 };

var index = collection.TakeWhile(IsNotSeparator).Count();

var part1 = new ArraySegment<int>( collection, 0, index );
var part2 = new ArraySegment<int>( collection, index, collection.Length - index );