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