ไม่มีการใช้งาน OrderDictionary ทั่วไป?


141

ดูเหมือนจะไม่มีการใช้งานทั่วไปของOrderedDictionary(ซึ่งอยู่ในSystem.Collections.Specializedเนมสเปซ) ใน. NET 3.5 มีใครที่ฉันหายไป?

ฉันพบการใช้งานที่นั่นเพื่อมอบฟังก์ชันการทำงาน แต่สงสัยว่า / เหตุใดจึงไม่มีการใช้งานทั่วไปนอกกรอบและถ้าใครรู้ว่าเป็นสิ่งที่อยู่ใน.


1
นี่คือการนำไปใช้ a OrderedDictionary<T>: codeproject.com/Articles/18615/…
Tim Schmelter


การใช้งาน OrderDictionary <T> ของฉันมีการแทรก / ลบ O (1) เนื่องจากใช้ LinkedList แทน ArrayList เพื่อรักษาลำดับการแทรก: clintonbrennan.com/2013/12/…
Clinton

2
หากคุณต้องการเพียงแค่สามารถทำซ้ำรายการตามลำดับที่เพิ่มเข้ามารายการ <KeyValuePair <TKey, TValue >> อาจจะดีพอ (จริงอยู่ไม่ใช่วิธีแก้ปัญหาทั่วไป แต่ดีพอสำหรับวัตถุประสงค์บางประการ)
โยโย่

1
มันเป็นการละเลยที่โชคร้าย Systems.Collections.Genericมีหลายประเภทข้อมูลที่ดีอื่น ๆ ขอOrderedDictionary<TKey,TValue>. NET 5 ตามที่คนอื่น ๆ ชี้กรณีที่คีย์เป็น int เสื่อมและจะต้องดูแลเป็นพิเศษ
พันเอก Panic

คำตอบ:



98

การใช้งานทั่วไปOrderedDictionaryไม่ใช่เรื่องยาก แต่ก็ใช้เวลานานโดยไม่จำเป็นและตรงไปตรงมาชั้นเรียนนี้เป็นส่วนหนึ่งของ Microsoft มีหลายวิธีในการใช้สิ่งนี้ แต่ฉันเลือกใช้ a KeyedCollectionสำหรับที่เก็บข้อมูลภายในของฉัน ฉันยังเลือกที่จะใช้วิธีการต่างๆในการจัดเรียงวิธีที่List<T>ทำเนื่องจากเป็นหลัก IList และ IDictionary แบบผสม ฉันได้รวมการใช้งานของฉันไว้ที่นี่เพื่อลูกหลาน

นี่คืออินเทอร์เฟซ โปรดสังเกตว่ามันมีSystem.Collections.Specialized.IOrderedDictionaryอินเทอร์เฟซที่ไม่ใช่เวอร์ชันทั่วไปที่ Microsoft ให้

// http://unlicense.org
using System;
using System.Collections.Generic;
using System.Collections.Specialized;

namespace mattmc3.Common.Collections.Generic {

    public interface IOrderedDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IOrderedDictionary {
        new TValue this[int index] { get; set; }
        new TValue this[TKey key] { get; set; }
        new int Count { get; }
        new ICollection<TKey> Keys { get; }
        new ICollection<TValue> Values { get; }
        new void Add(TKey key, TValue value);
        new void Clear();
        void Insert(int index, TKey key, TValue value);
        int IndexOf(TKey key);
        bool ContainsValue(TValue value);
        bool ContainsValue(TValue value, IEqualityComparer<TValue> comparer);
        new bool ContainsKey(TKey key);
        new IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator();
        new bool Remove(TKey key);
        new void RemoveAt(int index);
        new bool TryGetValue(TKey key, out TValue value);
        TValue GetValue(TKey key);
        void SetValue(TKey key, TValue value);
        KeyValuePair<TKey, TValue> GetItem(int index);
        void SetItem(int index, TValue value);
    }

}

นี่คือการใช้งานพร้อมกับคลาสตัวช่วย:

// http://unlicense.org
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Linq;

namespace mattmc3.Common.Collections.Generic {

    /// <summary>
    /// A dictionary object that allows rapid hash lookups using keys, but also
    /// maintains the key insertion order so that values can be retrieved by
    /// key index.
    /// </summary>
    public class OrderedDictionary<TKey, TValue> : IOrderedDictionary<TKey, TValue> {

        #region Fields/Properties

        private KeyedCollection2<TKey, KeyValuePair<TKey, TValue>> _keyedCollection;

        /// <summary>
        /// Gets or sets the value associated with the specified key.
        /// </summary>
        /// <param name="key">The key associated with the value to get or set.</param>
        public TValue this[TKey key] {
            get {
                return GetValue(key);
            }
            set {
                SetValue(key, value);
            }
        }

        /// <summary>
        /// Gets or sets the value at the specified index.
        /// </summary>
        /// <param name="index">The index of the value to get or set.</param>
        public TValue this[int index] {
            get {
                return GetItem(index).Value;
            }
            set {
                SetItem(index, value);
            }
        }

        public int Count {
            get { return _keyedCollection.Count; }
        }

        public ICollection<TKey> Keys {
            get {
                return _keyedCollection.Select(x => x.Key).ToList();
            }
        }

        public ICollection<TValue> Values {
            get {
                return _keyedCollection.Select(x => x.Value).ToList();
            }
        }

        public IEqualityComparer<TKey> Comparer {
            get;
            private set;
        }

