没有OrderedDictionary的通用实现?


136

.NET 3.5中似乎没有通用的实现OrderedDictionary(在System.Collections.Specialized名称空间中)。有我想念的吗?

我已经找到了提供功能的实现,但是想知道是否/为什么没有现成的通用实现,以及是否有人知道它是否在.NET 4.0中?


1
下面是一个实现OrderedDictionary<T>codeproject.com/Articles/18615/...
蒂姆Schmelter


我的OrderedDictionary <T>实现具有O(1)插入/删除,因为它使用LinkedList而不是ArrayList来保持插入顺序:clintonbrennan.com/2013/12/…–
Clinton

2
如果只需要按照添加顺序对条目进行迭代,则List <KeyValuePair <TKey,TValue >>可能就足够了。(允许的,不是一般的解决方案,但是对于某些目的来说已经足够了。)
yoyo 2014年

1
不幸的是遗漏了。还有其他好的数据类型 Systems.Collections.Generic。让我们请求OrderedDictionary<TKey,TValue>.NET5。正如其他人指出的那样,键是int的情况是简并的,需要特别注意。
Panic Panic 2014年

Answers:



95

实施泛型OrderedDictionary不是很困难,但是不必要地很耗时,坦率地说,此类是Microsoft的巨大监督。有多种实现方法,但是我选择对KeyedCollection内部存储使用。我还选择实现各种方法进行排序,List<T>因为这本质上是IList和IDictionary的混合。为了后代,我在这里包括了我的实现。

这是界面。请注意,它包括System.Collections.Specialized.IOrderedDictionary,这是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);
    }

}

这是实现以及帮助程序类:

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

没有一些测试就无法完成任何实现(但可悲的是,SO不会让我在一篇文章中发布那么多的代码),所以我将不得不由您来编写测试。但是,我留下了一些,以便您可以了解其工作原理:

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

-更新-

此处以及其他确实有用的缺少的.NET核心库的来源:https : //github.com/mattmc3/dotmore/blob/master/dotmore/Collections/Generic/OrderedDictionary.cs


6
在公共领域,您是否在询问是否可以使用,修改和将其视为您的后顾之忧-是的。放心吧。如果您是说要许可它并将其托管在某个地方-否...它仅在SO上存在。
mattmc3

3
@ mattmc3先生,感谢您的代码,但是您对公共领域问题的评论使我感到担忧,当您在评论中说:“如果您是将其许可并托管在某个地方-不……它仅在SO上存在。 ” 在尊重作者的前提下,一旦您在SO上发布了代码,您是否真的有权利做出该限制?例如,我们中的每个人是否真的有权限制我们的SO代码(例如,作为git gist)发布?任何人?
Nicholas Petersen 2013年

6
@NicholasPetersen-我想你误会了。作为对Panic上校的直接回应,我通知他,我个人未许可或将其托管在其他任何地方。(实际上,自从您提到它以来,我确实提出了要点:gist.github.com/mattmc3/6486878)。但这是免许可证的代码。随身携带,随心所欲。我100%写给我自己使用。毫无阻碍。请享用。实际上,如果Microsoft的某人曾经读过这篇文章,我完全希望他们能履行职责,最后将其放到下一版.NET中。无需注明出处。
mattmc3 2013年

2
如果TKeyint什么呢?this[]在这种情况下将如何工作?
VB

2
@klicker-只需使用常规数组样式索引即可。插入顺序就像列表一样保持。类型转换处理确定您是要使用int索引还是要通过键的类型进行引用。如果键的类型是int,则需要使用GetValue()方法。
mattmc3

32

作为记录,有一个通用的KeyedCollection,它允许对象通过int和key进行索引。密钥必须嵌入到值中。


2
这不会像OrderedDictionary那样保持初始化顺序!看看我的答案。
JoelFan 2011年

14
它确实保持添加/插入的顺序。
Guillaume

