No generic implementation of OrderedDictionary? - c#
There doesn't appear to be a generic implementation of OrderedDictionary (which is in the System.Collections.Specialized namespace) in .NET 3.5. Is there one that I'm missing?
I've found implementations out there to provide the functionality, but wondered if/why there isn't a generic implementation out-of-the-box and if anyone knows whether it's something in .NET 4.0?
Implementing a generic OrderedDictionary isn't terribly difficult, but it's unnecessarily time consuming and frankly this class is a huge oversight on Microsoft's part. There are multiple ways of implementing this, but I chose to use a KeyedCollection for my internal storage. I also chose to implement various methods for sorting the way that List<T> does since this is essentially a hybrid IList and IDictionary. I've included my implementation here for posterity.
Here's the interface. Notice that it includes System.Collections.Specialized.IOrderedDictionary, which is the non-generic version of this interface that was provided by Microsoft.
// http://unlicense.org
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
namespace mattmc3.Common.Collections.Generic {
public interface IOrderedDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IOrderedDictionary {
new TValue this[int index] { get; set; }
new TValue this[TKey key] { get; set; }
new int Count { get; }
new ICollection<TKey> Keys { get; }
new ICollection<TValue> Values { get; }
new void Add(TKey key, TValue value);
new void Clear();
void Insert(int index, TKey key, TValue value);
int IndexOf(TKey key);
bool ContainsValue(TValue value);
bool ContainsValue(TValue value, IEqualityComparer<TValue> comparer);
new bool ContainsKey(TKey key);
new IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator();
new bool Remove(TKey key);
new void RemoveAt(int index);
new bool TryGetValue(TKey key, out TValue value);
TValue GetValue(TKey key);
void SetValue(TKey key, TValue value);
KeyValuePair<TKey, TValue> GetItem(int index);
void SetItem(int index, TValue value);
}
}
Here's the implementation along with helper classes:
// http://unlicense.org
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Linq;
namespace mattmc3.Common.Collections.Generic {
/// <summary>
/// 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.
/// </summary>
public class OrderedDictionary<TKey, TValue> : IOrderedDictionary<TKey, TValue> {
#region Fields/Properties
private KeyedCollection2<TKey, KeyValuePair<TKey, TValue>> _keyedCollection;
/// <summary>
/// Gets or sets the value associated with the specified key.
/// </summary>
/// <param name="key">The key associated with the value to get or set.</param>
public TValue this[TKey key] {
get {
return GetValue(key);
}
set {
SetValue(key, value);
}
}
/// <summary>
/// Gets or sets the value at the specified index.
/// </summary>
/// <param name="index">The index of the value to get or set.</param>
public TValue this[int index] {
get {
return GetItem(index).Value;
}
set {
SetItem(index, value);
}
}
public int Count {
get { return _keyedCollection.Count; }
}
public ICollection<TKey> Keys {
get {
return _keyedCollection.Select(x => x.Key).ToList();
}
}
public ICollection<TValue> Values {
get {
return _keyedCollection.Select(x => x.Value).ToList();
}
}
public IEqualityComparer<TKey> Comparer {
get;
private set;
}
#endregion
#region Constructors
public OrderedDictionary() {
Initialize();
}
public OrderedDictionary(IEqualityComparer<TKey> comparer) {
Initialize(comparer);
}
public OrderedDictionary(IOrderedDictionary<TKey, TValue> dictionary) {
Initialize();
foreach (KeyValuePair<TKey, TValue> pair in dictionary) {
_keyedCollection.Add(pair);
}
}
public OrderedDictionary(IOrderedDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer) {
Initialize(comparer);
foreach (KeyValuePair<TKey, TValue> pair in dictionary) {
_keyedCollection.Add(pair);
}
}
#endregion
#region Methods
private void Initialize(IEqualityComparer<TKey> comparer = null) {
this.Comparer = comparer;
if (comparer != null) {
_keyedCollection = new KeyedCollection2<TKey, KeyValuePair<TKey, TValue>>(x => x.Key, comparer);
}
else {
_keyedCollection = new KeyedCollection2<TKey, KeyValuePair<TKey, TValue>>(x => x.Key);
}
}
public void Add(TKey key, TValue value) {
_keyedCollection.Add(new KeyValuePair<TKey, TValue>(key, value));
}
public void Clear() {
_keyedCollection.Clear();
}
public void Insert(int index, TKey key, TValue value) {
_keyedCollection.Insert(index, new KeyValuePair<TKey, TValue>(key, value));
}
public int IndexOf(TKey key) {
if (_keyedCollection.Contains(key)) {
return _keyedCollection.IndexOf(_keyedCollection[key]);
}
else {
return -1;
}
}
public bool ContainsValue(TValue value) {
return this.Values.Contains(value);
}
public bool ContainsValue(TValue value, IEqualityComparer<TValue> comparer) {
return this.Values.Contains(value, comparer);
}
public bool ContainsKey(TKey key) {
return _keyedCollection.Contains(key);
}
public KeyValuePair<TKey, TValue> 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];
}
/// <summary>
/// Sets the value at the index specified.
/// </summary>
/// <param name="index">The index of the value desired</param>
/// <param name="value">The value to set</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when the index specified does not refer to a KeyValuePair in this object
/// </exception>
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: {0}".FormatWith(index));
}
var kvp = new KeyValuePair<TKey, TValue>(_keyedCollection[index].Key, value);
_keyedCollection[index] = kvp;
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
return _keyedCollection.GetEnumerator();
}
public bool Remove(TKey key) {
return _keyedCollection.Remove(key);
}
public void RemoveAt(int index) {
if (index < 0 || index >= _keyedCollection.Count) {
throw new ArgumentException(String.Format("The index was outside the bounds of the dictionary: {0}", index));
}
_keyedCollection.RemoveAt(index);
}
/// <summary>
/// Gets the value associated with the specified key.
/// </summary>
/// <param name="key">The key associated with the value to get.</param>
public TValue GetValue(TKey key) {
if (_keyedCollection.Contains(key) == false) {
throw new ArgumentException("The given key is not present in the dictionary: {0}".FormatWith(key));
}
var kvp = _keyedCollection[key];
return kvp.Value;
}
/// <summary>
/// Sets the value associated with the specified key.
/// </summary>
/// <param name="key">The key associated with the value to set.</param>
/// <param name="value">The the value to set.</param>
public void SetValue(TKey key, TValue value) {
var kvp = new KeyValuePair<TKey, TValue>(key, value);
var idx = IndexOf(key);
if (idx > -1) {
_keyedCollection[idx] = kvp;
}
else {
_keyedCollection.Add(kvp);
}
}
public bool TryGetValue(TKey key, out TValue value) {
if (_keyedCollection.Contains(key)) {
value = _keyedCollection[key].Value;
return true;
}
else {
value = default(TValue);
return false;
}
}
#endregion
#region sorting
public void SortKeys() {
_keyedCollection.SortByKeys();
}
public void SortKeys(IComparer<TKey> comparer) {
_keyedCollection.SortByKeys(comparer);
}
public void SortKeys(Comparison<TKey> comparison) {
_keyedCollection.SortByKeys(comparison);
}
public void SortValues() {
var comparer = Comparer<TValue>.Default;
SortValues(comparer);
}
public void SortValues(IComparer<TValue> comparer) {
_keyedCollection.Sort((x, y) => comparer.Compare(x.Value, y.Value));
}
public void SortValues(Comparison<TValue> comparison) {
_keyedCollection.Sort((x, y) => comparison(x.Value, y.Value));
}
#endregion
#region IDictionary<TKey, TValue>
void IDictionary<TKey, TValue>.Add(TKey key, TValue value) {
Add(key, value);
}
bool IDictionary<TKey, TValue>.ContainsKey(TKey key) {
return ContainsKey(key);
}
ICollection<TKey> IDictionary<TKey, TValue>.Keys {
get { return Keys; }
}
bool IDictionary<TKey, TValue>.Remove(TKey key) {
return Remove(key);
}
bool IDictionary<TKey, TValue>.TryGetValue(TKey key, out TValue value) {
return TryGetValue(key, out value);
}
ICollection<TValue> IDictionary<TKey, TValue>.Values {
get { return Values; }
}
TValue IDictionary<TKey, TValue>.this[TKey key] {
get {
return this[key];
}
set {
this[key] = value;
}
}
#endregion
#region ICollection<KeyValuePair<TKey, TValue>>
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) {
_keyedCollection.Add(item);
}
void ICollection<KeyValuePair<TKey, TValue>>.Clear() {
_keyedCollection.Clear();
}
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) {
return _keyedCollection.Contains(item);
}
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
_keyedCollection.CopyTo(array, arrayIndex);
}
int ICollection<KeyValuePair<TKey, TValue>>.Count {
get { return _keyedCollection.Count; }
}
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly {
get { return false; }
}
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) {
return _keyedCollection.Remove(item);
}
#endregion
#region IEnumerable<KeyValuePair<TKey, TValue>>
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() {
return GetEnumerator();
}
#endregion
#region IEnumerable
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}
#endregion
#region IOrderedDictionary
IDictionaryEnumerator IOrderedDictionary.GetEnumerator() {
return new DictionaryEnumerator<TKey, TValue>(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 {
return 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<TKey, TValue>(this);
}
bool IDictionary.IsFixedSize {
get { return false; }
}
bool IDictionary.IsReadOnly {
get { return false; }
}
ICollection IDictionary.Keys {
get { return (ICollection)this.Keys; }
}
void IDictionary.Remove(object key) {
Remove((TKey)key);
}
ICollection IDictionary.Values {
get { return (ICollection)this.Values; }
}
object IDictionary.this[object key] {
get {
return 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 {
get { return ((ICollection)_keyedCollection).Count; }
}
bool ICollection.IsSynchronized {
get { return ((ICollection)_keyedCollection).IsSynchronized; }
}
object ICollection.SyncRoot {
get { return ((ICollection)_keyedCollection).SyncRoot; }
}
#endregion
}
public class KeyedCollection2<TKey, TItem> : KeyedCollection<TKey, TItem> {
private const string DelegateNullExceptionMessage = "Delegate passed cannot be null";
private Func<TItem, TKey> _getKeyForItemDelegate;
public KeyedCollection2(Func<TItem, TKey> getKeyForItemDelegate)
: base() {
if (getKeyForItemDelegate == null) throw new ArgumentNullException(DelegateNullExceptionMessage);
_getKeyForItemDelegate = getKeyForItemDelegate;
}
public KeyedCollection2(Func<TItem, TKey> getKeyForItemDelegate, IEqualityComparer<TKey> comparer)
: base(comparer) {
if (getKeyForItemDelegate == null) throw new ArgumentNullException(DelegateNullExceptionMessage);
_getKeyForItemDelegate = getKeyForItemDelegate;
}
protected override TKey GetKeyForItem(TItem item) {
return _getKeyForItemDelegate(item);
}
public void SortByKeys() {
var comparer = Comparer<TKey>.Default;
SortByKeys(comparer);
}
public void SortByKeys(IComparer<TKey> keyComparer) {
var comparer = new Comparer2<TItem>((x, y) => keyComparer.Compare(GetKeyForItem(x), GetKeyForItem(y)));
Sort(comparer);
}
public void SortByKeys(Comparison<TKey> keyComparison) {
var comparer = new Comparer2<TItem>((x, y) => keyComparison(GetKeyForItem(x), GetKeyForItem(y)));
Sort(comparer);
}
public void Sort() {
var comparer = Comparer<TItem>.Default;
Sort(comparer);
}
public void Sort(Comparison<TItem> comparison) {
var newComparer = new Comparer2<TItem>((x, y) => comparison(x, y));
Sort(newComparer);
}
public void Sort(IComparer<TItem> comparer) {
List<TItem> list = base.Items as List<TItem>;
if (list != null) {
list.Sort(comparer);
}
}
}
public class Comparer2<T> : Comparer<T> {
//private readonly Func<T, T, int> _compareFunction;
private readonly Comparison<T> _compareFunction;
#region Constructors
public Comparer2(Comparison<T> comparison) {
if (comparison == null) throw new ArgumentNullException("comparison");
_compareFunction = comparison;
}
#endregion
public override int Compare(T arg1, T arg2) {
return _compareFunction(arg1, arg2);
}
}
public class DictionaryEnumerator<TKey, TValue> : IDictionaryEnumerator, IDisposable {
readonly IEnumerator<KeyValuePair<TKey, TValue>> impl;
public void Dispose() { impl.Dispose(); }
public DictionaryEnumerator(IDictionary<TKey, TValue> value) {
this.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 { get { return impl.Current.Key; } }
public object Value { get { return impl.Current.Value; } }
public object Current { get { return Entry; } }
}
}
And no implementation would be complete without a few tests (but tragically, SO won't let me post that much code in one post), so I'll have to leave you to write your tests. But, I left a few of them in so that you could get an idea of how it works:
// http://unlicense.org
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using mattmc3.Common.Collections.Generic;
namespace mattmc3.Tests.Common.Collections.Generic {
[TestClass]
public class OrderedDictionaryTests {
private OrderedDictionary<string, string> GetAlphabetDictionary(IEqualityComparer<string> comparer = null) {
OrderedDictionary<string, string> alphabet = (comparer == null ? new OrderedDictionary<string, string>() : new OrderedDictionary<string, string>(comparer));
for (var a = Convert.ToInt32('a'); a <= Convert.ToInt32('z'); a++) {
var c = Convert.ToChar(a);
alphabet.Add(c.ToString(), c.ToString().ToUpper());
}
Assert.AreEqual(26, alphabet.Count);
return alphabet;
}
private List<KeyValuePair<string, string>> GetAlphabetList() {
var alphabet = new List<KeyValuePair<string, string>>();
for (var a = Convert.ToInt32('a'); a <= Convert.ToInt32('z'); a++) {
var c = Convert.ToChar(a);
alphabet.Add(new KeyValuePair<string, string>(c.ToString(), c.ToString().ToUpper()));
}
Assert.AreEqual(26, alphabet.Count);
return alphabet;
}
[TestMethod]
public void TestAdd() {
var od = new OrderedDictionary<string, string>();
Assert.AreEqual(0, od.Count);
Assert.AreEqual(-1, od.IndexOf("foo"));
od.Add("foo", "bar");
Assert.AreEqual(1, od.Count);
Assert.AreEqual(0, od.IndexOf("foo"));
Assert.AreEqual(od[0], "bar");
Assert.AreEqual(od["foo"], "bar");
Assert.AreEqual(od.GetItem(0).Key, "foo");
Assert.AreEqual(od.GetItem(0).Value, "bar");
}
[TestMethod]
public void TestRemove() {
var od = new OrderedDictionary<string, string>();
od.Add("foo", "bar");
Assert.AreEqual(1, od.Count);
od.Remove("foo");
Assert.AreEqual(0, od.Count);
}
[TestMethod]
public void TestRemoveAt() {
var od = new OrderedDictionary<string, string>();
od.Add("foo", "bar");
Assert.AreEqual(1, od.Count);
od.RemoveAt(0);
Assert.AreEqual(0, od.Count);
}
[TestMethod]
public void TestClear() {
var od = GetAlphabetDictionary();
Assert.AreEqual(26, od.Count);
od.Clear();
Assert.AreEqual(0, od.Count);
}
[TestMethod]
public void TestOrderIsPreserved() {
var alphabetDict = GetAlphabetDictionary();
var alphabetList = GetAlphabetList();
Assert.AreEqual(26, alphabetDict.Count);
Assert.AreEqual(26, alphabetList.Count);
var keys = alphabetDict.Keys.ToList();
var values = alphabetDict.Values.ToList();
for (var i = 0; i < 26; i++) {
var dictItem = alphabetDict.GetItem(i);
var listItem = alphabetList[i];
var key = keys[i];
var value = values[i];
Assert.AreEqual(dictItem, listItem);
Assert.AreEqual(key, listItem.Key);
Assert.AreEqual(value, listItem.Value);
}
}
[TestMethod]
public void TestTryGetValue() {
var alphabetDict = GetAlphabetDictionary();
string result = null;
Assert.IsFalse(alphabetDict.TryGetValue("abc", out result));
Assert.IsNull(result);
Assert.IsTrue(alphabetDict.TryGetValue("z", out result));
Assert.AreEqual("Z", result);
}
[TestMethod]
public void TestEnumerator() {
var alphabetDict = GetAlphabetDictionary();
var keys = alphabetDict.Keys.ToList();
Assert.AreEqual(26, keys.Count);
var i = 0;
foreach (var kvp in alphabetDict) {
var value = alphabetDict[kvp.Key];
Assert.AreEqual(kvp.Value, value);
i++;
}
}
[TestMethod]
public void TestInvalidIndex() {
var alphabetDict = GetAlphabetDictionary();
try {
var notGonnaWork = alphabetDict[100];
Assert.IsTrue(false, "Exception should have thrown");
}
catch (Exception ex) {
Assert.IsTrue(ex.Message.Contains("index is outside the bounds"));
}
}
[TestMethod]
public void TestMissingKey() {
var alphabetDict = GetAlphabetDictionary();
try {
var notGonnaWork = alphabetDict["abc"];
Assert.IsTrue(false, "Exception should have thrown");
}
catch (Exception ex) {
Assert.IsTrue(ex.Message.Contains("key is not present"));
}
}
[TestMethod]
public void TestUpdateExistingValue() {
var alphabetDict = GetAlphabetDictionary();
Assert.IsTrue(alphabetDict.ContainsKey("c"));
Assert.AreEqual(2, alphabetDict.IndexOf("c"));
Assert.AreEqual(alphabetDict[2], "C");
alphabetDict[2] = "CCC";
Assert.IsTrue(alphabetDict.ContainsKey("c"));
Assert.AreEqual(2, alphabetDict.IndexOf("c"));
Assert.AreEqual(alphabetDict[2], "CCC");
}
[TestMethod]
public void TestInsertValue() {
var alphabetDict = GetAlphabetDictionary();
Assert.IsTrue(alphabetDict.ContainsKey("c"));
Assert.AreEqual(2, alphabetDict.IndexOf("c"));
Assert.AreEqual(alphabetDict[2], "C");
Assert.AreEqual(26, alphabetDict.Count);
Assert.IsFalse(alphabetDict.ContainsValue("ABC"));
alphabetDict.Insert(2, "abc", "ABC");
Assert.IsTrue(alphabetDict.ContainsKey("c"));
Assert.AreEqual(2, alphabetDict.IndexOf("abc"));
Assert.AreEqual(alphabetDict[2], "ABC");
Assert.AreEqual(27, alphabetDict.Count);
Assert.IsTrue(alphabetDict.ContainsValue("ABC"));
}
[TestMethod]
public void TestValueComparer() {
var alphabetDict = GetAlphabetDictionary();
Assert.IsFalse(alphabetDict.ContainsValue("a"));
Assert.IsTrue(alphabetDict.ContainsValue("a", StringComparer.OrdinalIgnoreCase));
}
[TestMethod]
public void TestSortByKeys() {
var alphabetDict = GetAlphabetDictionary();
var reverseAlphabetDict = GetAlphabetDictionary();
Comparison<string> stringReverse = ((x, y) => (String.Equals(x, y) ? 0 : String.Compare(x, y) >= 1 ? -1 : 1));
reverseAlphabetDict.SortKeys(stringReverse);
for (int j = 0, k = 25; j < alphabetDict.Count; j++, k--) {
var ascValue = alphabetDict.GetItem(j);
var dscValue = reverseAlphabetDict.GetItem(k);
Assert.AreEqual(ascValue.Key, dscValue.Key);
Assert.AreEqual(ascValue.Value, dscValue.Value);
}
}
-- UPDATE --
Source for this and other really useful missing core .NET libraries here: https://github.com/mattmc3/dotmore/blob/master/dotmore/Collections/Generic/OrderedDictionary.cs
You're right. There's no generic equivalent of OrderedDictionary in the framework itself.
(That's still the case for .NET 4 too, as far as I'm aware.)
For the record, there is a generic KeyedCollection that allows objects to be indexed by an int and a key. The key must be embedded in the value.
Here's a bizarre find: the System.Web.Util namespace in System.Web.Extensions.dll contains a generic OrderedDictionary<TKey,TValue>
// Type: System.Web.Util.OrderedDictionary`2
// Assembly: System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
// Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Web.Extensions.dll
namespace System.Web.Util
{
internal class OrderedDictionary<TKey, TValue> : IDictionary<TKey, TValue>, ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable
Not sure why MS placed it there instead of the System.Collections.Generic package, but I assume you can simply copy paste the code and use it (it's internal, so can't use it directly). Looks like the implementation uses a standard dictionary and separate Key/Value lists. Pretty straightforward...
Source code: https://referencesource.microsoft.com/#System.Web.Extensions/Util/OrderedDictionary.cs
A different implementation in System.Runtime.Collections that wraps the non-generic System.Collections.Specialized.OrderedDictionary: https://referencesource.microsoft.com/#System.ServiceModel.Internals/System/Runtime/Collections/OrderedDictionary.cs
For what it's worth, here is how I solved it:
public class PairList<TKey, TValue> : List<KeyValuePair<TKey, TValue>> {
Dictionary<TKey, int> itsIndex = new Dictionary<TKey, int>();
public void Add(TKey key, TValue value) {
Add(new KeyValuePair<TKey, TValue>(key, value));
itsIndex.Add(key, Count-1);
}
public TValue Get(TKey key) {
var idx = itsIndex[key];
return this[idx].Value;
}
}
It can be initialized like this:
var pairList = new PairList<string, string>
{
{ "pitcher", "Ken" },
{ "catcher", "Brad"},
{ "left fielder", "Stan"},
};
and accessed like this:
foreach (var pair in pairList)
{
Console.WriteLine("position: {0}, player: {1}",
pair.Key, pair.Value);
}
// Guaranteed to print in the order of initialization
A major conceptual problem with a generic version of OrderedDictionary is that users of a OrderedDictionary<TKey,TValue> would expect expect to be able to index it either numerically using an int, or by lookup using a TKey. When the only type of key was Object, as was the case with non-generic OrderedDictionary, the type of argument passed to the indexer would be sufficient to distinguish whether what type of indexing operation should be performed. As it is, though, it's unclear how the indexer of an OrderedDictionary<int, TValue> should behave.
If classes like Drawing.Point had recommended and followed a rule that piecewise-mutable structures should expose their mutable elements as fields rather than properties, and refrain from using property setters that modify this, then an OrderedDictionary<TKey,TValue> could efficiently expose a ByIndex property that returned an Indexer struct which held a reference to the dictionary, and had an indexed property whose getter and setter would call GetByIndex and SetByIndex upon it. Thus, one could say something like MyDict.ByIndex[5] += 3; to add 3 to the sixth element of the dictionary.
Unfortunately, for the compiler to accept such a thing, it would be necessary to make the ByIndex property return a new class instance rather than a struct every time it's invoked, eliminating the advantages one would get by avoiding boxing.
In VB.NET, one could get around that issue by using a named indexed property (so MyDict.ByIndex[int] would be a member of MyDict, rather than requiring MyDict.ByIndex to be a member of MyDict which includes an indexer), but C# doesn't allow such things.
It might still have been worthwhile to offer an OrderedDictionary<TKey,TValue> where TKey:class, but much of the reason for providing generics in the first place was to allow their use with value types.
For a lot of purposes I've found one can get by with a List<KeyValuePair<K, V>>. (Not if you need it to extend Dictionary, obviously, and not if you need better than O(n) key-value lookup.)
Right, it's an unfortunate omission. I miss Python's OrderedDict
A dictionary that remembers the order that keys were first inserted. If a new entry overwrites an existing entry, the original insertion position is left unchanged. Deleting an entry and reinserting it will move it to the end.
So I wrote my own OrderedDictionary<K,V> class in C#. How does it work? It maintains two collections - a vanilla unordered dictionary and an ordered list of keys. With this solution, the standard dictionary operations keep their fast complexities, and look up by index is fast too.
https://gist.github.com/hickford/5137384
Here's the interface
/// <summary>
/// A dictionary that remembers the order that keys were first inserted. If a new entry overwrites an existing entry, the original insertion position is left unchanged. Deleting an entry and reinserting it will move it to the end.
/// </summary>
/// <typeparam name="TKey">The type of keys</typeparam>
/// <typeparam name="TValue">The type of values</typeparam>
public interface IOrderedDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
/// <summary>
/// The value of the element at the given index.
/// </summary>
TValue this[int index] { get; set; }
/// <summary>
/// Find the position of an element by key. Returns -1 if the dictionary does not contain an element with the given key.
/// </summary>
int IndexOf(TKey key);
/// <summary>
/// Insert an element at the given index.
/// </summary>
void Insert(int index, TKey key, TValue value);
/// <summary>
/// Remove the element at the given index.
/// </summary>
void RemoveAt(int index);
}
For those looking for an "official" package option in NuGet, an implementation of a generic OrderedDictionary has been accepted into .NET CoreFX Lab. If all goes well, the type will eventually be approved and integrated to the main .NET CoreFX repo.
There is a possibility that this implementation will be rejected.
The committed implementation can be referenced here
https://github.com/dotnet/corefxlab/blob/57be99a176421992e29009701a99a370983329a6/src/Microsoft.Experimental.Collections/Microsoft/Collections/Extensions/OrderedDictionary.cs
The NuGet package that definitively has this type available for use can be found here
https://www.nuget.org/packages/Microsoft.Experimental.Collections/1.0.6-e190117-3
Or you can install the package within Visual Studio. Browse for the package "Microsoft.Experimental.Collections" and make sure the "Include prerelease" checkbox is selected.
Will update this post if and when the type is made officially available.
There is SortedDictionary<TKey, TValue>. Although semantically close, I am not claiming it's the same as OrderedDictionary simply because they are not. Even from performance characteristics. However the very interesting and quite important difference between Dictionary<TKey, TValue> (and to that extent OrderedDictionary and implementations provided in answers) and SortedDictionary is that the latter is using binary tree underneath. This is critical distinction because it makes the class immune to memory constraints applied to generic class. See this thread about OutOfMemoryExceptions thrown when generic class is used for handling large set of key-value pairs.
How to figure out the max value for capacity parameter passed to Dictionary constructor to avoid OutOfMemoryException?
As a follow up to the comment from #V.B. here's an accessible implementation of the System.Runtime.Collections.OrderedDictionary<,>. I was originally going to access it by reflection and provide it via a factory but the dll this is in does not seem to be very accessible at all so I just pulled the source itself.
One thing to note is the indexer here will not throw KeyNotFoundException. I absolutely hate that convention and that was the 1 liberty i took in this implementation. If that's important to you, just replace the line for return default(TValue);. Uses C# 6 (compatible with Visual Studio 2013)
/// <summary>
/// System.Collections.Specialized.OrderedDictionary is NOT generic.
/// This class is essentially a generic wrapper for OrderedDictionary.
/// </summary>
/// <remarks>
/// Indexer here will NOT throw KeyNotFoundException
/// </remarks>
public class OrderedDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary
{
private readonly OrderedDictionary _privateDictionary;
public OrderedDictionary()
{
_privateDictionary = new OrderedDictionary();
}
public OrderedDictionary(IDictionary<TKey, TValue> dictionary)
{
if (dictionary == null) return;
_privateDictionary = new OrderedDictionary();
foreach (var pair in dictionary)
{
_privateDictionary.Add(pair.Key, pair.Value);
}
}
public bool IsReadOnly => false;
public int Count => _privateDictionary.Count;
int ICollection.Count => _privateDictionary.Count;
object ICollection.SyncRoot => ((ICollection)_privateDictionary).SyncRoot;
bool ICollection.IsSynchronized => ((ICollection)_privateDictionary).IsSynchronized;
bool IDictionary.IsFixedSize => ((IDictionary)_privateDictionary).IsFixedSize;
bool IDictionary.IsReadOnly => _privateDictionary.IsReadOnly;
ICollection IDictionary.Keys => _privateDictionary.Keys;
ICollection IDictionary.Values => _privateDictionary.Values;
void IDictionary.Add(object key, object value)
{
_privateDictionary.Add(key, value);
}
void IDictionary.Clear()
{
_privateDictionary.Clear();
}
bool IDictionary.Contains(object key)
{
return _privateDictionary.Contains(key);
}
IDictionaryEnumerator IDictionary.GetEnumerator()
{
return _privateDictionary.GetEnumerator();
}
void IDictionary.Remove(object key)
{
_privateDictionary.Remove(key);
}
object IDictionary.this[object key]
{
get { return _privateDictionary[key]; }
set { _privateDictionary[key] = value; }
}
void ICollection.CopyTo(Array array, int index)
{
_privateDictionary.CopyTo(array, index);
}
public TValue this[TKey key]
{
get
{
if (key == null) throw new ArgumentNullException(nameof(key));
if (_privateDictionary.Contains(key))
{
return (TValue) _privateDictionary[key];
}
return default(TValue);
}
set
{
if (key == null) throw new ArgumentNullException(nameof(key));
_privateDictionary[key] = value;
}
}
public ICollection<TKey> Keys
{
get
{
var keys = new List<TKey>(_privateDictionary.Count);
keys.AddRange(_privateDictionary.Keys.Cast<TKey>());
return keys.AsReadOnly();
}
}
public ICollection<TValue> Values
{
get
{
var values = new List<TValue>(_privateDictionary.Count);
values.AddRange(_privateDictionary.Values.Cast<TValue>());
return values.AsReadOnly();
}
}
public void Add(KeyValuePair<TKey, TValue> item)
{
Add(item.Key, item.Value);
}
public void Add(TKey key, TValue value)
{
if (key == null) throw new ArgumentNullException(nameof(key));
_privateDictionary.Add(key, value);
}
public void Clear()
{
_privateDictionary.Clear();
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
if (item.Key == null || !_privateDictionary.Contains(item.Key))
{
return false;
}
return _privateDictionary[item.Key].Equals(item.Value);
}
public bool ContainsKey(TKey key)
{
if (key == null) throw new ArgumentNullException(nameof(key));
return _privateDictionary.Contains(key);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
if (array == null) throw new ArgumentNullException(nameof(array));
if (arrayIndex < 0) throw new ArgumentOutOfRangeException(nameof(arrayIndex));
if (array.Rank > 1 || arrayIndex >= array.Length
|| array.Length - arrayIndex < _privateDictionary.Count)
throw new ArgumentException("Bad Copy ToArray", nameof(array));
var index = arrayIndex;
foreach (DictionaryEntry entry in _privateDictionary)
{
array[index] =
new KeyValuePair<TKey, TValue>((TKey) entry.Key, (TValue) entry.Value);
index++;
}
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
foreach (DictionaryEntry entry in _privateDictionary)
{
yield return
new KeyValuePair<TKey, TValue>((TKey) entry.Key, (TValue) entry.Value);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
if (false == Contains(item)) return false;
_privateDictionary.Remove(item.Key);
return true;
}
public bool Remove(TKey key)
{
if (key == null) throw new ArgumentNullException(nameof(key));
if (false == _privateDictionary.Contains(key)) return false;
_privateDictionary.Remove(key);
return true;
}
public bool TryGetValue(TKey key, out TValue value)
{
if (key == null) throw new ArgumentNullException(nameof(key));
var keyExists = _privateDictionary.Contains(key);
value = keyExists ? (TValue) _privateDictionary[key] : default(TValue);
return keyExists;
}
}
Pull requests/discussion accepted on GitHub
I implemented a generic OrderedDictionary<TKey, TValue> by wraping around SortedList<TKey, TValue> and adding a private Dictionary<TKey, int> _order. Then I created an internal implementation of Comparer<TKey>, passing a reference to the _order dictionary. Then I use this comparer for the internal SortedList. This class keeps the order of elements passed to the constructor and order of additions.
This implementation has almost the same big O characteristics as SortedList<TKey, TValue> since adding and removing to _order is O(1). Each element will take (according to the book 'C# 4 in a Nutshell', p. 292, table 7-1) additional memory space of 22 (overhead) + 4 (int order) + TKey size (let's assume 8) = 34. Together with SortedList<TKey, TValue>'s overhead of two bytes, the total overhead is 36 bytes, while the same book says that non-generic OrderedDictionary has an overhead of 59 bytes.
If I pass sorted=true to constructor, then _order is not used at all, the OrderedDictionary<TKey, TValue> is exactly SortedList<TKey, TValue> with minor overhead for wrapping, if at all meaningful.
I am going to store not-so-many large reference objects in the OrderedDictionary<TKey, TValue>, so for me this ca. 36 bytes overhead is tolerable.
The main code is below. The complete updated code is on this gist.
public class OrderedList<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary
{
private readonly Dictionary<TKey, int> _order;
private readonly SortedList<TKey, TValue> _internalList;
private readonly bool _sorted;
private readonly OrderComparer _comparer;
public OrderedList(IDictionary<TKey, TValue> dictionary, bool sorted = false)
{
_sorted = sorted;
if (dictionary == null)
dictionary = new Dictionary<TKey, TValue>();
if (_sorted)
{
_internalList = new SortedList<TKey, TValue>(dictionary);
}
else
{
_order = new Dictionary<TKey, int>();
_comparer = new OrderComparer(ref _order);
_internalList = new SortedList<TKey, TValue>(_comparer);
// Keep order of the IDictionary
foreach (var kvp in dictionary)
{
Add(kvp);
}
}
}
public OrderedList(bool sorted = false)
: this(null, sorted)
{
}
private class OrderComparer : Comparer<TKey>
{
public Dictionary<TKey, int> Order { get; set; }
public OrderComparer(ref Dictionary<TKey, int> order)
{
Order = order;
}
public override int Compare(TKey x, TKey y)
{
var xo = Order[x];
var yo = Order[y];
return xo.CompareTo(yo);
}
}
private void ReOrder()
{
var i = 0;
_order = _order.OrderBy(kvp => kvp.Value).ToDictionary(kvp => kvp.Key, kvp => i++);
_comparer.Order = _order;
_lastOrder = _order.Values.Max() + 1;
}
public void Add(TKey key, TValue value)
{
if (!_sorted)
{
_order.Add(key, _lastOrder);
_lastOrder++;
// Very rare event
if (_lastOrder == int.MaxValue)
ReOrder();
}
_internalList.Add(key, value);
}
public bool Remove(TKey key)
{
var result = _internalList.Remove(key);
if (!_sorted)
_order.Remove(key);
return result;
}
// Other IDictionary<> + IDictionary members implementation wrapping around _internalList
// ...
}
This is not yet another version/solution of an OrderedDictionary<,> but an experiment I did testing each of 4 versions mentioned in the answers: of #Colonel Panic, #mattmc3, #V.B. #Chris Marisic. It is meant as a feedback. Well, partial because I have to admit I haven't dissected the code, so there may be differences in functionality or safety checks. But still, I thought feedback would be useful on their performance. And as you'll see time can get from a couple of milliseconds to a quarter of hour.
Then I scribbled a naive minimal version with 2 lists of key and value class objects with O(n) search just to see the magnitude of the benefit of O(1) access.
Testbed is Microsoft Visual Studio Community 2019 with Unity 3D, 4 consecutive times for each test and the code that I wanted to replicate a real-ish scenario in is
using System.Text;
using UnityEngine;
public class TessyOne : MonoBehaviour
{
public const int iterations = 50000;
private System.Diagnostics.Stopwatch stopwatch;
private System.Random random;
public float stopwatchDuration;
public class Ala
{
public int inta;
public float fla;
public string stra;
public Ben bena;
public Ala(int i, float f, string s, Ben b)
{
inta = i; fla = f; stra = s; bena = b;
}
}
public class Ben
{
public int inte;
public float fle;
public string stre;
public Ben(int i, float f, string s)
{
inte = i; fle = f; stre = s;
}
}
//public Naive.OrderedDictionary<Ala, Ben> alasToBens = new Naive.OrderedDictionary<Ala, Ben>();
//public Hickford.OrderedDictionary<Ala, Ben> alasToBens = new Hickford.OrderedDictionary<Ala, Ben>();
//public Mattmc3.OrderedDictionary<Ala, Ben> alasToBens = new Mattmc3.OrderedDictionary<Ala, Ben>();
public Marisic.OrderedDictionary<Ala, Ben> alasToBens = new Marisic.OrderedDictionary<Ala, Ben>();
//public VB.OrderedList<Ala, Ben> alasToBens = new VB.OrderedList<Ala, Ben>(null, false);
Ala[] alarray = new Ala[iterations];
Ben[] berray = new Ben[iterations];
// This is the entry point of the application
private void Start()
{
stopwatch = new System.Diagnostics.Stopwatch();
random = new System.Random(2020);
for(int i = 0; i < iterations; ++i)
{
berray[i] = new Ben(random.Next(),
(float)random.NextDouble(),
MakeRandomString((ushort)random.Next(1, 10)));
alarray[i] = new Ala(random.Next(),
(float)random.NextDouble(),
MakeRandomString((ushort)random.Next(1, 10)),
berray[i]);
// uncomment for testing ContainsKey() and Remove(), comment for Add()
alasToBens.Add(alarray[i], berray[i]);
}
stopwatch.Start();
for(int i = iterations - 1; i > -1; --i)
{
//alasToBens.Add(alarray[i], berray[i]);
//alasToBens.ContainsKey(alarray[i]);
alasToBens.Remove(alarray[i]);
}
stopwatch.Stop();
stopwatchDuration = stopwatch.ElapsedMilliseconds;
}
public string MakeRandomString(ushort length)
{
StringBuilder sb = new StringBuilder();
for(ushort u = 0; u < length; ++u)
{
sb.Append((char)Random.Range(33, 126)); // regular ASCII chars
}
return sb.ToString();
}
}
Note that the tests are for worst case scenarios in the case of naive version at least, as it iterates through the collection from index 0 through iterations and searching is done from end to start. I measured Add(), ContainsKey() and Remove() in milliseconds for a dictionary of 50000 entries.
Results:
+----------+----------------+----------------+--------------------------------+
| ms | Add() | ContainsKey() | Remove() |
+----------+----------------+----------------+--------------------------------+
| Hickford | 7, 8, 7, 8 | 2, 2, 3, 2 | 7400, 7503, 7419, 7421 |
| Mattmc3 | 23, 24, 24, 23 | 3, 3, 3, 3 | 890404, 913465, 875387, 877792 |
| Marisic | 27, 28, 28, 27 | 4, 4, 4, 4 | 27401, 27627, 27341, 27349 |
| V.B. | 76, 76, 75, 75 | 59, 60, 60, 60 | 66, 67, 67, 67 |
| | | | |
| Naive | 19651, 19761 | 25335, 25416 | 25259, 25306 |
+----------+----------------+----------------+--------------------------------+
Related
How to evaluate custom hash function?
I have a Dictionary with a custom hashing function. I want to test the hash function, because even though it returns different hash results for my test values, some of them may still map to the same bucket due to the modulo % operation. So how to check if there are collisions in C# Dictionary with custom hash function and improve that function? This is a development test to fine-tune the hash function and won't go into production so no worries about the changes in internal implementation in other versions!!! In C++ it's possible to get the map's bucket size to check the collision status but I couldn't find a way to do that in C#. How can I know if Dictionary has been collided?
You can get internal buckets in the following way: var dictionary = new Dictionary<string, int>(); dictionary.Add("a", 8); dictionary.Add("b", 1); var buckets = dictionary.GetType().GetField("_buckets", BindingFlags.NonPublic | BindingFlags.Instance) .GetValue(dictionary); // use "buckets" for 4.x
You're probably better off creating a custom Dictionary implementation that changes the Add and Remove methods to check for hash collisions based on the computer GetHashCode of the elements. You can compose with a "real" Dictionary internally to do the real work of storing the elements. Here's a sample version. You could optimize the Add and Remove methods depending on the type of hashes your expecting. public class CollisionDetectingDictionary<TKey, TValue> : IDictionary<TKey, TValue> { private readonly Dictionary<TKey, TValue> InternalDictionary = new Dictionary<TKey, TValue>(); private readonly List<int> HashCodesInDictionary = new List<int>(); public event Action<int, TKey, IEnumerable<TKey>> HashCollision; public TValue this[TKey key] { get => InternalDictionary[key]; set => InternalDictionary[key] = value; } public ICollection<TKey> Keys => InternalDictionary.Keys; public ICollection<TValue> Values => InternalDictionary.Values; public int Count => InternalDictionary.Count; public bool IsReadOnly => false; public void Add(TKey key, TValue value) { Add(new KeyValuePair<TKey, TValue>(key, value)); } public void Add(KeyValuePair<TKey, TValue> item) { var hashCode = item.Key.GetHashCode(); if (HashCodesInDictionary.Contains(hashCode)) { var collisions = GetKeysByHashCode(hashCode); HashCollision?.Invoke(hashCode, item.Key, collisions); } Add(item); } private IEnumerable<TKey> GetKeysByHashCode(int hashCode) { foreach (var key in Keys) { if(key.GetHashCode() == hashCode) { yield return key; } } } public void Clear() { InternalDictionary.Clear(); } public bool Contains(KeyValuePair<TKey, TValue> item) { return InternalDictionary.Contains(item); } public bool ContainsKey(TKey key) { return InternalDictionary.ContainsKey(key); } public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) { ((IDictionary<TKey,TValue>)InternalDictionary).CopyTo(array, arrayIndex); } public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() { return InternalDictionary.GetEnumerator(); } public bool Remove(TKey key) { var hashCode = key.GetHashCode(); if(GetKeysByHashCode(hashCode).Count() == 1) { HashCodesInDictionary.Remove(hashCode); } return InternalDictionary.Remove(key); } public bool Remove(KeyValuePair<TKey, TValue> item) { return Remove(item.Key); } public bool TryGetValue(TKey key, out TValue value) { return InternalDictionary.TryGetValue(key, out value); } IEnumerator IEnumerable.GetEnumerator() { return InternalDictionary.GetEnumerator(); } }
Automatic dictionary key?
I kept googling for some time, and I found that the best way that enables you to have a list containing variables with a corresponding unique key is a HashTable or a Dictionary, but I didn't find anything that enables you to have automatic keys(of type integer). I want to call a function that adds an object(passed as a parameter) to the dictionary and returns the automatically generated key(int), and without any key duplicates. How could I accomplish this? I am completely struggling! EDIT: To clarify things up. This is a server, and I want to assign a unique key for each client. If I use the maximum key value, this value will soon get to the int maximum value on large servers. Because if a client connects then disconnects he leaves behind an unused value which should be reused in order to avoid reaching a very high key maximum value.
The following should do and it reuses freed up keys: internal class AutoKeyDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable { private readonly Dictionary<TKey, TValue> inner; private readonly Func<TKey, TKey> incrementor; private readonly Stack<TKey> freeKeys; private readonly TKey keySeed; private TKey currentKey; public AutoKeyDictionary(TKey keySeed, Func<TKey, TKey> incrementor) { if (keySeed == null) throw new ArgumentNullException("keySeed"); if (incrementor == null) throw new ArgumentNullException("incrementor"); inner = new Dictionary<TKey, TValue>(); freeKeys = new Stack<TKey>(); currentKey = keySeed; } public TKey Add(TValue value) //returns the used key { TKey usedKey; if (freeKeys.Count > 0) { usedKey = freeKeys.Pop(); inner.Add(usedKey, value); } else { usedKey = currentKey; inner.Add(usedKey, value); currentKey = incrementor(currentKey); } return usedKey; } public void Clear() { inner.Clear(); freeKeys.Clear(); currentKey = keySeed; } public bool Remove(TKey key) { if (inner.Remove(key)) { if (inner.Count > 0) { freeKeys.Push(key); } else { freeKeys.Clear(); currentKey = keySeed; } return true; } return false; } public bool TryGetValue(TKey key, out TValue value) { return inner.TryGetValue(key, out value); } public TValue this[TKey key] { get {return inner[key];} set{inner[key] = value;} } public bool ContainsKey(TKey key) { return inner.ContainsKey(key); } public bool ContainsValue(TValue value) { return inner.ContainsValue (value); } public int Count { get{ return inner.Count; } } public Dictionary<TKey,TValue>.KeyCollection Keys { get { return inner.Keys; } } public Dictionary<TKey, TValue>.ValueCollection Values { get { return inner.Values; } } public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() { return inner.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)inner).GetEnumerator(); } } Disclaimer: I haven't tested this code, it could have a few pesty bugs of little importance, the general approach is sound.
Write a class which does this. Something like this: class AutoIndexDictionary : IEnumerable<Whatever> { private readonly Dictionary<int, Whatever> myDict = new Dictionary<int, Whatever>(); private int currentIndex = 0; public int Add(Whatever item) { var myIndex = currentIndex myDict.Add(myIndex, item); currentIndex ++; return myIndex; } public void Remove(int index) { myDict.Remove(index); } // implement IEnumerable, indexer etc. // ... }
Create a method that gets the max key value from the dictionary using LINQ, adds 1 to it and then uses that as the key for the value you would like to add, like this: public void AddToMyDictionary(string value) { int NextKey = MyDictionary.Keys.Max() + 1; MyDictionary.Add(NextKey, value); } Obviously, this assumes your dictionary is a Dictionary<int, string>, but you can obviously modify for your purposes. If you want to re-use keys that have been removed, store the next index when something is added / removed. private int NextKey = 0; public int AddToMyDictionary(string value) { int currentKey = NextKey; MyDictionary.Add(currentKey, value); NextKey = MyDictionary.Keys.Max() + 1; return currentKey; } public void RemoveFromMyDictionary(int key) { MyDictionary.Remove(key); NextKey = key; }
This is what int Object.GetHashCode() is for.
Wouldn't a List do what you say, without any additional overhead? You call it a "unique integer key", but in List terminology, that's simply called an "index". If you really wanted a custom function to add a value and get a key all in one step, you could inherit from List<T>, like so: class MyCustomList<T> : List<T> { //Not thread-safe public int AddAndGetKey(T valueToAdd) { Add(valueToAdd); return LastIndexOf(valueToAdd); } } I use LastIndexOf() because the list may include duplicate values and adding to the list always adds to the end. So this should work unless you get into multithreaded situations where you'd have to add-and-get-index in one atomic operation. (Alternately maybe you could add an extension method to List<T>.) The advantage of using a List is that there would be no gaps in keys. On the flipside, removing an item in the middle would change the key of every item after it. But I guess it depends what behavior you're looking for.
Given the additional information provided in your edit then i don't think int is the correct datatype for you, you shouldn't reuse ID's the way you are describing as if a client with an ID gets disconnected but don't realise then you could have 1 ID in use by 2 clients. change your datatype to Guid then when you get a new client give it a key of Guid.NewGuid() and the chance of duplicate keys drops as close as possible to 0
I like Stefan Steinegger's solution. Here is an alternative that uses a List<> behind the scenes, but ensures the List<> is never removed from: class AutoKeyDictionary<TValue> : IEnumerable<TValue> where TValue : class { readonly List<TValue> list = new List<TValue>(); public int Add(TValue val) { if (val == null) throw new ArgumentNullException(nameof(val), "This collection will not allow null values."); list.Add(val); return list.Count - 1; } public void RemoveAt(int key) { // do not remove ('list.Count' must never decrease), overwrite with null // (consider throwing if key was already removed) list[key] = null; } public TValue this[int key] { get { var val = list[key]; if (val == null) throw new ArgumentOutOfRangeException(nameof(key), "The value with that key is no longer in this collection."); return val; } } public int NextKey => list.Count; public int Count => list.Count(v => v != null); // expensive O(n), Linq public bool ContainsKey(int key) => key >= 0 && key < list.Count && list[key] != null; public TValue TryGetValue(int key) => (key >= 0 && key < list.Count) ? list[key] : null; public void Clear() { for (var i = 0; i < list.Count; ++i) list[i] = null; } public IEnumerator<TValue> GetEnumerator() => list.Where(v => v != null).GetEnumerator(); // Linq IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public int FirstKeyOf(TValue val) => list.IndexOf(val); public IDictionary<int, TValue> ToDictionary() { var retColl = new SortedList<int, TValue>(list.Count); for (var i = 0; i < list.Count; ++i) { var val = list[i]; if (val != null) retColl.Add(i, val); } return retColl; } // and so on... } Not thread-safe, obviously. Be aware, the same value can be present several times in the collection, but with different keys.
Using JSON.NET with OrderedDictionary
I have an OrderedDictionary with int keys and System.Drawing.Rectangle values. JSON.NET won't serialize the OrderedDictionary...it returns an empty object. I wrote a custom converter, but I wondered if there was an easier way. Thinking that JSON.NET might use the presence of a typed enumerator as the trigger to use its built-in code for serializing and deserializing a Dictionary<TKey, TValue> I tried this: class Program { static void Main(string[] args) { var test = new OrderedDictionary<int, Rectangle>(); test.Add(1, new Rectangle(0, 0, 50, 50)); test.Add(42, new Rectangle(1, 1, 1, 1)); string s = JsonConvert.SerializeObject(test); var deserialized = JsonConvert.DeserializeObject<OrderedDictionary<int, Rectangle>>(s); var someRect = deserialized[(object)1]; // someRect is null var someOtherRect = (Rectangle)deserialized["1"]; // InvalidCastException } } public class OrderedDictionary<TKey, TValue> : OrderedDictionary, IEnumerable<KeyValuePair<TKey, TValue>> { IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() { foreach (TKey key in Keys) { yield return new KeyValuePair<TKey, TValue>(key, (TValue)this[key]); } } } Serialization works perfectly. However, when I deserialize, the keys in the dictionary become strings and the Rectangles are JObjects that can't be cast to Rectangle. Is there something I can add to my OrderedDictionary<> class that will allow for proper deserialization with JSON.NET? Thanks.
Your problem is that, although you've added an enumerator, things like the indexer cannot be overridden. So what you're getting is the default implementation of the non-generic OrderedDictionary which doesn't give you a typed result. So, instead of inheriting, you need a facade that fully implements the generic interface. You'll need to verify my class (I just made the test work). I've also cheated with the Keys and Values properties (they're not often used) and some of the other ICollection methods. Just lazy :) using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Drawing; using Newtonsoft.Json; using Xunit; namespace XUnitTestProject1 { public class UnitTest1 { [Fact] public void TestJsonRectange() { var test = new OrderedDictionary<int, Rectangle>(); test.Add(1, new Rectangle(0, 0, 50, 50)); test.Add(42, new Rectangle(1, 1, 1, 1)); string json = JsonConvert.SerializeObject(test); var deserialized = JsonConvert.DeserializeObject<OrderedDictionary<int, Rectangle>>(json); object someRect = deserialized[1]; Assert.NotNull(someRect); Assert.True(someRect is Rectangle); } [Fact] public void TestJsonString() { var test = new OrderedDictionary<string, string>(); test.Add("1", "11"); test.Add("42", "4242"); string json = JsonConvert.SerializeObject(test); var deserialized = JsonConvert.DeserializeObject<OrderedDictionary<string, string>>(json); object something = deserialized["1"]; Assert.NotNull(something); Assert.True(something is string); } public class OrderedDictionary<TKey, TValue> : IDictionary<TKey, TValue> { private readonly OrderedDictionary dic = new OrderedDictionary(); public TValue this[TKey key] { get { return (TValue)dic[key]; } set { dic[key] = value; } } public void Add(KeyValuePair<TKey, TValue> item) { dic.Add(item.Key, item.Value); } public void Add(TKey key, TValue value) { dic.Add(key, value); } public void Clear() { dic.Clear(); } public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) { } public int Count { get { return dic.Count; } } public bool IsReadOnly { get { return false; } } public bool Contains(TKey key) { return dic.Contains(key); } public bool ContainsKey(TKey key) { return dic.Contains(key); } public bool Remove(TKey key) { dic.Remove(key); return true; } public bool TryGetValue(TKey key, out TValue value) { value = default(TValue); return false; } bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) { throw new NotImplementedException(); } bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) { return false; } public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() { foreach (DictionaryEntry entry in dic) yield return new KeyValuePair<TKey, TValue>((TKey)entry.Key, (TValue)entry.Value); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } private static readonly TKey[] keys = new TKey[0]; private static readonly TValue[] values = new TValue[0]; ICollection<TKey> IDictionary<TKey, TValue>.Keys { get { return keys; } } ICollection<TValue> IDictionary<TKey, TValue>.Values { get { return values; } } } } }
.NET dictionary with two keys and one value
Is there a dictionary available in .NET that could hold 2 keys and one value. Like Dictionary(Of TKey, Of TKey, TValue) I have a need to store two keys and at certain times look an item by the key 1 and at other times by the key 2. My current solution is to maintain two dictionaries Dictionary<string, long> Dict1 = new Dictionary<string, long>(); Dictionary<long, long> Dict2 = new Dictionary<long, long>(); and when need to add item I will add it to both dictionaries. Dict1.Add("abc", 111); Dict2.Add(345, 111); and then I will look up an item from either one of those dictionaries depending by which one of the keys I need to look by. Same I will do when deleting or updating an item. I have thought about the composite key but I don't know how to set it up and I don't want to lose any speed of searching the item. Is there some solution available in .NET to have dictionary that can hold multiple keys?
As you wish your value to be “findable” from either key, I would just use two dictionaries like you are doing now. However I would wrap this up in a class, with methods names like FindByXXX and FindByYYY. The much harder question is how do you do a delete, as you need to know both keys at the time of the delete. Maybe your value stores both keys so you can pass the value into your delete method. Maybe you never need to remove items from the dictionaries. Or the code that needs to remove items knows both keys. Hence there is no standard dictionary to do this, as the requirements are different between each user. (Note you don’t want a dictionary with a composite key, as that would require you to know both keys whenever you wished to look up an item.)
Maybe, something like this: public class TwoKeyDictionary<Tkey1, Tkey2, TValue> { private object m_data_lock = new object(); private Dictionary<Tkey1, Tkey2> m_dic1 = new Dictionary<Tkey1, Tkey2>(); private Dictionary<Tkey2, TValue> m_dic2 = new Dictionary<Tkey2, TValue>(); public void AddValue(Tkey1 key1, Tkey2 key2, TValue value) { lock(m_data_lock) { m_dic1[key1] = key2; m_dic2[key2] = value; } } public TValue getByKey1(Tkey1 key1) { lock(m_data_lock) return m_dic2[m_dic1[key1]]; } public TValue getByKey2(Tkey key2) { lock(m_data_lock) return m_dic2[key2]; } public void removeByKey1(Tkey1 key1) { lock(m_data_lock) { Tkey2 tmp_key2 = m_dic1[key1]; m_dic1.Remove(key1); m_dic2.Remove(tmp_key2); } } public void removeByKey2(Tkey2 key2) { lock(m_data_lock) { Tkey1 tmp_key1 = m_dic1.First((kvp) => kvp.Value.Equals(key2)).Key; m_dic1.Remove(tmp_key1); m_dic2.Remove(key2); } } } I can offer a second solution, but it seems more slow and ugly vs. the first. public class TwoKeysDictionary<K1, K2, V> { private class TwoKeysValue<K1, K2, V> { public K1 Key1 { get; set; } public K2 Key2 { get; set; } public V Value { get; set; } } private List<TwoKeysValue<K1, K2, V>> m_list = new List<TwoKeysValue<K1, K2, V>>(); public void Add(K1 key1, K2 key2, V value) { lock (m_list) m_list.Add(new TwoKeysValue<K1, K2, V>() { Key1 = key1, Key2 = key2, Value = value }); } public V getByKey1(K1 key1) { lock (m_list) return m_list.First((tkv) => tkv.Key1.Equals(key1)).Value; } public V getByKey2(K2 key2) { lock (m_list) return m_list.First((tkv) => tkv.Key2.Equals(key2)).Value; } public void removeByKey1(K1 key1) { lock (m_list) m_list.Remove(m_list.First((tkv) => tkv.Key1.Equals(key1))); } public void removeByKey2(K2 key2) { lock (m_list) m_list.Remove(m_list.First((tkv) => tkv.Key2.Equals(key2))); } } In very bad case, when Keys are a big structures (i.e. big value-types) and Keys are equals by size, and values are small value-types (for instance, a byte), with first solution you had: one set of Key1 , two sets of Key2, one set of values = 3 sets of big objects and 1 set of small values. With second solution you had: one set of Key1 , one set of Key2, one set of values = 2 sets of big objects and small set with values. I.e. with using of first solution you need by 50% (or by lower) more memory space vs. second, but a second solution is a very, very slow vs. first.
Your solution has a big impact on the memory footprint of your application. As the dictionary grows it will take at least double the amount memory (for value types) required to store the actual data. You could probably approach this from a different angle. Have two dictionaries : var lookupDictionary = new Dictionary<string, string>(); var valuesDictionary = new Dictionary<string, [YourValueType]>(); From here on in its pretty simple. // Add a new entry into the values dictionary and give it a unique key valuesDictionary.Add("FooBar", "FUBAR VALUE"); // Add any number of lookup keys with the same value key lookupDictionary.Add("Foo", "FooBar"); lookupDictionary.Add("Bar", "FooBar"); lookupDictionary.Add("Rab", "FooBar"); lookupDictionary.Add("Oof", "FooBar"); When you need to find something from valuesDictionary you hit lookupDictionary first. This will give you the key of the value you are looking for in the valuesDictionary. EDIT I haven't addressed the deletion issue in my answer so here it goes :D You would hit lookupDictionary to find the value key and then delete all entries from lookupDictionary that have that value. Should be simple enough and safe since the valuesDictionary is guaranteed to have a unique key hence you will not accidentally delete a lookup key for some other value. However, as Ian Ringrose pointed out in a comment, you are going to do a full scan on the lookupDictionary to delete. This may have an undesirable impact on performance in tight loops etc. I can't really think of a good way to solve this issue at the moment. Perhaps someone else might have some ideas on how this could be improved. I hope this helps.
You can't do it just with a single Dictionary without losing look up speed. The reason is that if you were to create a composite key there is no meaningful value you can return when you override GetHashCode. This means an equality comparison would need to be done against every key until a dictionary entry is found. You would also have a potential problem with a composite key in this case: because your Equals method would check whether one property or the other are equal, the following keys would essentially be duplicate keys { Id=1, Name="Bob" } { Id=1, Name="Anna" }, which doesn't give me a warm fuzzy feeling. This leaves you with wrapping a dictionary, or pair of dictionaries with your own class.
interesting question, here's one solution. You have to add an indexer for every key type you want to support though. public class NewDic<T> { public void Add(string key1, long key2, T value) { mDic.Add(key1, value); mDic.Add(key2, value); } public T this[string s] { get { return mDic[s]; } } public T this[long l] { get { return mDic[l]; } } Dictionary<object, T> mDic = new Dictionary<object, T>(); } NewDic<long> dic = new NewDic<long>(); dic.Add("abc", 20, 10); Console.WriteLine(dic["abc"]); Console.WriteLine(dic[20]);
This is NOT a proper dictionary, but can be used for simple dictionary-like add remove functionalities. This can be made generic as well, with proper implementation of IComparable in the keys types, and changing the dictionary code accordingly. (Note, default values of keys are not allowed to manage ambiguity!) internal class KeyValueSet //this dictionary item is tailor made for this example { public string KeyStr { get; set; } public int KeyInt { get; set; } public int Value { get; set; } public KeyValueSet() { } public KeyValueSet(string keyStr, int keyInt, int value) { KeyStr = keyStr; KeyInt = keyInt; Value = value; } } public class DoubleKeyDictionary { List<KeyValueSet> _list = new List<KeyValueSet>(); private void Add(KeyValueSet set) { if (set == null) throw new InvalidOperationException("Cannot add null"); if (string.IsNullOrEmpty(set.KeyStr) && set.KeyInt == 0) throw new InvalidOperationException("Invalid key"); if (!string.IsNullOrEmpty(set.KeyStr) && _list.Any(l => l.KeyStr.Equals(set.KeyStr)) || set.KeyInt != 0 && _list.Any(l => l.KeyInt == set.KeyInt)) throw new InvalidOperationException("Either of keys exists"); _list.Add(set); } public void Add(string keyStr, int keyInt, int value) { Add(new KeyValueSet { KeyInt = keyInt, KeyStr = keyStr, Value = value }); } public void Add(string key, int value) { Add(new KeyValueSet { KeyInt = 0, KeyStr = key, Value = value }); } public void Add(int key, int value) { Add(new KeyValueSet { KeyInt = key, KeyStr = string.Empty, Value = value }); } public void Remove(int key) { if (key == 0) throw new InvalidDataException("Key not found"); var val = _list.First(l => l.KeyInt == key); _list.Remove(val); } public void Remove(string key) { if (string.IsNullOrEmpty(key)) throw new InvalidDataException("Key not found"); var val = _list.First(l => l.KeyStr == key); _list.Remove(val); } public void Remove(KeyValueSet item) { _list.Remove(item); } public int this[int index] { get { if (index != 0 && _list.Any(l => l.KeyInt == index)) return _list.First(l => l.KeyInt == index).Value; throw new InvalidDataException("Key not found"); } set { Add(index, value); } } public int this[string key] { get { if (!string.IsNullOrEmpty(key) && _list.Any(l => l.KeyStr == key)) return _list.First(l => l.KeyStr == key).Value; throw new InvalidDataException("Key not found"); } set { Add(key, value); } } } Testing the DoubleKeyDictionary var dict = new DoubleKeyDictionary(); dict.Add(123, 1); dict.Add(234, 2); dict.Add("k1", 3); dict.Add("k2", 4); dict[456] = 5; dict["k3"] = 6; dict.Add("k4", 567, 7); dict.Remove(123); Console.WriteLine(dict[234]); //2 Console.WriteLine(dict["k2"]); //4 Console.WriteLine(dict[456]); //5 Console.WriteLine(dict[567]); //7 Console.WriteLine(dict["k4"]); //7 Console.WriteLine(dict[123]); //exception
As a local solution I use the easy approach: Imagine I have a collection of products identified by a string and a form with buttons for each one of the products. When managing the state of the buttons I need to find buttons by string key. When handling the clicks I need to find product IDs by button instance. Instead of maintaining two separate dictionaries I do the following: public class SPurchaseOption { public Button Button; public string ProductID; public string SomeOtherAssociatedData; } Dictionary<object, SPurchaseOption> purchaseOptions; When the buttons are initialized I append two entries into the Dictionary i.e. Key: ProductID, Value: "SPurchaseOption" Key: Button, Value: "SPurchaseOption" For a more general approach and if you need a commonly used component you will have to build a wrap around two dictionaries i.e: public class DoubleKeyedDictionary<TKey1, TKey2, TValue> { class SItem { public TKey1 key1; public TKey2 key2; public TValue value; } Dictionary<TKey1, SItem> dic1; Dictionary<TKey2, SItem> dic2; } this will give access to both the value and alternative key by any of the keys.
As suggested in a comment to your question you could simply use an Object key for your Dictionary: Dictionary<Object, long> dict = new Dictionary<Object, long>(); dict.Add("abc", 111); dict.Add(345, 111); To get a cleaner solution you could wrap this dictionary in a custom class and create your version of Add method: public void Add(ISet<Object> keys, T value){ foreach(Object k in keys) { _privateDict.Add(k, value); } }
How about a Dictionary<Tuple<string, long>, long>? Tuples are compared by value, so it should index uniquely in the expected manner. Plus, now you won't have to pack the long value in two places (and deal with the wonderful pain of synchronizing the values everywhere). How about this approach? Basically, still use a dictionary-based strategy, but facade it through a class with overloaded indexer properties. So it looks like a dictionary, feels like a dictionary, but supports multiple keys (not like a dictionary, LOL). public class MultiKeyDictionary<TFirstKey, TSecondKey, TValue> { private readonly Dictionary<TFirstKey, TValue> firstKeyDictionary = new Dictionary<TFirstKey, TValue>(); private readonly Dictionary<TSecondKey, TFirstKey> secondKeyDictionary = new Dictionary<TSecondKey, TFirstKey>(); public TValue this[TFirstKey idx] { get { return firstKeyDictionary[idx]; } set { firstKeyDictionary[idx] = value; } } public TValue this[TSecondKey idx] { get { var firstKey = secondKeyDictionary[idx]; return firstKeyDictionary[firstKey]; } set { var firstKey = secondKeyDictionary[idx]; firstKeyDictionary[firstKey] = value; } } public IEnumerable<KeyValuePair<TFirstKey, TValue>> GetKeyValuePairsOfFirstKey() { return firstKeyDictionary.ToList(); } public IEnumerable<KeyValuePair<TSecondKey, TValue>> GetKeyValuePairsOfSecondKey() { var r = from s in secondKeyDictionary join f in firstKeyDictionary on s.Value equals f.Key select new KeyValuePair<TSecondKey, TValue>(s.Key, f.Value); return r.ToList(); } public void Add(TFirstKey firstKey, TSecondKey secondKey, TValue value) { firstKeyDictionary.Add(firstKey, value); secondKeyDictionary.Add(secondKey, firstKey); } public bool Remove(TFirstKey firstKey) { if (!secondKeyDictionary.Any(f => f.Value.Equals(firstKey))) return false; var secondKeyToDelete = secondKeyDictionary.First(f => f.Value.Equals(firstKey)); secondKeyDictionary.Remove(secondKeyToDelete.Key); firstKeyDictionary.Remove(firstKey); return true; } public bool Remove(TSecondKey secondKey) { if (!secondKeyDictionary.ContainsKey(secondKey)) return false; var firstKey = secondKeyDictionary[secondKey]; secondKeyDictionary.Remove(secondKey); firstKeyDictionary.Remove(firstKey); return true; } } Test the code... static void Main(string[] args) { var dict = new MultiKeyDictionary<string, long, long>(); dict.Add("abc", 111, 1234); dict.Add("def", 222, 7890); dict.Add("hij", 333, 9090); Console.WriteLine(dict["abc"]); // expect 1234 Console.WriteLine(dict["def"]); // expect 7890 Console.WriteLine(dict[333]); // expect 9090 Console.WriteLine(); Console.WriteLine("removing def"); dict.Remove("def"); Console.WriteLine(); Console.WriteLine("now we have:"); foreach (var d in dict.GetKeyValuePairsOfFirstKey()) { Console.WriteLine($"{d.Key} : {d.Value}"); } Console.WriteLine(); Console.WriteLine("removing 333"); dict.Remove(333); Console.WriteLine(); Console.WriteLine("now we have:"); foreach (var d in dict.GetKeyValuePairsOfSecondKey()) { Console.WriteLine($"{d.Key} : {d.Value}"); } Console.ReadLine(); }
At first I thought I could create a class that implmented IDictionary<TKey1, TValue> and IDictionary<TKey2, TValue>, and just have a single Dictionary as a field and delegate most methods to the single dictionary with minimal logic. The problem with this approach is that TKey1 and TKey2 could be of the same type, which is a problem because this new class would be implementing the same interface twice. Which method should the runtime invoke when TKey1 is a string and TKey2 is also a string? As others above have suggested, it is best to create your own data structure that utilizes one or two dictionaries behind the scenes. For example, if you knew ahead of time that you wanted to use a string and an int as your keys, you could use this approach: public class StringIntDictionary<TValue> : IDictionary<string, TValue>, IDictionary<int, TValue> { private IDictionary<object, TValue> _dictionary = new Dictionary<object, TValue>(); // implement interface below, delegate to _dictionary } That would allow you to look use both string and int keys: var dict = StringIntDictionary<bool>(); dict["abc"] = true; dict[123] = true;
A very crude way that may suffice until I find a better one. class MyClass { public string StringKey = ""; public int IntKey = 0; public override Equals(object obj) { // Code to return true if all fields are equal } } Dictionary <MyClass, string> MyDict; MyClass myClass; MyDict[MyDict.Keys.FirstOrDefault(x => x.Equals(MyClass))]; For my money, the answer saying to use tuples is the right one. Unfortunately, my NuGet is too old to get the ValueTuple package I'd want to use so my fields aren't 'item1', 'item2' etc. That would be more confusing than what I've done here. When I change VS/NuGet versions, it's ValueTuples all the way for this kind of situation. Second time this week I've encountered the need!
Here's something better in terms of efficiency. public class MultiKeyDictionary<TKeyType1, TKeyType2, TValueType> { private readonly object threadLock = new object(); private readonly Dictionary<TKeyType1, TValueType> _dictionary1 = new Dictionary<TKeyType1, TValueType>(); private readonly Dictionary<TKeyType2, TValueType> _dictionary2 = new Dictionary<TKeyType2, TValueType>(); private readonly Dictionary<TKeyType1, TKeyType2> _Key1Key2Map = new Dictionary<TKeyType1, TKeyType2>(); private readonly Dictionary<TKeyType2, TKeyType1> _Key2Key1Map = new Dictionary<TKeyType2, TKeyType1>(); public bool Add(TKeyType1 key1, TKeyType2 key2, TValueType v) { if (ContainsKey1(key1) || ContainsKey2(key2)) return false; _dictionary1.Add(key1, v); _dictionary2.Add(key2, v); _Key1Key2Map.Add(key1, key2); _Key2Key1Map.Add(key2, key1); return true; } public bool ContainsKey1(TKeyType1 key) { return _dictionary1.ContainsKey(key); } public bool ContainsKey2(TKeyType2 key) { return _dictionary2.ContainsKey(key); } //Note if TKeyType1 and TKeyType2 are the same then we are forced to use GetBy functions public TValueType GetByKey1(TKeyType1 key) { return _dictionary1[key]; } public TValueType GetByKey2(TKeyType2 key) { return _dictionary2[key]; } public bool SetByKey1(TKeyType1 key, TValueType val) { if (ContainsKey1(key)) return false; lock (threadLock) { var key2 = _Key1Key2Map[key]; _dictionary1[key] = val; _dictionary2[key2] = val; } return true; } public bool SetByKey2(TKeyType2 key, TValueType val) { if (ContainsKey2(key)) return false; lock (threadLock) { var key1 = _Key2Key1Map[key]; _dictionary1[key1] = val; _dictionary2[key] = val; } return true; } public void RemoveUsingKey1(TKeyType1 key) { lock (threadLock) { var key2 = _Key1Key2Map[key]; _dictionary1.Remove(key); _dictionary2.Remove(key2); _Key1Key2Map.Remove(key); _Key2Key1Map.Remove(key2); } } public void RemoveUsingKey2(TKeyType2 key) { lock (threadLock) { var key1 = _Key2Key1Map[key]; _dictionary1.Remove(key1); _dictionary2.Remove(key); _Key1Key2Map.Remove(key1); _Key2Key1Map.Remove(key); } } public bool Contains(TKeyType1 key) { return _dictionary1.ContainsKey(key); } public bool Contains(TKeyType2 key) { return _dictionary2.ContainsKey(key); } public TValueType this[TKeyType1 key] { get => GetByKey1(key); set => SetByKey1(key, value); } public TValueType this[TKeyType2 key] { get => GetByKey2(key); set => SetByKey2(key, value); } public void Remove(TKeyType1 key) { RemoveUsingKey1(key); } public void Remove(TKeyType2 key) { RemoveUsingKey2(key); } public int Count => _dictionary1.Count; public Dictionary<TKeyType1, TValueType>.KeyCollection Key1s => _dictionary1.Keys; public Dictionary<TKeyType2, TValueType>.KeyCollection Key2s => _dictionary2.Keys; public Dictionary<TKeyType1, TValueType>.ValueCollection Values => _dictionary1.Values; public void Clear() { lock (threadLock) { _dictionary1.Clear(); _dictionary2.Clear(); _Key1Key2Map.Clear(); _Key2Key1Map.Clear(); } } //Map between Keys public TKeyType2 Key2(TKeyType1 key) { return _Key1Key2Map[key]; } public TKeyType1 Key1(TKeyType2 key) { return _Key2Key1Map[key]; } }
I have created my own version of it. It is a bit more sophisticated. The documentation should explain some of the functionality. In essence it allows a reasonable way to handle 2 keys for the same value, auto-merges entries, is presumably thread-safe (untested), allows mapping keys together, and handles deleting entries, all the while having the functionality of dictionaries at its base. When adding an entry, but one of its keys already exists, it will just add the key and overwrite the value. It's quite distinct logically from other forms of collections, so these are the most I was able to implement. Some structure with key pairs didn't seem fitting, given that I need this to arbitrarily add a second key as needed to existing entries, and given the merging functionality. I also took regard for the situation where one uses the same types for both keys, but also for situations where they don't. /// <summary> A collection that internally uses a list (which in turn internally uses an array), and two dictionaries for the index. /// This allows operating it based on two keys and provides means to (automatically) map keys to each other. /// The indexing of the internal list is treated specially. In order to not infringe on the validity of the dictionaries' references to the indexes, /// they are kept identical. Removing is handled by setting the entries to 'null', and once a new item is added, they are overwritten. </summary> /// <typeparam name="TKey1"> The first key. </typeparam> /// <typeparam name="TKey2"> The second key. </typeparam> /// <typeparam name="T"> The stored value type. </typeparam> public class TwoKeyDictionary<TKey1, TKey2, T> : IEnumerable<TwoKeyDictionaryEntry<TKey1, TKey2, T>>, IReadOnlyCollection<TwoKeyDictionaryEntry<TKey1, TKey2, T>> { private readonly Dictionary<TKey1, int> _keys01 = new Dictionary<TKey1, int> (); private readonly Dictionary<TKey2, int> _keys02 = new Dictionary<TKey2, int> (); private readonly List<TwoKeyDictionaryEntry<TKey1, TKey2, T>> _items = new List<TwoKeyDictionaryEntry<TKey1, TKey2, T>> (); private int _freeIndex = 0; // The index of the first free slot. private int _freeCount = 0; // Free before the last value. private readonly object _lock = new object (); public TwoKeyDictionary () { } /// <summary> Adds an item. </summary> public bool Add (TKey1 key, T value) { return AddByKey1 (key, value); } /// <summary> Adds an item. </summary> public bool Add (TKey2 key, T value) { return AddByKey2 (key, value); } /// <summary> Adds an item. </summary> public bool AddByKey1 (TKey1 key, T value) { lock (_lock) { return AddByKey1Internal (key, value); } } /// <summary> Adds an item. </summary> public bool AddByKey2 (TKey2 key, T value) { lock (_lock) { return AddByKey2Internal (key, value); } } /// <summary> Adds an item with two keys. If either key already exists, it will map the other key to it. The value will only be overwritten if it's 'null'. </summary> public bool Add (TKey1 key1, TKey2 key2, T value) { return Add (key1, key2, value, false); } /// <summary> Adds an item with two keys. If either key already exists, it will map the other key to it. The value will only be overwritten if it's 'null'. /// This may also define how the key is mapped, if occurring. </summary> public bool Add (TKey1 key1, TKey2 key2, T value, bool mapToKey2) { lock (_lock) { return AddInternal (key1, key2, value, mapToKey2); } } /// <summary> Maps both keys together. If either key exists, it will add the other one to it. If both exist, it will merge the entries and delete the other. /// By default this will map to key1. </summary> public bool Map (TKey1 key1, TKey2 key2) { return MapToKey1 (key1, key2); } /// <summary> Maps both keys together. If either key exists, it will add the other one to it. If both exist, it will merge the entries and delete the one with key2. </summary> public bool MapToKey1 (TKey1 key1, TKey2 key2) { lock (_lock) { return MapToKey1Internal (key1, key2); } } /// <summary> Maps both keys together. If either key exists, it will add the other one to it. If both exist, it will merge the entries and delete the one with key1. </summary> public bool MapToKey2 (TKey1 key1, TKey2 key2) { lock (_lock) { return MapToKey2Internal (key1, key2); } } /// <summary> Removes an entry based on key1. If there is a key2 mapped to it, it will be removed as well. </summary> public bool Remove (TKey1 key) { return RemoveByKey1 (key); } /// <summary> Removes an entry based on key2. If there is a key1 mapped to it, it will be removed as well. </summary> public bool Remove (TKey2 key) { return RemoveByKey2 (key); } /// <summary> Removes an entry based on key1. If there is a key2 mapped to it, it will be removed as well. </summary> public bool RemoveByKey1 (TKey1 key) { lock (_lock) { return RemoveByKey1Internal (key); } } /// <summary> Removes an entry based on key2. If there is a key1 mapped to it, it will be removed as well. </summary> public bool RemoveByKey2 (TKey2 key) { lock (_lock) { return RemoveByKey2Internal (key); } } /// <summary> Removes an entry based on both, key1 and key2. Any entries related to either keys will be removed. </summary> public bool Remove (TKey1 key1, TKey2 key2) { lock (_lock) { return RemoveByKey1Internal (key1) | RemoveByKey2Internal (key2); } } /// <summary> Tries to return a value based on key1. </summary> public bool TryGetValue (TKey1 key, out T value) { return TryGetValueByKey1 (key, out value); } /// <summary> Tries to return a value based on key2. </summary> public bool TryGetValue (TKey2 key, out T value) { return TryGetValueByKey2 (key, out value); } /// <summary> Tries to return a value based on key1. </summary> public bool TryGetValueByKey1 (TKey1 key, out T value) { if (key == null) { value = default; return false; } if (_keys01.TryGetValue (key, out int index)) { TwoKeyDictionaryEntry<TKey1, TKey2, T> entry = _items[index]; if (entry != null) { value = entry.Value; return true; } } value = default; return false; } /// <summary> Tries to return a value based on key2. </summary> public bool TryGetValueByKey2 (TKey2 key, out T value) { if (key == null) { value = default; return false; } if (_keys02.TryGetValue (key, out int index)) { TwoKeyDictionaryEntry<TKey1, TKey2, T> entry = _items[index]; if (entry != null) { value = entry.Value; return true; } } value = default; return false; } /// <summary> Tries to return a value based on key1 or key2. Prioritizes key1. </summary> public bool TryGetValue (TKey1 key1, TKey2 key2, out T value) { return TryGetValue (key1, key2, false, out value); } /// <summary> Tries to return a value based on key1 or key2. </summary> public bool TryGetValue (TKey1 key1, TKey2 key2, bool prioritizeKey2, out T value) { return prioritizeKey2 ? TryGetValue (key1, out value) || TryGetValue (key2, out value) : TryGetValue (key2, out value) || TryGetValue (key1, out value); } /// <summary> Returns 'true' if they key and the entry still exists. The stored value itself may still be 'null' regardless. </summary> public bool ContainsKey (TKey1 key) { return ContainsKey1 (key); } /// <summary> Returns 'true' if they key and the entry still exists. The stored value itself may still be 'null' regardless. </summary> public bool ContainsKey (TKey2 key) { return ContainsKey2 (key); } /// <summary> Returns 'true' if they key and the entry still exists. The stored value itself may still be 'null' regardless. </summary> public bool ContainsKey1 (TKey1 key) { if (key == null) return false; if (_keys01.TryGetValue (key, out int index)) return _items[index] != null; else return false; } /// <summary> Returns 'true' if they key and the entry still exists. The stored value itself may still be 'null' regardless. </summary> public bool ContainsKey2 (TKey2 key) { if (key == null) return false; if (_keys02.TryGetValue (key, out int index)) return _items[index] != null; else return false; } /// <summary> Returns 'true' if they key and the entry still exists. The stored value itself may still be 'null' regardless. </summary> public bool ContainsKey (TKey1 key1, TKey2 key2) { return ContainsKey1 (key1) || ContainsKey2 (key2); } #region Internal // Returns true if this wasn't the last position. private bool GetFreeIndex (bool apply, out int index) { if (_freeCount == 0) { index = _items.Count; return false; } else { index = _freeIndex; if (apply) { // We must find the next free slot. int freeIndex = _freeIndex + 1; int count = _items.Count; while (freeIndex < count && _items[freeIndex] != null) { freeIndex++; } if (freeIndex == count) _freeCount = 0; else Interlocked.Decrement (ref _freeCount); _freeIndex = freeIndex; } return true; } } private bool MapToKey1Internal (TKey1 key1, TKey2 key2) { if (key1 == null || key2 == null) return false; bool s1 = _keys01.TryGetValue (key1, out int index1); bool s2 = _keys02.TryGetValue (key2, out int index2); if (s1 && s2) { TwoKeyDictionaryEntry<TKey1, TKey2, T> e1 = _items[index1]; TwoKeyDictionaryEntry<TKey1, TKey2, T> e2 = _items[index2]; RemoveByKey2Internal (key2); e1.Key2 = key2; if (e1.Value == null) e1.Value = e2.Value; return true; } else if (s1) { _items[index1].Key2 = key2; _keys02.Add (key2, index1); return true; } else if (s2) { _items[index2].Key1 = key1; _keys01.Add (key1, index2); return true; } else return false; } private bool MapToKey2Internal (TKey1 key1, TKey2 key2) { if (key1 == null || key2 == null) return false; bool s1 = _keys01.TryGetValue (key1, out int index1); bool s2 = _keys02.TryGetValue (key2, out int index2); if (s1 && s2) { TwoKeyDictionaryEntry<TKey1, TKey2, T> e1 = _items[index1]; TwoKeyDictionaryEntry<TKey1, TKey2, T> e2 = _items[index2]; RemoveByKey1Internal (key1); e2.Key1 = key1; if (e2.Value == null) e2.Value = e1.Value; return true; } else if (s1) { _items[index1].Key2 = key2; return true; } else if (s2) { _items[index2].Key1 = key1; return true; } else return false; } private bool AddByKey1Internal (TKey1 key, T value) { if (key == null) return false; if (_keys01.TryGetValue (key, out int index)) { TwoKeyDictionaryEntry<TKey1, TKey2, T> entry = _items[index]; if (entry != null) { entry.Value = value; return true; } else { _keys01.Remove (key); return AddByKey1Internal (key, value); } } else { TwoKeyDictionaryEntry<TKey1, TKey2, T> item = new TwoKeyDictionaryEntry<TKey1, TKey2, T> (key, default, value); if (GetFreeIndex (true, out int freeIndex)) { _items[freeIndex] = item; } else { _items.Add (item); } _keys01.Add (key, freeIndex); return true; } } private bool AddByKey2Internal (TKey2 key, T value) { if (key == null) return false; if (_keys02.TryGetValue (key, out int index)) { TwoKeyDictionaryEntry<TKey1, TKey2, T> entry = _items[index]; if (entry != null) { entry.Value = value; return true; } else { _keys02.Remove (key); return AddByKey2Internal (key, value); } } else { TwoKeyDictionaryEntry<TKey1, TKey2, T> item = new TwoKeyDictionaryEntry<TKey1, TKey2, T> (default, key, value); if (GetFreeIndex (true, out int freeIndex)) { _items[freeIndex] = item; } else { _items.Add (item); } _keys02.Add (key, freeIndex); return true; } } private bool AddInternal (TKey1 key1, TKey2 key2, T value, bool mapToKey2) { if (key1 == null) return AddByKey2Internal (key2, value); else if (key2 == null) return AddByKey1Internal (key1, value); bool hasKey1 = _keys01.TryGetValue (key1, out int index1); bool hasKey2 = _keys02.TryGetValue (key2, out int index2); if (hasKey1 && hasKey2) { // We have 2 different entries' keys that point to the same value. Merge them to one key, remove the other. if (mapToKey2) { if (MapToKey2Internal (key1, key2)) { _items[index2].Value = value; } } else { if (MapToKey1Internal (key1, key2)) { _items[index1].Value = value; } } } else if (hasKey1) { TwoKeyDictionaryEntry<TKey1, TKey2, T> entry = _items[index1]; entry.Key2 = key2; entry.Value = value; } else if (hasKey2) { TwoKeyDictionaryEntry<TKey1, TKey2, T> entry = _items[index2]; entry.Key1 = key1; entry.Value = value; } else { _items.Add (new TwoKeyDictionaryEntry<TKey1, TKey2, T> (key1, key2, value)); } return true; } private bool RemoveByKey1Internal (TKey1 key) { if (key == null) return false; if (_keys01.TryGetValue (key, out int index)) { TwoKeyDictionaryEntry<TKey1, TKey2, T> entry = _items[index]; if (entry != null) { _keys01.Remove (key); if (entry.Key2 != null) _keys02.Remove (entry.Key2); if (index == _items.Count - 1) { _items.RemoveAt (index); } else { _items[index] = null; _freeIndex = _freeCount > 0 ? Math.Min (_freeIndex, index) : index; Interlocked.Increment (ref _freeCount); } return true; } else { _keys01.Remove (key); } } return false; } private bool RemoveByKey2Internal (TKey2 key) { if (key == null) return false; if (_keys02.TryGetValue (key, out int index)) { TwoKeyDictionaryEntry<TKey1, TKey2, T> entry = _items[index]; if (entry != null) { _keys02.Remove (key); if (entry.Key1 != null) _keys01.Remove (entry.Key1); if (index == _items.Count - 1) { _items.RemoveAt (index); } else { _items[index] = null; _freeIndex = _freeCount > 0 ? Math.Min (_freeIndex, index) : index; Interlocked.Increment (ref _freeCount); } return true; } else { _keys02.Remove (key); } } return false; } #endregion #region Interface Implementations public int Count => _items.Count (j => j != null); public IEnumerator<TwoKeyDictionaryEntry<TKey1, TKey2, T>> GetEnumerator () { return _items.Where (j => j != null).GetEnumerator (); } IEnumerator IEnumerable.GetEnumerator () { return _items.Where (j => j != null).GetEnumerator (); } #endregion } /// <summary> The entry class of <see cref="TwoKeyDictionary{TKey1, TKey2, T}"/>, which grants references to the keys in both dictionaries used. </summary> /// <typeparam name="TKey1"> The first key. </typeparam> /// <typeparam name="TKey2"> The second key. </typeparam> /// <typeparam name="T"> The stored value type. </typeparam> public class TwoKeyDictionaryEntry<TKey1, TKey2, T> { public TKey1 Key1 { get; internal set; } public TKey2 Key2 { get; internal set; } public T Value { get; internal set; } internal TwoKeyDictionaryEntry () { } internal TwoKeyDictionaryEntry (TKey1 key1, TKey2 key2, T value) { Key1 = key1; Key2 = key2; Value = value; } public override string ToString () { return $"{Key1?.ToString () ?? "---"} | {Key2?.ToString () ?? "---"} | {Value}"; } }
What is the easiest way to maintain a list of key-value pairs sorted by value?
Each key is unique in the list. When a new key-value pair arrives, the pair is inserted into the list in the ascending order of value (if key already exists then updates the value). Please avoid sorting the list for every insertion.
I would suggest SortedDictionary or SortedList As per MSDN : SortedList uses less memory than SortedDictionary. SortedDictionary has faster insertion and removal operations for unsorted data: O(log n) as opposed to O(n) for SortedList. Update : After comments You will have to order the value by yourself for e.g using a dictioanry var dictionary = new Dictionary<int, string>{ {1, "Z"}, {2, "A"}}; IOrderedEnumerable<KeyValuePair<int, string>> orderedEnumerable = dictionary.OrderBy(d => d.Value);
You aren't going to get a built in component with this behaviour, it's too non-standard. I'd be looking at why and when I needed these competing behaviours. Effectively you are looking at an alternate key. Short of just writing some for of linked list, off the top of my head, I'd look at SortedList for the by value part of it, and a Dictionary for key. e.g. a Dictionary of CustomerID and SortKey and a SortedList of SortKey and value. I'd try and avoid it if I could on the baiss that maintaining both would cost more than simply returning a list of values in the required order on those occasions when you needed it.
If sorting the items for every enumeration is acceptable, you can use a Dictionary<TKey, TValue> and order the key-value pairs by value when you enumerate it: var dict = new Dictionary<MyKey, MyValue>(); // insertion (updates value when key already exists) dict[key] = value; // enumeration (ordered by value) foreach (var keyValuePair in dict.OrderBy(kvp => kvp.Value)) { ... }
I would write an ad-hoc class like the following (not completely tested): public class DictionarySortedByValue<TKey, TValue> : IDictionary<TKey, TValue> { class ValueWrapper : IComparable, IComparable<ValueWrapper> { public TKey Key { get; private set; } public TValue Value { get; private set; } public ValueWrapper(TKey k, TValue v) { this.Key = k; this.Value = v; } public int CompareTo(object obj) { if (!(obj is ValueWrapper)) throw new ArgumentException("obj is not a ValueWrapper type object"); return this.CompareTo(obj as ValueWrapper); } public int CompareTo(ValueWrapper other) { int c = Comparer<TValue>.Default.Compare(this.Value, other.Value); if (c == 0) c = Comparer<TKey>.Default.Compare(this.Key, other.Key); return c; } } private SortedSet<ValueWrapper> orderedElements; private SortedDictionary<TKey, TValue> innerDict; public DictionarySortedByValue() { this.orderedElements = new SortedSet<ValueWrapper>(); this.innerDict = new SortedDictionary<TKey, TValue>(); } public void Add(TKey key, TValue value) { var wrap = new ValueWrapper(key, value); this.innerDict.Add(key, value); this.orderedElements.Add(wrap); } public bool ContainsKey(TKey key) { return this.innerDict.ContainsKey(key); } public ICollection<TKey> Keys { get { return this.innerDict.Keys; } } public bool Remove(TKey key) { TValue val; if (this.TryGetValue(key, out val)) { var wrap = new ValueWrapper(key, val); this.orderedElements.Remove(wrap); this.innerDict.Remove(key); return true; } return false; } public bool TryGetValue(TKey key, out TValue value) { return this.innerDict.TryGetValue(key, out value); } public ICollection<TValue> Values { get { return this.innerDict.Values; } } public TValue this[TKey key] { get { return this.innerDict[key]; } set { bool removed = this.Remove(key); this.Add(key, value); } } public void Add(KeyValuePair<TKey, TValue> item) { this.Add(item.Key, item.Value); } public void Clear() { this.innerDict.Clear(); this.orderedElements.Clear(); } public bool Contains(KeyValuePair<TKey, TValue> item) { var wrap = new ValueWrapper(item.Key,item.Value); return this.orderedElements.Contains(wrap); } public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) { this.innerDict.CopyTo(array, arrayIndex); } public int Count { get { return this.innerDict.Count; } } public bool IsReadOnly { get { return false; } } public bool Remove(KeyValuePair<TKey, TValue> item) { if (this.Contains(item)) return this.Remove(item.Key); return false; } public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() { foreach (var el in this.orderedElements) yield return new KeyValuePair<TKey, TValue>(el.Key, el.Value); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this.GetEnumerator(); } } Notes : it requires that also the TKey type implements IComparable. the posted code uses only the default Comparer for TKey, and TValue, but you could pass a custom one through another constructor.