Нет универсальной реализации OrderedDictionary?

136

Похоже, что в .NET 3.5 нет общей реализации OrderedDictionary(которая находится в System.Collections.Specializedпространстве имен). Я скучаю по одному?

Я нашел реализации для обеспечения функциональности, но удивился, если / почему не существует универсальной реализации «из коробки», и если кто-нибудь знает, что-то в .NET 4.0?

AdaTheDev
источник
1
Вот реализация OrderedDictionary<T>: codeproject.com/Articles/18615/…
Тим Шмельтер
2
возможный дубликат коллекции пар
Nawfal
Моя реализация OrderedDictionary <T> имеет O (1) insert / delete, потому что она использует LinkedList вместо ArrayList для поддержания порядка вставки: clintonbrennan.com/2013/12/…
Clinton
2
Если вам просто нужно иметь возможность перебирать записи в порядке их добавления, то List <KeyValuePair <TKey, TValue >> может быть достаточно хорошим. (Конечно, это не общее решение, но достаточно для некоторых целей.)
yoyo
1
Это досадное упущение. Есть и другие хорошие типы данных Systems.Collections.Generic. Давайте запросим OrderedDictionary<TKey,TValue>.NET 5. Как уже отмечали другие, случай, когда ключ является целым, является вырожденным и потребует особого внимания.
полковник Паник

Ответы:

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

mattmc3
источник
6
По общественному достоянию вы спрашиваете, можете ли вы использовать его, изменить его и относиться к нему так, как будто это ваше беспокойство - да. Не стесняйтесь. Если вы имеете в виду лицензировать его и размещать где-нибудь - нет ... он живет здесь на SO только сейчас.
mattmc3
3
@ mattmc3 Спасибо за ваш код, сэр, но ваш комментарий о проблемах общественного достояния касается меня, когда вы сказали в комментарии: «Если вы имеете в виду лицензирование и размещение его где-то - нет ... он живет здесь на SO только сейчас. " При всем уважении (действительно подразумеваемом) к автору, действительно ли у вас есть право сделать это ограничение, как только вы разместите код на SO ??? Например, действительно ли кто-либо из нас имеет право ограничить публикацию нашего кода на SO, например, как git gist? Кто угодно?
Николас Петерсен
6
@NicholasPetersen - Я думаю, вы не поняли. В прямом ответе полковнику Панику я сообщил ему, что лично я не лицензировал и не размещал его где-либо еще. (На самом деле, так как вы упомянули об этом, я сделал суть: gist.github.com/mattmc3/6486878 ). Но это бесплатный код. Возьми и делай, что хочешь с этим. Я написал это 100% для моего личного использования. Это не обременено. Наслаждаться. На самом деле, если кто-то из Microsoft когда-либо прочитает это, я полностью ожидаю, что он выполнит свой долг и, наконец, включит его в следующую версию .NET. Нет необходимости приписывать.
mattmc3
2
Что если TKeyесть int? Как this[]будет работать в таком случае?
VB
2
@klicker - просто используйте обычную индексацию стиля массива. Порядок вставки поддерживается как список. Преобразование типов позволяет определить, хотите ли вы индексировать с помощью int или ссылаться через тип ключа. Если ключ имеет тип int, вам нужно использовать метод GetValue ().
mattmc3
32

Для записи существует универсальная KeyedCollection, которая позволяет индексировать объекты с помощью int и ключа. Ключ должен быть встроен в значение.

Гийом
источник
2
Это не поддерживает порядок инициализации, такой как OrderedDictionary! Проверьте мой ответ.
JoelFan
14
Это поддерживает порядок добавления / вставки.
Гийом
да, это так ... откуда вы, ребята, взяли это понятие, что коллекция ключей сортирует предметы ... я наткнулся на это во второй раз
Boppity Bop
1
Это определенно поддерживает порядок инициализации. Полезные ссылки включают stackoverflow.com/a/11802824/9344 и geekswithblogs.net/NewThingsILearned/archive/2010/01/07/… .
Тед
+1, это похоже на лучшее решение в рамках. Однако необходимость реализации абстрактного класса для каждой пары типов, которые вы хотите использовать, является своего рода перетаскиванием. Вы можете сделать это с помощью одной общей реализации, для которой требуется интерфейс, но тогда вам нужно будет реализовать интерфейс для каждого типа, который вы хотите сохранить.
DCShannon
19

Вот странная находка: пространство имен System.Web.Util в System.Web.Extensions.dll содержит универсальный 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, но я предполагаю, что вы можете просто скопировать вставить код и использовать его (он внутренний, поэтому не может использовать его напрямую). Похоже, что реализация использует стандартный словарь и отдельные списки Key / Value. Довольно просто ...

