//****************************************************************************************************** // 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 } }