如何用Linq选择递减子系列

时间:2011-11-11 20:12:12

标签: linq c#-4.0

我有按日期排序的价格清单。我需要选择所有单调递减的值。以下代码有效:

    public static List<DataPoint> SelectDecreasingValues(List<DataPoint> dataPoints)
    {
        var ret = new List<DataPoint>(dataPoints.Count);
        var previousPrice = dataPoints[0].Price;
        for (int i = 0; i < dataPoints.Count; i++)
        {
            if (dataPoints[i].Price <= previousPrice)
            {
                ret.Add(dataPoints[i]);
                previousPrice = dataPoints[i].Price;
            }
        }
        return ret;
    }

然而,使用Linq是否有更短/更清晰的方法来完成它?

4 个答案:

答案 0 :(得分:3)

此代码等同于:

previousPrice = dataPoints[0].Price;
var ret = dataPoints.Where(x => {
                                   if(x.Price <= previousPrice)
                                   { previousPrice = x.Price; return true;}
                                   return false; 
                                }).ToList();

但是,如果您不需要列表,请使用普通枚举,然后删除ToList。这样,您就可以使用LINQ中内置的延迟执行功能。


以下代码也是等效的:

DataPoint previous = dataPoints.FirstOrDefault();
var ret = dataPoints.Where(x => x.Price <= previous.Price)
                    .Select(x => previous = x).ToList();

这是因为LINQ中的延迟执行。对于dataPoints中的每个项目,它将首先执行Where部分,然后执行Select部分,然后才会移至dataPoints中的第二个项目。

您需要确定要使用的版本。第二个并不像第一个那样具有意图,因为你需要了解LINQ的内部工作原理。

答案 1 :(得分:2)

public IEnumerable<T> WhereMonotonicDecreasing<T>(
  IEnumerable<T> source,
  Func<T, IComparable> keySelector)
{
  IComparable key;
  bool first = true;
  foreach(T t in source)
  {
    if (first)
    {
      key = keySelector(t);
      yield return t;
      first = false;
    }
    else
    {
      IComparable newKey = keySelector(t);
      if (newKey.CompareTo(key) < 0)
      {
         key = newKey;
         yield return t;
      }
    }
  }
}

被叫:

dataPoints.WhereMonotonicDecreasing(x => x.Price);

答案 2 :(得分:0)

previousPrice = dataPoints[0];
dataPoints.Where(p => p.Price <= previousPrice.Price)
          .Select(p => previousPrice = p);

如果确实需要,可以使用.ToList()

答案 3 :(得分:0)

如何(未经测试):

return dataPoints.Take(1)
                 .Concat(dataPoints.Skip(1)
                                   .Zip(dataPoints,
                                          (next, previous) =>
                                           new { Next = next, Previous = previous })
                                   .Where(a => a.Next.Price <= a.Previous.Price)
                                   .Select(a => a.Next))
                 .ToList();

基本上,这会在序列上覆盖序列的“一个延迟”版本,以生成“next,previous”元组,然后在这些元组上应用相关的过滤器。 Take(1)是选择序列的第一项,它似乎总是需要。

如果您不关心变量名的可读性,可以将其缩短为:

return dataPoints.Take(1)
                 .Concat(dataPoints.Skip(1)
                                   .Zip(dataPoints, Tuple.Create)
                                   .Where(a => a.Item1.Price <= a.Item2.Price)
                                   .Select(a => a.Item1))
                 .ToList();