        #endregion

        #region Constructors

        public OrderedDictionary() {
            Initialize();
        }

        public OrderedDictionary(IEqualityComparer<TKey> comparer) {
            Initialize(comparer);
        }

        public OrderedDictionary(IOrderedDictionary<TKey, TValue> dictionary) {
            Initialize();
            foreach (KeyValuePair<TKey, TValue> pair in dictionary) {
                _keyedCollection.Add(pair);
            }
        }

        public OrderedDictionary(IOrderedDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer) {
            Initialize(comparer);
            foreach (KeyValuePair<TKey, TValue> pair in dictionary) {
                _keyedCollection.Add(pair);
            }
        }

        #endregion

        #region Methods

        private void Initialize(IEqualityComparer<TKey> comparer = null) {
            this.Comparer = comparer;
            if (comparer != null) {
                _keyedCollection = new KeyedCollection2<TKey, KeyValuePair<TKey, TValue>>(x => x.Key, comparer);
            }
            else {
                _keyedCollection = new KeyedCollection2<TKey, KeyValuePair<TKey, TValue>>(x => x.Key);
            }
        }

        public void Add(TKey key, TValue value) {
            _keyedCollection.Add(new KeyValuePair<TKey, TValue>(key, value));
        }

        public void Clear() {
            _keyedCollection.Clear();
        }

        public void Insert(int index, TKey key, TValue value) {
            _keyedCollection.Insert(index, new KeyValuePair<TKey, TValue>(key, value));
        }

        public int IndexOf(TKey key) {
            if (_keyedCollection.Contains(key)) {
                return _keyedCollection.IndexOf(_keyedCollection[key]);
            }
            else {
                return -1;
            }
        }

        public bool ContainsValue(TValue value) {
            return this.Values.Contains(value);
        }

        public bool ContainsValue(TValue value, IEqualityComparer<TValue> comparer) {
            return this.Values.Contains(value, comparer);
        }

        public bool ContainsKey(TKey key) {
            return _keyedCollection.Contains(key);
        }

        public KeyValuePair<TKey, TValue> GetItem(int index) {
            if (index < 0 || index >= _keyedCollection.Count) {
                throw new ArgumentException(String.Format("The index was outside the bounds of the dictionary: {0}", index));
            }
            return _keyedCollection[index];
        }

        /// <summary>
        /// Sets the value at the index specified.
        /// </summary>
        /// <param name="index">The index of the value desired</param>
        /// <param name="value">The value to set</param>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Thrown when the index specified does not refer to a KeyValuePair in this object
        /// </exception>
        public void SetItem(int index, TValue value) {
            if (index < 0 || index >= _keyedCollection.Count) {
                throw new ArgumentException("The index is outside the bounds of the dictionary: {0}".FormatWith(index));
            }
            var kvp = new KeyValuePair<TKey, TValue>(_keyedCollection[index].Key, value);
            _keyedCollection[index] = kvp;
        }

        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
            return _keyedCollection.GetEnumerator();
        }

        public bool Remove(TKey key) {
            return _keyedCollection.Remove(key);
        }

        public void RemoveAt(int index) {
            if (index < 0 || index >= _keyedCollection.Count) {
                throw new ArgumentException(String.Format("The index was outside the bounds of the dictionary: {0}", index));
            }
            _keyedCollection.RemoveAt(index);
        }

        /// <summary>
        /// Gets the value associated with the specified key.
        /// </summary>
        /// <param name="key">The key associated with the value to get.</param>
        public TValue GetValue(TKey key) {
            if (_keyedCollection.Contains(key) == false) {
                throw new ArgumentException("The given key is not present in the dictionary: {0}".FormatWith(key));
            }
            var kvp = _keyedCollection[key];
            return kvp.Value;
        }

        /// <summary>
        /// Sets the value associated with the specified key.
        /// </summary>
        /// <param name="key">The key associated with the value to set.</param>
        /// <param name="value">The the value to set.</param>
        public void SetValue(TKey key, TValue value) {
            var kvp = new KeyValuePair<TKey, TValue>(key, value);
            var idx = IndexOf(key);
            if (idx > -1) {
                _keyedCollection[idx] = kvp;
            }
            else {
                _keyedCollection.Add(kvp);
            }
        }

        public bool TryGetValue(TKey key, out TValue value) {
            if (_keyedCollection.Contains(key)) {
                value = _keyedCollection[key].Value;
                return true;
            }
            else {
                value = default(TValue);
                return false;
            }
        }

        #endregion

        #region sorting
        public void SortKeys() {
            _keyedCollection.SortByKeys();
        }

        public void SortKeys(IComparer<TKey> comparer) {
            _keyedCollection.SortByKeys(comparer);
        }

        public void SortKeys(Comparison<TKey> comparison) {
            _keyedCollection.SortByKeys(comparison);
        }

        public void SortValues() {
            var comparer = Comparer<TValue>.Default;
            SortValues(comparer);
        }

        public void SortValues(IComparer<TValue> comparer) {
            _keyedCollection.Sort((x, y) => comparer.Compare(x.Value, y.Value));
        }

        public void SortValues(Comparison<TValue> comparison) {
            _keyedCollection.Sort((x, y) => comparison(x.Value, y.Value));
        }
        #endregion

        #region IDictionary<TKey, TValue>

        void IDictionary<TKey, TValue>.Add(TKey key, TValue value) {
            Add(key, value);
        }

