Tidak ada implementasi generik OrderedDictionary?

141

Tampaknya tidak ada implementasi umum OrderedDictionary(yang ada di System.Collections.Specializednamespace) di .NET 3.5. Apakah ada satu yang saya lewatkan?

Saya telah menemukan implementasi di luar sana untuk menyediakan fungsionalitas, tetapi bertanya-tanya apakah / mengapa tidak ada implementasi generik out-of-the-box dan jika ada yang tahu apakah itu sesuatu di .NET 4.0?

AdaTheDev
sumber
1
Berikut adalah implementasi dari OrderedDictionary<T>: codeproject.com/Articles/18615/…
Tim Schmelter
Implementasi OrderedDictionary <T> saya memiliki penyisipan / penghapusan O (1) karena menggunakan LinkedList, bukan ArrayList untuk mempertahankan urutan penyisipan: clintonbrennan.com/2013/12/…
Clinton
2
Jika Anda hanya perlu untuk dapat mengulang entri dalam urutan penambahannya, maka Daftar <KeyValuePair <TKey, TValue >> mungkin cukup baik. (Memang, bukan solusi umum, tapi cukup baik untuk beberapa tujuan.)
yoyo
1
Ini adalah kelalaian yang tidak menguntungkan. Ada tipe data bagus lainnya Systems.Collections.Generic. Mari kita minta OrderedDictionary<TKey,TValue>.NET 5. Seperti yang ditunjukkan orang lain, kuncinya adalah int merosot, dan akan membutuhkan perawatan khusus.
Kolonel Panic

Jawaban:

60

Kamu benar. Tidak ada padanan generik OrderedDictionarydalam kerangka itu sendiri.

(Itu juga masih terjadi untuk .NET 4, sejauh yang saya ketahui.)

Tetapi Anda dapat memilihnya di Visual Studio's UserVoice (2016-10-04)!

LukeH
sumber
98

Menerapkan generik OrderedDictionarytidak terlalu sulit, tetapi memakan waktu yang tidak perlu dan terus terang kelas ini merupakan pengawasan besar di pihak Microsoft. Ada beberapa cara untuk menerapkan ini, tetapi saya memilih menggunakan a KeyedCollectionuntuk penyimpanan internal saya. Saya juga memilih untuk menerapkan berbagai metode untuk menyortir dengan cara yang List<T>dilakukan karena ini pada dasarnya adalah IList dan IDictionary hybrid. Saya telah memasukkan implementasi saya di sini untuk anak cucu.

Berikut antarmukanya. Perhatikan bahwa itu termasuk System.Collections.Specialized.IOrderedDictionary, yang merupakan versi non-generik dari antarmuka ini yang disediakan oleh 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);
    }

}

Berikut implementasinya bersama dengan kelas helper:

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

Dan tidak ada implementasi yang akan lengkap tanpa beberapa tes (tapi tragisnya, SO tidak akan membiarkan saya memposting banyak kode dalam satu posting), jadi saya harus meninggalkan Anda untuk menulis tes Anda. Tapi, saya tinggalkan beberapa di antaranya sehingga Anda bisa mendapatkan gambaran tentang cara kerjanya:

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

- PEMBARUAN -

Sumber untuk ini dan pustaka inti .NET hilang yang sangat berguna lainnya di sini: https://github.com/mattmc3/dotmore/blob/master/dotmore/Collections/Generic/OrderedDictionary.cs

mattmc3
sumber
6
Dengan domain publik, apakah Anda bertanya apakah Anda dapat menggunakannya, memodifikasinya, dan memperlakukannya seperti milik Anda tanpa khawatir - ya. Merasa bebas. Jika Anda bermaksud melisensikannya dan menyimpannya di suatu tempat - tidak ... ia tinggal di sini di SO hanya untuk saat ini.
mattmc3
3
@ mattmc3 Terima kasih atas kode Anda, Pak, tetapi komentar Anda tentang masalah domain publik mengkhawatirkan saya, ketika Anda berkata dalam komentar: "Jika Anda bermaksud melisensikannya dan menyimpannya di suatu tempat - tidak ... ia tinggal di sini di SO hanya untuk saat ini. " Dengan segala hormat (benar-benar bermaksud) kepada penulis, apakah Anda benar-benar berhak membuat batasan itu, setelah Anda memposting kode di SO ??? Misalnya, apakah ada di antara kita yang benar-benar memiliki hak untuk membatasi kode di SO agar tidak diposting, misalnya, sebagai intinya? Siapa saja?
Nicholas Petersen
7
@NicholasPetersen - Saya pikir Anda salah paham. Sebagai tanggapan langsung kepada Kolonel Panic, saya memberi tahu dia bahwa saya secara pribadi tidak melisensikan atau menyimpannya di tempat lain. (Sebenarnya, sejak Anda menyebutkannya, saya membuat intinya: gist.github.com/mattmc3/6486878 ). Tapi ini kode bebas lisensi. Ambillah dan lakukan apa yang Anda inginkan dengannya. Saya menulisnya 100% untuk penggunaan pribadi saya. Itu tidak terbebani. Nikmati. Faktanya, jika seseorang dari Microsoft pernah membaca ini, saya sangat mengharapkan mereka untuk melakukan tugas mereka dan akhirnya memasukkannya ke dalam rilis .NET berikutnya. Tidak perlu atribusi.
mattmc3
2
Bagaimana jika TKeyyang int? Bagaimana cara this[]kerjanya dalam kasus seperti itu?
VB
2
@ mattmc3 Berbagi ini, dan terutama seluruh perpustakaan dotmore Anda, benar-benar merupakan inspirasi terhadap kode yang dapat saya bagikan. Saya tahu banyak orang berbagi banyak perpustakaan antara ukuran kecil dan besar, tetapi tetap saja, itu selalu layak mendapatkan lebih dari sekedar ucapan terima kasih.
Joe Reguler
35

Sebagai catatan, ada KeyedCollection generik yang memungkinkan objek diindeks oleh int dan kunci. Kuncinya harus tertanam dalam nilainya.

Guillaume
sumber
2
Ini tidak mempertahankan urutan inisialisasi seperti OrderedDictionary! Lihat jawaban saya.
JoelFan
14
Itu menjaga urutan penambahan / penyisipan.
Guillaume
ya itu benar .. dari mana kalian mendapat anggapan bahwa item jenis keyedcollection ... saya tersandung pada kali kedua ini
Boppity Bop
1
Itu pasti mempertahankan urutan inisialisasi. Tautan yang berguna termasuk stackoverflow.com/a/11802824/9344 dan geekswithblogs.net/NewThingsILearned/archive/2010/01/07/… .
Ted
1
@DCShannon, Anda dapat menulis implementasi yang membutuhkan Func <TValue, TKey> menghindari kebutuhan untuk mengimplementasikan antarmuka / mengganti metode.
Guillaume
19

Berikut adalah penemuan yang aneh: namespace System.Web.Util di System.Web.Extensions.dll berisi OrderedDictionary generik

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

Tidak yakin mengapa MS menempatkannya di sana daripada paket System.Collections.Generic, tetapi saya berasumsi Anda cukup menyalin dan menempel kode dan menggunakannya (ini internal, jadi tidak dapat menggunakannya secara langsung). Sepertinya penerapannya menggunakan kamus standar dan daftar Kunci / Nilai terpisah. Cukup mudah ...

Ismail Degani
sumber
2
System.Runtime.Collectionsjuga berisi internal OrderedDictionary<TKey, TValue>yang hanya membungkus versi non-generik
VB
1
System.Web.Util.OrderedDictionary<TKey, TValue>diimplementasikan secara internal di sekitar generik Dictionary. Anehnya itu tidak diimplementasikan IListtetapiICollection<KeyValuePair<TKey, TValue>>
Mikhail
1
@rboy Seperti yang saya katakan - itu internal dan membungkus implementasi non-generik. Tapi itu 3+ tahun yang lalu ... Untuk ukuran di bawah beberapa ratusan pencarian linier List<KeyValuePair<TKey,TValue>>akan kompetitif karena pola akses memori, untuk ukuran yang lebih besar cukup gunakan daftar yang sama + Dictionary<TKey,int>sebagai pencarian. AFAIK tidak ada struktur data yang lebih baik dalam hal kecepatan / memori di BigO.
VB
1
@rboy di sini adalah tautan ke yang generik , ini merujuk pada yang non-generik yang menggunakan HashTable. Saya benar-benar yakin bahwa untuk ukuran kecil menggunakan pencarian linier pada Daftar / Array generik akan lebih cepat.
VB
1
@PeterMortensen System.Collections.Specialized.OrderedDictionarybukanlah tipe generik. Lihat bu, tidak ada tanda kurung siku di halaman dokumen yang Anda
tautkan
17

