Không có triển khai chung của OrderedDipedia?


136

Dường như không có một triển khai chung nào OrderedDictionary(trong System.Collections.Specializedkhông gian tên) trong .NET 3.5. Có cái nào tôi thiếu không?

Tôi đã tìm thấy các triển khai ngoài đó để cung cấp chức năng, nhưng tự hỏi liệu / tại sao không có triển khai chung ngoài luồng và liệu có ai biết liệu đó có phải là một thứ gì đó trong .NET 4.0 không?


1
Đây là một triển khai của OrderedDictionary<T>: codeproject.com/Articles/18615/ Khăn
Tim Schmelter


Thực hiện của tôi OrderedDictionary <T> có O (1) chèn / xóa vì nó sử dụng một LinkedList thay vì ArrayList để duy trì trật tự chèn: clintonbrennan.com/2013/12/...
Clinton

2
Nếu bạn chỉ cần có thể lặp lại các mục theo thứ tự chúng đã được thêm thì Danh sách <KeyValuePair <TKey, TValue >> có thể đủ tốt. (Được cấp, không phải là một giải pháp chung, nhưng đủ tốt cho một số mục đích.)
yoyo

1
Đó là một thiếu sót đáng tiếc. Có các loại dữ liệu tốt khác Systems.Collections.Generic. Hãy yêu cầu OrderedDictionary<TKey,TValue>.NET 5. Như những người khác đã chỉ ra, trường hợp khóa là int bị suy biến và sẽ cần được chăm sóc đặc biệt.
Đại tá hoảng loạn

Câu trả lời:



95

Việc thực hiện một cái chung OrderedDictionarykhông khó lắm, nhưng nó tốn thời gian một cách không cần thiết và thật lòng mà nói, lớp này là một sự giám sát quá lớn đối với phần của Microsoft. Có nhiều cách để thực hiện điều này, nhưng tôi đã chọn sử dụng một KeyedCollectioncho lưu trữ nội bộ của mình. Tôi cũng đã chọn thực hiện các phương pháp khác nhau để sắp xếp theo cách đó List<T>vì đây thực chất là một IList lai và IDadata. Tôi đã bao gồm việc thực hiện của tôi ở đây cho hậu thế.

Đây là giao diện. Lưu ý rằng nó bao gồm System.Collections.Specialized.IOrderedDictionary, đó là phiên bản không chung của giao diện này được cung cấp bởi 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);
    }

}

Đây là việc thực hiện cùng với các lớp của trình trợ giúp:

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

Và không có triển khai nào được hoàn thành nếu không có một vài bài kiểm tra (nhưng bi thảm thay, SO sẽ không cho phép tôi đăng nhiều mã đó trong một bài đăng), vì vậy tôi sẽ phải để bạn viết bài kiểm tra của mình. Nhưng, tôi đã để lại một vài trong số chúng để bạn có thể biết được cách thức hoạt động của nó:

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

- CẬP NHẬT -

Nguồn cho điều này và các thư viện .NET lõi bị thiếu thực sự hữu ích khác tại đây: https://github.com/mattmc3/dotmore/blob/master/dotmore/Collections/Generic/OrderedDipedia.cs


6
Theo miền công cộng, bạn đang hỏi liệu bạn có thể sử dụng nó, sửa đổi nó và coi nó như là lo lắng của bạn không - vâng. Đừng ngại. Nếu bạn có nghĩa là cấp phép cho nó và lưu trữ nó ở đâu đó - không ... hiện tại nó chỉ tồn tại trên SO.
mattmc3

3
@ mattmc3 Cảm ơn bạn vì mã của bạn, nhưng nhận xét của bạn về các vấn đề thuộc phạm vi công cộng liên quan đến tôi, khi bạn nói trong bình luận: "Nếu bạn có nghĩa là cấp phép cho nó và lưu trữ nó ở đâu đó - không ... hiện tại nó chỉ tồn tại trên SO. " Với tất cả sự tôn trọng (thực sự có ý nghĩa) đối với tác giả, bạn có thực sự có quyền đưa ra hạn chế đó không, khi bạn đã đăng mã lên SO ??? Ví dụ, có ai trong chúng ta thực sự có quyền hạn chế mã của chúng tôi trên SO không được đăng, chẳng hạn, như một ý chính không? Bất kỳ ai?
Nicholas Petersen