        bool IDictionary<TKey, TValue>.ContainsKey(TKey key) {
            return ContainsKey(key);
        }

        ICollection<TKey> IDictionary<TKey, TValue>.Keys {
            get { return Keys; }
        }

        bool IDictionary<TKey, TValue>.Remove(TKey key) {
            return Remove(key);
        }

        bool IDictionary<TKey, TValue>.TryGetValue(TKey key, out TValue value) {
            return TryGetValue(key, out value);
        }

        ICollection<TValue> IDictionary<TKey, TValue>.Values {
            get { return Values; }
        }

        TValue IDictionary<TKey, TValue>.this[TKey key] {
            get {
                return this[key];
            }
            set {
                this[key] = value;
            }
        }

        #endregion

        #region ICollection<KeyValuePair<TKey, TValue>>

        void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) {
            _keyedCollection.Add(item);
        }

        void ICollection<KeyValuePair<TKey, TValue>>.Clear() {
            _keyedCollection.Clear();
        }

        bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) {
            return _keyedCollection.Contains(item);
        }

        void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
            _keyedCollection.CopyTo(array, arrayIndex);
        }

        int ICollection<KeyValuePair<TKey, TValue>>.Count {
            get { return _keyedCollection.Count; }
        }

        bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly {
            get { return false; }
        }

        bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) {
            return _keyedCollection.Remove(item);
        }

        #endregion

        #region IEnumerable<KeyValuePair<TKey, TValue>>

        IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() {
            return GetEnumerator();
        }

        #endregion

        #region IEnumerable

        IEnumerator IEnumerable.GetEnumerator() {
            return GetEnumerator();
        }

        #endregion

        #region IOrderedDictionary

        IDictionaryEnumerator IOrderedDictionary.GetEnumerator() {
            return new DictionaryEnumerator<TKey, TValue>(this);
        }

        void IOrderedDictionary.Insert(int index, object key, object value) {
            Insert(index, (TKey)key, (TValue)value);
        }

        void IOrderedDictionary.RemoveAt(int index) {
            RemoveAt(index);
        }

        object IOrderedDictionary.this[int index] {
            get {
                return this[index];
            }
            set {
                this[index] = (TValue)value;
            }
        }

        #endregion

        #region IDictionary

        void IDictionary.Add(object key, object value) {
            Add((TKey)key, (TValue)value);
        }

        void IDictionary.Clear() {
            Clear();
        }

        bool IDictionary.Contains(object key) {
            return _keyedCollection.Contains((TKey)key);
        }

        IDictionaryEnumerator IDictionary.GetEnumerator() {
            return new DictionaryEnumerator<TKey, TValue>(this);
        }

        bool IDictionary.IsFixedSize {
            get { return false; }
        }

        bool IDictionary.IsReadOnly {
            get { return false; }
        }

        ICollection IDictionary.Keys {
            get { return (ICollection)this.Keys; }
        }

        void IDictionary.Remove(object key) {
            Remove((TKey)key);
        }

        ICollection IDictionary.Values {
            get { return (ICollection)this.Values; }
        }

        object IDictionary.this[object key] {
            get {
                return this[(TKey)key];
            }
            set {
                this[(TKey)key] = (TValue)value;
            }
        }

        #endregion

        #region ICollection

        void ICollection.CopyTo(Array array, int index) {
            ((ICollection)_keyedCollection).CopyTo(array, index);
        }

        int ICollection.Count {
            get { return ((ICollection)_keyedCollection).Count; }
        }

        bool ICollection.IsSynchronized {
            get { return ((ICollection)_keyedCollection).IsSynchronized; }
        }

        object ICollection.SyncRoot {
            get { return ((ICollection)_keyedCollection).SyncRoot; }
        }

        #endregion
    }

    public class KeyedCollection2<TKey, TItem> : KeyedCollection<TKey, TItem> {
        private const string DelegateNullExceptionMessage = "Delegate passed cannot be null";
        private Func<TItem, TKey> _getKeyForItemDelegate;

        public KeyedCollection2(Func<TItem, TKey> getKeyForItemDelegate)
            : base() {
            if (getKeyForItemDelegate == null) throw new ArgumentNullException(DelegateNullExceptionMessage);
            _getKeyForItemDelegate = getKeyForItemDelegate;
        }

        public KeyedCollection2(Func<TItem, TKey> getKeyForItemDelegate, IEqualityComparer<TKey> comparer)
            : base(comparer) {
            if (getKeyForItemDelegate == null) throw new ArgumentNullException(DelegateNullExceptionMessage);
            _getKeyForItemDelegate = getKeyForItemDelegate;
        }

        protected override TKey GetKeyForItem(TItem item) {
            return _getKeyForItemDelegate(item);
        }

        public void SortByKeys() {
            var comparer = Comparer<TKey>.Default;
            SortByKeys(comparer);
        }

        public void SortByKeys(IComparer<TKey> keyComparer) {
            var comparer = new Comparer2<TItem>((x, y) => keyComparer.Compare(GetKeyForItem(x), GetKeyForItem(y)));
            Sort(comparer);
        }

        public void SortByKeys(Comparison<TKey> keyComparison) {
            var comparer = new Comparer2<TItem>((x, y) => keyComparison(GetKeyForItem(x), GetKeyForItem(y)));
            Sort(comparer);
        }

        public void Sort() {
            var comparer = Comparer<TItem>.Default;
            Sort(comparer);
        }

        public void Sort(Comparison<TItem> comparison) {
            var newComparer = new Comparer2<TItem>((x, y) => comparison(x, y));
            Sort(newComparer);
        }

        public void Sort(IComparer<TItem> comparer) {
            List<TItem> list = base.Items as List<TItem>;
            if (list != null) {
                list.Sort(comparer);
            }
        }
    }

    public class Comparer2<T> : Comparer<T> {
        //private readonly Func<T, T, int> _compareFunction;
        private readonly Comparison<T> _compareFunction;

        #region Constructors

        public Comparer2(Comparison<T> comparison) {
            if (comparison == null) throw new ArgumentNullException("comparison");
            _compareFunction = comparison;
        }

        #endregion

        public override int Compare(T arg1, T arg2) {
            return _compareFunction(arg1, arg2);
        }
    }

    public class DictionaryEnumerator<TKey, TValue> : IDictionaryEnumerator, IDisposable {
        readonly IEnumerator<KeyValuePair<TKey, TValue>> impl;
        public void Dispose() { impl.Dispose(); }
        public DictionaryEnumerator(IDictionary<TKey, TValue> value) {
            this.impl = value.GetEnumerator();
        }
        public void Reset() { impl.Reset(); }
        public bool MoveNext() { return impl.MoveNext(); }
        public DictionaryEntry Entry {
            get {
                var pair = impl.Current;
                return new DictionaryEntry(pair.Key, pair.Value);
            }
        }
        public object Key { get { return impl.Current.Key; } }
        public object Value { get { return impl.Current.Value; } }
        public object Current { get { return Entry; } }
    }
}

