LINQ将列表划分为8个成员的列表

LINQ将列表划分为8个成员的列表,linq,partitioning,skip,take,Linq,Partitioning,Skip,Take,如何获取一个列表(使用LINQ)并将其拆分为一个列表列表,在每8个条目上对原始列表进行分区 我想像这样的事情会涉及跳过和/或接受,但我对LINQ还是相当陌生 编辑:使用C#/.Net 3.5 编辑2:这个问题的措辞与另一个“重复”问题不同。尽管问题相似,但这个问题的答案都是优越的:“被接受”的答案非常可靠(带有收益率声明)以及Jon Skeet使用MoreLinq的建议(在“其他”问题中不推荐)有时,重复是好的,因为它们迫使重新检查问题。我们在中有这样一种方法,即: //作为IEnumerabl

如何获取一个列表(使用LINQ)并将其拆分为一个列表列表,在每8个条目上对原始列表进行分区

我想像这样的事情会涉及跳过和/或接受,但我对LINQ还是相当陌生

编辑:使用C#/.Net 3.5


编辑2:这个问题的措辞与另一个“重复”问题不同。尽管问题相似,但这个问题的答案都是优越的:“被接受”的答案非常可靠(带有
收益率
声明)以及Jon Skeet使用MoreLinq的建议(在“其他”问题中不推荐)有时,重复是好的,因为它们迫使重新检查问题。

我们在中有这样一种方法,即:

//作为IEnumerable
var项目=列表。批次(8);

//作为IEnumerable
var items=list.Batch(8,seq=>seq.ToList());

使用以下扩展方法将输入分解为子集

public static class IEnumerableExtensions
{
    public static IEnumerable<List<T>> InSetsOf<T>(this IEnumerable<T> source, int max)
    {
        List<T> toReturn = new List<T>(max);
        foreach(var item in source)
        {
                toReturn.Add(item);
                if (toReturn.Count == max)
                {
                        yield return toReturn;
                        toReturn = new List<T>(max);
                }
        }
        if (toReturn.Any())
        {
                yield return toReturn;
        }
    }
}
公共静态类IEnumerableExtensions
{
公共静态IEnumerable insettsof(此IEnumerable源,int max)
{
列表返回=新列表(最大值);
foreach(源中的var项)
{
返回。添加(项目);
如果(toReturn.Count==最大值)
{
收益率回报率;
toReturn=新列表(最大值);
}
}
if(toReturn.Any())
{
收益率回报率;
}
}
}

您最好使用像这样的库,但是如果您真的必须使用“普通LINQ”来实现这一点,您可以使用
GroupBy

var sequence = new[] {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};

var result = sequence.Select((x, i) => new {Group = i/8, Value = x})
                     .GroupBy(item => item.Group, g => g.Value)
                     .Select(g => g.Where(x => true));

