Skip to content

Instantly share code, notes, and snippets.

@DenchiSoft
Created February 13, 2022 15:41
Show Gist options
  • Select an option

  • Save DenchiSoft/ddda0bc2f3ed43b36bf8888ba64eaa53 to your computer and use it in GitHub Desktop.

Select an option

Save DenchiSoft/ddda0bc2f3ed43b36bf8888ba64eaa53 to your computer and use it in GitHub Desktop.
using System.Collections.Generic;
using System.Linq;
/// <summary>
/// Queue for calculating sliding averages. Not thread safe.
/// </summary>
public class SlidingWindowQueue
{
/// <summary>
/// The queue wrapped by this class.
/// </summary>
private Queue<float> wrappedQueue = new Queue<float>();
/// <summary>
/// True if the exact average has to be recalculated on next access.
/// </summary>
private bool exactAverageDirty;
/// <summary>
/// Backing field for capacity.
/// </summary>
private int _capacity;
/// <summary>
/// Window size. If the queue is bigger than this, old items will be dequeued.
/// Will be at least 1.
/// </summary>
public int Capacity
{
get { return _capacity; }
set { _capacity = value > 0 ? value : 1; }
}
/// <summary>
/// The average of all queue items. May be not be as exact as AverageExact() due to
/// floating point rounding errors.
/// </summary>
public float Average { get; private set; }
/// <summary>
/// Backing field for exact average.
/// </summary>
private float _averageExact;
/// <summary>
/// The exact average of all queue items.
/// Calling this every frame for large queues (10k+ items) can be expensive.
/// </summary>
public float AverageExact
{
get
{
// Recalculate average only if queue was modified.
if (exactAverageDirty)
{
_averageExact = wrappedQueue.Average();
exactAverageDirty = false;
}
return _averageExact;
}
}
/// <summary>
/// Constructor for SlidingWindowQueue.
/// </summary>
/// <param name="queueCapacity">The sliding window size.</param>
public SlidingWindowQueue(int queueCapacity)
{
Capacity = queueCapacity;
}
/// <summary>
/// Resets this SlidingWindowQueue by removing all elements.
/// </summary>
public void Reset()
{
wrappedQueue.Clear();
Average = 0;
_averageExact = 0;
exactAverageDirty = false;
}
/// <summary>
/// Add value to queue and return average.
/// </summary>
/// <param name="newValue">The value to add.</param>
/// <returns>The average of all values in queue.</returns>
public float Enqueue(float newValue)
{
// Recalculate average including new value.
int queueLenBeforeEnqueue = wrappedQueue.Count();
int queueLenAfterEnqueue = queueLenBeforeEnqueue + 1;
wrappedQueue.Enqueue(newValue);
float averageAfterEnqueue = (queueLenBeforeEnqueue * Average + newValue) / queueLenAfterEnqueue;
// Get sum of dequeued elements.
float dequeuedSum = 0;
bool dequeuedItems = false;
while (wrappedQueue.Count > _capacity)
{
dequeuedSum += wrappedQueue.Dequeue();
dequeuedItems = true;
}
// Recalculate average after removing old elements.
Average = dequeuedItems ? (queueLenAfterEnqueue * averageAfterEnqueue - dequeuedSum) / wrappedQueue.Count() : averageAfterEnqueue;
// Set exact average dirty so it's recalculated on next access.
exactAverageDirty = true;
return Average;
}
/// <summary>
/// Add value to queue and return average.
/// </summary>
/// <param name="newValue">The value to add.</param>
/// <returns>The average of all values in queue.</returns>
private float Enqueue(double newValue) => Enqueue((float) newValue);
/// <summary>
/// Add value to queue and return average.
/// </summary>
/// <param name="newValue">The value to add.</param>
/// <returns>The average of all values in queue.</returns>
private float Enqueue(int newValue) => Enqueue((float) newValue);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment