我有一个包含 500 个元素的 C# Queue<TimeSpan>。
我需要通过以 10 个 TimeSpan 为一组并选择它们的平均值,将它们减少为 50 个元素。
有没有一种干净的方法可以做到这一点?我认为 LINQ 会有所帮助,但我想不出一个干净的方法。有任何想法吗?
我会使用 Chunk 函数和循环。
foreach(var set in source.ToList().Chunk(10)){
target.Enqueue(TimeSpan.FromMilliseconds(
set.Average(t => t.TotalMilliseconds)));
}
Chunk 是我的标准助手库的一部分。 http://clrextensions.codeplex.com/
查看.Skip() 和 .Take()扩展方法,将队列划分为集合。然后,您可以使用 .Average(t => 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;
}
将其与整数 (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);
}
笔记:
IEnumerable<TimeSpan>
所以使用 Ticks(长)。编辑:以 10 人为一组,以更好地解决问题。
EDIT2:现在使用经过测试的代码。
我会使用循环,但只是为了好玩:
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));
}
}
}
这只检查每个元素一次,因此性能比其他 LINQ 产品要好得多。不幸的是,它改变了队列,但也许它是一个特性而不是一个错误?
它确实具有作为迭代器的好处,所以它一次给你一个平均值。
你可以使用
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),但很容易看到它在做什么......(你可以很容易地从一个数组/从一个队列)