diff --git a/MonkeyLoader/MonkeyLoader.csproj b/MonkeyLoader/MonkeyLoader.csproj index 9426542..8a34122 100644 --- a/MonkeyLoader/MonkeyLoader.csproj +++ b/MonkeyLoader/MonkeyLoader.csproj @@ -11,7 +11,7 @@ True MonkeyLoader Banane9 - 0.11.0-beta + 0.12.0-beta A convenience and extendability focused mod loader using NuGet packages. README.md LGPL-3.0-or-later diff --git a/MonkeyLoader/SortedCollection.cs b/MonkeyLoader/SortedCollection.cs new file mode 100644 index 0000000..946ca5e --- /dev/null +++ b/MonkeyLoader/SortedCollection.cs @@ -0,0 +1,220 @@ +using HarmonyLib; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace MonkeyLoader +{ + /// + /// Represents a collection which is kept sorted using an + /// or the default comparer for . + /// + /// + /// Duplicate items, null items, and multiple items comparing equal are supported. + /// + /// The type of the items. + public class SortedCollection : ICollection + { + private readonly IComparer _comparer; + private readonly List _values; + + /// + public int Count => _values.Count; + + bool ICollection.IsReadOnly => ((ICollection)_values).IsReadOnly; + + /// + /// Gets the element at the specified index in the collection. + /// + /// The index to get the element at. + /// The element at the specified index. + /// When the index is not in the collection. + public T this[int index] => _values[index]; + + /// + /// Constructs an empty sorted collection with the + /// default comparer for . + /// + /// If is not . + public SortedCollection() : this(null, null) + { } + + /// + /// Constructs an empty sorted collection with the given comparer to keep it sorted. + /// + /// The comparer to use to keep the collection sorted. + /// If is null and is not . + public SortedCollection(IComparer? comparer) : this(null, comparer) + { } + + /// + /// Constructs a sorted collection with the given elements and comparer to keep it sorted. + /// + /// The elements to add to the collection. + /// The comparer to use to keep the collection sorted. + /// If is null and is not . + public SortedCollection(IEnumerable? collection, IComparer? comparer) + { + if (comparer is null && !typeof(IComparable).IsAssignableFrom(typeof(T))) + throw new NotSupportedException("Can't create a sorted collection of an uncomparable type without a comparer!"); + + _comparer = comparer ?? Comparer.Default; + + _values = new(collection ?? Array.Empty()); + _values.Sort(_comparer); + } + + /// + /// Constructs a sorted collection with the given elements and + /// default comparer for . + /// + /// The elements to add to the collection. + /// If is not . + public SortedCollection(IEnumerable? collection) : this(collection, null) + { } + + /// + public void Add(T item) => _values.Insert(FindInsertIndex(item), item); + + /// + /// Adds each item in the given sequence to this sorted collection. + /// + /// The collection of items to add. + public void AddRange(IEnumerable collection) + => collection?.Do(Add); + + /// + public void Clear() => _values.Clear(); + + /// + public bool Contains(T item) => _values.IndexOf(item) != -1; + + /// + public void CopyTo(T[] array, int arrayIndex) => _values.CopyTo(array, arrayIndex); + + /// + public IEnumerator GetEnumerator() => _values.GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_values).GetEnumerator(); + + /// + /// Creates a shallow copy of a range of elements this sorted collection. + /// + /// The zero-based index at which the range starts. + /// The number of elements in the range. + /// A shallow copy of a range of elements in this sorted collection. + /// is less than 0. -or- is less than 0. + /// and do not denote a valid range of elements in this sorted collection. + public SortedCollection GetRange(int index, int count) + => new(_values.GetRange(index, count), _comparer); + + /// + /// Attempts to find the first index of the given item. + /// + /// The item to look for. + /// The first index of the item if it was found; otherwise, -1. + public int IndexOf(T item) + { + var i = ÍndexOfEqualityClass(item); + + if (i < 0) + return -1; + + // search forwards + for (; i < _values.Count && _comparer.Compare(item, _values[i]) == 0; ++i) + { + if (item?.Equals(_values[i]) ?? _values[i] is null) + return i; + } + + return -1; + } + + /// + /// Attempts to find the first index in this collection with an item that compares equal to the needle. + /// + /// The item to search for. + /// The last index if equal items are found; otherwise, -1. + public int ÍndexOfEqualityClass(T needle) + { + var i = IndexInEqualityClass(needle); + + if (i < 0) + return -1; + + // search backwards + while (--i >= 0 && _comparer.Compare(needle, _values[i]) == 0) + { } + + return ++i; + } + + /// + /// Attempts to find the last index of the given item. + /// + /// The item to look for. + /// The last index of the item if it was found; otherwise, -1. + public int LastIndexOf(T item) + { + var i = LastIndexOfEqualityClass(item); + + if (i < 0) + return -1; + + // search backwards + for (; i >= 0 && _comparer.Compare(item, _values[i]) == 0; --i) + { + if (item?.Equals(_values[i]) ?? _values[i] is null) + return i; + } + + return -1; + } + + /// + /// Attempts to find the last index in this collection with an item that compares equal to the needle. + /// + /// The item to search for. + /// The last index if equal items are found; otherwise, -1. + public int LastIndexOfEqualityClass(T needle) + { + var i = IndexInEqualityClass(needle); + + if (i < 0) + return -1; + + // search forwards + while (++i < _values.Count && _comparer.Compare(needle, _values[i]) == 0) + { } + + return --i; + } + + /// + public bool Remove(T item) => _values.Remove(item); + + /// + /// Removes the element at the given index. + /// + /// The index of the item to remove. + /// When the index is not in the collection. + public void RemoveAt(int index) => _values.RemoveAt(index); + + private int FindInsertIndex(T needle) + { + var i = IndexInEqualityClass(needle); + if (i < 0) + return ~i; + + // search forwards + while (++i < _values.Count && _comparer.Compare(needle, _values[i]) == 0) + { } + + return i; + } + + private int IndexInEqualityClass(T needle) => _values.BinarySearch(needle, _comparer); + } +} \ No newline at end of file