Как вычислить разницу каждого элемента в последовательности в списке, а затем получить среднее значение в LINQ?

#c# #.net #linq

#c# #.net #linq

Вопрос:

У меня есть список этого класса:

 public class DailData
{
    public string Date { get; set; }
    public long Number { get; set; }
}
  

Мне нужно вычислять разницу для каждой даты от предыдущей и суммировать их и делить на общее количество дней, чтобы получить среднее значение. Как я могу это сделать с помощью одного запроса LINQ?

Комментарии:

1. Это было бы легко сделать с помощью foreach , если вы не женаты на LINQ. Это также было бы легко документировать и поддерживать.

2. Но я женат на Linq 🙂

3. Что вы уже пробовали?

4. Есть ли какая-либо причина не использовать Enumerable.Average ? Можем ли мы предположить, что List<DailData> все в Date порядке?

5. Под «diff», я полагаю, вы имеете в виду разницу? Дата представляет собой строку. Каков формат этой строки?

Ответ №1:

     private double calculateAvg(List<DailData> data)
    {
        var list1 = data.Take(data.Count() - 1);
        var list2 = data.Skip(1);
        var diff = list1.Zip(list2, (item1, item2) => item2.Number - item1.Number).Sum() / data.Count;
        return diff;
    }
  

Комментарии:

1. Поскольку Zip заканчивается на самом коротком IEnumerable , вы можете просто сделать data.Zip(data.Skip(1),

2. Вам следует пересмотреть свой брак. Это может сработать, но это довольно непрозрачно для читателя. Он также создает и заполняет два дополнительных списка. Я почти уверен, что foreach решение было бы проще и понятнее.

3. @Flydog57 Он не заполняет два дополнительных списка. Take , Skip и Zip просто создаст сквозные итераторы.

4. Ваш счет будет на единицу меньше того, что у вас есть (у вас будет только N-1 разница). Я бы просто сделал data.Zip(data.Skip(1), (item1, item2) => item2.Number - item1.Number).Average()

5. @DStanley: Спасибо, приятно знать! Я все еще думаю, что выбрал бы простое foreach решение

Ответ №2:

Поскольку мы знаем, что источник является List<T> индексируемым, используйте эмуляцию цикла LINQ for :

 var ans = Enumerable.Range(1, data.Count-1).Select(n => data[n].Number-data[n-1].Number).Average();
  

В качестве альтернативы, используя (один из) методов расширения для сканирования парами (scan — это оператор APL, подобный Aggregate , но возвращает промежуточные значения):

 // TRes combineFn(T prevValue, T curValue)
public static IEnumerable<TRes> ScanByPairs<T, TRes>(this IEnumerable<T> src, Func<T, T, TRes> combineFn) {
    using (var srce = src.GetEnumerator())
        if (srce.MoveNext()) {
            var prev = srce.Current;

            while (srce.MoveNext())
                yield return combineFn(prev, prev = srce.Current);
        }
}
  

Тогда вы можете легко обрабатывать пары:

 var ans2 = data.ScanByPairs((prev,cur) => cur.Number-prev.Number).Average();