我有IEnumerable<string
&gt;我想分成三个一组,所以如果我的输入有6个项目,我会得到一个IEnumerable<IEnumerable<string>>
返回两个项目,每个项目将包含IEnumerable<string>
我的字符串内容。
我正在寻找如何使用Linq而不是简单的for循环
由于
答案 0 :(得分:29)
var result = sequence.Select((s, i) => new { Value = s, Index = i })
.GroupBy(item => item.Index / 3, item => item.Value);
请注意,这将返回IEnumerable<IGrouping<int,string>>
,其功能与您想要的相似。但是,如果您确实需要将其键入为IEnumerable<IEnumerable<string>>
(要传递给C#3.0中不支持泛型差异的方法),则应使用Enumerable.Cast
:
var result = sequence.Select((s, i) => new { Value = s, Index = i })
.GroupBy(item => item.Index / 3, item => item.Value)
.Cast<IEnumerable<string>>();
答案 1 :(得分:28)
这是对此主题的延迟回复,但这是一个不使用任何临时存储的方法:
public static class EnumerableExt
{
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> input, int blockSize)
{
var enumerator = input.GetEnumerator();
while (enumerator.MoveNext())
{
yield return nextPartition(enumerator, blockSize);
}
}
private static IEnumerable<T> nextPartition<T>(IEnumerator<T> enumerator, int blockSize)
{
do
{
yield return enumerator.Current;
}
while (--blockSize > 0 && enumerator.MoveNext());
}
}
还有一些测试代码:
class Program
{
static void Main(string[] args)
{
var someNumbers = Enumerable.Range(0, 10000);
foreach (var block in someNumbers.Partition(100))
{
Console.WriteLine("\nStart of block.");
foreach (int number in block)
{
Console.Write(number);
Console.Write(" ");
}
}
Console.WriteLine("\nDone.");
Console.ReadLine();
}
}
答案 2 :(得分:20)
我知道这已经得到了解答,但是如果您打算经常使用IEnumerables片段,那么我建议制作这样的通用扩展方法:
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, int chunkSize)
{
return source.Where((x,i) => i % chunkSize == 0).Select((x,i) => source.Skip(i * chunkSize).Take(chunkSize));
}
然后您可以使用sequence.Split(3)
来获得您想要的内容。
(你可以把它命名为'slice'或'chunk',如果你不喜欢'split'已经为字符串定义了。'Split'就是我碰巧称之为我的。)< / p>
答案 3 :(得分:15)
受到@ dicegiuy30的实现的启发,我想创建一个只迭代源一次的版本,并且不会在内存中构建整个结果集来补偿。我想出的最好的是:
public static IEnumerable<IEnumerable<T>> Split2<T>(this IEnumerable<T> source, int chunkSize) {
var chunk = new List<T>(chunkSize);
foreach(var x in source) {
chunk.Add(x);
if(chunk.Count <= chunkSize) {
continue;
}
yield return chunk;
chunk = new List<T>(chunkSize);
}
if(chunk.Any()) {
yield return chunk;
}
}
这样我就可以按需构建每个块。我希望我也应该避免使用List<T>
,并且只是简单地说明这一点,但还没有想出来。
答案 4 :(得分:2)
使用Microsoft.Reactive,您可以非常简单地执行此操作,并且您将只通过源迭代一次。
IEnumerable<string> source = new List<string>{"1", "2", "3", "4", "5", "6"};
IEnumerable<IEnumerable<string>> splited = source.ToObservable().Buffer(3).ToEnumerable();
答案 5 :(得分:2)
我们可以改进@ Afshari的解决方案来进行真正的懒惰评估。我们使用GroupAdjacentBy
方法生成具有相同键的连续元素组:
sequence
.Select((x, i) => new { Value = x, Index = i })
.GroupAdjacentBy(x=>x.Index/3)
.Select(g=>g.Select(x=>x.Value))
由于这些组是逐个产生的,因此该解决方案可以有效地使用长序列或无限序列。
答案 6 :(得分:0)
我想出了一个不同的方法。它使用while
迭代器,但结果会像需要的常规LINQ一样缓存在内存中。
这是代码。
public IEnumerable<IEnumerable<T>> Paginate<T>(this IEnumerable<T> source, int pageSize)
{
List<IEnumerable<T>> pages = new List<IEnumerable<T>>();
int skipCount = 0;
while (skipCount * pageSize < source.Count) {
pages.Add(source.Skip(skipCount * pageSize).Take(pageSize));
skipCount += 1;
}
return pages;
}
答案 7 :(得分:0)
Mehrdad Afshari的回答非常好。这是一个封装它的扩展方法:
using System.Collections.Generic;
using System.Linq;
public static class EnumerableExtensions
{
public static IEnumerable<IEnumerable<T>> GroupsOf<T>(this IEnumerable<T> enumerable, int size)
{
return enumerable.Select((v, i) => new {v, i}).GroupBy(x => x.i/size, x => x.v);
}
}