为了账号安全,请及时绑定邮箱和手机立即绑定

C#如何yield返回SelectMany?

C#如何yield返回SelectMany?

C#
翻过高山走不出你 2023-09-24 15:57:55
假设我有以下通用组合生成器静态方法:public static IEnumerable<IEnumerable<T>> GetAllPossibleCombos<T>(    IEnumerable<IEnumerable<T>> items){    IEnumerable<IEnumerable<T>> combos = new[] {new T[0]};    foreach (var inner in items)        combos = combos.SelectMany(c => inner, (c, i) => c.Append(i));     return combos;}也许我没有正确理解这一点,但这不是在 RAM 中构建整个组合列表吗?如果有大量项目,该方法可能会导致计算机内存不足。有没有办法重写方法以yield return在每个组合上使用 a ,而不是返回整个组合集?
查看完整描述

2 回答

?
杨魅力

TA贡献1811条经验 获得超6个赞

你的问题中有一些误解,这很棒,因为现在你有机会了解事实而不是神话。


首先,您正在实现的方法通常称为CartesianProduct,而不是GetAllPossibleCombos,因此请考虑重命名它。


也许我没有正确理解这一点

你没有正确理解它。

这不是在 RAM 中构建整个组合列表吗?

不。 查询构建器构建查询,而不是执行查询的结果。 当你执行 a 时SelectMany,你得到的是一个将在将来进行选择的对象。您不会得到该选择的结果。

如果有大量项目,该方法可能会导致计算机内存不足。

今天是停止将内存和 RAM 视为同一事物的好日子。当进程耗尽内存时,它不会耗尽 RAM。它耗尽了地址空间(不是 RAM)。考虑内存的更好方法是:内存是磁盘上的页面文件,而 RAM 是特殊的硬件,可以使页面文件更快当 RAM 耗尽时,计算机的运行速度可能会慢得令人无法接受,但在地址空间耗尽之前,内存不会耗尽。请记住,进程内存是虚拟化的

现在,可能存在执行此代码效率低下的情况,因为枚举查询耗尽了堆栈。在某些情况下,执行可能会变得低效,因为您将 n 个项目向上移动到堆栈 n 深。我建议您对代码进行更深入的分析,看看是否是这种情况,然后进行报告。


有没有办法重写方法以在每个组合上使用收益返回,而不是返回整个组合集?

SelectMany是作为循环yield return中的a 实现的foreach,因此您已经yield return在每个组合上将其实现为 a ;你刚刚隐藏了yield return对 的调用SelectMany

也就是说,SelectMany<A, B, C>(IE<A> items, Func<A, IE<B>> f, Func<A, B, C> g)实现如下:

foreach(A a in items)
  foreach(B b in f(a))
      yield return g(a, b);

所以你已经在 中完成了yield return

如果你想编写一个直接执行 a的方法yield return,那就有点困难了;最简单的方法是在每个子序列上形成一个枚举器数组,然后从每个Current枚举器创建一个向量,yield return即向量,然后将正确的迭代器前进一步。继续这样做,直到不再有正确的迭代器可以前进。

正如您可能从描述中看出的那样,簿记变得混乱。这是可行的,但编写起来并不是很令人愉快的代码。不过不妨尝试一下!该解决方案的好处是,您可以保证获得良好的性能,因为您不消耗任何堆栈。

查看完整回答
反对 回复 2023-09-24
?
繁花不似锦

TA贡献1851条经验 获得超4个赞

SelectMany和其他 Linq 方法返回一个IEnumerable,仅在枚举集合时才延迟计算。ToList()这可以采用orToArray()调用或在循环中对其进行迭代的形式foreach。当您在调试器中看到消息警告扩展集合将枚举可枚举对象时,这就是它警告您的行为。该集合尚未枚举 - Linq 查询仅构建一个调用链,告诉它如何枚举数据。


因此,您对 RAM 使用情况的担忧不一定准确(取决于启动的具体类型IEnumerable)。即使您调用ToList()orToArray()并将对该集合的引用存储在变量中,如果集合元素是引用类型,那么它也不会是副本。


在您的示例中,yield return如果您想延迟构建元素集合而不将其存储在单独的集合中(例如返回列表或数组,这需要额外的复制),则可以为您提供方便。我认为它不适用于您想要做的事情,因为SelectMany已经有这种行为。


如果您想尝试一下,Linq 可以轻松生成大型列表Enumerable.Repeat


// Define a collection with 10000000 items (items not created yet)

var manyItems = Enumerable.Repeat(123, 10000000);


// Enumerate the enumerable via ToList: creates the int 10000000 times

var manyItemsConcrete = manyItems.ToList();


// same deal with reference types

var manyReferenceTypes = Enumerable.Repeate(new object(), 10000000);

var manyReferenceTypesConcrete = manyReferenceTypes.ToList();


// This list already exists in RAM taking up space

var list = new List<object> { new object(), new object() /* ... x10000000 */ }

// This defines a transform on list, but doesn't take up RAM

var enumerable = list.Select(x => x.ToString());


// Now, there are two lists taking up RAM

var newList = enumerable.ToList();


查看完整回答
反对 回复 2023-09-24
  • 2 回答
  • 0 关注
  • 100 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信