6
@NicholasPeteren - Tôi nghĩ bạn đã hiểu lầm. Trả lời trực tiếp với Đại tá Panic, tôi đã thông báo với anh ta rằng cá nhân tôi không cấp phép cho nó hoặc lưu trữ nó ở bất cứ nơi nào khác. (Trên thực tế, vì bạn đã đề cập đến nó, tôi đã thực hiện một ý chính: gist.github.com/mattmc3/6486878 ). Nhưng đây là mã miễn phí giấy phép. Lấy nó và làm những gì bạn muốn với nó. Tôi đã viết nó 100% cho sử dụng cá nhân của riêng tôi. Nó không bị cản trở. Thưởng thức. Trên thực tế, nếu ai đó từ Microsoft từng đọc điều này, tôi hoàn toàn mong đợi họ thực hiện nhiệm vụ của mình và cuối cùng đưa nó vào phiên bản tiếp theo của .NET. Không có sự phân bổ cần thiết.
mattmc3

2
Nếu TKeyintgì? Làm thế nào this[]sẽ làm việc trong trường hợp như vậy?
VB

2
@klicker - Chỉ cần sử dụng lập chỉ mục kiểu mảng thông thường. Thứ tự chèn được duy trì giống như một danh sách. Chuyển đổi loại xử lý xác định xem bạn có ý định lập chỉ mục với int hay tham chiếu qua loại khóa. Nếu kiểu của khóa là int, thì bạn cần sử dụng phương thức GetValue ().
mattmc3

32

Đối với bản ghi, có một KeyedCollection chung cho phép các đối tượng được lập chỉ mục bởi một int và một khóa. Khóa phải được nhúng trong giá trị.


2
Điều này không duy trì thứ tự khởi tạo như OrderedDipedia! Kiểm tra câu trả lời của tôi.
JoelFan

14
Nó không duy trì thứ tự thêm / chèn.
Guillaume

đúng vậy .. các bạn ở đâu có khái niệm rằng các mục sắp xếp khóa ... tôi bị vấp ngã lần thứ hai
Boppity Bop

1
Nó chắc chắn không duy trì thứ tự khởi tạo. Các liên kết hữu ích bao gồm stackoverflow.com/a/11802824/9344geekswithbloss.net/NewThingsILearned/archive/2010/01/07/ chất .
Ted

+1, Đây có vẻ như là giải pháp tốt nhất trong khung. Tuy nhiên, việc phải triển khai lớp trừu tượng cho từng cặp loại bạn muốn sử dụng là một loại kéo. Bạn có thể làm điều đó với một triển khai chung yêu cầu giao diện, nhưng sau đó bạn phải triển khai giao diện trên từng loại bạn muốn có thể lưu trữ.
DCShannon

19

Đây là một phát hiện kỳ ​​lạ: không gian tên System.Web.Util trong System.Web.Extensions.dll chứa một OrderedDixi chung

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

Không chắc chắn tại sao MS đặt nó ở đó thay vì gói System.Collections.Generic, nhưng tôi giả sử bạn chỉ cần sao chép dán mã và sử dụng nó (nó là nội bộ, vì vậy không thể sử dụng trực tiếp). Có vẻ như việc triển khai sử dụng một từ điển tiêu chuẩn và các danh sách Khóa / Giá trị riêng biệt. Khá đơn giản ...


2
System.Runtime.Collectionscũng chứa một nội bộ OrderedDictionary<TKey, TValue>chỉ bao quanh phiên bản không chung chung
VB

1
System.Web.Util.OrderedDictionary<TKey, TValue>được thực hiện trong nội bộ xung quanh chung chung Dictionary. Điều kỳ lạ là nó không thực hiện IListnhưngICollection<KeyValuePair<TKey, TValue>>
Mikhail