Исмаил Дегани
источник
2
System.Runtime.Collectionsтакже содержит внутреннее, OrderedDictionary<TKey, TValue>которое просто обтекает неуниверсальную версию
VB
1
System.Web.Util.OrderedDictionary<TKey, TValue>внутренне реализовано вокруг универсального Dictionary. Как ни странно, это не реализуется, IListноICollection<KeyValuePair<TKey, TValue>>
Михаил
1
@rboy Как я уже сказал - он был внутренним и содержал неуниверсальную реализацию. Но это было 3 с лишним года назад ... Для размеров ниже пары сотен линейный поиск List<KeyValuePair<TKey,TValue>>будет конкурентоспособным из-за схемы доступа к памяти, для больших размеров просто используйте тот же список + 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
JoelFan
источник
3
Спасибо! Я не осознавал, что инициализаторы коллекций были просто специальным синтаксисом для Addметодов.
Сэм
10
Это не словарь. Словарь означает индексирование по ключам и не дублирует ключи .
Nawfal
тем не менее, если вам не нужно индексировать по ключу (что не так уж сложно добавить) и дублировать ключи, это пригодится
stijn
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состоит в том, что пользователи OrderedDictionary<TKey,TValue>ожидают, что смогут индексировать ее численно с помощью intили путем поиска с помощью TKey. Когда единственным типом ключа был Object, как в случае с неуниверсальным OrderedDictionary, тип аргумента, передаваемый индексатору, было бы достаточно, чтобы отличить, какой тип операции индексации должен быть выполнен. Тем не менее, неясно, как OrderedDictionary<int, TValue>должен вести себя индексатор .

Если классы like Drawing.Pointрекомендовали и следовали правилу, согласно которому кусочно-изменяемые структуры должны предоставлять свои изменяемые элементы в виде полей, а не свойств, и воздерживаться от использования модификаторов свойств, которые изменяют this, тогда они OrderedDictionary<TKey,TValue>могли бы эффективно предоставлять ByIndexсвойство, которое возвращало Indexerструктуру, которая содержала ссылку на словарь и имел индексированное свойство, геттер и сеттер назвал бы GetByIndexи SetByIndexна него. Таким образом, можно сказать что-то вроде MyDict.ByIndex[5] += 3;добавления 3 к шестому элементу словаря.

К сожалению, чтобы компилятор мог принять такую ​​вещь, было бы необходимо заставить ByIndexсвойство возвращать новый экземпляр класса, а не структуру при каждом вызове, исключая преимущества, которые можно было бы получить, избегая бокса.

В VB.NET эту проблему можно обойти, используя именованное индексированное свойство ( MyDict.ByIndex[int]то есть член MyDict, а не MyDict.ByIndexобязательный член, MyDictвключающий индексатор), но C # не допускает таких вещей.

Возможно, все еще стоило бы предложить OrderedDictionary<TKey,TValue> where TKey:class, но во многом причина предоставления обобщений в первую очередь заключалась в том, чтобы разрешить их использование с типами значений.

Supercat
источник
Хороший момент в том, что intключи с типами представляют проблему, но этого можно избежать, следуя примеру связанного SortedList<TKey, TValue>типа: поддержка ключей только с помощью [...]и использование .Values[...]для доступа по индексу. ( SortedDictionary<TKey, TValue>напротив, который не оптимизирован для индексированного доступа, требует использования .ElementAt(...).Value)
mklement0
7

Для многих целей я нашел, что можно обойтись с List<KeyValuePair<K, V>>. (Нет, если вам нужно расширить его Dictionary, очевидно, и нет, если вам нужен лучший, чем O (n) поиск по значению ключа.)

Дэвид Моулз
источник
Просто пришел к такому же выводу и сам!
Питер
1
Как я уже сказал, «для многих целей».
Дэвид Моулз
2
Вы также можете использовать, Tuple<T1, T2>если они не имеют отношения ключ-значение.
cdmckay
1
Какой смысл иметь пары ключ-значение, если вы не можете индексировать по ключу?
DCShannon
1
@DCShannon Вы все еще можете сопоставить ключи со значениями, но вы не можете искать их быстрее, чем O (n) (или автоматически обрабатывать дубликаты ключей). Для небольших значений n этого иногда достаточно, особенно в тех случаях, когда вы все равно часто просматриваете все ключи.
Дэвид Моулз
5

