использование LINQ для поиска кумулятивной суммы массива чисел в C#
У меня есть строка csv, содержащая двойники (e.g "0.3, 0.4, 0.3"), и я хочу иметь возможность выводить двойной массив, содержащий совокупную сумму этих чисел (e.g [0.3, 0.7, 1.0]).
до сих пор у меня
double[] probabilities = textBox_f.Text.Split(new char[]{','}).Select(s => double.Parse(s)).ToArray();
который дает числа как массив, но не совокупную сумму чисел.
есть ли способ продолжить это выражение, чтобы получить то, что я хочу, или мне нужно использовать итерацию для создания нового массива из массива I уже?
9 ответов
есть время для общности, и есть время для решения проблемы позировал. Это один из последних случаев. Если вы хотите сделать метод, который превращает последовательность двойников в последовательность частичных сумм, просто сделайте это:
public static IEnumerable<double> CumulativeSum(this IEnumerable<double> sequence)
{
double sum = 0;
foreach(var item in sequence)
{
sum += item;
yield return sum;
}
}
легко. Не возиться с агрегатами, сложными запросами и тому подобным. Легко понять, легко отлаживать, легко использовать:
textBox_f.Text
.Split(new char[]{','})
.Select(s => double.Parse(s))
.CumulativeSum()
.ToArray();
Теперь я отмечаю, что если это пользовательский ввод, то двойной.Parse может бросить исключение; может быть, лучше сделать что-то вроде:
public static double? MyParseDouble(this string s)
{
double d;
if (double.TryParse(s, out d))
return d;
return null;
}
public static IEnumerable<double?> CumulativeSum(this IEnumerable<double?> sequence)
{
double? sum = 0;
foreach(var item in sequence)
{
sum += item;
yield return sum;
}
}
...
textBox_f.Text
.Split(new char[]{','})
.Select(s => s.MyParseDouble())
.CumulativeSum()
.ToArray();
и теперь вы не получите исключение, если пользователь делает ошибку; вы получаете нули.
у меня было аналогичное требование некоторое время назад. В принципе, мне нужно было сделать агрегацию, но мне также нужно было выбрать каждое промежуточное значение. Поэтому я написал метод расширения с именем SelectAggregate
(вероятно, не самое подходящее имя, но я не мог найти ничего лучше), которое можно использовать так:
double[] numbers = new [] { 0.3, 0.4, 0.3 };
double[] cumulativeSums = numbers.SelectAggregate(0.0, (acc, x) => acc + x).ToArray();
вот код :
public static IEnumerable<TAccumulate> SelectAggregate<TSource, TAccumulate>(
this IEnumerable<TSource> source,
TAccumulate seed,
Func<TAccumulate, TSource, TAccumulate> func)
{
source.CheckArgumentNull("source");
func.CheckArgumentNull("func");
return source.SelectAggregateIterator(seed, func);
}
private static IEnumerable<TAccumulate> SelectAggregateIterator<TSource, TAccumulate>(
this IEnumerable<TSource> source,
TAccumulate seed,
Func<TAccumulate, TSource, TAccumulate> func)
{
TAccumulate previous = seed;
foreach (var item in source)
{
TAccumulate result = func(previous, item);
previous = result;
yield return result;
}
}
вы хотите использовать Aggregate
оператора List<double>
как аккумулятор агрегации. Таким образом, вы можете создать проекцию, которая сама по себе является последовательностью сумм.
вот пример, чтобы вы начали:
double[] runningTotal = textBox_f.Text
.Split(new char[]{','})
.Select(s => double.Parse(s))
.Aggregate((IEnumerable<double>)new List<double>(),
(a,i) => a.Concat(new[]{a.LastOrDefault() + i}))
.ToArray();
почему это должен быть LINQ?
var cumulative = new double[probabilities.Length];
for (int i = 0; i < probabilities.Length; i++)
cumulative[i] = probabilities[i] + (i == 0 ? 0 : cumulative[i-1]);
прежде всего, я не думаю, что это хорошая задача для LINQ. Старая добрая foreach
сделает это лучше. Но, как головоломка, это нормально.
первой идеей было использовать подзапросы, но мне это не нравится, потому что это O(n^2). Вот мое линейное решение:
double[] probabilities = new double[] { 0.3, 0.4, 0.3};
probabilities
.Aggregate(
new {sum=Enumerable.Empty<double>(), last = 0.0d},
(a, c) => new {
sum = a.sum.Concat(Enumerable.Repeat(a.last+c,1)),
last = a.last + c
},
a => a.sum
);
использовать RX:
var input=new double[]{ ... }
var output = new List<double>();
input.ToObservable().Scan((e, f) => f + e).Subscribe(output.Add);
здесь a способ сделать это с помощью LINQ:
double[] doubles = { 1.7, 2.3, 1.9, 4.1, 2.9 };
var doublesSummed = new List<double>();
Enumerable.Aggregate(doubles, (runningSum, nextFactor) => {
double currentSum = runningSum + nextFactor;
doublesSummed.Add(currentSum);
return currentSum;
});
doublesSummed.Dump();
В LINQPad:
- 4
- 5.9
- 10
- 12.9
это на самом деле довольно просто обобщить с помощью генератора. Вот новый метод расширения под названием Accumulate
это работает как комбинация Select
и Aggregate
. Он возвращает новую последовательность, применяя двоичную функцию к каждому элементу в последовательности и накопленное значение до сих пор.
public static class EnumerableHelpers
{
public static IEnumerable<U> Accumulate<T, U>(this IEnumerable<T> self, U init, Func<U, T, U> f)
{
foreach (var x in self)
yield return init = f(init, x);
}
public static IEnumerable<T> Accumulate<T>(this IEnumerable<T> self, Func<T, T, T> f)
{
return self.Accumulate(default(T), f);
}
public static IEnumerable<double> PartialSums(this IEnumerable<double> self)
{
return self.Accumulate((x, y) => x + y);
}
public static IEnumerable<int> PartialSums(this IEnumerable<int> self)
{
return self.Accumulate((x, y) => x + y);
}
}