Untuk apa nilainya, inilah cara saya menyelesaikannya:

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

Ini dapat diinisialisasi seperti ini:

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

dan diakses seperti ini:

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

// Guaranteed to print in the order of initialization
JoelFan
sumber
3
Terima kasih! Saya tidak menyadari bahwa penginisialisasi koleksi hanyalah sintaks khusus untuk Addmetode.
Sam
10
Ini bukan kamus. Dictionary adalah singkatan dari keyed indexing dan no duplikat kunci .
nawfal
namun jika Anda kebetulan tidak perlu mengindeks dengan kunci (yang tidak terlalu sulit untuk ditambahkan) dan kunci duplikat, ini akan berguna
stijn
1
Anda memiliki masalah adalah panggilan kode pairList.Add(new KeyValuePair<K,V>())(yaitu metode di Listkelas). Dalam hal ini, itsIndexkamus tidak diperbarui, dan sekarang daftar dan kamus tidak sinkron. Bisa menyembunyikan List.Addmetode dengan membuat new PairList.Add(KeyValuePair<K,V>)metode, atau bisa menggunakan komposisi alih-alih warisan dan hanya menerapkan semua Listmetode lagi ... lebih banyak kode ...
neizan
1
@nawfal, untuk mengatasi kekhawatiran Anda, seseorang dapat menambahkan centang seperti: if (itsindex.Contains(key)) return;di awal Adduntuk mencegah duplikat
JoelFan
14

Masalah konseptual utama dengan versi generik dari OrderedDictionaryadalah bahwa pengguna OrderedDictionary<TKey,TValue>diharapkan dapat mengindeksnya baik secara numerik menggunakan int, atau dengan pencarian menggunakan TKey. Jika satu-satunya jenis kunci adalah Object, seperti halnya non-generik OrderedDictionary, jenis argumen yang diteruskan ke pengindeks akan cukup untuk membedakan apakah jenis operasi pengindeksan harus dilakukan. Namun, karena itu, tidak jelas bagaimana OrderedDictionary<int, TValue>seharusnya pengindeks sebuah berperilaku.

Jika kelas seperti Drawing.Pointtelah merekomendasikan dan mengikuti aturan bahwa struktur yang dapat diubah sepotong-sepotong harus mengekspos elemen mereka yang dapat berubah sebagai bidang daripada properti, dan menahan diri dari menggunakan penyetel properti yang memodifikasi this, maka secara OrderedDictionary<TKey,TValue>efisien dapat mengekspos ByIndexproperti yang mengembalikan Indexerstruct yang memiliki referensi ke kamus, dan memiliki properti terindeks yang pengambil dan penyetel akan memanggil GetByIndexdan mengikutinya SetByIndex. Jadi, orang bisa mengatakan sesuatu seperti MyDict.ByIndex[5] += 3;menambahkan 3 ke elemen keenam kamus.

Sayangnya, bagi kompiler untuk menerima hal seperti itu, perlu membuat ByIndexproperti mengembalikan instance kelas baru daripada struct setiap kali dipanggil, menghilangkan keuntungan yang didapat dengan menghindari tinju.

Di VB.NET, seseorang bisa mengatasi masalah itu dengan menggunakan properti terindeks bernama (jadi MyDict.ByIndex[int]akan menjadi anggota, MyDictdaripada harus MyDict.ByIndexmenjadi anggota MyDictyang menyertakan pengindeks), tetapi C # tidak mengizinkan hal seperti itu.