1
@rboy Như tôi đã nói - đó là nội bộ và bao bọc việc thực hiện không chung chung. Nhưng đã hơn 3 năm trước ... Đối với các kích thước dưới vài trăm tìm kiếm tuyến tính trên List<KeyValuePair<TKey,TValue>>sẽ có khả năng cạnh tranh do mẫu truy cập bộ nhớ, đối với các kích thước lớn hơn, chỉ cần sử dụng cùng một danh sách + Dictionary<TKey,int>như một tra cứu. AFAIK không có cơ sở hạ tầng nào tốt hơn về mặt tốc độ / bộ nhớ trong BigO.
VB

1
@rboy ở đây là liên kết đến cái chung , nó tham chiếu cái không chung chung sử dụng HashTable. Tôi thực sự đặt cược rằng đối với các kích thước nhỏ sử dụng tìm kiếm tuyến tính trên Danh sách / Mảng chung sẽ nhanh hơn.
VB

1
@PeterMortensen System.Collections.Specialized.OrderedDictionarykhông phải là một loại chung chung. Nhìn ma, không có dấu ngoặc góc ở trang tài liệu bạn đã liên kết: P
user7610

17

Đối với những gì nó có giá trị, đây là cách tôi giải quyết nó:

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

Nó có thể được khởi tạo như thế này:

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

và truy cập như thế này:

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

// Guaranteed to print in the order of initialization

3
Cảm ơn! Tôi đã nhận ra rằng bộ khởi tạo bộ sưu tập chỉ là cú pháp đặc biệt cho Addcác phương thức.
Sam

10
Đây không phải là một cuốn từ điển. Từ điển là viết tắt của lập chỉ mục khóakhông có khóa trùng lặp .
nawfal

Tuy nhiên, nếu bạn tình cờ không cần lập chỉ mục theo khóa (không quá khó để thêm) và các khóa dupliacte thì điều này rất hữu ích
stijn

1
Bạn có một vấn đề là các cuộc gọi mã pairList.Add(new KeyValuePair<K,V>())(tức là phương thức trên Listlớp). Trong trường hợp đó, itsIndextừ điển không được cập nhật, và bây giờ danh sách và từ điển không đồng bộ. Có thể ẩn List.Addphương thức bằng cách tạo một new PairList.Add(KeyValuePair<K,V>)phương thức hoặc có thể sử dụng thành phần thay vì kế thừa và chỉ cần thực hiện lại tất cả các Listphương thức ... nhiều mã hơn ...
neizan

1
@nawfal, để giải quyết mối quan tâm của bạn, người ta có thể chỉ cần thêm một kiểm tra như: if (itsindex.Contains(key)) return;vào đầu Addđể ngăn ngừa trùng lặp
JoelFan

14

Một vấn đề khái niệm lớn với một phiên bản chung OrderedDictionarylà người dùng OrderedDictionary<TKey,TValue>sẽ mong đợi có thể lập chỉ mục bằng số bằng cách sử dụng inthoặc bằng cách tra cứu bằng cách sử dụng TKey. Khi loại khóa duy nhất là Object, như trong trường hợp không chung chung OrderedDictionary, loại đối số được truyền cho bộ chỉ mục sẽ đủ để phân biệt xem loại hoạt động lập chỉ mục nào sẽ được thực hiện. Mặc dù vậy, không rõ là người lập chỉ mục của một người OrderedDictionary<int, TValue>nên cư xử như thế nào .

Nếu các lớp như Drawing.Pointđã khuyến nghị và tuân theo một quy tắc rằng các cấu trúc có thể thay đổi từng phần sẽ phơi bày các phần tử có thể thay đổi của chúng dưới dạng các trường chứ không phải là các thuộc tính và không sử dụng các setters thuộc tính có thể sửa đổi this, thì một thuộc tính OrderedDictionary<TKey,TValue>có thể hiển thị một thuộc ByIndextính có Indexertham chiếu đến một cấu trúc có tham chiếu đến từ điển, và có một thuộc tính được lập chỉ mục mà getter và setter sẽ gọi GetByIndexSetByIndextheo nó. Vì vậy, người ta có thể nói một cái gì đó như MyDict.ByIndex[5] += 3;thêm 3 vào yếu tố thứ sáu của từ điển.

