2011-03-26 47 views
8
Enumerable.Range(0, int.MaxValue) 
      .Select(n => Math.Pow(n, 2)) 
      .Where(squared => squared % 2 != 0) 
      .TakeWhile(squared => squared < 10000).Sum() 

此代码是否会迭代所有从0到最大范围的整数值或者只是通过整数值来满足take-while,where和select操作符? 有人可以澄清吗?我可以使用无限范围并对其进行操作吗?

编辑:我第一次尝试确保它按预期工作是愚蠢的。我撤销它:)

+0

呃......你超过了整数的最大范围......你期望会发生什么? – 2011-03-26 17:25:20

回答

11

int.MaxValue + 5溢出是一个负数。自己尝试一下:

unchecked 
{ 
    int count = int.MaxValue + 5; 
    Console.WriteLine(count); // Prints -2147483644 
} 

第二个参数为Enumerable.Range必须为非负 - 因此例外。

你当然可以在LINQ中使用无限序列。这里有这样一个序列的例子:

public IEnumerable<int> InfiniteCounter() 
{ 
    int counter = 0; 
    while (true) 
    { 
     unchecked 
     { 
      yield return counter; 
      counter++; 
     } 
    } 
} 

这会溢出为好,当然,但它会继续下去......

注意一些 LINQ运营商(如Reverse)需要读取全部数据,然后才能得出其第一个结果。其他人(如Select)只能在输入时读取流结果。请参阅我的Edulinq blog posts以了解每个运算符(在LINQ to Objects中)的行为的详细信息。

+0

+1 Eduling。精彩的一套文章。你在第一或第二篇文章中有类似的代码。 – gideon 2011-03-26 18:30:30

+0

您的Edulinq博客帖子的链接现在已损坏。你有另一个参考是有效的吗? – 2015-10-07 18:58:31

+0

@LucaCremonesi:修正,谢谢。 – 2015-10-07 18:59:22

2

只要满足TakeWhile条件,您的第一个代码将只会迭代。它不会迭代,直到int.MaxValue

int.MaxValue + 5将导致一个负整数。如果第二个参数为负数,则Enumerable.Range将引发ArgumentOutOfRangeException。所以这就是为什么你会得到异常(在任何迭代发生之前)。

+0

感谢sepp2k的清晰解释 – suhair 2011-03-27 13:25:35

3

一般来说,解决这类问题的方法就是想一步一步来看看发生了什么。

Linq将linq代码转换为将由查询提供程序执行的内容。这可能类似于生成SQL代码或所有方式的东西。在linq-to-objects的情况下,它会产生一些等效的.NET代码。关于什么的.NET代码会思考让我们有理由对会发生什么*

随着你的代码有:

Enumerable.Range(0, int.MaxValue) 
         .Select(n => Math.Pow(n, 2)) 
         .Where(squared => squared % 2 != 0) 
         .TakeWhile(squared => squared < 10000).Sum() 

Enumerable.Range略大于更复杂:

for(int i = start; i != start + count; ++i) 
    yield return i; 

...但是为了论证的缘故,这已经足够接近了。

选择是足够接近:

foreach(T item in source) 
    yield return func(item); 

哪里是足够接近:

foreach(T item in source) 
    if(func(item)) 
    yield return item; 

TakeWhile足够接近:

foreach(T item in source) 
    if(func(item)) 
    yield return item; 
    else 
    yield break; 

总和足够接近:

T tmp = 0;//must be numeric type 
foreach(T x in source) 
    tmp += x; 
return tmp; 

这简化了一些优化等等,但足够接近的原因。以每个这些反过来,你的代码就相当于:

double ret = 0; // part of equivalent of sum 
for(int i = 0; i != int.MaxValue; ++i) // equivalent of Range 
{ 
    double j = Math.Pow(i, 2); // equivalent of Select(n => Math.Pow(n, 2)) 
    if(j % 2 != 0) //equivalent of Where(squared => squared %2 != 0) 
    { 
    if(j < 10000) //equivalent of TakeWhile(squared => squared < 10000) 
    { 
     ret += j; //equaivalent of Sum() 
    } 
    else //TakeWhile stopping further iteration 
    { 
     break; 
    } 
    } 
} 
return ret; //end of equivalent of Sum() 

现在,在某些方面,上面的代码更简单,而且在某些方面它更复杂。使用LINQ的重点在很多方面都比较简单。仍然,回答你的问题:“这个代码是否会迭代所有从0到最大范围的整数值,或者只是通过整数值来满足take-while,where和select操作符?”我们可以看看上面的内容,看看那些不满足在哪里被迭代的人发现他们不满足他们的地方,但没有更多的工作与他们完成,一旦TakeWhile满意,所有进一步的工作被停止了(我的非LINQ重写中的break)。

当然这只是TakeWhile()在这种情况下,这意味着电话会在合理的时间内返回,但我们也需要简单思考其他电话以确保它们随时走完。考虑你的代码的以下变型:

Enumerable.Range(0, int.MaxValue) 
    .Select(n => Math.Pow(n, 2)) 
    .Where(squared => squared % 2 != 0) 
    .ToList() 
    .TakeWhile(squared => squared < 10000).Sum() 

从理论上讲,这会给一模一样的答案,但它会需要更长的时间和更内存这样做(可能足以导致内存溢出异常)。这里虽然等效的非LINQ代码:

List<double> tmpList = new List<double>(); // part of ToList equivalent 
for(int i = 0; i != int.MaxValue; ++i) // equivalent of Range 
{ 
    double j = Math.Pow(i, 2); // equivalent of Select(n => Math.Pow(n, 2)) 
    if(j % 2 != 0) //equivalent of Where(squared => squared %2 != 0) 
    { 
    tmpList.Add(j);//part of equivalent to ToList() 
    } 
} 
double ret = 0; // part of equivalent of sum 
foreach(double k in tmpList) 
{ 
    if(k < 10000) //equivalent of TakeWhile(squared => squared < 10000) 
    { 
    ret += k; //equaivalent of Sum() 
    } 
    else //TakeWhile stopping further iteration 
    { 
    break; 
    } 
} 
return ret; //end of equivalent of Sum() 

在这里我们可以看到如何添加ToList()到LINQ查询极大地影响了查询,以便由Range()调用生成每个项目必须处理。像ToList()ToArray()这样的方法打破了链接,因此非linq等价物不再适合彼此“内部”,因此没有一个可以停止那些之前的操作。 (Sum()是另一个例子,但是因为它在你的例子中是TakeWhile()之后的,所以这不是问题)。

如果你有While(x => false)因为它实际上永远不会在TakeWhile中执行测试,那么它将会经历范围的每次迭代。 *尽管可能会有进一步的优化,尤其是在SQL代码的情况下,并且在概念上也是如此。 Count()相当于:

int c = 0; 
foreach(item in src) 
    ++c; 
return c; 

,这将变成一个呼叫到一个ICollection的或阵列的Length属性的Count属性是指O(n)的上述由O替代(1)(对于大多数ICollection实现来说),这对于大型序列来说是一个巨大的收益。

相关问题