是的,它确实是..你们从哪里得到这个keyedcollection对项目进行排序的概念……我第二次被偶然发现
Boppity Bop 2013年

1
它肯定会保持初始化的顺序。有用的链接包括stackoverflow.com/a/11802824/9344geekswithblogs.net/NewThingsILearned/archive/2010/01/07/…
Ted

+1,这似乎是框架中最好的解决方案。但是,必须为要使用的每对类型实现抽象类有点麻烦。您可以使用一个需要接口的通用实现来完成此操作,但是随后您必须在希望存储的每种类型上实现该接口。
DCShannon 2015年

19

这是一个奇怪的发现:System.Web.Extensions.dll中的System.Web.Util命名空间包含通用的OrderedDictionary

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

不确定为什么将MS放置在这里而不是System.Collections.Generic包,但是我假设您可以简单地复制粘贴代码并使用它(它是内部的,因此不能直接使用它)。看起来该实现使用标准字典和单独的键/值列表。非常简单...


2
System.Runtime.Collections还包含一个内部OrderedDictionary<TKey, TValue>刚刚周围无通用版包装
VB

1
System.Web.Util.OrderedDictionary<TKey, TValue>在内部围绕泛型实现Dictionary。奇怪的是,它没有实现,IList但是ICollection<KeyValuePair<TKey, TValue>>
Mikhail 2015年

1
@rboy正如我所说-它是内部的,并且包装了非通用实现。但这是3年前的事...对于小于几百的大小,List<KeyValuePair<TKey,TValue>>由于内存访问模式的原因,线性搜索将具有竞争力,对于较大的大小,只需使用相同的list + Dictionary<TKey,int>作为查找即可。AFAIK在BigO中没有这样的数据结构在速度/内存方面有更好的表现。
VB

1
@rboy是通用链接的链接,它引用使用HashTable的非通用链接。我真的打赌,对于较小的尺寸,对通用列表/数组使用线性搜索会更快。
VB

1
@PeterMortensen System.Collections.Specialized.OrderedDictionary不是通用类型。看起来
不错,

17

对于它的价值,这是我如何解决的方法:

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

可以这样初始化:

var pairList = new PairList<string, string>
    {
        { "pitcher", "Ken" },
        { "catcher", "Brad"},
        { "left fielder", "Stan"},
    };

并像这样访问:

foreach (var pair in pairList)
{
    Console.WriteLine("position: {0}, player: {1}",
        pair.Key, pair.Value);
}

// Guaranteed to print in the order of initialization

3
谢谢!我还没有意识到集合初始化器只是Add方法的特殊语法。
山姆

10
这不是字典。字典代表键索引没有重复的键
2013年

但是,如果您碰巧不需要按键(很难添加)和双键索引,这将派上用场
stijn 2014年

1
您有一个问题是代码调用pairList.Add(new KeyValuePair<K,V>())(即List类上的方法)。在这种情况下,itsIndex字典不会更新,现在列表和字典不同步。可以List.Add通过创建方法来隐藏该方法new PairList.Add(KeyValuePair<K,V>),或者可以使用合成方法而不是继承方法,而只需List再次实现所有方法...更多代码...
neizan

1
@nawfal,以解决您的问题,一个可以只添加一个检查,如:if (itsindex.Contains(key)) return;到年初Add,以防止重复
JoelFan

14

泛型版本的一个主要概念性问题OrderedDictionary是,a的用户OrderedDictionary<TKey,TValue>期望能够使用int或通过使用查找进行数字索引TKey。当键的唯一类型是Object(与非泛型一样)时OrderedDictionary,传递给索引器的参数类型将足以区分是否应执行哪种类型的索引操作。不过,目前尚不清楚an的索引器OrderedDictionary<int, TValue>应如何运行。