Thật không may, để trình biên dịch chấp nhận một điều như vậy, cần phải làm cho thuộc ByIndextính trả về một thể hiện lớp mới thay vì một cấu trúc mỗi khi nó được gọi, loại bỏ những lợi thế mà người ta sẽ có được bằng cách tránh quyền anh.

Trong VB.NET, người ta có thể khắc phục vấn đề đó bằng cách sử dụng một thuộc tính được lập chỉ mục có tên (vì vậy MyDict.ByIndex[int]sẽ là thành viên của MyDict, thay vì yêu cầu MyDict.ByIndexphải là thành viên MyDictbao gồm một người lập chỉ mục), nhưng C # không cho phép những điều đó.

Có thể vẫn đáng để cung cấp một OrderedDictionary<TKey,TValue> where TKey:class, nhưng phần lớn lý do để cung cấp thuốc generic ở nơi đầu tiên là cho phép sử dụng chúng với các loại giá trị.


Điểm hay là intcác khóa được đánh dấu là một thách thức, nhưng có thể tránh được bằng cách làm theo ví dụ về SortedList<TKey, TValue>loại liên quan : chỉ các khóa hỗ trợ [...]và yêu cầu sử dụng .Values[...]để truy cập theo chỉ mục. ( SortedDictionary<TKey, TValue>, ngược lại, không được tối ưu hóa cho truy cập được lập chỉ mục, yêu cầu sử dụng .ElementAt(...).Value)
mkuity0

7

Đối với nhiều mục đích tôi đã tìm thấy một người có thể nhận được bằng một List<KeyValuePair<K, V>>. (Không phải nếu bạn cần nó để mở rộng Dictionary, rõ ràng, và không phải nếu bạn cần tốt hơn tra cứu giá trị khóa O (n).)


Chỉ cần đi đến kết luận tương tự bản thân mình!
Peter

1
Như tôi đã nói, "cho rất nhiều mục đích."
David Moles

2
Bạn cũng có thể sử dụng Tuple<T1, T2>nếu họ không có mối quan hệ khóa-giá trị.
cdmckay

1
Điểm có các cặp giá trị khóa là gì nếu bạn không thể lập chỉ mục theo khóa?
DCShannon

1
@DCShannon Bạn vẫn có thể ánh xạ các khóa thành các giá trị, bạn không thể tìm kiếm chúng nhanh hơn O (n) (hoặc tự động xử lý các khóa trùng lặp). Đối với các giá trị nhỏ của n đôi khi đủ tốt, đặc biệt là trong các tình huống mà bạn thường lặp đi lặp lại qua tất cả các phím.
David Moles

5

SortedDictionary<TKey, TValue>. Mặc dù về mặt ngữ nghĩa, tôi không khẳng định nó giống như OrderedDictionaryđơn giản vì chúng không như vậy. Ngay cả từ đặc điểm hiệu suất. Tuy nhiên, sự khác biệt rất thú vị và khá quan trọng giữa Dictionary<TKey, TValue>(và trong phạm vi đó OrderedDictionaryvà các triển khai được cung cấp trong câu trả lời) và SortedDictionarylà cái sau đang sử dụng cây nhị phân bên dưới. Đây là điểm khác biệt quan trọng vì nó làm cho lớp miễn dịch với các hạn chế về bộ nhớ được áp dụng cho lớp chung. Xem chủ đề này về OutOfMemoryExceptionsném khi lớp chung được sử dụng để xử lý tập hợp lớn các cặp khóa-giá trị.

Làm thế nào để tìm ra giá trị tối đa cho tham số dung lượng được truyền cho hàm tạo Dictionary để tránh OutOfMemoryException?


