using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; namespace MonoGame.Extended.Collections { internal static class Deque { internal static readonly Func DefaultResizeFunction = x => x * 2; } /// /// Represents a collection of objects which elements can added to or removed either from the front or back; a /// double ended queue (deque). /// /// /// circular array is used as the internal data /// structure for the . /// /// The type of the elements in the deque. public class Deque : IList { private const int _defaultCapacity = 4; private static readonly T[] _emptyArray = new T[0]; private int _frontArrayIndex; private T[] _items; private Func _resizeFunction = Deque.DefaultResizeFunction; /// /// Initializes a new instance of the class that is empty and has the default initial capacity. /// /// /// /// The capacity of a is the number of elements that the can /// hold. As elements are added to a , is automatically increased by /// as required by reallocating the internal array. /// /// /// If the size of the collection can be estimated, using the constructor and /// specifying the initial capacity eliminates the need to perform a number of resizing operations while adding /// elements to the . /// /// /// The capacity can be decreased by calling the method or by setting the /// property explicitly. Decreasing, or increasing, the capacity reallocates memory and /// copies all the /// elements in the . /// /// This constructor is an O(1) operation. /// public Deque() { _items = _emptyArray; } /// /// Initializes a new instance of the class that contains elements copied from the specified /// collection and has sufficient capacity to accommodate the number of elements copied. /// /// The collection whose elements are copied to the new deque. /// is null. /// /// /// The elements are copied onto the in the same order they are read by the enumerator of /// . /// /// This constructor is an O(n) operation, where n is the number of elements in . /// public Deque(IEnumerable collection) { if (collection == null) throw new ArgumentNullException(nameof(collection)); var array = collection as T[] ?? collection.ToArray(); var count = array.Length; if (count == 0) _items = _emptyArray; else { _items = new T[count]; array.CopyTo(_items, 0); Count = count; } } /// /// Initializes a new instance of the class that is empty and has the specified initial /// capacity. /// /// The number of elements that the new can initially store. /// is less than 0. /// /// /// The capacity of a is the number of elements that the can /// hold. As elements are added to a , is automatically increased by /// as required by reallocating the internal array. /// /// /// If the size of the collection can be estimated, specifying the initial capacity eliminates the need to /// perform a number of resizing operations while adding elements to the . /// /// /// The capacity can be decreased by calling the method or by setting the /// property explicitly. Decreasing, or increasing, the capacity reallocates memory and /// copies all the elements in the . /// /// This constructor is an O(n) operation, where n is . /// public Deque(int capacity) { if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity was less than zero."); _items = capacity == 0 ? _emptyArray : new T[capacity]; } /// /// Gets or sets the resize function used to calculate and set when a greater capacity is /// required. /// /// /// The used to calculate and set when a greater capacity /// is required. /// /// /// The default resize function is twice the . Setting /// to null will set it back to the default. /// public Func ResizeFunction { get => _resizeFunction; set => _resizeFunction = value ?? Deque.DefaultResizeFunction; } /// /// Gets or sets the total number of elements the internal data structure can hold without resizing. /// /// The number of elements that the can contain before resizing is required. /// /// cannot be set to a value less than . /// /// /// Changing reallocates memory and copies all the /// elements in the . /// public int Capacity { get => _items.Length; set { if (value < Count) throw new ArgumentOutOfRangeException(nameof(value), "capacity was less than the current size."); if (value == Capacity) return; if (value == 0) { _items = _emptyArray; return; } var newItems = new T[value]; CopyTo(newItems); _frontArrayIndex = 0; _items = null; _items = newItems; } } /// /// Gets or sets the element at the specified index. /// /// The zero-based index of the element to get or set. /// The element at the specified index. /// /// Index was out of range. Must be non-negative and less than . /// /// /// /// /// Use 0 for the to get or set the element at the beginning of the /// , and use - 1 for the to get the /// element at the end of the . /// /// public T this[int index] { get { var arrayIndex = GetArrayIndex(index); if (arrayIndex == -1) { throw new ArgumentOutOfRangeException(nameof(index), "Index was out of range. Must be non-negative and less than the size of the collection."); } return _items[arrayIndex]; } set { var arrayIndex = GetArrayIndex(index); if (arrayIndex == -1) { throw new ArgumentOutOfRangeException(nameof(index), "Index was out of range. Must be non-negative and less than the size of the collection."); } _items[arrayIndex] = value; } } /// /// Gets the number of elements contained in the . /// /// The number of elements contained in the . public int Count { get; private set; } bool ICollection.IsReadOnly => false; /// /// Returns an enumerator that iterates through the . /// /// An that can be used to iterate through the . public IEnumerator GetEnumerator() { if (Count == 0) yield break; if (Count <= _items.Length - _frontArrayIndex) { for (var i = _frontArrayIndex; i < _frontArrayIndex + Count; i++) yield return _items[i]; } else { for (var i = _frontArrayIndex; i < Capacity; i++) yield return _items[i]; for (var i = 0; i < (_frontArrayIndex + Count) % Capacity; i++) yield return _items[i]; } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } void ICollection.Add(T item) { AddToBack(item); } /// /// Searches for the specified element and returns the zero-based index of the first occurrence within the entire /// . /// /// /// The element to locate in the . The value can be null for reference /// types. /// /// /// The zero-based index of the first occurrence of within the entire /// , if found; otherwise, -1. /// /// /// /// This method is an O(1) operation if is at the front or back of the /// ; otherwise, this method is an O(n) operation where n is . /// /// public int IndexOf(T item) { var comparer = EqualityComparer.Default; if (Get(0, out var checkFrontBackItem) && comparer.Equals(checkFrontBackItem, item)) return 0; var backIndex = Count - 1; if (Get(backIndex, out checkFrontBackItem) && comparer.Equals(checkFrontBackItem, item)) return backIndex; int index; if (Count <= _items.Length - _frontArrayIndex) index = Array.IndexOf(_items, item, _frontArrayIndex, Count); else { index = Array.IndexOf(_items, item, _frontArrayIndex, _items.Length - _frontArrayIndex); if (index < 0) index = Array.IndexOf(_items, item, 0, _frontArrayIndex + Count - _items.Length); } var circularIndex = (index - _frontArrayIndex + _items.Length) % _items.Length; return circularIndex; } void IList.Insert(int index, T item) { throw new NotImplementedException(); } /// /// Removes the first occurrence of a specific element from the . /// /// /// The element to remove from the . The value can be null for reference /// types. /// /// /// true if was successfully removed; otherwise, false. This method also returns false /// if is not found in the . /// /// /// /// This method is an O(1) operation if is at the front or back of the /// ; otherwise, this method is an O(n) operation where n is . /// /// public bool Remove(T item) { var index = IndexOf(item); if (index == -1) return false; RemoveAt(index); return true; } /// /// Removes the element at the specified index of the . /// /// The zero-based index of the element to remove. /// /// is less than 0. /// -or- /// is equal to or greater than . /// public void RemoveAt(int index) { if (index < 0) throw new ArgumentOutOfRangeException(nameof(index), index, "Index was less than zero."); if (index >= Count) throw new ArgumentOutOfRangeException(nameof(index), index, "Index was equal or greater than TotalCount."); if (index == 0) { RemoveFromFront(); } else { if (index == Count - 1) { RemoveFromBack(); } else { if (index < Count / 2) { var arrayIndex = GetArrayIndex(index); // shift the array from 0 to before the index to remove by 1 to the right // the element to remove is replaced by the copy Array.Copy(_items, 0, _items, 1, arrayIndex); // the first element in the arrya is now either a duplicate or it's default value // to be safe set it to it's default value regardless of circumstance _items[0] = default(T); // if we shifted the front element, adjust the front index if (_frontArrayIndex < arrayIndex) _frontArrayIndex = (_frontArrayIndex + 1) % _items.Length; // decrement the count so the back index is calculated correctly Count--; } else { var arrayIndex = GetArrayIndex(index); // shift the array from the center of the array to before the index to remove by 1 to the right // the element to remove is replaced by the copy var arrayCenterIndex = _items.Length / 2; Array.Copy(_items, arrayCenterIndex, _items, arrayCenterIndex + 1, _items.Length - 1 - arrayIndex); // the last element in the array is now either a duplicate or it's default value // to be safe set it to it's default value regardless of circumstance _items[_items.Length - 1] = default(T); // if we shifted the front element, adjust the front index if (_frontArrayIndex < arrayIndex) _frontArrayIndex = (_frontArrayIndex + 1) % _items.Length; // decrement the count so the back index is calculated correctly Count--; } } } } /// /// Removes all elements from the . /// /// /// /// is set to 0, and references to other objects from elements of the collection are /// also released. /// /// /// remains unchanged. To reset the capacity of the , call the /// method or set the property explictly. Decreasing, or /// increasing, the capacity reallocates memory and copies all the elements in the . /// Trimming an empty sets to the default capacity. /// /// This method is an O(n) operation, where n is . /// public void Clear() { // allow the garbage collector to reclaim the references if (Count == 0) return; if (Count > _items.Length - _frontArrayIndex) { Array.Clear(_items, _frontArrayIndex, _items.Length - _frontArrayIndex); Array.Clear(_items, 0, _frontArrayIndex + Count - _items.Length); } else Array.Clear(_items, _frontArrayIndex, Count); Count = 0; _frontArrayIndex = 0; } /// /// Determines whether an element is in the . /// /// /// The element to locate in the . The value can be null for reference /// types. /// /// true if is found in the ; otherwise, false. /// /// /// This method determines equality by using the default equality comparer, as defined by the object's /// implementation /// of the method for the type of values in the list. /// /// /// This method performs a linear search; therefore, this method is an O(n) operation, where n is /// . /// /// public bool Contains(T item) { return this.Contains(item, EqualityComparer.Default); } /// /// Copies the entire to a compatible one-dimensional array, starting at the specified index of /// the target array. /// /// /// 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 . /// /// /// This method uses to copy the elements. The elements are /// copied to the in the same order in which the enumerator iterates /// through the . This method is an O(n) operation, where n is . /// public void CopyTo(T[] array, int arrayIndex = 0) { if (array == null) throw new ArgumentNullException(nameof(array)); if (array.Rank != 1) throw new ArgumentException("Only single dimensional arrays are supported for the requested action."); if (arrayIndex < 0) throw new ArgumentOutOfRangeException(nameof(arrayIndex), "Index was less than the array's lower bound."); if (arrayIndex >= array.Length) { throw new ArgumentOutOfRangeException(nameof(arrayIndex), "Index was greater than the array's upper bound."); } if (array.Length - arrayIndex < Count) throw new ArgumentException("Destination array was not long enough."); if (Count == 0) return; try { var loopsAround = Count > _items.Length - _frontArrayIndex; if (!loopsAround) Array.Copy(_items, _frontArrayIndex, array, arrayIndex, Count); else { Array.Copy(_items, _frontArrayIndex, array, arrayIndex, Capacity - _frontArrayIndex); Array.Copy(_items, 0, array, arrayIndex + Capacity - _frontArrayIndex, _frontArrayIndex + (Count - Capacity)); } } catch (ArrayTypeMismatchException) { throw new ArgumentException( "Target array type is not compatible with the type of items in the collection."); } } /// /// Sets the capacity to the actual number of elements in the , if that number is less than a /// threshold value. /// /// /// /// This method can be used to minimize the 's memory overhead if no new elements will be /// added. The cost of reallocating and copying the elements of a can be considerable. /// However, the method does nothing if the is more than 90% of /// . This avoids incurring a large reallocation cost for a relatively small gain. /// /// /// If is more than 90% of , this method is an O(1) operation; O(n) /// otherwise, where n is . /// /// /// To reset a to its initial state, call the method before calling /// the method. Trimming an empty sets to /// the default capacity. /// /// The capacity can also be set using the property. /// public void TrimExcess() { if (Count > (int)(_items.Length * 0.9)) return; Capacity = Count; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private int GetArrayIndex(int index) { if ((index < 0) || (index >= Count)) return -1; return _items.Length != 0 ? (_frontArrayIndex + index) % _items.Length : 0; } private void EnsureCapacity(int minimumCapacity) { if (_items.Length >= minimumCapacity) return; var newCapacity = _defaultCapacity; if (_items.Length > 0) newCapacity = _resizeFunction(_items.Length); newCapacity = Math.Max(newCapacity, minimumCapacity); Capacity = newCapacity; } /// /// Adds an element to the beginning of the . /// /// The element to add to the . The value can be null. /// /// /// As elements are added to a , is automatically increased by /// as required by reallocating the internal circular array. /// /// /// If is less than , this method is an O(1) operation. Otherwise the /// internal circular array needs to be resized to accommodate the new element and this method becomes an O(n) /// operation, where n is . /// /// public void AddToFront(T item) { EnsureCapacity(Count + 1); _frontArrayIndex = (_frontArrayIndex - 1 + _items.Length) % _items.Length; _items[_frontArrayIndex] = item; Count++; } /// /// Adds an element to the end of the . /// /// The element to add to the . The value can be null. /// /// /// As elements are added to a , is automatically increased by /// as required by reallocating the internal circular array. /// /// /// If is less than , this method is an O(1) operation. Otherwise the /// internal circular array needs to be resized to accommodate the new element and this method becomes an O(n) /// operation, where n is . /// /// public void AddToBack(T item) { EnsureCapacity(Count + 1); var index = (_frontArrayIndex + Count++) % _items.Length; _items[index] = item; } /// /// Returns the element at the specified index of the . /// /// The zero-based index of the element to get. /// /// When this method returns, contains the element at the specified index of the /// , if was non-negative and less than ; /// otherwise, the default value for the type of the value parameter. This parameter is passed uninitialized. /// /// /// true if was successfully retrieved at from the of the /// ; /// otherwise, false if was non-negative and less than . /// public bool Get(int index, out T item) { var arrayIndex = GetArrayIndex(index); if (arrayIndex == -1) { item = default(T); return false; } item = _items[arrayIndex]; return true; } /// /// Returns the element at the beginning of the . /// /// /// When this method returns, contains the element at the beginning of the , if /// was not empty; otherwise, the default value for the type of the value parameter. This /// parameter is passed uninitialized. /// /// /// true if was successfully from the beginning of the ; /// otherwise, false if the is empty. /// public bool GetFront(out T item) { return Get(0, out item); } /// /// Returns the element at the end of the . /// /// /// When this method returns, contains the element at the end of the , if /// was not empty; otherwise, the default value for the type of the value parameter. This /// parameter is passed uninitialized. /// /// /// true if was successfully from the end of the ; /// otherwise, false if the is empty. /// public bool GetBack(out T item) { return Get(Count - 1, out item); } /// /// Removes the element at the beginning of the . /// /// /// When this method returns, contains the element removed from the beginning of the /// , if the was not empty; otherwise, the default value for the type of /// the value parameter. This parameter is passed uninitialized. /// /// /// true if was successfully removed from the beginning of the ; /// otherwise, false if the is empty. /// /// /// /// This method is similar to the method, but does not /// modify the . /// /// /// null can be added to the as a value. To distinguish between a null value and /// the end of the , check whether the return value of /// is /// false or /// is 0. /// /// /// This method is an O(1) operation. /// /// public bool RemoveFromFront(out T item) { if (Count == 0) { item = default(T); return false; } var index = _frontArrayIndex % _items.Length; item = _items[index]; _items[index] = default(T); _frontArrayIndex = (_frontArrayIndex + 1) % _items.Length; Count--; return true; } /// /// Removes the element at the beginning of the . /// /// /// true if the element was successfully removed from the beginning of the ; /// otherwise, false if the is empty. /// /// /// /// This method is similar to the method, but does not /// modify the . /// /// /// null can be added to the as a value. To distinguish between a null value and /// the end of the , check whether the return value of is /// false or /// is 0. /// /// /// This method is an O(1) operation. /// /// public bool RemoveFromFront() { if (Count == 0) return false; var index = _frontArrayIndex % _items.Length; _items[index] = default(T); _frontArrayIndex = (_frontArrayIndex + 1) % _items.Length; Count--; return true; } /// /// Removes the element at the end of the . /// /// /// When this method returns, contains the element removed from the end of the /// , if the was not empty; otherwise, the default value for the type of /// the value parameter. This parameter is passed uninitialized. /// /// /// true if was successfully removed from the end of the ; /// otherwise, false if the is empty. /// /// /// /// This method is similar to the method, but does not /// modify the . /// /// /// null can be added to the as a value. To distinguish between a null value and /// the end of the , check whether the return value of /// is /// false or /// is 0. /// /// /// This method is an O(1) operation. /// /// public bool RemoveFromBack(out T item) { if (Count == 0) { item = default(T); return false; } var circularBackIndex = (_frontArrayIndex + (Count - 1)) % _items.Length; item = _items[circularBackIndex]; _items[circularBackIndex] = default(T); Count--; return true; } /// /// Removes the element at the end of the . /// /// /// true if the element was successfully removed from the end of the ; /// otherwise, false if the is empty. /// /// /// /// This method is similar to the method, but does not /// modify the . /// /// /// null can be added to the as a value. To distinguish between a null value and /// the end of the , check whether the return value of is /// false or /// is 0. /// /// /// This method is an O(1) operation. /// /// public bool RemoveFromBack() { if (Count == 0) return false; var circularBackIndex = (_frontArrayIndex + (Count - 1)) % _items.Length; _items[circularBackIndex] = default(T); Count--; return true; } /// /// Removes and returns the last item. /// /// The item that was removed public T Pop() { if (RemoveFromBack(out var item)) return item; throw new InvalidOperationException(); } } }