将大的IEnumerable分成更小的IEnumerable项目的修复量
为了支持只接受特定数量的项目(5项)的API,我想将LINQ结果转换为总是包含更小的项目组设定的项目数量。将大的IEnumerable分成更小的IEnumerable项目的修复量
假设列表{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}
我希望得到一个最大的5个项目每个
{1, 2, 3, 4, 5}
{6, 7, 8, 9, 10}
{11, 12, 13, 14, 15}
{16, 17, 18}
我怎么用LINQ来做到这一点?我假设它涉及Group
或Aggregate
,但我无法确定如何编写该文件。
尝试这样:
var result = items.Select((value, index) => new { Index = index, Value = value})
.GroupBy(x => x.Index/5)
.Select(g => g.Select(x => x.Value).ToList())
.ToList();
它通过划分项目到根据他们在原来的列表索引组。
读你的代码引发了一个想法。 'int index = 0; var groups = items.GroupBy(x => index ++/5);'这个工作。谢谢。像平常一样,它总是很简单。 – 2011-03-02 19:47:52
@ Pierre-Alain:在LINQ查询中使用副作用通常会被忽视;它违背了LINQ的设计原则,这主要是功能性的。它在某种程度上会起作用......但是,例如,如果您评估“groups”两次,则会得到一些有趣的效果。 – 2011-03-02 19:50:28
@Pierre传递给具有副作用的“GroupBy”的函数很难看。 Linq是关于功能的,因此是免费编程的副作用。 – CodesInChaos 2011-03-02 19:51:56
一个简单的可能性是使用Enumerable.Skip
和Enumerable.Take
方法,例如:
List<int> nums = new List<int>(){1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18};
var list1 = nums.Take(5);
var list2 = nums.Skip(5).Take(5);
var list3 = nums.Skip(10).Take(5);
var list4 = nums.Skip(15).Take(5);
由于乔恩在评论中提到的,虽然,像这样的一个简单的方法将重新评估nums
(在这个例子中)每次都会影响性能(取决于集合的大小)。
请注意,这将每次重新评估源 - 这在某些情况下很好(例如,内存中的集合),但不是全部(例如从大型日志文件中读取条目)。在可能的情况下,我尝试编写我的LINQ代码来仅评估一次源代码。 – 2011-03-02 19:35:34
@Jon好点,谢谢。 – Donut 2011-03-02 19:39:04
我们在MoreLINQ有Batch
方法。您需要小心如何使用它,因为每次传递给选择器的批次都是对同一个数组的引用 - 但它确实有效。
您可以使用GroupBy
,但不能偷懒 - 它必须积累全部结果才能返回任何内容。这可能对你很好,但值得注意。
'第86行:bucket = null;'这是否确保每次都是不同的数组? – CodesInChaos 2011-03-02 20:18:32
@CodeInChaos:'x => x'不会导致每个批次的一次分配 - 这意味着每个结果都是相同的数组引用(直到结束)。它实际上有点危险(例如'x.Batch(10).ToList()') – 2011-03-02 20:18:49
@Jon我删除了原来的评论,因为我最初错过了'bucket = null'。 – CodesInChaos 2011-03-02 20:19:42
我只是做这样的事情:
public static IEnumerable<IEnumerable<T>> TakeChunks<T>(this IEnumerable<T> source, int size)
{
// Typically you'd put argument validation in the method call and then
// implement it using a private method... I'll leave that to your
// imagination.
var list = new List<T>(size);
foreach (T item in source)
{
list.Add(item);
if (list.Count == size)
{
List<T> chunk = list;
list = new List<T>(size);
yield return chunk;
}
}
if (list.Count > 0)
{
yield return list;
}
}
用法:
var list = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
foreach (var chunk in list.TakeChunks(3))
{
Console.WriteLine(string.Join(", ", chunk));
}
输出:
1, 2, 3 4, 5, 6 7, 8, 9 10
理由:
与其它方法相比这种多打电话给t ØSkip
和Take
或大花式LINQ查询,上面是:
- 更高效
- 更多的功能明显(在我看来)
- 实施更具可读性(同样,在我看来)
如果您每次都要返回一份副本,为什么不直接为每个批次启动一个新列表并返回?调用ToArray的好处在哪里? – 2011-03-02 19:40:29
@Jon:好问题。我想这没什么好的理由。 – 2011-03-02 19:50:39
var list = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
var result = new List<List<int>>();
while (list.Count != 0) {
result.Add(list.TakeWhile(x => x++ <= 5).ToList());
list.RemoveRange(0, list.Count < 5 ? list.Count : 5);
}
http://*.com/questions/1349491/how-can-i-split-an-ienumerablestring-into-groups-of-ienumerablestring – diceguyd30 2011-03-02 19:34:51
的[拆分名单可能重复与子列表LINQ](http://*.com/questions/419019/split-list-into-sublists-with-linq) – nawfal 2013-02-18 10:28:43