Mungkin masih bermanfaat untuk menawarkannya OrderedDictionary<TKey,TValue> where TKey:class, tetapi sebagian besar alasan untuk menyediakan obat generik adalah untuk memungkinkan penggunaannya dengan tipe nilai.

supercat
sumber
Poin bagus yang int-typeed keys memberikan tantangan, tetapi itu bisa dihindari dengan mengikuti contoh SortedList<TKey, TValue>tipe terkait : hanya mendukung kunci dengan [...], dan memerlukan penggunaan .Values[...]untuk akses oleh indeks. ( SortedDictionary<TKey, TValue>, sebaliknya, yang tidak dioptimalkan untuk akses yang diindeks, memerlukan penggunaan .ElementAt(...).Value)
mklement0
Ini (int sebagai key vs int as index) sering muncul (bagi saya) di PowerShell. Versi PowerShell yang lebih baru memberikan akses mudah ke OrderedDictionary tanpa tipe melalui [dipesan] @ {}. Solusi norak saya untuk mendapatkan int sebagai kunci dan indeks adalah dengan membuang int ke lama ketika saya menginginkannya sebagai kunci. Tapi, sekarang kamu membuatku berpikir tentang ini. . .PowerShell mengekspos pengindeks sebagai Item () dan $ odict.Item ([object] $ key) akan memaksa PowerShell untuk menggunakan int sebagai kunci. Lebih bertele-tele, tetapi tidak terlalu keras. Terima kasih!
unbob
7

Untuk banyak tujuan, saya telah menemukan orang bisa bertahan dengan a List<KeyValuePair<K, V>>. (Tidak jika Anda membutuhkannya untuk memperluas Dictionary, tentu saja, dan tidak jika Anda membutuhkan pencarian nilai kunci yang lebih baik daripada O (n).)

David Moles
sumber
Saya sendiri baru saja sampai pada kesimpulan yang sama!
Peter
1
Seperti yang saya katakan, "untuk banyak tujuan."
David Moles
2
Anda juga dapat menggunakan Tuple<T1, T2>jika mereka tidak memiliki hubungan nilai kunci.
cdmckay
1
Apa gunanya memiliki pasangan nilai kunci jika Anda tidak dapat mengindeks dengan kunci?
DCShannon
1
@DCShannon Anda masih dapat memetakan kunci ke nilai, Anda tidak dapat mencarinya lebih cepat dari O (n) (atau menangani kunci duplikat secara otomatis). Untuk nilai n yang kecil terkadang cukup baik, terutama dalam situasi di mana Anda biasanya mengulang semua kunci.
David Moles
5

Ada SortedDictionary<TKey, TValue>. Meskipun secara semantik dekat, saya tidak mengklaim itu sama OrderedDictionaryhanya karena mereka tidak. Bahkan dari karakteristik performanya. Namun perbedaan yang sangat menarik dan cukup penting antara Dictionary<TKey, TValue>(dan sejauh itu OrderedDictionarydan implementasi disediakan dalam jawaban) dan SortedDictionaryyang terakhir menggunakan pohon biner di bawahnya. Ini adalah perbedaan penting karena membuat kelas kebal terhadap batasan memori yang diterapkan ke kelas generik. Lihat utas tentang OutOfMemoryExceptionsdilempar saat kelas generik digunakan untuk menangani sekumpulan besar pasangan nilai kunci.

Bagaimana cara mengetahui nilai maks untuk parameter kapasitas yang diteruskan ke konstruktor kamus untuk menghindari OutOfMemoryException?

Schultz9999
sumber
Apakah ada cara untuk mendapatkan kunci atau nilai a SortedDictionary dalam urutan penambahannya ? Itulah yang OrderedDictionarymemungkinkan. Konsep berbeda dari pada diurutkan . (Saya telah membuat kesalahan ini di masa lalu; Saya pikir menyediakan konstruktor Comparer to OrderedDictionary akan membuatnya diurutkan, tetapi semua yang dilakukannya dengan Comparer adalah menentukan persamaan kunci; misalnya pembanding tidak sensitif string memungkinkan pencarian kunci tidak sensitif string.)
ToolmakerSteve
5

Benar, ini adalah kelalaian yang tidak menguntungkan. Saya merindukan OrderedDict dari Python

Kamus yang mengingat urutan saat kunci dimasukkan pertama kali. Jika entri baru menimpa entri yang sudah ada, posisi penyisipan awal dibiarkan tidak berubah. Menghapus entri dan memasukkannya kembali akan memindahkannya ke akhir.

Jadi saya menulis OrderedDictionary<K,V>kelas saya sendiri di C #. Bagaimana cara kerjanya? Itu memelihara dua koleksi - kamus tidak berurutan vanilla dan daftar kunci yang dipesan. Dengan solusi ini, operasi kamus standar menjaga kerumitannya dengan cepat, dan pencarian menurut indeks juga cepat.

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

Berikut antarmukanya

/// <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);
}
Kolonel Panik
sumber
3

Sebagai tindak lanjut dari komentar dari @VB, berikut adalah implementasi System.Runtime.Collections.OrderedDictionary <,> yang dapat diakses . Saya awalnya akan mengaksesnya dengan refleksi dan memberikannya melalui pabrik tetapi dll ini tampaknya tidak dapat diakses sama sekali jadi saya hanya menarik sumbernya sendiri.

Satu hal yang perlu diperhatikan adalah pengindeks di sini tidak akan melempar KeyNotFoundException . Saya benar-benar membenci konvensi itu dan itulah kebebasan pertama yang saya ambil dalam penerapan ini. Jika itu penting bagi Anda, cukup ganti baris untuk return default(TValue);. Menggunakan C # 6 ( kompatibel dengan 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;
    }
}

Permintaan pull / diskusi diterima di GitHub

Chris Marisic
sumber
3

Bagi mereka yang mencari opsi paket "resmi" di NuGet, implementasi OrderedDictionary generik telah diterima di .NET CoreFX Lab. Jika semuanya berjalan dengan baik, jenis tersebut pada akhirnya akan disetujui dan diintegrasikan ke repo utama .NET CoreFX.

Ada kemungkinan penerapan ini akan ditolak.

Implementasi yang berkomitmen dapat dirujuk di sini https://github.com/dotnet/corefxlab/blob/57be99a176421992e29009701a99a370983329a6/src/Microsoft.Experimental.Collections/Microsoft/Collections/Extensions/OrderedDictionary.cs

Paket NuGet yang secara definitif memiliki tipe ini tersedia untuk digunakan dapat ditemukan di sini https://www.nuget.org/packages/Microsoft.Experimental.Collections/1.0.6-e190117-3

Atau Anda dapat menginstal paket dalam Visual Studio. Jelajahi paket "Microsoft.Experimental.Collections" dan pastikan kotak centang "Sertakan prarilis" dipilih.

Akan memperbarui posting ini jika dan ketika jenisnya tersedia secara resmi.

charlie
sumber
Bisakah Anda memperkirakan kapan akan dirilis?
mvorisek
Saya tidak mengambil bagian dalam pengembangan perpustakaan ini, jadi sayangnya saya tidak tahu. Kemungkinan itu akan menjadi kumpulan kerangka kerja bawaan jika pernah mendapat "disetujui".
Charlie
1

Saya menerapkan generik OrderedDictionary<TKey, TValue>dengan membungkus SortedList<TKey, TValue>dan menambahkan file pribadi Dictionary<TKey, int> _order. Lalu saya membuat implementasi internal Comparer<TKey>, meneruskan referensi ke kamus _order. Kemudian saya menggunakan pembanding ini untuk SortedList internal. Kelas ini menjaga urutan elemen yang diteruskan ke konstruktor dan urutan penambahan.

Implementasi ini memiliki karakteristik O besar yang hampir sama seperti SortedList<TKey, TValue>sejak menambahkan dan menghapus ke _order adalah O (1). Setiap elemen akan mengambil (menurut buku 'C # 4 in a Nutshell', hal. 292, tabel 7-1) ruang memori tambahan 22 (overhead) + 4 (urutan int) + ukuran TKey (anggap saja 8) = 34 Bersama dengan SortedList<TKey, TValue>overhead sebesar dua byte, total overhead adalah 36 byte, sedangkan buku yang sama mengatakan bahwa non-generik OrderedDictionarymemiliki overhead sebesar 59 byte.

Jika saya meneruskan sorted=trueke konstruktor, maka _order tidak digunakan sama sekali, OrderedDictionary<TKey, TValue>persis SortedList<TKey, TValue>dengan overhead kecil untuk pembungkus, jika berarti sama sekali.

Saya akan menyimpan tidak banyak objek referensi besar di OrderedDictionary<TKey, TValue>, jadi bagi saya ca ini. Overhead 36 byte dapat ditoleransi.

Kode utama ada di bawah. Kode lengkap yang diperbarui ada di inti ini .

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
sumber
Setidaknya ada empat kasus penggunaan berbeda yang dapat saya lihat untuk sesuatu seperti OrderedDictionary, berkenaan dengan penyisipan atau penghapusan: (1) Tidak akan pernah ada penghapusan; (2) akan ada penghapusan, tetapi yang penting adalah item yang dicacah sesuai urutan ditambah; tidak perlu akses menurut indeks; (3) indeks numerik item harus (atau setidaknya mungkin) tetap konstan, dan tidak lebih dari 2 miliar item akan ditambahkan selama masa koleksi, jadi jika item # 7 dihapus, tidak akan pernah ada lagi item # 7; (4) indeks item harus menjadi peringkatnya sehubungan dengan yang selamat.
supercat
1
Skenario # 1 bisa ditangani dengan menggunakan array secara paralel dengan file Dictionary. Skenario # 2 dan # 3 dapat ditangani dengan meminta setiap item mempertahankan indeks yang mengatakan kapan ditambahkan dan tautan ke item ditambahkan sebelum atau sesudahnya. Skenario # 4 adalah satu-satunya yang tampaknya tidak dapat mencapai kinerja O (1) untuk operasi dalam urutan arbitrer. Bergantung pada pola penggunaan, # 4 dapat dibantu dengan menggunakan berbagai strategi pembaruan malas (tetap menghitung di pohon, dan memiliki perubahan pada simpul tidak valid daripada memperbarui simpul dan induknya).
supercat
1
SortedList internal memiliki elemen dalam urutan penyisipan karena penggunaan pembanding khusus. Mungkin lambat tapi komentar Anda tentang pencacahan salah. Tunjukkan tes tentang pencacahan ...
VB
1
Apa yang sejalan dengan ToDictionary yang Anda bicarakan? Itu tidak mempengaruhi daftar internal, tetapi hanya kamus pesanan.
VB
1
@VB Maaf, saya melewatkan keduanya. Karena itu belum mengujinya. Maka satu-satunya masalah adalah dengan penambahan. Dua pencarian kamus, serta dua sisipan. Saya akan membatalkan suara negatif.
nawfal
0

Ini bukan versi / solusi lain dari sebuah OrderedDictionary<,>percobaan yang saya lakukan untuk menguji masing-masing dari 4 versi yang disebutkan dalam jawaban: dari @Colonel Panic, @ mattmc3, @VB @Chris Marisic. Ini dimaksudkan sebagai umpan balik. Yah, sebagian karena saya harus mengakui bahwa saya belum membedah kodenya, jadi mungkin ada perbedaan dalam fungsi atau pemeriksaan keamanan. Tapi tetap saja, saya pikir umpan balik akan berguna untuk penampilan mereka. Dan seperti yang akan Anda lihat, waktu dapat berubah dari beberapa milidetik hingga seperempat jam. Kemudian saya menulis versi minimal yang naif dengan 2 daftar objek kelas kunci dan nilai dengan pencarian O (n) hanya untuk melihat besarnya manfaat akses O (1).

Testbed adalah Microsoft Visual Studio Community 2019 dengan Unity 3D, 4 kali berturut-turut untuk setiap pengujian dan kode yang ingin saya tiru dari skenario real-ish adalah

using System.Text;
using UnityEngine;

public class TessyOne : MonoBehaviour
{
    public const int iterations = 50000;
    private System.Diagnostics.Stopwatch stopwatch;
    private System.Random random;
    public float stopwatchDuration;

