//******************************************************************************************************
// RollingWindow.cs - Gbtc
//
// Copyright © 2015, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
// not use this file except in compliance with the License. You may obtain a copy of the License at:
//
// http://www.opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 02/02/2015 - Stephen C. Wills
// Generated original version of source code.
//
//******************************************************************************************************
using System;
using System.Collections;
using System.Collections.Generic;
using GSF.NumericalAnalysis;
namespace GSF.Collections
{
///
/// Represents a rolling window of data with FIFO semantics that will
/// automatically truncate the data when the window size is exceeded.
///
/// The type of objects to be stored in the rolling window.
public class RollingWindow : IList
{
#region [ Members ]
// Fields
private int m_windowSize;
private T[] m_window;
private int m_start;
private int m_count;
private int m_version;
#endregion
#region [ Constructors ]
///
/// Creates a new instance of the class.
///
/// The size of the window maintained by the collection.
public RollingWindow(int windowSize)
{
if (windowSize <= 0)
throw new ArgumentOutOfRangeException(nameof(windowSize), "windowSize must be at least 1");
m_windowSize = windowSize;
m_window = new T[windowSize];
}
#endregion
#region [ Properties ]
///
/// Gets the number of elements contained in the .
///
public int Count
{
get
{
return m_count;
}
}
///
/// Gets the size of the window maintained by the .
///
public int WindowSize
{
get
{
return m_windowSize;
}
}
///
/// Gets a value indicating whether the is read-only.
///
public bool IsReadOnly
{
get
{
return false;
}
}
///
/// Gets or sets the element at the specified index.
///
///
/// The element at the specified index.
///
/// The zero-based index of the element to get or set.
/// is not a valid index in the .
public T this[int index]
{
get
{
if (index < 0 || index >= m_count)
throw new ArgumentOutOfRangeException(nameof(index), "index must be greater than zero and less than the size of the collection");
return m_window[WrapIndex(m_start + index)];
}
set
{
if (index < 0 || index >= m_count)
throw new ArgumentOutOfRangeException(nameof(index), "index must be greater than zero and less than the size of the collection");
m_window[WrapIndex(m_start + index)] = value;
}
}
#endregion
#region [ Methods ]
///
/// Adds an item to the .
///
/// The object to add to the .
public void Add(T item)
{
if (m_count < m_windowSize)
m_count++;
else
m_start = WrapIndex(m_start + 1);
this[m_count - 1] = item;
m_version++;
}
///
/// Inserts an item to the at the specified index.
///
/// The zero-based index at which should be inserted.
/// The object to insert into the .
/// is not a valid index in the .
/// Rolling window is full ( = .
public void Insert(int index, T item)
{
if (index < 0 || index > m_count)
throw new ArgumentOutOfRangeException(nameof(index), "index must be greater than zero and less than or equal to the size of the collection");
if (m_count >= m_windowSize)
throw new InvalidOperationException("Unable to insert item; rolling window is full");
// Increase count first to
// prevent out of range indexes
m_count++;
if (index <= m_windowSize / 2)
{
// Shift left to make room
m_start = WrapIndex(m_start - 1);
for (int i = 0; i < index; i++)
this[i] = this[i + 1];
}
else
{
// Shift right to make room
for (int i = m_count - 1; i > index; i--)
this[i] = this[i - 1];
}
// Insert the item and
// increment the version
this[index] = item;
m_version++;
}
///
/// Removes the first occurrence of a specific object from the .
///
///
/// true if was successfully removed from the ; otherwise, false.
/// This method also returns false if is not found in the original .
///
/// The object to remove from the .
public bool Remove(T item)
{
int index = IndexOf(item);
if (index >= 0)
{
RemoveAt(index);
return true;
}
return false;
}
///
/// Removes the item at the specified index.
///
/// The zero-based index of the item to remove.
/// is not a valid index in the .
public void RemoveAt(int index)
{
if (index <= m_windowSize / 2)
{
// Shift right to fill space
for (int i = index; i > 0; i--)
this[i] = this[i - 1];
this[0] = default(T);
m_start++;
}
else
{
// Shift left to fill space
for (int i = index; i < m_count - 1; i++)
this[i] = this[i + 1];
this[m_count - 1] = default(T);
}
m_count--;
m_version++;
}
///
/// Determines the index of a specific item in the .
///
///
/// The index of if found in the list; otherwise, -1.
///
/// The object to locate in the .
public int IndexOf(T item)
{
int i = 0;
foreach (T obj in this)
{
if (Equals(obj, item))
return i;
i++;
}
return -1;
}
///
/// Determines whether the contains a specific value.
///
///
/// true if is found in the ; otherwise, false.
///
/// The object to locate in the .
public bool Contains(T item)
{
return IndexOf(item) >= 0;
}
///
/// Removes all items from the .
///
public void Clear()
{
for (int i = 0; i < m_windowSize; i++)
m_window[i] = default(T);
m_count = 0;
}
///
/// Copies the elements of the to an , starting at a particular index.
///
/// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing.
/// The zero-based index in at which copying begins.
/// is null.
/// is less than 0.
/// The number of elements in the source is greater than the available space from to the end of the destination .
public void CopyTo(T[] array, int arrayIndex)
{
int i;
if ((object)array == null)
throw new ArgumentNullException(nameof(array));
if (arrayIndex < 0)
throw new ArgumentOutOfRangeException(nameof(arrayIndex), "arrayIndex must be greater than zero");
if (m_count > array.Length - arrayIndex)
throw new ArgumentException("Not enough available space in array to copy elements from rolling window");
i = 0;
foreach (T item in this)
{
array[i] = item;
i++;
}
}
///
/// Returns an enumerator that iterates through the collection.
///
///
/// A that can be used to iterate through the collection.
///
/// 1
public IEnumerator GetEnumerator()
{
int version = m_version;
int count = 0;
for (int i = m_start; i < m_windowSize; i++)
{
if (version != m_version)
throw new InvalidOperationException("Collection was modified; enumeration operation may not execute");
if (count >= m_count)
break;
count++;
yield return m_window[i];
}
for (int i = 0; i < m_start; i++)
{
if (version != m_version)
throw new InvalidOperationException("Collection was modified; enumeration operation may not execute");
if (count >= m_count)
break;
count++;
yield return m_window[i];
}
}
///
/// Returns an enumerator that iterates through a collection.
///
///
/// An object that can be used to iterate through the collection.
///
/// 2
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
private int WrapIndex(int index)
{
return (int)Euclidean.Mod(index, m_windowSize);
}
#endregion
}
}