Накопление подпоследовательностей последовательности с использованием C#/Linq
Я пытаюсь найти лучший способ обработки последовательности чисел на основе следующего требования: значение sequence[i]
это сумма его собственной стоимости плюс накопление от sequence[0]
в sequence[i-1]
,
Например: если последовательность представляет собой список
List<double> list = new List<double> { 10.0, 20.0, 30.0, 40.0 };
выходной результат должен быть
list[0] = 10.0
list[1] = 20.0 + 10.0
list[2] = 30.0 + 10.0 + 20.0
list[3] = 40.0 + 10.0 + 20.0 + 30.0
Я знаю способ грубой силы, который использует несколько итераций, но мне интересно, должно быть какое-то лучшее решение (возможно, с LINQ).
6 ответов
Предполагая, что у вас есть доступ к LINQ:
using System.Linq;
List<int> numbers = new List<int> {10, 20, 30, 40};
List<int> runningTotals = new List<int>(numbers.Count);
numbers.Aggregate(0, (sum, value) => {
sum += value;
runningTotals.Add(sum);
return sum;
});
Моя версия, которая является модификацией того, что я положил в комментарии, и возвращает IEnumerable, а не List, но ToList() это уладит.
Должно быть довольно эффективным. А кто не любит пользоваться yield return
?;-)
public IEnumerable<double> GetCumulativeSequence (IEnumerable<double> input)
{
var runningTotal = 0.0;
foreach (double current in input)
{
runningTotal+=current;
yield return runningTotal;
}
}
void Main()
{
List<double> list = new List<double> { 10.0, 20.0, 30.0, 40.0 };
var foo = GetCumulativeSequence(list);
}
Основным преимуществом этого является то, что он выполняет только один цикл над входным массивом. и если вы на самом деле не используете весь возвращенный материал (то есть вы смотрите только на первые три), то остальные не будут вычисляться. Потенциально полезен в длинных списках и т. Д. То же самое будет сказано о вещах, подобных ответу Криса Доггетта, но не обо всех, кто здесь использует linq.
Используйте относительно неизвестную перегрузку Select, которая позволяет увидеть индекс:
var result = list.Select((val, index) => list.Take(index + 1).Sum())
Версия не LINQ, просто чтобы отличаться:
List<double> GetSums(List<double> values)
{
List<double> sums = new List<double>();
double total = 0;
foreach(double d in values)
{
total += d;
sums.Add(total);
}
return sums;
}
Я создал универсальный (с большим количеством вариантов) метод расширения, основанный на операторе сканирования APL (аналогично тому, как Aggregate
по сути, оператор сокращения APL), который возвращает промежуточные результаты последовательности операций:
public static IEnumerable<TResult> Scan<T, TResult>(this IEnumerable<T> src, TResult seed, Func<TResult, T, TResult> combine) {
foreach (var s in src) {
seed = combine(seed, s);
yield return seed;
}
}
Который затем может быть использован как:
var ans = list.Scan(0.0, (a, d) => a+d).ToList();
Вот еще один способ, похожий на один из постов, но этот отдельный:
// C#
Enumerable.Range(1, 100)
.Aggregate(new List<int>{0},
(acc, x) => { acc.Add(acc.Last() + x); return acc; })
.Dump(); // Dump using LINQPad; ans: 5050
Если мы переключаемся на int64, используем 10 000 000 в качестве верхней границы и читаем окончательный накопленный результат, код выполняется примерно за 450 миллисекунд на Corei5 с окончательным выводом:
50000005000000
Кстати, для верхней границы 1 000 000 выполнение составляет около 40 миллисекунд, поэтому увеличение размера на 10 увеличило время на 10.