linqを使用して、c#の数字の配列の累積合計を見つけます
-
26-10-2019 - |
質問
ダブルを含むCSV文字列(「0.3,0.4,0.3」など)があり、これらの数字の累積合計を含むダブル配列を出力できるようにしたい([0.3,0.7,1.0])。
これまでのところ、私は持っています
double[] probabilities = textBox_f.Text.Split(new char[]{','}).Select(s => double.Parse(s)).ToArray();
数字は配列として与えられますが、数字の累積合計ではありません。
私が望むものを手に入れるためにこの表現を続ける方法はありますか、それとも私がすでに持っている配列から新しい配列を作成するために反復を使用する必要がありますか?
解決
var input=new double[]{ ... }
double sum=0;
var output=input
.Select(w=>sum+=w);
他のヒント
一般性の時間があり、実際に提起された問題を解決する時があります。これは後期の1つです。ダブルスのシーケンスを一連の部分的な合計に変えるメソッドを作成する場合は、次のことを行います。
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();
ここで、それがユーザー入力である場合、Double.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
オペレーター、a 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);
これは実際には、ジェネレーターを使用して一般化するのが非常に簡単です。以下は、呼ばれる新しい拡張メソッドです 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);
}
}
これがそうです 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
の累積合計 List<double>
:
var nums = new List<double>() { 0.3, 0.0, 0.4, 1.1 };
var cumsum = nums.Aggregate(new List<double> (),
(list, next) => { list.Add(list.LastOrDefault() + next); return list; });