是否有任何方法可以将List<SomeObject>分离为SomeObject的几个单独的列表,使用项目索引作为每个分割的分隔符?

让我举个例子:

我有一个List<SomeObject>,我需要一个List<List<SomeObject>>或List<SomeObject>[],这样每个结果列表将包含一组原始列表的3个项目(依次)。

eg.:

原始列表:[a, g, e, w, p, s, q, f, x, y, i, m, c] 结果列表:[a、g e], [w、p, s], [q, f, x]、[y,我,m], [c]

我还需要结果列表的大小是这个函数的参数。


当前回答

问题是如何“用LINQ将列表拆分为子列表”,但有时你可能希望这些子列表是对原始列表的引用,而不是副本。这允许您从子列表中修改原始列表。在这种情况下,这可能对你有用。

public static IEnumerable<Memory<T>> RefChunkBy<T>(this T[] array, int size)
{
    if (size < 1 || array is null)
    {
        throw new ArgumentException("chunkSize must be positive");
    }

    var index = 0;
    var counter = 0;

    for (int i = 0; i < array.Length; i++)
    {
        if (counter == size)
        {
            yield return new Memory<T>(array, index, size);
            index = i;
            counter = 0;
        }
        counter++;

        if (i + 1 == array.Length)
        {
            yield return new Memory<T>(array, index, array.Length - index);
        }
    }
}

用法:

var src = new[] { 1, 2, 3, 4, 5, 6 };

var c3 = RefChunkBy(src, 3);      // {{1, 2, 3}, {4, 5, 6}};
var c4 = RefChunkBy(src, 4);      // {{1, 2, 3, 4}, {5, 6}};

// as extension method
var c3 = src.RefChunkBy(3);      // {{1, 2, 3}, {4, 5, 6}};
var c4 = src.RefChunkBy(4);      // {{1, 2, 3, 4}, {5, 6}};

var sum = c3.Select(c => c.Span.ToArray().Sum());    // {6, 15}
var count = c3.Count();                 // 2
var take2 = c3.Select(c => c.Span.ToArray().Take(2));  // {{1, 2}, {4, 5}}

请随意修改代码。

其他回答

几年前我写了一个集群扩展方法。工作得很好,是这里最快的实现。: P

/// <summary>
/// Clumps items into same size lots.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">The source list of items.</param>
/// <param name="size">The maximum size of the clumps to make.</param>
/// <returns>A list of list of items, where each list of items is no bigger than the size given.</returns>
public static IEnumerable<IEnumerable<T>> Clump<T>(this IEnumerable<T> source, int size)
{
    if (source == null)
        throw new ArgumentNullException("source");
    if (size < 1)
        throw new ArgumentOutOfRangeException("size", "size must be greater than 0");

    return ClumpIterator<T>(source, size);
}

private static IEnumerable<IEnumerable<T>> ClumpIterator<T>(IEnumerable<T> source, int size)
{
    Debug.Assert(source != null, "source is null.");

    T[] items = new T[size];
    int count = 0;
    foreach (var item in source)
    {
        items[count] = item;
        count++;

        if (count == size)
        {
            yield return items;
            items = new T[size];
            count = 0;
        }
    }
    if (count > 0)
    {
        if (count == size)
            yield return items;
        else
        {
            T[] tempItems = new T[count];
            Array.Copy(items, tempItems, count);
            yield return tempItems;
        }
    }
}

我认为下面的建议是最快的。为了能够使用数组,我牺牲了源Enumerable的惰性。复制和提前知道每个子列表的长度。

public static IEnumerable<T[]> Chunk<T>(this IEnumerable<T> items, int size)
{
    T[] array = items as T[] ?? items.ToArray();
    for (int i = 0; i < array.Length; i+=size)
    {
        T[] chunk = new T[Math.Min(size, array.Length - i)];
        Array.Copy(array, i, chunk, 0, chunk.Length);
        yield return chunk;
    }
}

对于任何对打包/维护解决方案感兴趣的人来说,MoreLINQ库提供了符合您所请求行为的批处理扩展方法:

IEnumerable<char> source = "Example string";
IEnumerable<IEnumerable<char>> chunksOfThreeChars = source.Batch(3);

批处理实现类似于Cameron MacFarland的答案,在返回之前添加了用于转换块/批处理的重载,并且性能相当好。

问题是如何“用LINQ将列表拆分为子列表”,但有时你可能希望这些子列表是对原始列表的引用,而不是副本。这允许您从子列表中修改原始列表。在这种情况下,这可能对你有用。

public static IEnumerable<Memory<T>> RefChunkBy<T>(this T[] array, int size)
{
    if (size < 1 || array is null)
    {
        throw new ArgumentException("chunkSize must be positive");
    }

    var index = 0;
    var counter = 0;

    for (int i = 0; i < array.Length; i++)
    {
        if (counter == size)
        {
            yield return new Memory<T>(array, index, size);
            index = i;
            counter = 0;
        }
        counter++;

        if (i + 1 == array.Length)
        {
            yield return new Memory<T>(array, index, array.Length - index);
        }
    }
}

用法:

var src = new[] { 1, 2, 3, 4, 5, 6 };

var c3 = RefChunkBy(src, 3);      // {{1, 2, 3}, {4, 5, 6}};
var c4 = RefChunkBy(src, 4);      // {{1, 2, 3, 4}, {5, 6}};

// as extension method
var c3 = src.RefChunkBy(3);      // {{1, 2, 3}, {4, 5, 6}};
var c4 = src.RefChunkBy(4);      // {{1, 2, 3, 4}, {5, 6}};

var sum = c3.Select(c => c.Span.ToArray().Sum());    // {6, 15}
var count = c3.Count();                 // 2
var take2 = c3.Select(c => c.Span.ToArray().Take(2));  // {{1, 2}, {4, 5}}

请随意修改代码。

没有办法在一个解决方案中结合所有理想的特性,如完全懒惰、无复制、完全通用性和安全性。最根本的原因是不能保证在访问块之前输入不发生变化。 假设我们有一个如下签名的函数:

public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunkSize)
{
    // Some implementation
}

那么下面的使用方式就有问题了:

var myList = new List<int>()
{
    1,2,3,4
};
var myChunks = myList.Chunk(2);
myList.RemoveAt(0);
var firstChunk = myChunks.First();    
Console.WriteLine("First chunk:" + String.Join(',', firstChunk));
myList.RemoveAt(0);
var secondChunk = myChunks.Skip(1).First();
Console.WriteLine("Second chunk:" + String.Join(',', secondChunk));
// What outputs do we see for first and second chunk? Probably not what you would expect...

根据具体的实现,代码将失败并产生运行时错误或产生不直观的结果。

所以,至少有一个属性需要减弱。如果你想要一个无懈无击的惰性解决方案,你需要将输入类型限制为不可变类型,即使这样也不能直接覆盖所有用例。但是,如果您可以控制使用,您仍然可以选择最通用的解决方案,只要您确保以一种有效的方式使用它。否则,你可能会放弃懒惰,接受一定数量的复制。

最后,这完全取决于您的用例和需求,哪种解决方案最适合您。