Чистый способ сократить многие TimeSpans на меньшее, среднее TimeSpans? - PullRequest
4 голосов
/ 17 марта 2009

У меня есть очередь C # , содержащая 500 элементов.

Мне нужно сократить их до 50 элементов, взяв группы из 10 временных интервалов и выбрав их среднее значение.

Есть ли чистый способ сделать это? Я думаю, что LINQ поможет, но я не могу придумать чистый способ. Есть идеи?

Ответы [ 7 ]

3 голосов
/ 17 марта 2009

Я бы использовал функцию Chunk и цикл.

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

Чанк является частью моей стандартной вспомогательной библиотеки. http://clrextensions.codeplex.com/

Источник для чанка

2 голосов
/ 17 марта 2009

Посмотрите на методы расширения .Skip () и .Take () , чтобы разбить вашу очередь на наборы. Затем вы можете использовать .Average (t => t.Ticks), чтобы получить новый интервал времени, представляющий среднее значение. Просто вставьте каждое из этих 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% записывается в этом примере. Дайте мне знать, и я все исправлю!

1 голос
/ 17 марта 2009

Вы можете просто использовать

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), но довольно легко увидеть, что он делает ... (Вы можете довольно легко поместить очередь в / из массива)

1 голос
/ 17 марта 2009

Я бы использовал цикл, но просто для удовольствия:

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. К сожалению, он изменяет очередь, но, возможно, это особенность, а не ошибка?

Он имеет приятный бонус итератора, поэтому он дает вам средние значения по одному за раз.

1 голос
/ 17 марта 2009

Как будет выполняться группировка?

Предполагая что-то очень простое (по 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 групп. Может использовать модуль, чтобы перевести каждый десятый элемент в одну группу, каждый десятый + 1 в другую, ...
  • Результатом группировки является последовательность перечислений со свойством Key. Но здесь нужны только эти отдельные последовательности.
  • Перегрузки Enumerable.Average для IEnumerable<TimeSpan> не существует, поэтому используйте тики (длинные).

РЕДАКТИРОВАТЬ: Возьмите группы по 10 человек, чтобы лучше соответствовать вопросу.
EDIT2: теперь с проверенным кодом.

1 голос
/ 17 марта 2009

Сжатие вместе с целыми числами (0..n) и группировка по порядковому номеру div 10?

Я не пользователь linq, но я верю, что это будет выглядеть примерно так:

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

Решение take (10), вероятно, лучше.

1 голос
/ 17 марта 2009

Вероятно, в этом случае ничто не сравнится со старым добрым процедурным выполнением при вызове метода. Это не модно, но легко и может поддерживаться разработчиками младшего уровня.

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