有没有什么方法可以将 List<SomeObject>
分成几个单独的 SomeObject
列表,使用项索引作为每个拆分的分隔符?
让我举例说明:
我有一个 List<SomeObject>
,我需要 List<List<SomeObject>>
或 List<SomeObject>[]
,这样每个结果列表将包含一组3个原始列表项(顺序) .
例如 . :
-
原始清单:
[a, g, e, w, p, s, q, f, x, y, i, m, c]
-
结果清单:
[a, g, e], [w, p, s], [q, f, x], [y, i, m], [c]
我还需要将结果列表大小作为此函数的参数 .
27 回答
使用模块化分区:
请尝试以下代码 .
我们的想法是首先按索引对元素进行分组 . 除以3具有将它们分组为3的组的效果 . 然后将每个组转换为列表并将
List
的List
转换为List
的List
s这个问题有点陈旧,但我刚刚写了这篇文章,我认为它比其他提议的解决方案更优雅:
一般来说CaseyB建议的方法运行正常,事实上如果你传入一个
List<T>
很难对它进行错误,也许我会把它改为:这将避免大规模的呼叫链 . 尽管如此,这种方法有一个普遍的缺陷 . 它实现了每个块的两个枚举,以突出显示尝试运行的问题:
为了克服这一点,我们可以尝试Cameron's方法,它通过上面的测试以飞行颜色,因为它只进行一次枚举 .
麻烦的是它有一个不同的缺陷,它实现了每个块中的每个项目,这种方法的麻烦在于你在内存上运行得很高 .
为了说明尝试运行:
最后,任何实现都应该能够处理块的乱序迭代,例如:
许多高度优化的解决方案,比如我的第一个revision这个答案就失败了 . 在casperOne's optimized答案中可以看到同样的问题 .
要解决所有这些问题,您可以使用以下内容:
您还可以为块的无序迭代引入一轮优化,这超出了范围 .
至于你应该选择哪种方法?这完全取决于您试图解决的问题 . 如果你不关心第一个缺陷,简单的答案是非常吸引人的 .
_1185633_和大多数方法一样,这对于多线程来说是不安全的,如果你想让它保证线程安全,那么东西会变得很奇怪你需要修改
EnumeratorWrapper
.您可以使用多个使用Take和Skip的查询,但是我认为这会在原始列表中添加太多迭代 .
相反,我认为你应该创建一个自己的迭代器,如下所示:
然后,您可以调用它并启用LINQ,以便对结果序列执行其他操作 .
鉴于Sam's answer,我觉得有一种更简单的方法可以做到这一点:
再次遍历列表(我最初没有这样做)
在释放块之前实现组中的项目(对于大块项目,会出现内存问题)
Sam发布的所有代码
也就是说,这里's another pass, which I'编成了IEnumerable<T>的扩展方法,名为
Chunk
:没有什么令人惊讶的,只是基本的错误检查 .
继续
ChunkInternal
:基本上,它获取IEnumerator<T>并手动遍历每个项目 . 它会检查当前是否有任何项目被枚举 . 在枚举每个块之后,如果没有剩余任何项目,它就会爆发 .
一旦检测到序列中有项目,它就会将内部
IEnumerable<T>
实施的责任委托给ChunkSequence
:由于MoveNext已经被
IEnumerator<T>
调用传递给ChunkSequence
,它产生Current返回的项目,然后递增计数,确保永远不会返回超过chunkSize
项目并在每次迭代后移动到序列中的下一个项目(但是如果产生的项目数量超过块大小,则进行循环 .如果没有剩下的项目,那么
InternalChunk
方法将在外部循环中进行另一次传递,但是当第二次调用MoveNext
时,它仍将返回false,as per the documentation(强调我的):在这点,循环将中断,序列序列将终止 .
这是一个简单的测试:
输出:
重要的一点是,如果不排除整个子序列或在父序列中的任何点断开,这将不起作用 . 这是一个重要的警告,但如果您的用例是您将使用序列序列的每个元素,那么这将适合您 .
此外,如果您使用订单,它会做一些奇怪的事情,就像Sam's did at one point一样 .
好的,这是我的看法:
完全懒惰:适用于无限的枚举
没有中间复制/缓冲
O(n)执行时间
当内部序列仅部分消耗时,
也起作用
Example Usage
Explanations
该代码通过嵌套两个基于
yield
的迭代器来工作 .外部迭代器必须跟踪内部(块)迭代器有效消耗的元素数量 . 这是通过使用
innerMoveNext()
结束remaining
来完成的 . 在外迭代器产生下一个块之前,丢弃未使用的块元素 . 这是必要的,因为否则当内部枚举不被(完全)消耗时(例如c3.Count()
将返回6),你会得到不一致的结果 .完全懒惰,不计数或复制:
我认为以下建议将是最快的 . 我正在牺牲源Enumerable的懒惰,因为它能够使用Array.Copy并提前知道每个子列表的长度 .
我们可以改进@JaredPar的解决方案来进行真正的懒惰评估 . 我们使用GroupAdjacentBy方法生成具有相同键的连续元素组:
由于这些组是逐个产生的,因此该解决方案可以有效地使用长序列或无限序列 .
System.Interactive为此目的提供
Buffer()
. 一些快速测试显示性能类似于Sam的解决方案 .几年前我写了一个Clump扩展方法 . 效果很好,是这里最快的实现 . :P
这是我几个月前写的一个列表拆分程序:
这是一个老问题,但这是我最终得到的;它只列举一次枚举,但会为每个分区创建列表 . 当某些实现调用
ToArray()
时,它不会受到意外行为的影响:我发现这个小片段做得很好 .
我们发现David B的解决方案效果最好 . 但我们将其改编为更通用的解决方案:
以下解决方案是我能想到的最紧凑的O(n) .
旧代码,但这是我一直在使用的:
如果列表的类型为system.collections.generic,则可以使用“CopyTo”方法将数组的元素复制到其他子数组 . 您指定要复制的元素和元素数 .
您还可以在原始列表中创建3个克隆,并使用每个列表中的“RemoveRange”将列表缩小到所需的大小 .
或者只是创建一个帮助方法来为您完成 .
这个如何?
据我所知,GetRange()在采取的项目数量方面是线性的 . 所以这应该表现良好 .
这是一个旧的解决方案,但我有一个不同的方法 . 我使用
Skip
移动到所需的偏移量,并使用Take
来提取所需数量的元素:只需加入我的两美分 . 如果您想“清空”列表(从左到右可视化),您可以执行以下操作:
对于对打包/维护解决方案感兴趣的任何人,MoreLINQ库提供符合您请求的行为的Batch扩展方法:
The Batch implementation类似于Cameron MacFarland's answer,在返回之前添加了用于转换块/批的重载,并且执行得非常好 .
我接受了主要答案,并将其作为IOC容器来确定拆分的位置 . (对于谁真的只想分成3个项目,在寻找答案的同时阅读这篇文章?)
此方法允许根据需要拆分任何类型的项目 .
所以对于OP代码将会
所以表现为_1585696的方法 .
}
可以使用无限生成器:
演示代码:https://ideone.com/GKmL7M
但实际上我更愿意在没有linq的情况下编写相应的方法 .
另一种方法是使用Rx Buffer operator
插入我的两分钱......
通过使用源的列表类型进行分块,我发现了另一个非常紧凑的解决方案: