Немає загальної реалізації OrdersDictionary?


136

Здається, не існує загальної реалізації OrderedDictionary(яка знаходиться в System.Collections.Specializedпросторі імен) в .NET 3.5. Чи є такий, який мені не вистачає?

Я знайшов реалізацію там, щоб забезпечити функціональність, але поцікавився, чи / чому не існує загальної реалізації нестандартної програми, і якщо хтось знає, чи є це щось у .NET 4.0?


1
Ось реалізація OrderedDictionary<T>: codeproject.com/Articles/18615/…
Тім

2
можливий дублікат колекції пари
nawfal

Моя реалізація OrdersDictionary <T> має O (1) вставити / видалити, оскільки для підтримки порядку вставки використовується LinkedList замість ArrayList: Clintonbrennan.com/2013/12/…
Клінтон

2
Якщо вам просто потрібно мати можливість повторити записи в тому порядку, коли вони були додані, тоді Список <KeyValuePair <TKey, TValue >> може бути досить хорошим. (Звичайно, не загальне рішення, але досить хороший для деяких цілей.)
йойо

1
Це прикро упущення. Є й інші хороші типи даних Systems.Collections.Generic. Давайте OrderedDictionary<TKey,TValue>запитаємо.
Полковник Паніка

Відповіді:



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

І жодна реалізація не була б повноцінною без кількох тестів (але трагічно, ТАК не дасть мені розмістити стільки коду в одному дописі), тому мені доведеться залишити вас написати свої тести. Але я залишив декілька з них, щоб ви могли зрозуміти, як це працює:

// 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
За загальнодоступним доступом, ви запитуєте, чи можете ви користуватися ним, змінювати його та ставитися до нього так, як це - ваше безтурботно? - так. Почувайся вільно. Якщо ви маєте на увазі ліцензію та десь розміщуєте її - ні ... вона зараз живе тут на ТАК.
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 та key. Ключ повинен бути вбудований у значення.


2
Це не підтримує порядок ініціалізації, як OrdersDictionary! Перевірте мою відповідь.
JoelFan

14
Він підтримує порядок додавання / вставки.
Гійом

так, це так .. звідки ви, хлопці, зрозуміли, що keyedcollection сортує предмети ... я натрапив на цей другий раз
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 містить загальний OrdersDictionary

// 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не є родовим типом. Подивіться, немає кутових дужок на сторінці документа, яку ви пов’язали: P
user7610

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
Це не словник. Словник розшифровується як індексація за допомогою клавіш та відсутність дублікатів ключів .
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, або шляхом пошуку a TKey. Коли єдиним типом ключа був Object, як це було у випадку негенеричного OrderedDictionaryтипу, тип аргументу, переданого індексатору, був би достатнім, щоб визначити, який тип операції індексації слід виконувати. Оскільки це, однак, незрозуміло, як OrderedDictionary<int, TValue>повинен поводитись індексатор .

Якщо такі класи, як 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, але більша частина причин надання загальної інформації в першу чергу полягала в тому, щоб дозволити їх використання з типовими значеннями.


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

7

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


Просто я прийшов до такого самого висновку!
Петро

1
Як я вже сказав, "для багатьох цілей".
Девід Молес

2
Ви також можете використовувати a, Tuple<T1, T2>якщо вони не мають співвідношення ключ-значення.
cdmckay

1
Який сенс мати пари ключових значень, якщо ви не можете індексувати за ключем?
DCShannon

1
@DCShannon Ви все ще можете зіставляти ключі до значень, ви просто не можете їх шукати швидше, ніж O (n) (або автоматично обробляти дублюючі ключі). Для невеликих значень n, що іноді достатньо добре, особливо в ситуаціях, коли ти зазвичай все-таки перебираєш усі клавіші.
Девід Молес

5

Існує SortedDictionary<TKey, TValue>. Хоча семантично близькі, але я не стверджую, що це те саме, що OrderedDictionaryпросто тому, що їх немає. Навіть з експлуатаційних характеристик. Однак дуже цікава і досить важлива відмінність Dictionary<TKey, TValue>(а в тому обсязі OrderedDictionaryі реалізацій, передбачених у відповідях), SortedDictionaryполягає в тому, що в останньому використовується бінарне дерево під ним. Це вирішальне відмінність, оскільки воно робить клас імунізованим до обмежень пам'яті, застосованих до класу родових. Дивіться цю тему про OutOfMemoryExceptionsкинуте, коли загальний клас використовується для обробки великого набору пар ключ-значення.

Як з'ясувати максимальне значення параметра ємності, переданого конструктору словника, щоб уникнути OutOfMemoryException?


Чи є спосіб отримати ключі або значення а SortedDictionary в тому порядку, коли вони були додані ? Ось що OrderedDictionaryдозволяє. Інше поняття, ніж відсортоване . (Я робив цю помилку в минулому; я думав, що подання порівняльника до конструктора OrriedDictionary зробить його відсортованим, але все, що він робить із Порівняльником, визначає рівність ключів; наприклад, нечутливий до рядків порівнянь дозволяє шукати нечутливий рядок ключів.)
ToolmakerSteve

5

Правильно, це нещасний упущення. Мені не вистачає Python's OrriedDict

Словник, який запам'ятовує порядок введення ключів вперше. Якщо новий запис замінить існуючий запис, вихідне положення вставки залишається незмінним. Видалення запису та його повторне вставлення перемістить його до кінця.

Тому я написав власний 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, реалізація загального OrdersDictionary була прийнята в .NET CoreFX Lab. Якщо все піде добре, тип врешті-решт буде затверджений та інтегрований у основний репост .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>і додавши приватне 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
    // ...
}

Є щонайменше чотири різні випадки використання, які я можу побачити на щось подібне OrderedDictionary, що стосується вставок чи видалень: (1) Ніколи не буде жодних видалень; (2) буде видалення, але важливо, щоб пункти перераховувались у порядку доданого; немає потреби в доступі за індексом; (3) числовий індекс предмета повинен (або принаймні може) залишатися незмінним, і протягом життя колекції буде додано не більше 2 мільярдів предметів, тому якщо предмет № 7 буде видалений, він більше ніколи не буде пункт №7; (4) індекс предмета має бути його рангом щодо тих, хто вижив.
supercat

1
Сценарії №1 можна обробити, використовуючи масив паралельно до Dictionary. Сценарії №2 та №3 можна обробляти, якщо кожен елемент підтримуватиме індекс із зазначенням, коли він доданий, та посилання на елементи, додані до або після нього. Сценарій №4 є єдиним, який, здавалося б, не повинен мати змогу досягти продуктивності O (1) для операцій у довільній послідовності. Залежно від моделей використання, №4 може допомогти, використовуючи різні ліниві стратегії оновлення (зберігайте підрахунок у дереві та внесення змін до вузла недійсним, а не оновлення вузла та його батьків).
supercat

1
Внутрішній SortedList має елементи в порядку вставки завдяки використанню користувацького порівняння. Це може бути повільним, але ваш коментар про перерахування неправильний. Покажіть тести про перерахування ...
VB

1
Про який рядок із ToDictionary ви говорите? Це не впливає на внутрішній список, а лише на словник замовлення.
ВБ

1
@ VB Вибачте, я пропустив обидва. Як такого не перевіряли. Тоді єдиною проблемою було б додавання. Два пошукові словники, а також дві вставки. Я скасую шлюбну заявку.
nawfal
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.