Có cách nào để lấy khóa hoặc giá trị của SortedDictionary thứ tự chúng được thêm vào không? Đó là những gì OrderedDictionarycho phép. Khái niệm khác với sắp xếp . (Tôi đã thực hiện sai lầm này trong quá khứ, tôi nghĩ cung cấp một Comparer để OrderedDictionary constructor sẽ làm cho nó sắp xếp, nhưng tất cả nó với Comparer là xác định sự bình đẳng quan trọng, ví dụ như Comparer chuỗi-insensitive cho phép chuỗi-insensitive tra cứu trọng điểm.)
Người tạo công cụSteve

5

Phải, đó là một thiếu sót đáng tiếc. Tôi nhớ OrderedDict của Python

Một từ điển ghi nhớ thứ tự các phím được chèn đầu tiên. Nếu một mục mới ghi đè lên một mục hiện có, vị trí chèn ban đầu được giữ nguyên. Xóa một mục và xác nhận lại nó sẽ di chuyển đến cuối.

Vì vậy, tôi đã viết OrderedDictionary<K,V>lớp học của riêng tôi trong C #. Làm thế nào nó hoạt động? Nó duy trì hai bộ sưu tập - một từ điển không có thứ tự vanilla và một danh sách các phím được sắp xếp. Với giải pháp này, các hoạt động từ điển tiêu chuẩn giữ cho độ phức tạp nhanh của chúng và việc tìm kiếm theo chỉ mục cũng nhanh.

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

Đây là giao diện

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

Theo dõi nhận xét từ @VB, đây là một triển khai có thể truy cập của System.R nb.Collections.OrderedDixi <,> . Ban đầu tôi sẽ truy cập nó bằng phản xạ và cung cấp nó thông qua một nhà máy nhưng dll này dường như không thể truy cập được vì vậy tôi chỉ tự rút nguồn.

Một điều cần lưu ý là người lập chỉ mục ở đây sẽ không ném KeyNotFoundException . Tôi hoàn toàn ghét quy ước đó và đó là quyền tự do mà tôi đã thực hiện trong lần thực hiện này. Nếu điều đó quan trọng với bạn, chỉ cần thay thế dòng cho return default(TValue);. Sử dụng C # 6 ( tương thích với 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;
    }
}

Kéo yêu cầu / thảo luận được chấp nhận trên GitHub


3

Đối với những người tìm kiếm tùy chọn gói "chính thức" trong NuGet, việc triển khai OrderedDipedia chung đã được chấp nhận vào .NET CoreFX Lab. Nếu mọi việc suôn sẻ, loại cuối cùng sẽ được phê duyệt và tích hợp vào repo .NET CoreFX chính.

Có khả năng việc thực hiện này sẽ bị từ chối.

Việc triển khai đã cam kết có thể được tham khảo tại đây https://github.com/dotnet/corefxlab/blob/57be99a176421992e29009701a99a370983329a6/src/Microsoft.Experimental.Collections/Microsoft/Collos/

Gói NuGet chắc chắn có loại này có sẵn để sử dụng có thể được tìm thấy ở đây https://www.nuget.org/packages/Microsoft.Experimental.Collections/1.0.6-e190117-3

Hoặc bạn có thể cài đặt gói trong Visual Studio. Duyệt tìm gói "Microsoft.Experimental.Collections" và đảm bảo hộp kiểm "Bao gồm phát hành trước" được chọn.

Sẽ cập nhật bài đăng này nếu và khi loại được cung cấp chính thức.


Bạn có thể ước tính khi nào nó sẽ được phát hành?
mvorisek

Tôi không tham gia vào việc phát triển thư viện này nên rất tiếc là tôi không có ý tưởng. Có khả năng nó sẽ là một bộ sưu tập khung dựng sẵn nếu nó được "phê duyệt".
charlie

1