// result is: { {1,2,3,4,5,6,7,8}, {9,10,11,12,13,14,15,16} }
基本上,我们使用的是
Select()
版本,它为正在使用的值提供索引,我们将索引除以8以确定每个值属于哪个组。然后我们通过这个分组键对序列进行分组。最后一个
选择
只是将
i分组
减少为
IEnumerable
(这不是严格必要的,因为
i分组
是一个
IEnumerable

通过分解示例中的常量
8
,并用指定的参数替换它,很容易将其转换为可重用的方法。 它不一定是最优雅的解决方案,也不再是一个懒惰的流式解决方案。。。但它确实有效


您还可以使用迭代器块(
yield-return
)编写自己的扩展方法,它可以提供比
GroupBy
更好的性能和更少的内存。这就是MoreLinq的
Batch()
方法所做的IIRC。

Take不会非常有效,因为它不会删除所获取的条目

为什么不使用一个简单的循环:

public IEnumerable<IList<T>> Partition<T>(this/* <-- see extension methods*/ IEnumerable<T> src,int num)  
{  
    IEnumerator<T> enu=src.getEnumerator();  
    while(true)  
    {  
        List<T> result=new List<T>(num);  
        for(int i=0;i<num;i++)  
        {  
            if(!enu.MoveNext())  
            {  
                if(i>0)yield return result;  
                yield break;  
            }  
            result.Add(enu.Current);  
        }  
        yield return result;  
    }  
}

public IEnumerable分区(这个/*这根本不是最初的Linq设计者所想的,但是看看GroupBy的这种滥用:

public static IEnumerable<IEnumerable<T>> BatchBy<T>(this IEnumerable<T> items, int batchSize)
{
    var count = 0;
    return items.GroupBy(x => (count++ / batchSize)).ToList();
}

[TestMethod]
public void BatchBy_breaks_a_list_into_chunks()
{
    var values = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    var batches = values.BatchBy(3);
    batches.Count().ShouldEqual(4);
    batches.First().Count().ShouldEqual(3);
    batches.Last().Count().ShouldEqual(1);
}
公共静态IEnumerable BatchBy(此IEnumerable项,int-batchSize)
{
var计数=0;
return items.GroupBy(x=>(count++/batchSize)).ToList();
}
[测试方法]
public void BatchBy_将_列表_分解为_块()
{
var值=新[]{1,2,3,4,5,6,7,8,9,10};
var batches=值。BatchBy(3);
batches.Count()应等于(4);
批次。第一个()。计数()。应等于(3);
batches.Last().Count().ShouldEqual(1);
}

我认为它在这个问题上赢得了“高尔夫”奖。
ToList
非常重要,因为你想确保在你尝试对输出进行任何操作之前已经执行了分组。如果你删除
ToList
,你会得到一些奇怪的副作用。

最简单的解决方案是Mel:

from b in Enumerable.Range(0,8) select items.Where((x,i) => (i % 8) == b);
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items, 
                                                       int partitionSize)
{
    int i = 0;
    return items.GroupBy(x => i++ / partitionSize).ToArray();
}
公共静态IEnumerable分区(此IEnumerable项,
int分区大小)
{
int i=0;
return items.GroupBy(x=>i++/partitionSize.ToArray();
}
简洁但较慢。上述方法将IEnumerable拆分为所需固定大小的块,而块的总数并不重要。要将IEnumerable拆分为N个大小相等或接近相等的块,可以执行以下操作:

public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> items, 
                                                   int numOfParts)
{
    int i = 0;
    return items.GroupBy(x => i++ % numOfParts);
}
公共静态IEnumerable拆分(此IEnumerable项,
国际货币基金组织(部分)
{
int i=0;
returnitems.GroupBy(x=>i++%numOfParts);
}
为了加快速度,一个简单的方法可以做到:

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items, 
                                                       int partitionSize)
{
    if (partitionSize <= 0)
        throw new ArgumentOutOfRangeException("partitionSize");

    int innerListCounter = 0;
    int numberOfPackets = 0;
    foreach (var item in items)
    {
        innerListCounter++;
        if (innerListCounter == partitionSize)
        {
            yield return items.Skip(numberOfPackets * partitionSize).Take(partitionSize);
            innerListCounter = 0;
            numberOfPackets++;
        }
    }

    if (innerListCounter > 0)
        yield return items.Skip(numberOfPackets * partitionSize);
}
公共静态IEnumerable分区(此IEnumerable项,
int分区大小)
{
如果(分区大小0)
生成返回项目。跳过(numberOfPackets*partitionSize);
}

这比现在地球上的任何东西都要快:)用于
拆分
操作的等效方法

您使用的是VB还是C#?迭代器的存在有很大的不同,这不是重复的。另一个问题需要将列表分解为每个第n个元素的子列表,因此一个包含元素0、8、16、24等的列表,一个包含元素1、9、17、25等的列表,以及一个包含元素2、10、18等的列表。该用户希望将列表分解为包含0..7、包含元素8..15和包含元素16..24的列表,类似于pagingCool,这是很好地实现的,包括一个resultSelector(对于操作/排序内部列表很重要)。我想我没能弄明白这一点可能有点傻。很高兴看到,从常规LINQ到对象,有一些东西是“缺失的”。)@椒盐卷饼:这并不是说用普通的老林克是不可能的。。。只是它既不是非常有效,也不容易理解。请参阅我的答案中的“普通LINQ”示例。+1,感谢您提供此库的链接。我会看看我是否能在未来的项目中使用它。小问题-我碰巧复制了
批处理
代码,Resharper警告我在
中返回批处理(source,size,x=>x)x是一个pos
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> items, 
                                                   int numOfParts)
{
    int i = 0;
    return items.GroupBy(x => i++ % numOfParts);
}
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items, 
                                                       int partitionSize)
{
    if (partitionSize <= 0)
        throw new ArgumentOutOfRangeException("partitionSize");

    int innerListCounter = 0;
    int numberOfPackets = 0;
    foreach (var item in items)
    {
        innerListCounter++;
        if (innerListCounter == partitionSize)
        {
            yield return items.Skip(numberOfPackets * partitionSize).Take(partitionSize);
            innerListCounter = 0;
            numberOfPackets++;
        }
    }

    if (innerListCounter > 0)
        yield return items.Skip(numberOfPackets * partitionSize);
}