如果像这样的类Drawing.Point建议并遵循这样的规则:分段可变结构应将其可变元素公开为字段而不是属性,并且避免使用可修改的属性设置器this,则OrderedDictionary<TKey,TValue>可以有效地公开一个ByIndex属性,该属性返回Indexer持有对的引用的结构字典,并有一个索引的属性,它的getter和setter会打电话GetByIndexSetByIndex在其上。因此,人们可能会说MyDict.ByIndex[5] += 3;要在字典的第六个元素上加3。

不幸的是,为了使编译器接受这样的事情,有必要使该ByIndex属性在每次调用时都返回一个新的类实例,而不是一个结构,从而消除了避免装箱的优点。

在VB.NET中,可以使用命名的索引属性(因此MyDict.ByIndex[int]将成为的成员MyDict,而不是要求其中包括索引器MyDict.ByIndex的成员)来解决MyDict该问题,但是C#不允许这种情况。

提供一个也许仍然值得OrderedDictionary<TKey,TValue> where TKey:class,但首先提供泛型的许多原因是允许它们与值类型一起使用。


很好的一点是,int类型化的密钥提出了一个挑战,但是可以通过以下相关SortedList<TKey, TValue>类型的示例来避免:仅支持带有的密钥[...],并且需要使用.Values[...]进行索引访问。(SortedDictionary<TKey, TValue>相比之下,尚未针对索引访问进行优化,则需要使用.ElementAt(...).Value
mklement0

7

对于许多目的,我发现一个人可以接受List<KeyValuePair<K, V>>。(Dictionary显然,如果您需要它来扩展,则不需要,如果您需要比O(n)键值查找更好的话,就不需要。)


我自己也得出了同样的结论!
彼得

1
正如我所说,“有很多目的。”
大卫·摩尔

2
Tuple<T1, T2>如果他们没有键值关系,也可以使用a 。
cdmckay 2013年

1
如果无法按键索引,则具有键值对有什么意义?
DCShannon

1
@DCShannon您仍然可以将键映射到值,只是查找它们的速度不会比O(n)快(或自动处理重复的键)。对于较小的n值,有时就足够了,特别是在通常无论如何都要遍历所有键的情况下。
大卫·摩尔

5

SortedDictionary<TKey, TValue>。尽管从语义上讲是紧密的,但我并没有声称它与OrderedDictionary仅仅因为它们不同而相同。即使从性能特征上来说。但是,两者之间非常有趣且非常重要的区别Dictionary<TKey, TValue>(以及OrderedDictionary答案中提供的实现)SortedDictionary,并且后者之间使用的是二叉树。这是至关重要的区别,因为它使类不受应用于通用类的内存限制的影响。有关OutOfMemoryExceptions泛型类用于处理大量键值对的情况,请参见此线程。

如何计算传递给Dictionary构造函数的Capacity参数的最大值,以避免OutOfMemoryException?


有没有一种方法可以SortedDictionary 按添加顺序获取键或键的值?那就是OrderedDictionary允许的。比排序的概念不同。(我以前犯过这个错误;我认为向OrderedDictionary构造函数提供一个Comparer可以使它排序,但是对Comparer所做的只是确定键的相等性;例如,字符串不敏感的比较器允许对字符串不敏感的键查找。)
史蒂夫

5

是的,这是一个不幸的遗漏。我想念Python的OrderedDict

记住键第一次插入顺序的字典。如果新条目覆盖现有条目,则原始插入位置将保持不变。删除条目并重新插入将其移至末尾。

所以我OrderedDictionary<K,V>用C#编写了自己的类。它是如何工作的?它维护着两个集合-香草无序字典和键的有序列表。使用此解决方案,标准字典操作可以保持其快速的复杂性,并且按索引查找也很快。

https://gist.github.com/hickford/5137384

这是界面

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

3

@VB发表评论的后续内容,这是System.Runtime.Collections.OrderedDictionary <,>的可访问实现。我原本打算通过反射来访问它,并通过工厂提供它,但是该文件所在的dll似乎根本无法访问,因此我只是拉动了源代码本身。

要注意的一件事是这里的索引器不会抛出 KeyNotFoundException。我绝对讨厌那个约定,那是我在实现中的第一自由。如果这对您很重要,只需将替换为 return default(TValue);。使用C#6(与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;
    }
}