และจะไม่มีการติดตั้งใด ๆ ที่สมบูรณ์หากไม่มีการทดสอบ (แต่น่าเศร้าที่ SO จะไม่ให้ฉันโพสต์โค้ดมากขนาดนั้นในโพสต์เดียว) ดังนั้นฉันจะต้องปล่อยให้คุณเขียนการทดสอบของคุณ แต่ฉันทิ้งไว้สองสามตัวเพื่อให้คุณได้ทราบว่ามันทำงานอย่างไร:

// http://unlicense.org
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using mattmc3.Common.Collections.Generic;

namespace mattmc3.Tests.Common.Collections.Generic {
    [TestClass]
    public class OrderedDictionaryTests {

        private OrderedDictionary<string, string> GetAlphabetDictionary(IEqualityComparer<string> comparer = null) {
            OrderedDictionary<string, string> alphabet = (comparer == null ? new OrderedDictionary<string, string>() : new OrderedDictionary<string, string>(comparer));
            for (var a = Convert.ToInt32('a'); a <= Convert.ToInt32('z'); a++) {
                var c = Convert.ToChar(a);
                alphabet.Add(c.ToString(), c.ToString().ToUpper());
            }
            Assert.AreEqual(26, alphabet.Count);
            return alphabet;
        }

        private List<KeyValuePair<string, string>> GetAlphabetList() {
            var alphabet = new List<KeyValuePair<string, string>>();
            for (var a = Convert.ToInt32('a'); a <= Convert.ToInt32('z'); a++) {
                var c = Convert.ToChar(a);
                alphabet.Add(new KeyValuePair<string, string>(c.ToString(), c.ToString().ToUpper()));
            }
            Assert.AreEqual(26, alphabet.Count);
            return alphabet;
        }

        [TestMethod]
        public void TestAdd() {
            var od = new OrderedDictionary<string, string>();
            Assert.AreEqual(0, od.Count);
            Assert.AreEqual(-1, od.IndexOf("foo"));

            od.Add("foo", "bar");
            Assert.AreEqual(1, od.Count);
            Assert.AreEqual(0, od.IndexOf("foo"));
            Assert.AreEqual(od[0], "bar");
            Assert.AreEqual(od["foo"], "bar");
            Assert.AreEqual(od.GetItem(0).Key, "foo");
            Assert.AreEqual(od.GetItem(0).Value, "bar");
        }

        [TestMethod]
        public void TestRemove() {
            var od = new OrderedDictionary<string, string>();

            od.Add("foo", "bar");
            Assert.AreEqual(1, od.Count);

            od.Remove("foo");
            Assert.AreEqual(0, od.Count);
        }

        [TestMethod]
        public void TestRemoveAt() {
            var od = new OrderedDictionary<string, string>();

            od.Add("foo", "bar");
            Assert.AreEqual(1, od.Count);

            od.RemoveAt(0);
            Assert.AreEqual(0, od.Count);
        }

        [TestMethod]
        public void TestClear() {
            var od = GetAlphabetDictionary();
            Assert.AreEqual(26, od.Count);
            od.Clear();
            Assert.AreEqual(0, od.Count);
        }

        [TestMethod]
        public void TestOrderIsPreserved() {
            var alphabetDict = GetAlphabetDictionary();
            var alphabetList = GetAlphabetList();
            Assert.AreEqual(26, alphabetDict.Count);
            Assert.AreEqual(26, alphabetList.Count);

            var keys = alphabetDict.Keys.ToList();
            var values = alphabetDict.Values.ToList();

            for (var i = 0; i < 26; i++) {
                var dictItem = alphabetDict.GetItem(i);
                var listItem = alphabetList[i];
                var key = keys[i];
                var value = values[i];

                Assert.AreEqual(dictItem, listItem);
                Assert.AreEqual(key, listItem.Key);
                Assert.AreEqual(value, listItem.Value);
            }
        }

