Pergunta

Eu tenho uma fila C#u003CTimeSpan> contendo 500 elementos.

Preciso reduzi -los em 50 elementos, tomando grupos de 10 tempos e selecionando sua média.

Existe uma maneira limpa de fazer isso? Estou pensando que o LINQ ajudará, mas não consigo descobrir uma maneira limpa. Alguma ideia?

Foi útil?

Solução

Eu usaria a função de pedaços e um loop.

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

Chunk faz parte da minha biblioteca auxiliar padrão.http://clrextensions.codeplex.com/

Fonte para Chunk

Outras dicas

Dê uma olhada no .Skip () e .Take () Métodos de extensão para particionar sua fila em conjuntos. Você pode então usar .ERAGEM (T => T.TICKS) para obter a nova capital de tempo que representa a média. Apenas toce cada uma dessas 50 médias em uma nova fila e você está pronto para ir.

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

Eu sou um cara do VB.NET, então pode haver alguma sintaxe que não seja 100% escreva nesse exemplo. Deixe -me saber e eu vou consertar!

Provavelmente nada supera uma boa e velha execução processual em uma chamada de método neste caso. Não é chique, mas é fácil e pode ser mantido pelos desenvolvedores de nível 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;
}

Você poderia apenas usar

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

É um pouco mais detalhado (não usa LINQ), mas é muito fácil ver o que está fazendo ... (você pode uma fila para/para uma matriz com bastante facilidade)

Eu usaria um loop, mas apenas por diversão:

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

Isso examina apenas cada elemento uma vez, portanto o desempenho é muito melhor do que as outras ofertas do LINQ. Infelizmente, ele se afasta da fila, mas talvez seja um recurso e não um bug?

Ele tem o bom bônus de ser um iterador, por isso fornece as médias uma de cada vez.

Aproveitando -o com os números inteiros (0..n) e agrupamento pelo número da sequência div 10?

Não sou um usuário do LINQ, mas acredito que seria algo assim:

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

A solução de Take (10) provavelmente é melhor.

Como o agrupamento será realizado?

Supondo que algo muito simples (tome 10 de cada vez), você pode começar com algo como:

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

Notas:

  • Sobrecarga de selecionar para obter o índice, depois usar isso e a divisão inteira retirada grupos de 10. poderia usar o módulo para levar cada 10º elemento em um grupo, a cada 10º+1 em outro, ...
  • O resultado do agrupamento é uma sequência de enumerações com uma propriedade -chave. Mas só preciso dessas seqüências separadas aqui.
  • Não há enumerável. Sobrecarga para IEnumerable<TimeSpan> Portanto, use ticks (um longo).

EDIT: Tome grupos de 10 para se encaixar melhor com a pergunta.
Edit2: Agora com código testado.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top