// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; namespace FlaxEngine.Collections { /// /// A dictionary object that allows rapid hash lookups using keys, but also maintains the key insertion order so that values can be retrieved by key index. /// [Serializable] public class OrderedDictionary : IOrderedDictionary { #region Fields/Properties private KeyedCollectionInternal> _keyedCollection; /// /// Gets or sets the value associated with the specified key. /// /// The key associated with the value to get or set. public TValue this[TKey key] { get => GetValue(key); set => SetValue(key, value); } /// /// Gets or sets the value at the specified index. /// /// The index of the value to get or set. public TValue this[int index] { get => GetItem(index).Value; set => SetItem(index, value); } /// /// Gets the number of elements contained in the . /// /// The number of elements contained in the T:System.Collections.ICollection. public int Count => _keyedCollection.Count; /// /// Gets an object containing the keys in the /// object. /// /// /// An object containing the keys in the /// object. /// public ICollection Keys { get { return _keyedCollection.Select(x => x.Key).ToList(); } } /// /// Gets an object containing the values in the /// object. /// /// /// An object containing the values in the /// object. /// public ICollection Values { get { return _keyedCollection.Select(x => x.Value).ToList(); } } /// /// The keys equality comparer. /// public IEqualityComparer Comparer { get; private set; } #endregion #region Constructors /// /// A dictionary object that allows rapid hash lookups using keys, but also /// maintains the key insertion order so that values can be retrieved by /// key index. /// public OrderedDictionary() { Initialize(); } /// /// /// /// Allows custom comparer for items public OrderedDictionary(IEqualityComparer comparer) { Initialize(comparer); } /// /// /// /// Copy constructor public OrderedDictionary(IOrderedDictionary dictionary) { Initialize(); foreach (KeyValuePair pair in dictionary) _keyedCollection.Add(pair); } /// /// /// /// Copy constructor with custom items comparer public OrderedDictionary(IOrderedDictionary dictionary, IEqualityComparer comparer) { Initialize(comparer); foreach (KeyValuePair pair in dictionary) _keyedCollection.Add(pair); } #endregion #region Methods private void Initialize(IEqualityComparer comparer = null) { Comparer = comparer; if (comparer != null) _keyedCollection = new KeyedCollectionInternal>(x => x.Key, comparer); else _keyedCollection = new KeyedCollectionInternal>(x => x.Key); } /// Adds an element with the specified key and value into the . /// The key of the element to add. /// The value of the element to add. public void Add(TKey key, TValue value) { _keyedCollection.Add(new KeyValuePair(key, value)); } /// /// Clears the contents of the instance. /// public void Clear() { _keyedCollection.Clear(); } /// /// Performs additional custom processes before inserting a new element into the /// instance. /// /// The index. /// The key of the element to insert. /// The value of the element to insert. public void Insert(int index, TKey key, TValue value) { _keyedCollection.Insert(index, new KeyValuePair(key, value)); } /// /// Determines whether an element is in the . /// /// The object to locate in the current dictionary. The element to locate can be null for reference types. /// The index of the item. public int IndexOf(TKey key) { if (_keyedCollection.Contains(key)) return _keyedCollection.IndexOf(_keyedCollection[key]); return -1; } /// /// Determines whether the contains a specific value. /// /// The value to locate in the . /// True if the contains an element with the specified value; otherwise, false. public bool ContainsValue(TValue value) { return Values.Contains(value); } /// /// Determines whether the contains a specific value. /// /// The value to locate in the . /// The custom for this search /// True if the contains an element with the specified value; otherwise, false. public bool ContainsValue(TValue value, IEqualityComparer comparer) { return Values.Contains(value, comparer); } /// /// Determines whether the contains a specific key. /// /// The key to locate in the . /// True if the contains an element with the specified key; otherwise, false. public bool ContainsKey(TKey key) { return _keyedCollection.Contains(key); } /// /// Gets item at given index. /// /// Requested key at index /// /// Thrown when the index specified does not refer to a KeyValuePair in this object /// public KeyValuePair GetItem(int index) { if (index < 0 || index >= _keyedCollection.Count) throw new ArgumentException(string.Format("The index was outside the bounds of the dictionary: {0}", index)); return _keyedCollection[index]; } /// /// Sets the value at the index specified. /// /// The index of the value desired /// The value to set /// /// Thrown when the index specified does not refer to a KeyValuePair in this object /// public void SetItem(int index, TValue value) { if (index < 0 || index >= _keyedCollection.Count) throw new ArgumentException($"The index is outside the bounds of the dictionary: {index}"); var kvp = new KeyValuePair(_keyedCollection[index].Key, value); _keyedCollection[index] = kvp; } /// /// Returns an that iterates through the /// . /// /// An for the . public IEnumerator> GetEnumerator() { return _keyedCollection.GetEnumerator(); } /// /// Performs additional custom processes before removing an element from the /// instance. /// /// The key of the element to remove. public bool Remove(TKey key) { return _keyedCollection.Remove(key); } /// /// Removes the item at the specified index. /// /// The zero-based index of the item to remove. public void RemoveAt(int index) { if (index < 0 || index >= _keyedCollection.Count) throw new ArgumentException($"The index was outside the bounds of the dictionary: {index}"); _keyedCollection.RemoveAt(index); } /// /// Gets the value associated with the specified key. /// /// The key associated with the value to get. public TValue GetValue(TKey key) { if (_keyedCollection.Contains(key) == false) throw new ArgumentException($"The given key is not present in the dictionary: {key}"); var kvp = _keyedCollection[key]; return kvp.Value; } /// /// Sets the value associated with the specified key. /// /// The key associated with the value to set. /// The the value to set. public void SetValue(TKey key, TValue value) { var kvp = new KeyValuePair(key, value); var idx = IndexOf(key); if (idx > -1) _keyedCollection[idx] = kvp; else _keyedCollection.Add(kvp); } /// /// Tries to get value at specified key. /// /// The key associated with the value to find. /// Found value. /// true if value existed, false if not public bool TryGetValue(TKey key, out TValue value) { if (_keyedCollection.Contains(key)) { value = _keyedCollection[key].Value; return true; } value = default; return false; } #endregion #region Sorting /// /// Sorts the keys. /// public void SortKeys() { _keyedCollection.SortByKeys(); } /// /// Sorts the keys. /// /// The comparer. public void SortKeys(IComparer comparer) { _keyedCollection.SortByKeys(comparer); } /// /// Sorts the keys. /// /// The comparison. public void SortKeys(Comparison comparison) { _keyedCollection.SortByKeys(comparison); } /// /// Sorts the values. /// public void SortValues() { var comparer = Comparer.Default; SortValues(comparer); } /// /// Sorts the values. /// /// The comparer. public void SortValues(IComparer comparer) { _keyedCollection.Sort((x, y) => comparer.Compare(x.Value, y.Value)); } /// /// Sorts the values. /// /// The comparison. public void SortValues(Comparison comparison) { _keyedCollection.Sort((x, y) => comparison(x.Value, y.Value)); } #endregion #region IDictionary void IDictionary.Add(TKey key, TValue value) { Add(key, value); } bool IDictionary.ContainsKey(TKey key) { return ContainsKey(key); } ICollection IDictionary.Keys => Keys; bool IDictionary.Remove(TKey key) { return Remove(key); } bool IDictionary.TryGetValue(TKey key, out TValue value) { return TryGetValue(key, out value); } ICollection IDictionary.Values => Values; TValue IDictionary.this[TKey key] { get => this[key]; set => this[key] = value; } #endregion #region ICollection> void ICollection>.Add(KeyValuePair item) { _keyedCollection.Add(item); } void ICollection>.Clear() { _keyedCollection.Clear(); } bool ICollection>.Contains(KeyValuePair item) { return _keyedCollection.Contains(item); } void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { _keyedCollection.CopyTo(array, arrayIndex); } int ICollection>.Count => _keyedCollection.Count; bool ICollection>.IsReadOnly => false; bool ICollection>.Remove(KeyValuePair item) { return _keyedCollection.Remove(item); } #endregion #region IEnumerable> IEnumerator> IEnumerable>.GetEnumerator() { return GetEnumerator(); } #endregion #region IEnumerable IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion #region IOrderedDictionary IDictionaryEnumerator IOrderedDictionary.GetEnumerator() { return new DictionaryEnumerator(this); } void IOrderedDictionary.Insert(int index, object key, object value) { Insert(index, (TKey)key, (TValue)value); } void IOrderedDictionary.RemoveAt(int index) { RemoveAt(index); } object IOrderedDictionary.this[int index] { get => this[index]; set => this[index] = (TValue)value; } #endregion #region IDictionary void IDictionary.Add(object key, object value) { Add((TKey)key, (TValue)value); } void IDictionary.Clear() { Clear(); } bool IDictionary.Contains(object key) { return _keyedCollection.Contains((TKey)key); } IDictionaryEnumerator IDictionary.GetEnumerator() { return new DictionaryEnumerator(this); } bool IDictionary.IsFixedSize => false; bool IDictionary.IsReadOnly => false; ICollection IDictionary.Keys => (ICollection)Keys; void IDictionary.Remove(object key) { Remove((TKey)key); } ICollection IDictionary.Values => (ICollection)Values; object IDictionary.this[object key] { get => this[(TKey)key]; set => this[(TKey)key] = (TValue)value; } #endregion #region ICollection void ICollection.CopyTo(Array array, int index) { ((ICollection)_keyedCollection).CopyTo(array, index); } int ICollection.Count => ((ICollection)_keyedCollection).Count; bool ICollection.IsSynchronized => ((ICollection)_keyedCollection).IsSynchronized; object ICollection.SyncRoot => ((ICollection)_keyedCollection).SyncRoot; #endregion } [Serializable] internal class KeyedCollectionInternal : KeyedCollection { private const string DelegateNullExceptionMessage = "Delegate passed cannot be null"; private Func _getKeyForItemDelegate; public KeyedCollectionInternal(Func getKeyForItemDelegate) { _getKeyForItemDelegate = getKeyForItemDelegate ?? throw new ArgumentNullException(DelegateNullExceptionMessage); } public KeyedCollectionInternal(Func getKeyForItemDelegate, IEqualityComparer comparer) : base(comparer) { _getKeyForItemDelegate = getKeyForItemDelegate ?? throw new ArgumentNullException(DelegateNullExceptionMessage); } protected override TKey GetKeyForItem(TItem item) { return _getKeyForItemDelegate(item); } public void SortByKeys() { var comparer = Comparer.Default; SortByKeys(comparer); } public void SortByKeys(IComparer keyComparer) { var comparer = new ComparerInternal((x, y) => keyComparer.Compare(GetKeyForItem(x), GetKeyForItem(y))); Sort(comparer); } public void SortByKeys(Comparison keyComparison) { var comparer = new ComparerInternal((x, y) => keyComparison(GetKeyForItem(x), GetKeyForItem(y))); Sort(comparer); } public void Sort() { var comparer = Comparer.Default; Sort(comparer); } public void Sort(Comparison comparison) { var newComparer = new ComparerInternal(comparison); Sort(newComparer); } public void Sort(IComparer comparer) { if (Items is List list) list.Sort(comparer); } } internal class ComparerInternal : Comparer { private readonly Comparison _compareFunction; /// public ComparerInternal(Comparison comparison) { _compareFunction = comparison ?? throw new ArgumentNullException(nameof(comparison)); } /// public override int Compare(T arg1, T arg2) { return _compareFunction(arg1, arg2); } } /// /// The enumerator implementation for dictionary /// /// The type of the key. /// The type of the value. /// /// [Serializable] public class DictionaryEnumerator : IDictionaryEnumerator, IDisposable { private readonly IEnumerator> _impl; /// public void Dispose() { _impl.Dispose(); } /// /// Initializes a new instance of the class. /// /// The value. public DictionaryEnumerator(IDictionary value) { _impl = value.GetEnumerator(); } /// public void Reset() { _impl.Reset(); } /// public bool MoveNext() { return _impl.MoveNext(); } /// public DictionaryEntry Entry { get { var pair = _impl.Current; return new DictionaryEntry(pair.Key, pair.Value); } } /// public object Key => _impl.Current.Key; /// public object Value => _impl.Current.Value; /// public object Current => Entry; } }