        [TestMethod]
        public void TestTryGetValue() {
            var alphabetDict = GetAlphabetDictionary();
            string result = null;
            Assert.IsFalse(alphabetDict.TryGetValue("abc", out result));
            Assert.IsNull(result);
            Assert.IsTrue(alphabetDict.TryGetValue("z", out result));
            Assert.AreEqual("Z", result);
        }

        [TestMethod]
        public void TestEnumerator() {
            var alphabetDict = GetAlphabetDictionary();

            var keys = alphabetDict.Keys.ToList();
            Assert.AreEqual(26, keys.Count);

            var i = 0;
            foreach (var kvp in alphabetDict) {
                var value = alphabetDict[kvp.Key];
                Assert.AreEqual(kvp.Value, value);
                i++;
            }
        }

        [TestMethod]
        public void TestInvalidIndex() {
            var alphabetDict = GetAlphabetDictionary();
            try {
                var notGonnaWork = alphabetDict[100];
                Assert.IsTrue(false, "Exception should have thrown");
            }
            catch (Exception ex) {
                Assert.IsTrue(ex.Message.Contains("index is outside the bounds"));
            }
        }

        [TestMethod]
        public void TestMissingKey() {
            var alphabetDict = GetAlphabetDictionary();
            try {
                var notGonnaWork = alphabetDict["abc"];
                Assert.IsTrue(false, "Exception should have thrown");
            }
            catch (Exception ex) {
                Assert.IsTrue(ex.Message.Contains("key is not present"));
            }
        }

        [TestMethod]
        public void TestUpdateExistingValue() {
            var alphabetDict = GetAlphabetDictionary();
            Assert.IsTrue(alphabetDict.ContainsKey("c"));
            Assert.AreEqual(2, alphabetDict.IndexOf("c"));
            Assert.AreEqual(alphabetDict[2], "C");
            alphabetDict[2] = "CCC";
            Assert.IsTrue(alphabetDict.ContainsKey("c"));
            Assert.AreEqual(2, alphabetDict.IndexOf("c"));
            Assert.AreEqual(alphabetDict[2], "CCC");
        }

        [TestMethod]
        public void TestInsertValue() {
            var alphabetDict = GetAlphabetDictionary();
            Assert.IsTrue(alphabetDict.ContainsKey("c"));
            Assert.AreEqual(2, alphabetDict.IndexOf("c"));
            Assert.AreEqual(alphabetDict[2], "C");
            Assert.AreEqual(26, alphabetDict.Count);
            Assert.IsFalse(alphabetDict.ContainsValue("ABC"));

            alphabetDict.Insert(2, "abc", "ABC");
            Assert.IsTrue(alphabetDict.ContainsKey("c"));
            Assert.AreEqual(2, alphabetDict.IndexOf("abc"));
            Assert.AreEqual(alphabetDict[2], "ABC");
            Assert.AreEqual(27, alphabetDict.Count);
            Assert.IsTrue(alphabetDict.ContainsValue("ABC"));
        }

        [TestMethod]
        public void TestValueComparer() {
            var alphabetDict = GetAlphabetDictionary();
            Assert.IsFalse(alphabetDict.ContainsValue("a"));
            Assert.IsTrue(alphabetDict.ContainsValue("a", StringComparer.OrdinalIgnoreCase));
        }

