多くのTimeSpansをより少ない平均的なTimeSpansに削減するためのクリーンな方法ですか?
質問
C#Queue <!> lt; TimeSpan <!> gt;があります500個の要素が含まれています。
10個のTimeSpansのグループを取得し、それらの平均を選択することにより、これらを50個の要素に減らす必要があります。
これを行うためのクリーンな方法はありますか? LINQが役立つと考えていますが、きれいな方法がわかりません。アイデアはありますか?
解決
チャンク関数とループを使用します。
foreach(var set in source.ToList().Chunk(10)){
target.Enqueue(TimeSpan.FromMilliseconds(
set.Average(t => t.TotalMilliseconds)));
}
チャンクは標準のヘルパーライブラリの一部です。 http://clrextensions.codeplex.com/
他のヒント
.Skip()および.Take()拡張メソッドを使用して、キューをセットに分割します。その後、.Average(t = <!> gt; t.Ticks)を使用して、平均を表す新しいTimeSpanを取得できます。これらの50個の平均をそれぞれ新しいキューに詰め込むだけで、準備完了です。
Queue<TimeSpan> allTimeSpans = GetQueueOfTimeSpans();
Queue<TimeSpan> averages = New Queue<TimeSpan>(50);
int partitionSize = 10;
for (int i = 0; i <50; i++) {
var avg = allTimeSpans.Skip(i * partitionSize).Take(partitionSize).Average(t => t.Ticks)
averages.Enqueue(new TimeSpan(avg));
}
私はVB.NETの男なので、その例では100%記述していない構文があるかもしれません。お知らせください。修正します!
この場合、メソッド呼び出しで古き良き手続き実行に勝るものはないでしょう。派手なものではありませんが、簡単で、Jr。レベルの開発者が保守できます。
public static Queue<TimeSpan> CompressTimeSpan(Queue<TimeSpan> original, int interval)
{
Queue<TimeSpan> newQueue = new Queue<TimeSpan>();
if (original.Count == 0) return newQueue;
int current = 0;
TimeSpan runningTotal = TimeSpan.Zero;
TimeSpan currentTimeSpan = original.Dequeue();
while (original.Count > 0 && current < interval)
{
runningTotal += currentTimeSpan;
if (++current >= interval)
{
newQueue.Enqueue(TimeSpan.FromTicks(runningTotal.Ticks / interval));
runningTotal = TimeSpan.Zero;
current = 0;
}
currentTimeSpan = original.Dequeue();
}
if (current > 0)
newQueue.Enqueue(TimeSpan.FromTicks(runningTotal.Ticks / current));
return newQueue;
}
そのまま使用できます
static public TimeSpan[] Reduce(TimeSpan[] spans, int blockLength)
{
TimeSpan[] avgSpan = new TimeSpan[original.Count / blockLength];
int currentIndex = 0;
for (int outputIndex = 0;
outputIndex < avgSpan.Length;
outputIndex++)
{
long totalTicks = 0;
for (int sampleIndex = 0; sampleIndex < blockLength; sampleIndex++)
{
totalTicks += spans[currentIndex].Ticks;
currentIndex++;
}
avgSpan[outputIndex] =
TimeSpan.FromTicks(totalTicks / blockLength);
}
return avgSpan;
}
もう少し冗長です(LINQを使用しません)が、それが何をしているのかを見るのはかなり簡単です...
ループを使用しますが、ただ楽しみのために:
IEnumerable<TimeSpan> AverageClumps(Queue<TimeSpan> lots, int clumpSize)
{
while (lots.Any())
{
var portion = Math.Min(clumpSize, lots.Count);
yield return Enumerable.Range(1, portion).Aggregate(TimeSpan.Zero,
(t, x) => t.Add(lots.Dequeue()),
(t) => new TimeSpan(t.Ticks / portion));
}
}
}
各要素を1回だけ検査するため、パフォーマンスは他のLINQ製品よりもはるかに優れています。残念ながら、キューを変更しますが、バグではなく機能である可能性がありますか?
イテレータであるという素晴らしいボーナスがあるので、一度に1つの平均が得られます。
整数(0..n)と一緒に圧縮し、シーケンス番号div 10でグループ化しますか?
私はlinqユーザーではありませんが、次のように見えると思います:
for (n,item) from Enumerable.Range(0, queue.length).zip(queue) group by n/10
おそらくtake(10)ソリューションの方が優れています。
グループ化はどのように実行されますか?
非常に単純なもの(一度に10個ずつ)を想定して、次のようなものから始めることができます:
List<TimeSpan> input = Enumerable.Range(0, 500)
.Select(i => new TimeSpan(0, 0, i))
.ToList();
var res = input.Select((t, i) => new { time=t.Ticks, index=i })
.GroupBy(v => v.index / 10, v => v.time)
.Select(g => new TimeSpan((long)g.Average()));
int n = 0;
foreach (var t in res) {
Console.WriteLine("{0,3}: {1}", ++n, t);
}
注:
- Selectのオーバーロードでインデックスを取得し、これと10の整数除算を使用してグループ10を取得
- グループ化の結果は、Keyプロパティを持つ列挙のシーケンスです。ただし、ここではそれらの個別のシーケンスが必要です。
-
IEnumerable<TimeSpan>
にはEnumerable.Averageオーバーロードがないため、Ticks(長い)を使用します。
編集:質問に合わせて10個のグループを取ります。
EDIT2:テスト済みのコードが追加されました。