我可以使用无限范围并对其进行操作吗?
Enumerable.Range(0, int.MaxValue)
.Select(n => Math.Pow(n, 2))
.Where(squared => squared % 2 != 0)
.TakeWhile(squared => squared < 10000).Sum()
此代码会迭代从 0 到 max-range 的所有整数值,还是仅迭代整数值以满足 take-while、where 和 select 运算符? 有人可以澄清一下吗?
编辑:我第一次尝试确保它按预期工作是愚蠢的。我撤销它:)
Enumerable.Range(0, int.MaxValue)
.Select(n => Math.Pow(n, 2))
.Where(squared => squared % 2 != 0)
.TakeWhile(squared => squared < 10000).Sum()
Will this code iterate over all of the integer values from 0 to max-range or just through the integer values to satisfy the take-while, where, and select operators?
Can somebody clarify?
EDIT: My first try to make sure it works as expected was dumb one. I revoke it :)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
int.MaxValue + 5
溢出为负数。自己尝试一下:Enumerable.Range
的第二个参数必须是非负数 - 因此是例外。不过,您当然可以在 LINQ 中使用无限序列。下面是这样一个序列的示例:
当然,这也会溢出,但它会继续下去...
请注意,一些 LINQ 运算符(例如
Reverse
)需要在产生第一个结果之前读取所有数据。其他人(例如Select
)可以在从输入读取结果时保持流式传输结果。有关每个运算符的行为的详细信息(在 LINQ to Objects 中),请参阅我的 Edulinq 博客文章。int.MaxValue + 5
overflows to be a negative number. Try it yourself:The second argument for
Enumerable.Range
has to be non-negative - hence the exception.You can certainly use infinite sequences in LINQ though. Here's an example of such a sequence:
That will overflow as well, of course, but it'll keep going...
Note that some LINQ operators (e.g.
Reverse
) need to read all the data before they can yield their first result. Others (likeSelect
) can just keep streaming results as they read them from the input. See my Edulinq blog posts for details of the behaviour of each operator (in LINQ to Objects).一般来说,解决此类问题的方法是逐步思考正在发生的事情。
Linq 将 linq 代码转换为由查询提供程序执行的代码。这可能是生成 SQL 代码之类的事情,或者各种各样的事情。对于 linq-to-objects,它会生成一些等效的 .NET 代码。思考 .NET 代码将是什么,让我们推断将会发生什么。*
通过您的代码,您可以得到:
Enumerable.Range 比:
...但是对于争论来说已经足够接近了。
Select 足够接近:
Where 足够接近:
TakeWhile 足够接近:
Sum 足够接近:
这简化了一些优化等等,但足够接近推理。依次考虑这些,您的代码相当于:
现在,在某些方面,上面的代码更简单,在某些方面,它更复杂。使用 LINQ 的全部意义在于,它在很多方面都更简单。不过,要回答您的问题“此代码是否会迭代从 0 到最大范围的所有整数值,或者只是遍历整数值以满足 take-while、where 和 select 运算符?”我们可以看上面,看到那些不满足where的会被迭代,发现不满足where,但是不再对它们做更多的工作,一旦TakeWhile满足了,所有进一步的工作已停止(我的非 LINQ 重写中的
break
)。当然,在这种情况下,只有
TakeWhile()
意味着调用将在合理的时间长度内返回,但我们还需要简要考虑其他调用,以确保它们在运行时产生。考虑代码的以下变体:理论上,这将给出完全相同的答案,但需要更长的时间和更多的内存来执行此操作(可能足以导致内存不足异常)。这里等效的非 linq 代码是:
这里我们可以看到将
ToList()
添加到 Linq 查询如何极大地影响查询,以便由Range()
生成的每个项目> 必须处理呼叫。像ToList()
和ToArray()
这样的方法会打破链接,以便非 linq 等价物不再适合彼此“内部”,因此没有人可以停止这些方法的操作之前的。 (Sum()
是另一个示例,但由于它位于示例中的TakeWhile()
之后,因此这不是问题)。使它经历范围的每次迭代的另一件事是如果您有
While(x => false)
,因为它永远不会在TakeWhile
中实际执行测试。*尽管可能有进一步的优化,特别是在 SQL 代码的情况下,而且在概念上,例如
Count()
相当于:这将被转换为对
Count
的调用ICollection 的 code> 属性或数组的 Length 属性意味着上面的 O(n) 被 O(1)(对于大多数 ICollection 实现)调用替换,这对于大型序列。The way to solve these sort of questions in general, is to think about what's going on in steps.
Linq turns the linq code into something that'll be executed by query provider. This could be something like producing SQL code, or all manner of things. In the case of linq-to-objects, it produces some equivalent .NET code. Thinking about what that .NET code will be lets us reason about what will happen.*
With your code you have:
Enumerable.Range is slightly more complicated than:
...but that's close enough for argument's sake.
Select is close enough to:
Where is close enough to:
TakeWhile is close enough to:
Sum is close enough to:
This simplifies a few optimisations and so on, but is close enough to reason with. Taking each of these in turn, your code is equivalent to:
Now, in some ways the code above is simpler, and in some ways it's more complicated. The whole point of using LINQ is that in many ways its simpler. Still, to answer your question "Will this code iterate over all of the integer values from 0 to max-range or just through the integer values to satisfy the take-while, where, and select operators?" we can look at the above and see that those that don't satisfy the where are iterated through to find that they don't satisfy the where, but no more work is done with them, and once the TakeWhile is satisfied, all further work is stopped (the
break
in my non-LINQ re-write).Of course it's only the
TakeWhile()
in this case that means the call will return in a reasonable length of time, but we also need to think briefly about the others to make sure they yield as they go. Consider the following variant of your code:Theoretically, this will give exactly the same answer, but it will take far longer and far more memory to do so (probably enough to cause an out of memory exception). The equivalent non-linq code here though is:
Here we can see how adding
ToList()
to the Linq query vastly affects the query so that every item produced by theRange()
call must be dealt with. Methods likeToList()
andToArray()
break up the chaining so that non-linq equivalents no longer fit "inside" each other and none can therefore stop the operation of those that come before. (Sum()
is another example, but since it's after yourTakeWhile()
in your example, that isn't an issue).Another thing that would make it go through every iteration of the range is if you had
While(x => false)
because it would never actually perform the test inTakeWhile
.*Though there may be further optimisations, esp in the case of SQL code and also while conceptually e.g.
Count()
is equivalent to:That this will be turned into a call to the
Count
property of an ICollection or theLength
property of an array means the O(n) above is replaced by an O(1) (for most ICollection implementations) call, which is a massive gain for large sequences.仅当满足
TakeWhile
条件时,您的第一个代码才会迭代。在int.MaxValue
之前它不会进行迭代。int.MaxValue + 5
将得到负整数。如果第二个参数为负数,Enumerable.Range
会抛出 ArgumentOutOfRangeException。这就是为什么你会得到异常(在任何迭代发生之前)。Your first code will only iterate as long the
TakeWhile
condition is met. It will not iterate untilint.MaxValue
.int.MaxValue + 5
will result in a negative integer.Enumerable.Range
throws an ArgumentOutOfRangeException if its second argument is negative. So that's why you get the exception (before any iteration takes place).