Есть SortedDictionary<TKey, TValue>. Хотя семантически близко, я не утверждаю, что это то же самое, что OrderedDictionaryпросто потому, что это не так. Даже из характеристик производительности. Тем не менее, очень интересное и довольно важное различие между Dictionary<TKey, TValue>(и в той степени OrderedDictionaryи реализациями, представленными в ответах) и SortedDictionaryв том, что последний использует двоичное дерево внизу. Это критическое различие, потому что оно делает класс невосприимчивым к ограничениям памяти, применяемым к универсальному классу. Посмотрите эту ветку о OutOfMemoryExceptionsброске, когда универсальный класс используется для обработки большого набора пар ключ-значение.

Как определить максимальное значение параметра емкости, передаваемого в конструктор Dictionary, чтобы избежать исключения OutOfMemoryException?

Schultz9999
источник
Есть ли способ получить ключи или значения SortedDictionary в порядке их добавления ? Вот что OrderedDictionaryпозволяет. Отличная концепция, чем отсортированная . (Я делал эту ошибку в прошлом; я думал, что предоставление конструктора Comparer для OrderedDictionary сделает его сортированным, но все, что он делает с Comparer, это определяет равенство ключей; например, сравнение с нечувствительной к строковым значениям разрешает поиск с учетом нечувствительной к строковым значениям.)
ToolmakerSteve
5

Да, это неудачное упущение. Я скучаю по Python's 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 . Я абсолютно ненавижу это соглашение, и это была 1 свобода, которую я взял в этой реализации. Если это важно для вас, просто замените строку для 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» и убедитесь, что установлен флажок «Включить предварительный выпуск».

Обновлю этот пост, если и когда тип станет официально доступным.

Чарли
источник
Можете ли вы оценить, когда он будет выпущен?
Мворисек
Я не принимаю участия в разработке этой библиотеки, поэтому, к сожалению, понятия не имею. Вероятно, это будет встроенная коллекция фреймворков, если она когда-нибудь будет «одобрена».
Чарли
1

Я реализовал общее OrderedDictionary<TKey, TValue>, обтекание SortedList<TKey, TValue>и добавление частного Dictionary<TKey, int> _order. Затем я создал внутреннюю реализацию Comparer<TKey>, передав ссылку на словарь _order. Затем я использую этот компаратор для внутреннего SortedList. Этот класс сохраняет порядок элементов, передаваемых конструктору, и порядок дополнений.

Эта реализация имеет почти те же большие характеристики O, что SortedList<TKey, TValue>и добавление и удаление в _order - O (1). Каждый элемент займет (в соответствии с книгой «C # 4 в двух словах», стр. 292, таблица 7-1) дополнительное пространство памяти, равное 22 (накладные расходы) + 4 (порядок int) + размер 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
    // ...
}
VB
источник
Существует как минимум четыре различных варианта использования, которые я могу увидеть для чего-то вроде в OrderedDictionaryотношении вставок или удалений: (1) никогда не будет никаких удалений; (2) будут исключения, но важно, чтобы элементы перечислялись в порядке добавления; нет необходимости доступа по индексу; (3) числовой индекс элемента должен (или, по крайней мере, может) оставаться постоянным, и в течение срока действия коллекции будет добавлено не более 2 миллиардов элементов, поэтому, если элемент № 7 будет удален, больше никогда не будет пункт № 7; (4) индекс предмета должен быть его рангом по отношению к выжившим.
суперкат
1
Сценарии # 1 могут быть обработаны с использованием массива параллельно с Dictionary. Сценарии № 2 и № 3 могут быть обработаны, если каждый элемент поддерживает индекс, сообщающий, когда он был добавлен, и ссылки на элементы, добавленные до или после него. Сценарий № 4 является единственным, который, по-видимому, не сможет достичь производительности O (1) для операций в произвольной последовательности. В зависимости от моделей использования, № 4 может помочь при использовании различных стратегий ленивого обновления (вести учет в дереве и вносить изменения в узел как недействительный, а не обновлять узел и его родителей).
суперкат
1
Внутренний SortedList имеет элементы в порядке вставки благодаря использованию пользовательского компаратора. Это может быть медленно, но ваш комментарий о перечислении неверен. Показать тесты по перечислению ...
VB
1
О какой строке с ToDictionary вы говорите? Это не влияет на внутренний список, а только на порядок словаря.
VB
1
@VB Извинения, я пропустил оба. Как таковой еще не проверял. Тогда единственная проблема будет с дополнением. Два поиска по словарю, а также две вставки. Я отменю понижение голосов.
nawfal