Tôi đã thực hiện một cái chung OrderedDictionary<TKey, TValue>bằng cách bọc xung quanh SortedList<TKey, TValue>và thêm một cái riêng Dictionary<TKey, int> _order. Sau đó, tôi đã tạo một triển khai nội bộ Comparer<TKey>, chuyển một tham chiếu đến từ điển _order. Sau đó, tôi sử dụng bộ so sánh này cho SortedList nội bộ. Lớp này giữ thứ tự các phần tử được truyền cho hàm tạo và thứ tự bổ sung.

Việc triển khai này có các đặc điểm O lớn tương tự như SortedList<TKey, TValue>khi thêm và xóa vào _order là O (1). Mỗi phần tử sẽ lấy (theo cuốn sách 'C # 4 trong một Nutshell', trang 292, bảng 7-1) không gian bộ nhớ bổ sung là 22 (trên cao) + 4 (thứ tự int) + kích thước TKey (giả sử 8) = 34 Cùng với tổng SortedList<TKey, TValue>phí của hai byte, tổng chi phí là 36 byte, trong khi cùng một cuốn sách nói rằng không chung chung OrderedDictionarycó tổng phí là 59 byte.

Nếu tôi chuyển sorted=trueđến hàm tạo, thì _order hoàn toàn không được sử dụng, OrderedDictionary<TKey, TValue>chính xác là SortedList<TKey, TValue>có chi phí nhỏ để gói, nếu hoàn toàn có ý nghĩa.

Tôi sẽ lưu trữ không nhiều đối tượng tham chiếu lớn trong OrderedDictionary<TKey, TValue>, vì vậy đối với tôi ca này. Chi phí 36 byte là chấp nhận được.

Mã chính là dưới đây. Mã cập nhật đầy đủ là trên ý chính này .

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

Có ít nhất bốn trường hợp sử dụng khác nhau mà tôi có thể thấy đối với một cái gì đó như OrderedDictionary, liên quan đến việc chèn hoặc xóa: (1) Sẽ không bao giờ có bất kỳ thao tác xóa nào; (2) sẽ có xóa, nhưng điều quan trọng là các mục liệt kê theo thứ tự được thêm vào; không có nhu cầu truy cập theo chỉ mục; (3) chỉ số bằng số của một mặt hàng nên (hoặc ít nhất có thể) không đổi và sẽ không được thêm hơn 2 tỷ mặt hàng trong suốt thời gian thu thập, vì vậy, nếu mặt hàng số 7 bị xóa, sẽ không bao giờ có lại mục số 7; (4) chỉ số của một vật phẩm phải là thứ hạng của nó đối với người sống sót.
supercat

1
Kịch bản # 1 có thể được xử lý bằng cách sử dụng một mảng song song với Dictionary. Kịch bản # 2 và # 3 có thể được xử lý bằng cách mỗi mục duy trì một chỉ mục cho biết khi nào nó được thêm vào và liên kết đến các mục được thêm trước hoặc sau nó. Kịch bản # 4 là kịch bản duy nhất có vẻ như không thể đạt được hiệu suất O (1) cho các hoạt động theo trình tự tùy ý. Tùy thuộc vào các kiểu sử dụng, số 4 có thể được trợ giúp bằng cách sử dụng các chiến lược cập nhật lười biếng khác nhau (giữ số lượng trong một cây và thay đổi nút không hợp lệ thay vì cập nhật nút và cha mẹ của nó).
supercat

1
Internal SortedList có các phần tử theo thứ tự chèn do sử dụng bộ so sánh tùy chỉnh. Nó có thể chậm nhưng nhận xét của bạn về liệt kê là sai. Hiển thị các bài kiểm tra về liệt kê ...
VB

1
Bạn đang nói về dòng nào với ToDipedia? Nó không ảnh hưởng đến danh sách nội bộ, nhưng chỉ từ điển đặt hàng.
VB

1
@VB Xin lỗi, tôi đã bỏ lỡ cả hai. Như vậy đã không kiểm tra nó. Sau đó, vấn đề duy nhất sẽ là với bổ sung. Hai tra cứu từ điển, cũng như hai chèn. Tôi sẽ hủy bỏ downvote.
nawfal
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.