清洁方式将许多TimeSpans减少到更少的平均TimeSpans?

时间:2009-03-17 17:46:37

标签: c# linq timespan average

我有一个C#Queue< TimeSpan>包含500个元素。

我需要通过组合10个TimeSpans并选择它们的平均值来将这些元素减少到50个元素。

有干净的方法吗?我认为LINQ会有所帮助,但我无法找到一个干净的方法。有什么想法吗?

7 个答案:

答案 0 :(得分:3)

我会使用Chunk函数和循环。

foreach(var set in source.ToList().Chunk(10)){
    target.Enqueue(TimeSpan.FromMilliseconds(
                            set.Average(t => t.TotalMilliseconds)));
}

Chunk是我的标准帮助程序库的一部分。 http://clrextensions.codeplex.com/

Source for Chunk

答案 1 :(得分:2)

查看.Skip() and .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%写入的语法。让我知道,我会解决它!

答案 2 :(得分:1)

在这种情况下,在方法调用中可能没有什么能胜过旧的程序执行。这不是花哨的,但它很容易,而且可以由Jr. level devs维护。

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;
}

答案 3 :(得分:1)

将它与整数(0..n)一起压缩并按序列号div 10分组?

我不是linq用户,但我相信它看起来像这样:

for (n,item) from Enumerable.Range(0, queue.length).zip(queue) group by n/10

take(10)解决方案可能更好。

答案 4 :(得分:1)

如何进行分组?

假设一些非常简单的东西(一次取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);
}

注意:

  • 重载选择以获取索引,然后使用此和整数除法拾取组10.可以使用模数将每个第10个元素分成一个组,每10个+ 1到另一个组,...
  • 分组的结果是一系列具有Key属性的枚举。但这里只需要那些单独的序列。
  • IEnumerable<TimeSpan>没有Enumerable.Average重载,所以请使用Ticks(很长)。

编辑:以10人为一组,以更好地适应问题 EDIT2:现在有了经过测试的代码。

答案 5 :(得分:1)

我会使用循环,但只是为了好玩:

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产品好很多。不幸的是,它改变了队列,但也许这是一个功能,而不是一个错误?

它确实具有成为迭代器的好处,因此它一次为您提供一个平均值。

答案 6 :(得分:1)

你可以使用

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),但它很容易看到它正在做什么......(你可以很容易地从阵列进行队列排队)