在GitHub上接受请求/讨论


3

对于那些在NuGet中寻找“官方”软件包选项的用户,.NET CoreFX Lab已接受了通用OrderedDictionary的实现。如果一切顺利,最终将批准该类型并将其集成到主要的.NET CoreFX存储库中。

此实现有可能会被拒绝。

可以在这里引用已提交的实现 https://github.com/dotnet/corefxlab/blob/57be99a176421992e29009701a99a370983329a6/src/Microsoft.Experimental.Collections/Microsoft/Collections/Extensions/OrderedDictionary.cs

绝对可以使用此类型的NuGet程序包可以在以下位置找到 :https://www.nuget.org/packages/Microsoft.Experimental.Collections/1.0.6-e190117-3

或者,您可以在Visual Studio中安装该软件包。浏览包“ Microsoft.Experimental.Collections”,并确保选中“包括预发行版”复选框。

如果该类型正式可用,则将更新此帖子。


您能估计何时发布吗?
mvorisek

我没有参与该库的开发,因此很遗憾,我对此一无所知。如果得到“批准”,它很有可能是一个内置的框架集合。
查理

1

OrderedDictionary<TKey, TValue>通过环绕SortedList<TKey, TValue>并添加一个private 实现了一个泛型Dictionary<TKey, int> _order。然后,我创建了的内部实现Comparer<TKey>,将对_order字典的引用传递给了。然后,我将此比较器用于内部SortedList。此类保留传递给构造函数的元素顺序和添加顺序。

此实现具有几乎与O相同的大O特性,SortedList<TKey, TValue>因为对_order进行添加和删除是O(1)。每个元素(根据书中的“ C#4 in a Nutshell”,第292页,表7-1)将占用22(开销)+ 4(整数顺序)+ TKey大小(假设8)= 34的额外存储空间。再加SortedList<TKey, TValue>上两个字节的开销,总开销为36个字节,而同一本书则说非泛型OrderedDictionary的开销为59个字节。

如果我将其传递sorted=true给构造函数,则根本不使用_order,这对于包装几乎没有OrderedDictionary<TKey, TValue>什么SortedList<TKey, TValue>开销,如果有任何意义的话。

我要在中存储很多参考对象OrderedDictionary<TKey, TValue>,所以对我来说这大约 36字节的开销是可以容忍的。

主要代码如下。完整的更新代码在此要点上

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

OrderedDictionary关于插入或删除,我至少可以看到四种类似的使用情况:(1)永远不会有任何删除;(2)将会删除,但重要的是项目按添加顺序枚举;无需按索引访问;(3)项目的数字索引应该(或至少可以保持)恒定,并且在收集的整个生命周期内不会添加超过20亿个项目,因此,如果删除项目#7,就永远不会再出现项目#7;(4)项目的索引应该是其相对于幸存者的等级。
2013年

1
场景#1可以通过使用与并行的数组来处理Dictionary。方案#2和#3可以通过让每个项目都维护一个索引来说明何时添加,以及指向该项目之前或之后添加的项目的链接来处理。方案#4是唯一似乎无法以任意顺序实现O(1)性能的方案。根据使用模式,可以通过使用各种延迟更新策略(保持树中的计数,并使节点的更改无效而不是更新节点及其父节点)来帮助#4。
2013年

1
由于使用了自定义比较器,因此内部SortedList具有按插入顺序排列的元素。这可能很慢,但是您对枚举的评论是错误的。显示有关枚举的测试...
VB

1
您在说什么与ToDictionary一致?它不影响内部列表,而仅影响字典。
VB 2014年

1
@VB抱歉,我都错过了。因此,尚未对其进行测试。那么唯一的问题就是加法。两个字典查找以及两个插入。我将取消投票。
nawfal 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.