OrderBy的效率和延迟执行

时间:2013-08-13 19:05:48

标签: c# linq

我有一个包含日期和值的对象列表。每个日期有一个对象,过去几个月的每个日期都有一个对象。我正在寻找将值更改为最新值的日期。

这是我的意思的一个例子:

<datevalue>
    <date>8-9</date>
    <value>5</value>
</datevalue>
<datevalue>
    <date>8-10</date>
    <value>6</value>
</datevalue>
<datevalue>
    <date>8-11</date>
    <value>5</value>
</datevalue>
<datevalue>
    <date>8-12</date>
    <value>5</value>
</datevalue>
<datevalue>
    <date>8-13</date>
    <value>5</value>
</datevalue>

在上面的示例中,当前值为5,因为它是8-13(最近的日期)上的值。我想返回8-11 datevalue对象,因为它是将值更改为最新值的日期。我不想要8-9值,因为即使它是当前值的最早日期,该值也在该日期之后发生了变化。

这是我第一次尝试解决这个问题:

DateValue FindMostRecentValueChange(List<DateValue> dateValues)
{
    var currentValue = dateValues
                        .OrderByDesc(d => d.date)
                        .Select(d => d.value)
                        .First();
    var mostRecentChange = dateValues
                            .OrderByDesc(d => d.date)
                            .TakeWhile(d => d.value = currentValue)
                            .Last();
    return mostRecentChange;
}

这很有效。但是,我向我指出,我正在为这两个操作重复OrderByDesc。考虑到OrderByDesc可能是一项昂贵的操作,我想不必两次。因此我做了一个改变:

DateValue FindMostRecentValueChange(List<DateValue> dateValues)
{
    var orderedDateValues = dateValues.OrderByDesc(d => d.date);
    var currentValue = orderedDateValues;
                        .Select(d => d.value)
                        .First();
    var mostRecentChange = orderedDateValues
                            .TakeWhile(d => d.value = currentValue)
                            .Last();
    return mostRecentChange;
}

现在我只调用一次OrderByDesc。这是一个改进,对吗?好吧,也许不是。 OrderByDesc是延迟执行。

据我所知,这意味着直到你要求它的价值才能完成实际的排序。因此,当您在查找currentValue时调用First()时,执行OrderByDesc,然后在查找lastRecentChange时调用Last()时再次执行它。那么这是否意味着我仍在执行OrderByDesc两次?

我是否正确地解释了延迟执行的运作方式?我希望编译器能够识别这种情况并在幕后进行优化,以便只执行一次执行,但我找不到任何支持这种理论的信息。您能否帮助我了解优化此解决方案的最佳方法?

2 个答案:

答案 0 :(得分:3)

  

这是否意味着我仍在执行OrderByDesc两次?

是的,这是正确的。

  

我希望编译器能够识别这种情况并在幕后进行优化,以便只执行一次执行,但我找不到任何支持这种理论的信息。

它不能,因为这会以几种关键方式改变预期的功能。

  1. 如果基础数据发生变化,则在再次迭代序列时应反映这些变化。如果您在第一个查询和第二个查询之间向dateValues添加了一个新项,那么它应该在第二个查询中。如果您删除了某个项目,则该项目不应该存在,等等。

  2. 要获得你所要求的东西,即使在第一个消费者“完成”之后,也需要将所有项目存储在某种集合中。这是不可取的。这里的想法是你可以流式传输数据,一旦你完成了一个项目的处理,你就完成了它,并且不需要将它保存在内存中。如果您没有足够的内存来保留查询中的所有项目以进行后续运行,该怎么办?

  3.   

    您能否帮我解决优化此解决方案的最佳方法?

    这是非常微不足道的。只需使用查询结果填充数据结构即可。最简单的方法是将它们全部放入列表中。在查询末尾添加ToList调用,它将对其进行一次评估,然后可以多次迭代结果列表而不会产生负面后果。由于这个解决方案,当需要这样的语义时,很容易获得,而延迟执行的语义更难获得,尽管它们更强大,但他们选择不在物化集合上建立LINQ。

答案 1 :(得分:0)

不,如果您使用First()Last()以及其他一些人,您的查询将会立即执行。这意味着您拨打OrderBy两次(包括OrderByDescending)。

你可以试试这个:

var mostRecentChange = dateValues.OrderBy(d=>d.Date)
                                 .SkipWhile((x,i)=>i==dateValues.Count-1||x.Value == dateValues[i+1].Value)
                                 .Take(1);