是否有任何方法可以将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]

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


当前回答

只是说说我的意见。如果你想要“桶”列表(从左到右可视化),你可以做以下事情:

 public static List<List<T>> Buckets<T>(this List<T> source, int numberOfBuckets)
    {
        List<List<T>> result = new List<List<T>>();
        for (int i = 0; i < numberOfBuckets; i++)
        {
            result.Add(new List<T>());
        }

        int count = 0;
        while (count < source.Count())
        {
            var mod = count % numberOfBuckets;
            result[mod].Add(source[count]);
            count++;
        }
        return result;
    }

其他回答

试试下面的代码。

public static List<List<T>> Split<T>(IList<T> source)
{
    return  source
        .Select((x, i) => new { Index = i, Value = x })
        .GroupBy(x => x.Index / 3)
        .Select(x => x.Select(v => v.Value).ToList())
        .ToList();
}

其思想是首先根据索引对元素进行分组。除以3的效果是把它们分成3组。然后将每个组转换为一个列表,将list的IEnumerable转换为list的list

这是一个古老的解决方案,但我有一个不同的方法。我使用Skip来移动到所需的偏移量,并使用Take来提取所需的元素数量:

public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, 
                                                   int chunkSize)
{
    if (chunkSize <= 0)
        throw new ArgumentOutOfRangeException($"{nameof(chunkSize)} should be > 0");

    var nbChunks = (int)Math.Ceiling((double)source.Count()/chunkSize);

    return Enumerable.Range(0, nbChunks)
                     .Select(chunkNb => source.Skip(chunkNb*chunkSize)
                     .Take(chunkSize));
}

只是说说我的意见。如果你想要“桶”列表(从左到右可视化),你可以做以下事情:

 public static List<List<T>> Buckets<T>(this List<T> source, int numberOfBuckets)
    {
        List<List<T>> result = new List<List<T>>();
        for (int i = 0; i < numberOfBuckets; i++)
        {
            result.Add(new List<T>());
        }

        int count = 0;
        while (count < source.Count())
        {
            var mod = count % numberOfBuckets;
            result[mod].Add(source[count]);
            count++;
        }
        return result;
    }

另一种方法是使用Rx Buffer操作符

//using System.Linq;
//using System.Reactive.Linq;
//using System.Reactive.Threading.Tasks;

var observableBatches = anAnumerable.ToObservable().Buffer(size);

var batches = aList.ToObservable().Buffer(size).ToList().ToTask().GetAwaiter().GetResult();

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

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...

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

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

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