Чтобы достичь асимптотической производительности O (n) (как это делает решение с ручным кодированием), вы можете использовать функцию Aggregate
, как в
series.Skip(period-1).Aggregate(
new {
Result = new SortedList<DateTime, double>(),
Working = List<double>(series.Take(period-1).Select(item => item.Value))
},
(list, item)=>{
list.Working.Add(item.Value);
list.Result.Add(item.Key, list.Working.Average());
list.Working.RemoveAt(0);
return list;
}
).Result;
Накопленное значение (реализовано в виде анонимного типа) содержит два поля: Result
содержит построенный список результатов. Working
содержит последние period-1
элементов. Функция агрегирования добавляет текущее значение в рабочий список, строит текущее среднее и добавляет его к результату, а затем удаляет первое (то есть самое старое) значение из рабочего списка.
"Семя" (т.е. начальное значение для накопления) создается путем помещения первых period-1
элементов в Working
и инициализации Result
пустым списком.
Следовательно, агрегация начинается с элемента period
(пропуская (period-1)
элементов в начале)
В функциональном программировании это типичный шаблон использования для совокупной (или fold
) функции, кстати.
Два замечания:
Решение не является "функционально" чистым в том смысле, что одни и те же объекты списка (Working
и Result
) повторно используются на каждом шаге. Я не уверен, что это может вызвать проблемы, если некоторые будущие компиляторы попытаются распараллелить функцию Aggregate автоматически (с другой стороны, я также не уверен, если это все-таки возможно ...). Чисто функциональное решение должно «создавать» новые списки на каждом этапе.
Также обратите внимание, что в C # отсутствуют мощные выражения списка. В некотором гипотетическом псевдокоде, смешанном с Python-C #, можно написать функцию агрегирования, например
(list, item)=>
new {
Result = list.Result + [(item.Key, (list.Working+[item.Value]).Average())],
Working=list.Working[1::]+[item.Value]
}
что было бы немного более элегантно, по моему скромному мнению :) 1033 *