Как выбрать убывающую подсерию с Linq - PullRequest
0 голосов
/ 12 ноября 2011

У меня есть список цен, упорядоченных по дате.Мне нужно выделить все монотонно убывающие значения.Работает следующий код:

    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 ]

3 голосов
/ 12 ноября 2011

Этот код эквивалентен:

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.

2 голосов
/ 12 ноября 2011
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);
0 голосов
/ 12 ноября 2011

Как насчет (не проверено):

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();

По сути, это накладывает «одностороннюю» версию последовательности на последовательность для создания «следующего, предыдущего» кортежей, а затем применяет соответствующие фильтры к этим кортежам. 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();
0 голосов
/ 12 ноября 2011
previousPrice = dataPoints[0];
dataPoints.Where(p => p.Price <= previousPrice.Price)
          .Select(p => previousPrice = p);

Вы можете использовать .ToList(), если он вам действительно нужен.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...