我有一个IEnumerable<string>
,我想将其分成每组三个,所以如果我的输入有6个项目,我将得到一个IEnumerable<IEnumerable<string>>
,其中每个包含一个IEnumerable<string>
,其中包含我的字符串内容。
我正在寻找如何使用Linq来实现此操作,而不是使用简单的for循环。
谢谢
这是对该帖子的晚回复,但这里有一种不使用任何临时存储的方法:
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();
}
}
然而,请注意以下评论中此方法的限制:
如果你将测试代码中的foreach
更改为foreach(var block in someNumbers.Partition(100).ToArray())
,那么它就无法工作。
它不是线程安全的。
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>>();
.GroupBy
不会枚举序列。它会在调用.GetEnumerator
时(例如在foreach
或其他地方使用时)立即枚举整个序列。 - Mehrdad AfshariIGrouping
继承自IEnumerable
,结果可以声明为IEnumerable<IEnumerable<string>>
。 - Anupam我知道这个问题已经有答案了,但是如果你经常打算对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)
来获取您想要的内容。
(如果您不喜欢“split”已经为字符串定义了名称,您可以将其命名为其他名称,例如'slice'或'chunk'。 'Split'只是我随意定义的名称。)
.Where((x,i) => i % chunkSize == 0)
部分,但是它仍然能够正常工作。如果你不介意,能否向我解释一下你的代码是如何工作的呢?谢谢。 - Alex EssilfieWhere
和Select
中的索引感兴趣。在Where
中,我的索引从'0-8'变成了Select
中的'0-2',因为Where
子句只返回9个项目中的3个(通过检查Enumerable.Range(0,9).Select((x,i) => i % 3)
的结果来证明!)。所以我首先跳过0(0 * 3)并取3,然后跳过3(1 * 3)并取3,最后跳过6(2 * 3)并取3! - diceguyd30受 @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>
,并且也只是将其流传,但是还没有弄清楚。
if(chunk.Count <= chunkSize)
。正确的代码应该是:if(chunk.Count < chunkSize)
。 - Arne LundGroupAdjacentBy
方法,该方法生成具有相同键的连续元素组:sequence
.Select((x, i) => new { Value = x, Index = i })
.GroupAdjacentBy(x=>x.Index/3)
.Select(g=>g.Select(x=>x.Value))
IEnumerable<string> source = new List<string>{"1", "2", "3", "4", "5", "6"};
IEnumerable<IEnumerable<string>> splited = source.ToObservable().Buffer(3).ToEnumerable();
Reactive
,你需要使用 ToObservable。否则,你必须使用 Interactive
。 - Emaborsausing 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);
}
}
我想到了一种不同的方法。它使用了一个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;
}
Runtime.CompilerServices.Extension
,所以我查了一下MSDN,它说:“在C#中,您不需要使用此属性;您应该使用this
修饰符作为第一个参数来创建扩展方法。” 换句话说,虽然它在功能上等效,但最好使用 public IEnumerable<IEnumerable<T>> Paginate<T>(this IEnumerable<T> source, int pageSize)
而不是该属性。 - Davy8source.Count
的?它是一个 IEnumerable,对其执行 Count()
会枚举集合一次。 - nawfal