        [TestMethod]
        public void TestSortByKeys() {
            var alphabetDict = GetAlphabetDictionary();
            var reverseAlphabetDict = GetAlphabetDictionary();
            Comparison<string> stringReverse = ((x, y) => (String.Equals(x, y) ? 0 : String.Compare(x, y) >= 1 ? -1 : 1));
            reverseAlphabetDict.SortKeys(stringReverse);
            for (int j = 0, k = 25; j < alphabetDict.Count; j++, k--) {
                var ascValue = alphabetDict.GetItem(j);
                var dscValue = reverseAlphabetDict.GetItem(k);
                Assert.AreEqual(ascValue.Key, dscValue.Key);
                Assert.AreEqual(ascValue.Value, dscValue.Value);
            }
        }

- อัพเดท -

แหล่งที่มาของสิ่งนี้และไลบรารีคอร์. NET ที่ขาดหายไปที่มีประโยชน์จริงๆที่นี่: https://github.com/mattmc3/dotmore/blob/master/dotmore/Collections/Generic/OrperedDictionary.cs


6
โดยสาธารณสมบัติคุณกำลังถามว่าคุณสามารถใช้แก้ไขและปฏิบัติกับมันเหมือนเป็นของคุณได้โดยไม่ต้องกังวลใช่หรือไม่ ตามสบาย. หากคุณหมายถึงอนุญาตให้ใช้สิทธิ์และโฮสต์ไว้ที่ไหนสักแห่ง - ไม่ ... มันอาศัยอยู่ที่ SO เท่านั้น
mattmc3

3
@ mattmc3 ขอบคุณสำหรับรหัสของคุณ แต่ความคิดเห็นของคุณเกี่ยวกับปัญหาสาธารณสมบัติเกี่ยวกับฉันเมื่อคุณพูดในความคิดเห็น: "ถ้าคุณหมายถึงใบอนุญาตและโฮสต์ไว้ที่ไหนสักแห่ง - ไม่ ... ตอนนี้มันอยู่ที่ SO เท่านั้น " ด้วยความเคารพ (หมายถึงอย่างแท้จริง) ต่อผู้เขียนคุณมีสิทธิ์ที่จะ จำกัด ข้อ จำกัด นั้นจริงๆหรือไม่เมื่อคุณโพสต์รหัสบน SO ??? เช่นพวกเราทุกคนมีสิทธิ์ที่จะ จำกัด โค้ดของเราใน SO ไม่ให้ถูกโพสต์เช่นเป็น git gist หรือไม่? ทุกคน?
Nicholas Petersen

7
@NicholasPetersen - ฉันคิดว่าคุณเข้าใจผิด เพื่อตอบสนองโดยตรงต่อผู้พัน Panic ฉันแจ้งให้เขาทราบว่าโดยส่วนตัวแล้วฉันไม่ได้อนุญาตหรือโฮสต์ที่อื่น (อันที่จริงตั้งแต่คุณพูดถึงฉันได้สร้างส่วนสำคัญ: gist.github.com/mattmc3/6486878 ) แต่นี่เป็นรหัสใบอนุญาตฟรี รับมันและทำสิ่งที่คุณต้องการกับมัน ฉันเขียนมัน 100% สำหรับการใช้งานส่วนตัวของฉันเอง มันไม่มีภาระผูกพัน สนุก. ในความเป็นจริงถ้ามีคนจาก Microsoft เคยอ่านข้อความนี้ฉันคาดหวังอย่างเต็มที่ว่าพวกเขาจะทำหน้าที่ของตนและสุดท้ายก็วางไว้ใน. NET ไม่จำเป็นต้องมีการระบุแหล่งที่มา
mattmc3

2
เกิดอะไรขึ้นถ้าTKeyเป็นint? วิธีthis[]จะทำงานในกรณีดังกล่าวหรือไม่
VB

2
@ mattmc3 การแบ่งปันสิ่งนี้และโดยเฉพาะอย่างยิ่งห้องสมุด dotmore ทั้งหมดของคุณเป็นแรงบันดาลใจสำหรับรหัสที่ฉันสามารถแบ่งปันได้ ฉันรู้ว่าหลายคนใช้ห้องสมุดร่วมกันระหว่างขนาดเล็กและขนาดใหญ่ แต่ถึงกระนั้นมันก็สมควรได้รับมากกว่าความกตัญญูเสมอ
ปกติโจ

35

สำหรับเร็กคอร์ดมีKeyedCollectionทั่วไปที่อนุญาตให้อ็อบเจ็กต์ถูกทำดัชนีโดย int และคีย์ ต้องฝังคีย์ไว้ในค่า


2
สิ่งนี้ไม่ได้รักษาลำดับของการเริ่มต้นเหมือน OrderDictionary! ตรวจสอบคำตอบของฉัน
JoelFan

14
จะรักษาลำดับของการเพิ่ม / การแทรก
Guillaume

ใช่แล้ว .. พวกคุณมีความคิดว่า keyedcollection จัดเรียงรายการ ... ฉันสะดุดครั้งที่สองนี้
Boppity Bop

1
แน่นอนมันจะรักษาลำดับของการเริ่มต้น ลิงก์ที่เป็นประโยชน์รวมถึงstackoverflow.com/a/11802824/9344และgeekswithblogs.net/NewThingsILearned/archive/2010/01/07/...
Ted

1
@DCShannon คุณสามารถเขียนการใช้งานที่ต้องใช้ Func <TValue, TKey> โดยไม่จำเป็นต้องใช้อินเทอร์เฟซ / แทนที่เมธอด
Guillaume

19

นี่คือการค้นพบที่แปลกประหลาด: System.Web.Util namespace ใน System.Web.Extensions.dll มี OrderDictionary ทั่วไป

// Type: System.Web.Util.OrderedDictionary`2
// Assembly: System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
// Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Web.Extensions.dll

namespace System.Web.Util
{
    internal class OrderedDictionary<TKey, TValue> : IDictionary<TKey, TValue>, ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable

ไม่แน่ใจว่าทำไม MS ถึงวางไว้ที่นั่นแทนที่จะเป็นแพ็คเกจ System.Collections.Generic แต่ฉันคิดว่าคุณสามารถคัดลอกวางรหัสและใช้งานได้ (เป็นแบบภายในจึงไม่สามารถใช้โดยตรงได้) ดูเหมือนการใช้งานจะใช้พจนานุกรมมาตรฐานและรายการคีย์ / ค่าแยกกัน ค่อนข้างตรงไปตรงมา ...


2
System.Runtime.Collectionsนอกจากนี้ยังมีภายในOrderedDictionary<TKey, TValue>ซึ่งล้อมรอบเวอร์ชันที่ไม่ใช่ทั่วไป
VB

1
System.Web.Util.OrderedDictionary<TKey, TValue>ถูกนำไปใช้ภายในทั่ว ๆDictionaryไป มันไม่ได้ใช้งานผิดปกติIListแต่ICollection<KeyValuePair<TKey, TValue>>
มิคาอิล

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ค้นหาหรือโดยการค้นหาโดยใช้ไฟล์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

สิ่งนี้ (int เป็นคีย์เทียบกับ int เป็นดัชนี) เกิดขึ้น (สำหรับฉัน) มากใน PowerShell PowerShell เวอร์ชันล่าสุดช่วยให้เข้าถึง OrderDictionary ที่ไม่ได้พิมพ์ผ่าน [สั่งซื้อ] @ {} วิธีแก้ปัญหาที่ไม่ซับซ้อนของฉันในการรับ int เป็นคีย์และดัชนีคือการร่าย int ให้ยาวเมื่อฉันต้องการให้เป็นคีย์ แต่ตอนนี้คุณทำให้ฉันคิดถึงเรื่องนี้ . .PowerShell แสดงตัวสร้างดัชนีเป็น Item () และ $ odictItem ([object] $ key) จะบังคับให้ PowerShell ใช้ int เป็นคีย์ ละเอียดกว่า แต่แฮ็คน้อยลง ขอบคุณ!
unbob

7

สำหรับวัตถุประสงค์มากมายฉันพบว่าหนึ่งสามารถทำได้ด้วยไฟล์List<KeyValuePair<K, V>>. (ไม่ใช่ถ้าคุณต้องการขยายDictionaryให้เห็นได้ชัดและไม่ใช่ถ้าคุณต้องการการค้นหาคีย์ - ค่าที่ดีกว่า O (n))


เพิ่งมาสรุปเองเหมือนกัน!
Peter

1
อย่างที่บอก "เพื่อวัตถุประสงค์มากมาย"
David Moles

2
คุณยังสามารถใช้Tuple<T1, T2>ถ้าไม่มีความสัมพันธ์ของคีย์ - ค่า
cdmckay

1
อะไรคือจุดสำคัญของการมีคู่ค่าคีย์หากคุณไม่สามารถทำดัชนีด้วยคีย์ได้?
DCShannon

1
@DCShannon คุณยังสามารถแมปคีย์กับค่าได้คุณไม่สามารถค้นหาได้เร็วกว่า O (n) (หรือจัดการโดยอัตโนมัติด้วยคีย์ที่ซ้ำกัน) สำหรับค่าเล็ก ๆ ของ n นั้นบางครั้งก็ดีพอโดยเฉพาะอย่างยิ่งในสถานการณ์ที่คุณมักจะวนซ้ำผ่านคีย์ทั้งหมดอยู่ดี
David Moles

5

มี SortedDictionary<TKey, TValue>. แม้ว่าจะมีความหมายใกล้เคียง แต่ฉันก็ไม่ได้อ้างว่ามันเหมือนกับOrderedDictionaryเพียงเพราะมันไม่ใช่ แม้จากลักษณะการทำงาน อย่างไรก็ตามความแตกต่างที่น่าสนใจและค่อนข้างสำคัญระหว่างDictionary<TKey, TValue>(และในระดับนั้นOrderedDictionaryและการใช้งานที่ให้ไว้ในคำตอบ) และSortedDictionaryหลังใช้ไบนารีทรีด้านล่าง นี่เป็นความแตกต่างที่สำคัญเนื่องจากทำให้คลาสนี้มีภูมิคุ้มกันต่อข้อ จำกัด ของหน่วยความจำที่ใช้กับคลาสทั่วไป ดูหัวข้อนี้เกี่ยวกับการOutOfMemoryExceptionsโยนเมื่อคลาสทั่วไปถูกใช้สำหรับจัดการคู่คีย์ - ค่าชุดใหญ่

จะหาค่าสูงสุดสำหรับพารามิเตอร์ความจุที่ส่งไปยังตัวสร้างพจนานุกรมเพื่อหลีกเลี่ยง OutOfMemoryException ได้อย่างไร


มีวิธีรับคีย์หรือค่าของ a SortedDictionary ตามลำดับที่เพิ่มหรือไม่? นั่นคือสิ่งที่OrderedDictionaryอนุญาต แนวความคิดที่แตกต่างกันกว่าเรียง (ฉันเคยทำผิดพลาดนี้มาแล้วฉันคิดว่าการจัดหา Comparer ให้กับตัวสร้าง OrderDictionary จะทำให้มันถูกจัดเรียง แต่สิ่งที่ทำกับ Comparer คือการกำหนดความเท่าเทียมกันของคีย์เช่นตัวเปรียบเทียบที่ไม่ไวต่อสตริงช่วยให้สามารถค้นหาคีย์ที่ไม่ไวต่อสตริงได้)
ToolmakerSteve

5

ใช่แล้วมันเป็นการละเลยที่โชคร้าย ฉันคิดถึงOrderDictของ Python

พจนานุกรมที่จำลำดับการใส่คีย์ครั้งแรก หากรายการใหม่เขียนทับรายการที่มีอยู่ตำแหน่งการแทรกเดิมจะไม่เปลี่ยนแปลง การลบรายการและการใส่เข้าไปใหม่จะเป็นการย้ายรายการไปยังจุดสิ้นสุด

ฉันจึงเขียน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 การใช้งาน OrderDictionary ทั่วไปได้รับการยอมรับใน. NET CoreFX Lab หากทุกอย่างเป็นไปด้วยดีประเภทจะได้รับการอนุมัติและรวมเข้ากับ repo. NET CoreFX หลักในที่สุด

มีความเป็นไปได้ที่การดำเนินการนี้จะถูกปฏิเสธ

สามารถอ้างอิงการใช้งานที่มุ่งมั่นได้ที่นี่ https://github.com/dotnet/corefxlab/blob/57be99a176421992e29009701a99a370983329a6/src/Microsoft.Experimental.Collections/Microsoft/Collections/Extensions/Or แป้งDictionary.cs

แพคเกจ NuGet ที่มีประเภทนี้พร้อมใช้งานสามารถพบได้ที่นี่ https://www.nuget.org/packages/Microsoft.Experimental.Collections/1.0.6-e190117-3

หรือคุณสามารถติดตั้งแพคเกจภายใน Visual Studio เรียกดูแพ็คเกจ "Microsoft.Experimental.Collections" และตรวจสอบให้แน่ใจว่าได้เลือกช่องทำเครื่องหมาย "รวมรุ่นก่อนวางจำหน่าย"

จะอัปเดตโพสต์นี้หากและเมื่อประเภทพร้อมใช้งานอย่างเป็นทางการ


ประมาณว่าจะออกเมื่อไหร่?
mvorisek

ฉันไม่ได้มีส่วนร่วมในการพัฒนาห้องสมุดนี้ดังนั้นฉันจึงไม่รู้ มีแนวโน้มว่าจะเป็นคอลเลคชันเฟรมเวิร์กในตัวหากได้รับการ "อนุมัติ"
charlie

1

ฉันใช้งานทั่วไปOrderedDictionary<TKey, TValue>โดยการล้อมรอบSortedList<TKey, TValue>และเพิ่มไฟล์Dictionary<TKey, int> _order. จากนั้นฉันสร้างการใช้งานภายในComparer<TKey>โดยส่งการอ้างอิงไปยังพจนานุกรม _order จากนั้นฉันใช้ตัวเปรียบเทียบนี้สำหรับ SortedList ภายใน คลาสนี้จะเก็บลำดับขององค์ประกอบที่ส่งผ่านไปยังตัวสร้างและลำดับของการเพิ่ม

การใช้งานนี้มีลักษณะ O ใหญ่เกือบจะเหมือนกับSortedList<TKey, TValue>ตั้งแต่การเพิ่มและการลบไปยัง _order คือ O (1) แต่ละองค์ประกอบจะใช้เวลา (ตามหนังสือ 'C # 4 in a Nutshell', หน้า 292, ตารางที่ 7-1) พื้นที่หน่วยความจำเพิ่มเติม 22 (เหนือศีรษะ) + 4 (ลำดับ int) + ขนาด TKey (สมมติว่า 8) = 34 เมื่อรวมกับSortedList<TKey, TValue>ค่าโสหุ้ยของสองไบต์แล้วค่าโสหุ้ยรวมคือ 36 ไบต์ในขณะที่หนังสือเล่มเดียวกันกล่าวว่า non-generic 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 ที่คุณกำลังพูดถึงคืออะไร? ไม่มีผลกับรายการภายใน แต่สั่งเฉพาะพจนานุกรมเท่านั้น
VB

1
@VB ขออภัยฉันพลาดทั้งสองอย่าง เช่นนี้ยังไม่ได้ทดสอบ จากนั้นปัญหาเดียวก็คือการเพิ่ม การค้นหาพจนานุกรมสองรายการและการแทรกสองรายการ ฉันจะยกเลิกการโหวตลด
nawfal

0

นี่ยังไม่ใช่เวอร์ชัน / โซลูชันอื่นของการOrderedDictionary<,>ทดลอง แต่เป็นการทดลองที่ฉันได้ทดสอบแต่ละเวอร์ชัน 4 เวอร์ชันที่กล่าวถึงในคำตอบ: ของ @Colonel Panic, @ mattmc3, @VB @Chris Marisic มันหมายถึงข้อเสนอแนะ บางส่วนเพราะฉันต้องยอมรับว่าฉันไม่ได้ผ่ารหัสดังนั้นจึงอาจมีความแตกต่างในการทำงานหรือการตรวจสอบความปลอดภัย แต่ถึงกระนั้นฉันก็คิดว่าคำติชมจะมีประโยชน์ต่อประสิทธิภาพ และอย่างที่คุณเห็นเวลาจะได้รับจากสองสามมิลลิวินาทีถึงหนึ่งในสี่ของชั่วโมง จากนั้นฉันเขียนแบบมินิมอลที่ไร้เดียงสาโดยมีรายการคีย์และออบเจ็กต์ระดับค่า 2 รายการด้วยการค้นหา O (n) เพียงเพื่อดูขนาดของประโยชน์ของการเข้าถึง O (1)

Testbed เป็น Microsoft Visual Studio Community 2019 พร้อม Unity 3D 4 ครั้งติดต่อกันสำหรับการทดสอบแต่ละครั้งและรหัสที่ฉันต้องการจำลองสถานการณ์จริงคือ

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

โปรดทราบว่าการทดสอบมีไว้สำหรับสถานการณ์ที่เลวร้ายที่สุดในกรณีของเวอร์ชันที่ไร้เดียงสาอย่างน้อยที่สุดเนื่องจากการทดสอบซ้ำผ่านคอลเลกชันจากดัชนี 0 ถึงiterationsและการค้นหาจะทำตั้งแต่ต้นจนจบ ฉันวัดAdd(), ContainsKey()และRemove()ในหน่วยมิลลิวินาทีสำหรับในพจนานุกรมของ 50000 รายการ ผล:

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

นี้ได้รับการตั้งค่าสถานะเป็นไม่ได้คำตอบ ถูกต้องที่สุด แต่ยังให้ข้อมูลที่เป็นประโยชน์ซึ่งควรเป็นประโยชน์ต่อผู้อ่านในอนาคต แต่จะไม่เหมาะสมกับความคิดเห็น ด้วยเหตุนี้ฉันจึงมีแนวโน้มที่จะปล่อยให้มันยืนหยัด
Jeremy Caney
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.