    public class Ala
    {
        public int inta;
        public float fla;
        public string stra;
        public Ben bena;

        public Ala(int i, float f, string s, Ben b)
        {
            inta = i; fla = f; stra = s; bena = b;
        }
    }

    public class Ben
    {
        public int inte;
        public float fle;
        public string stre;

        public Ben(int i, float f, string s)
        {
            inte = i; fle = f; stre = s;
        }
    }

    //public Naive.OrderedDictionary<Ala, Ben> alasToBens = new Naive.OrderedDictionary<Ala, Ben>();
    //public Hickford.OrderedDictionary<Ala, Ben> alasToBens = new Hickford.OrderedDictionary<Ala, Ben>();
    //public Mattmc3.OrderedDictionary<Ala, Ben> alasToBens = new Mattmc3.OrderedDictionary<Ala, Ben>();
    public Marisic.OrderedDictionary<Ala, Ben> alasToBens = new Marisic.OrderedDictionary<Ala, Ben>();
    //public VB.OrderedList<Ala, Ben> alasToBens = new VB.OrderedList<Ala, Ben>(null, false);

    Ala[] alarray = new Ala[iterations];
    Ben[] berray = new Ben[iterations];

    // This is the entry point of the application 
    private void Start()
    {
        stopwatch = new System.Diagnostics.Stopwatch();
        random = new System.Random(2020);

        for(int i = 0; i < iterations; ++i)
        {
            berray[i] = new Ben(random.Next(),
                                (float)random.NextDouble(),
                                MakeRandomString((ushort)random.Next(1, 10)));
            alarray[i] = new Ala(random.Next(),
                                 (float)random.NextDouble(),
                                  MakeRandomString((ushort)random.Next(1, 10)),
                                  berray[i]);
            // uncomment for testing ContainsKey() and Remove(), comment for Add()
            alasToBens.Add(alarray[i], berray[i]);
        }
    
        stopwatch.Start();
        for(int i = iterations - 1; i > -1; --i)
        {
            //alasToBens.Add(alarray[i], berray[i]);
            //alasToBens.ContainsKey(alarray[i]);
            alasToBens.Remove(alarray[i]);
        }
        stopwatch.Stop();
        stopwatchDuration = stopwatch.ElapsedMilliseconds;
    }

    public string MakeRandomString(ushort length)
    {
        StringBuilder sb = new StringBuilder();
        for(ushort u = 0; u < length; ++u)
        {
            sb.Append((char)Random.Range(33, 126)); // regular ASCII chars
        }
        return sb.ToString();
    }
}

Perhatikan bahwa pengujian tersebut untuk skenario terburuk dalam kasus versi naif setidaknya, karena iterasi melalui pengumpulan dari indeks 0 hingga iterationsdan pencarian dilakukan dari akhir ke awal. Saya mengukur Add(), ContainsKey()dan Remove()dalam milidetik untuk kamus 50000 entri. Hasil:

+----------+----------------+----------------+--------------------------------+
|    ms    |     Add()      | ContainsKey()  |            Remove()            |
+----------+----------------+----------------+--------------------------------+
| Hickford |     7, 8, 7, 8 |     2, 2, 3, 2 |         7400, 7503, 7419, 7421 |
| Mattmc3  | 23, 24, 24, 23 |     3, 3, 3, 3 | 890404, 913465, 875387, 877792 |
| Marisic  | 27, 28, 28, 27 |     4, 4, 4, 4 |     27401, 27627, 27341, 27349 |
| V.B.     | 76, 76, 75, 75 | 59, 60, 60, 60 |                 66, 67, 67, 67 |
|          |                |                |                                |
| Naive    |   19651, 19761 |   25335, 25416 |                   25259, 25306 |
+----------+----------------+----------------+--------------------------------+
mireazma
sumber
Ini ditandai sebagai Bukan Jawaban . Benar sekali. Tapi itu juga memberikan informasi berguna yang seharusnya bermanfaat bagi pembaca di masa depan, tetapi tidak cocok dengan komentar. Untuk alasan itu, saya cenderung membiarkannya berdiri.
Jeremy Caney