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