OrderedDictionary의 일반적인 구현이 없습니까?


136

.NET 3.5 OrderedDictionary에는 System.Collections.Specialized네임 스페이스 에 있는 일반적인 구현이없는 것 같습니다 . 내가 빠진 것이 있습니까?

기능을 제공하기 위해 구현을 찾았지만 기본 구현이 기본적으로 제공되지 않는 이유와 이유가 무엇인지, .NET 4.0에 무엇이 있는지 아는 사람이 있는지 궁금합니다.


1
여기의 구현입니다 OrderedDictionary<T>: codeproject.com/Articles/18615/...
팀 Schmelter


OrderedDictionary <T>의 구현은 ArrayList 대신 LinkedList를 사용하여 삽입 순서를 유지하기 때문에 O (1) 삽입 / 삭제가 있습니다. clintonbrennan.com/2013/12/…
Clinton

2
추가 된 순서대로 항목을 반복 할 수 있어야하는 경우 List <KeyValuePair <TKey, TValue >>로 충분할 수 있습니다. (일반적인 솔루션은 아니지만 어떤 목적에는 충분합니다.)
yoyo

1
불행한 누락입니다. 다른 좋은 데이터 유형이 있습니다 Systems.Collections.Generic. OrderedDictionary<TKey,TValue>.NET 5를 요청합시다 . 다른 사람들이 지적했듯이, 키가 int 인 경우는 변질되어 있으며 특별한주의가 필요합니다.
대령 패닉

답변:


60

네가 옳아. 이에 상응하는 일반적인 내용은 없습니다OrderedDictionary프레임 워크 자체에 .

(알고있는 한 여전히 .NET 4의 경우입니다.)

그러나 Visual Studio의 UserVoice (2016-10-04) 에서 투표 할 수 있습니다 !


95

제네릭을 구현하는 OrderedDictionary것은 그리 어렵지 않지만 불필요하게 시간이 걸리고 솔직히이 클래스는 Microsoft의 큰 감독입니다. 이를 구현하는 여러 가지 방법이 있지만 KeyedCollection내부 스토리지에 를 사용하기로 선택했습니다 . 또한 List<T>본질적으로 하이브리드 IList와 IDictionary이므로 정렬 방법에 대한 다양한 방법을 구현하기로 결정했습니다 . 나는 후손을 위해 구현을 여기에 포함시켰다.

인터페이스는 다음과 같습니다. 가 포함 공지 System.Collections.Specialized.IOrderedDictionaryMicrosoft에서 제공 한이 인터페이스의 제네릭이 아닌 버전이다.

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

namespace mattmc3.Common.Collections.Generic {

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

}

다음은 도우미 클래스와 함께 구현 된 것입니다.

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

namespace mattmc3.Common.Collections.Generic {

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

        #region Fields/Properties

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

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

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

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

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

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

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

        #endregion

        #region Constructors

        public OrderedDictionary() {
            Initialize();
        }

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

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

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

        #endregion

        #region Methods

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        #endregion

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

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

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

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

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

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

        #region IDictionary<TKey, TValue>

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

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

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

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

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

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

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

        #endregion

        #region ICollection<KeyValuePair<TKey, TValue>>

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

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

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

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

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

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

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

        #endregion

        #region IEnumerable<KeyValuePair<TKey, TValue>>

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

        #endregion

        #region IEnumerable

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

        #endregion

        #region IOrderedDictionary

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

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

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

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

        #endregion

        #region IDictionary

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

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

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

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

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

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

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

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

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

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

        #endregion

        #region ICollection

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

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

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

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

        #endregion
    }

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

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

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

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

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

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

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

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

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

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

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

        #region Constructors

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

        #endregion

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

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

그리고 몇 가지 테스트 없이는 구현이 완료되지 않습니다 (그러나 비극적으로, 한 게시물에 많은 코드를 게시 할 수는 없습니다). 그래서 테스트를 작성해야합니다. 그러나 몇 가지를 남겨 두어 작동 방식에 대한 아이디어를 얻을 수 있습니다.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

-업데이트-

이것과 다른 유용한 누락 된 핵심 .NET 라이브러리에 대한 소스는 https://github.com/mattmc3/dotmore/blob/master/dotmore/Collections/Generic/OrderedDictionary.cs입니다.


6
퍼블릭 도메인에서는 사용, 수정 및 걱정없이 처리 할 수 ​​있는지 묻고 있습니까? 부담없이 라이센스를 부여하고 어딘가에 호스팅하면 안됩니다 ... 아직 지금 여기에만 있습니다.
mattmc3

3
@ mattmc3 코드 감사합니다. 그러나 공개 도메인 문제에 대한 귀하의 의견은 다음과 같은 의견에서 언급했을 때 저와 관련이 있습니다. " 저자에 대한 모든 존중 (정확한 의미)으로, 당신은 SO에 코드를 게시하면 실제로 그 제한을 할 권리가 있습니까 ?? 예를 들어, git gist로 SO에 대한 코드가 게시되는 것을 제한 할 권리가 있습니까? 누군가?
Nicholas Petersen

6
@NicholasPetersen-당신이 오해 한 것 같아요. Panic 대령에 직접 응답하여 개인적으로 라이센스를 부여하거나 다른 곳에서는 호스팅하지 않았다고 알 렸습니다 . (실제로, 당신이 그것을 언급 한 이후로, 나는 요점을 만들었습니다 : gist.github.com/mattmc3/6486878 ). 그러나 이것은 라이센스가없는 코드입니다. 그것을 가지고 당신이 원하는 것을하십시오. 나는 개인적인 용도로 100 % 썼다. 방해받지 않습니다. 즐겨. 실제로 Microsoft의 누군가가 이것을 읽었다면, 그들이 그들의 의무를 다하고 마침내 다음 .NET 릴리스에 넣을 것으로 기대합니다. 속성이 필요하지 않습니다.
mattmc3

2
어떤 경우 TKey이다 int? this[]그러한 경우 어떻게 작동합니까?
VB

2
@klicker-일반 배열 스타일 인덱싱 만 사용하십시오. 삽입 순서는 목록과 동일하게 유지됩니다. 타입 변환은 int로 인덱싱 할 것인지 키 타입을 통해 참조 할 것인지를 결정합니다. 키의 유형이 int이면 GetValue () 메소드를 사용해야합니다.
mattmc3

32

레코드 에는 int와 키로 객체를 색인 할 수 있는 일반 KeyedCollection 이 있습니다. 키는 값에 포함되어야합니다.


2
이것은 OrderedDictionary와 같은 초기화 순서를 유지하지 않습니다! 내 대답을 확인하십시오.
JoelFan

14
추가 / 삽입 순서를 유지합니다.
기 illa

네, 그렇습니다. 여러분들은 keyedcollection이 아이템을 분류한다는이 개념을 어디에서 얻었습니까 ... 나는 두 번째로 우연히 발견되었습니다
Boppity Bop

1
초기화 순서를 확실히 유지합니다. 유용한 링크는 stackoverflow.com/a/11802824/9344geekswithblogs.net/NewThingsILearned/archive/2010/01/07/…을 포함 합니다.
Ted

+1, 이것은 프레임 워크에서 최고의 솔루션 인 것 같습니다. 그러나 사용하려는 각 유형의 쌍에 대해 추상 클래스를 구현 해야하는 것은 일종의 드래그입니다. 인터페이스가 필요한 하나의 일반 구현으로 할 수 있지만 저장할 수있는 각 유형에 인터페이스를 구현해야합니다.
DCShannon

19

기괴한 발견 : System.Web.Extensions.dll의 System.Web.Util 네임 스페이스에는 일반적인 OrderedDictionary가 포함되어 있습니다.

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

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

MS가 왜 System.Collections.Generic 패키지 대신 그것을 배치했는지 확실하지 않지만 코드를 복사하여 붙여 넣을 수 있다고 가정합니다 (내부이므로 직접 사용할 수 없음). 구현시 표준 사전과 별도의 키 / 값 목록을 사용하는 것 같습니다. 꽤 직설적 인...


2
System.Runtime.Collections또한 OrderedDictionary<TKey, TValue>제네릭이 아닌 버전을 둘러싼 내부도 포함되어 있습니다.
VB

1
System.Web.Util.OrderedDictionary<TKey, TValue>generic 내부에서 구현됩니다 Dictionary. 이상하게도 구현 IList하지는 않지만ICollection<KeyValuePair<TKey, TValue>>
Mikhail

1
@rboy 내가 말했듯이 내부와 비 제네릭 구현을 감쌌습니다. 그러나 3 년 전이었습니다 ... List<KeyValuePair<TKey,TValue>>메모리 검색 패턴으로 인해 수백 개 이하의 선형 검색 이 경쟁 우위 를 차지할 수 있습니다. 더 큰 크기의 경우 동일한 목록 + Dictionary<TKey,int>를 조회로 사용하십시오 . AFAIK는 BigO에서 속도 / 메모리 측면에서 더 나은 데이터 구조가 없습니다.
VB

1
@rboy 여기에 일반 링크가 있으며 HashTable을 사용하는 제네릭이 아닌 것을 참조 합니다. 일반 목록 / 배열에서 선형 검색을 사용하는 작은 크기의 경우 더 빠를 것이라고 확신합니다.
VB

1
@PeterMortensen System.Collections.Specialized.OrderedDictionary은 일반적인 유형이 아닙니다. 링크 된 문서 페이지에서 괄호가없는
것처럼 보입니다

17

그만한 가치가있는 방법은 다음과 같습니다.

   public class PairList<TKey, TValue> : List<KeyValuePair<TKey, TValue>> {
        Dictionary<TKey, int> itsIndex = new Dictionary<TKey, int>();

        public void Add(TKey key, TValue value) {
            Add(new KeyValuePair<TKey, TValue>(key, value));
            itsIndex.Add(key, Count-1);
        }

        public TValue Get(TKey key) {
            var idx = itsIndex[key];
            return this[idx].Value;
        }
    }

다음과 같이 초기화 할 수 있습니다.

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

다음과 같이 액세스하십시오.

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

// Guaranteed to print in the order of initialization

3
감사! 컬렉션 이니셜 라이저가 Add메소드의 특수 구문이라는 것을 알지 못했습니다 .
Sam

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권장 구분-변경 가능한 구조 수정 속성 setter를 사용하는 필드보다는 속성 및 후렴 그들의 가변 요소를 노출해야한다는 규칙을 따랐다 this, 다음은 OrderedDictionary<TKey,TValue>효율적으로 노출 될 수 ByIndex미복귀 특성 Indexer에 대한 참조를 보유 구조체를 사전, 그리고 누구의 getter 및 setter 부를 것이다 인덱스 첨부 프로퍼티했다 GetByIndexSetByIndex그것을 바탕으로합니다. 따라서 MyDict.ByIndex[5] += 3;사전의 여섯 번째 요소에 3을 더하는 것과 같은 것을 말할 수 있습니다.

불행히도, 컴파일러가 그러한 것을 받아들이 기 위해서는 ByIndex호출 될 때마다 구조체가 아닌 새로운 클래스 인스턴스를 반환 하도록 속성 을 작성해야 합니다. 복싱을 피함으로써 얻을 수있는 이점을 제거하십시오.

VB.NET에서는 명명 된 인덱스 속성을 사용하여이 문제를 해결할 수 있지만 (인덱서를 포함하는 멤버가 아니고 MyDict.ByIndex[int]의 멤버 MyDictMyDict.ByIndex수 있음 MyDict) C #에서는 그러한 것을 허용하지 않습니다.

여전히을 제공하는 것이 가치가 OrderedDictionary<TKey,TValue> where TKey:class있었지만 처음에 제네릭을 제공하는 많은 이유는 값 유형과 함께 사용하도록 허용했기 때문입니다.


유형이 int지정된 키는 문제가 있지만, 다음과 같은 관련 SortedList<TKey, TValue>유형 의 예를 따르면 피할 수 있습니다 . 키만 지원 하고 인덱스 액세스에 [...]사용해야 .Values[...]합니다. ( SortedDictionary<TKey, TValue>, 대조적으로, 인덱스 액세스에 최적화되지 않은을 사용해야합니다. .ElementAt(...).Value)
mklement0

7

많은 목적을 위해 하나를 사용할 수 있음을 발견했습니다 List<KeyValuePair<K, V>>. (확장하기 위해 Dictionary필요한 경우가 아니라 O (n) 키-값 조회보다 나은 경우는 아닙니다.)


방금 같은 결론을 내 렸습니다!
Peter

1
내가 말했듯이 "많은 목적으로"
David Moles

2
Tuple<T1, T2>키-값 관계가없는 경우 a를 사용할 수도 있습니다 .
cdmckay

1
키별로 색인을 생성 할 수없는 경우 키 값 쌍을 갖는 요점은 무엇입니까?
DCShannon

1
@DCShannon 키를 여전히 값에 매핑 할 수 있습니다. O (n)보다 더 빨리 찾을 수 없습니다 (또는 중복 키를 자동으로 처리). 작은 값의 n의 경우, 특히 어쨌든 모든 키를 반복하는 상황에서 특히 충분합니다.
David Moles

5

있습니다 SortedDictionary<TKey, TValue>. 의미 적으로 가깝지만, 그것이 OrderedDictionary단순히 그들이 아니라고 주장하는 것은 아닙니다. 성능 특성에서도 마찬가지입니다. 그러나 매우 흥미롭고 중요한 차이점은 Dictionary<TKey, TValue>(그리고 OrderedDictionary답변에서 제공되는 범위 와 구현 까지 ) SortedDictionary후자는 이진 트리를 사용하고 있다는 것입니다. 클래스가 일반 클래스에 적용되는 메모리 제약 조건에 영향을받지 않기 때문에 이것은 중요한 차이점입니다. OutOfMemoryExceptions제네릭 클래스가 큰 키-값 쌍 세트를 처리하는 데 사용될 때 발생하는 스레드에 대해서는이 스레드를 참조하십시오 .

OutOfMemoryException을 피하기 위해 Dictionary 생성자로 전달 된 용량 매개 변수의 최대 값을 계산하는 방법은 무엇입니까?


SortedDictionary 추가 된 순서대로 키 또는 값을 얻는 방법 있습니까? 그것이 OrderedDictionary허용하는 것입니다. 정렬 된 것과 다른 개념 입니다. (나는 그것을 분류 만들 수있는 비교 자 OrderedDictionary에 생성자를 공급 생각했지만, 그것은 비교 자와 않는 모든 키 평등을 결정이며, 나는 과거에 실수를 한 예를 들어 캐릭터를 구분 비교 자 문자열 문자를 구분 키 조회를 할 수 있습니다.)
ToolmakerSteve

5

맞아, 그것은 불행한 누락입니다. 파이썬의 OrderedDict가 그립습니다.

키가 처음 삽입 된 순서를 기억하는 사전. 새 항목이 기존 항목을 덮어 쓰면 원래 삽입 위치는 변경되지 않습니다. 항목을 삭제하고 다시 삽입하면 끝으로 이동합니다.

그래서 나는 내 자신을 썼다 OrderedDictionary<K,V> C #으로 수업을 . 어떻게 작동합니까? 바닐라 정렬되지 않은 사전과 정렬 된 키 목록이라는 두 가지 컬렉션을 유지 관리합니다. 이 솔루션을 사용하면 표준 사전 작업이 빠른 복잡성을 유지하고 색인으로 조회하는 것도 빠릅니다.

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

인터페이스는 다음과 같습니다

/// <summary>
/// A dictionary that remembers the order that keys were first inserted. If a new entry overwrites an existing entry, the original insertion position is left unchanged. Deleting an entry and reinserting it will move it to the end.
/// </summary>
/// <typeparam name="TKey">The type of keys</typeparam>
/// <typeparam name="TValue">The type of values</typeparam>
public interface IOrderedDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
    /// <summary>
    /// The value of the element at the given index.
    /// </summary>
    TValue this[int index] { get; set; }

    /// <summary>
    /// Find the position of an element by key. Returns -1 if the dictionary does not contain an element with the given key.
    /// </summary>
    int IndexOf(TKey key);

    /// <summary>
    /// Insert an element at the given index.
    /// </summary>
    void Insert(int index, TKey key, TValue value);

    /// <summary>
    /// Remove the element at the given index.
    /// </summary>
    void RemoveAt(int index);
}

3

@VB의 주석에 대한 후속 조치로 System.Runtime.Collections.OrderedDictionary <,> 의 액세스 가능한 구현이 있습니다. 있습니다. 나는 원래 리플렉션으로 액세스하고 공장을 통해 제공하려고했지만이 DLL은 전혀 액세스 할 수없는 것처럼 보이므로 소스 자체를 가져 왔습니다.

여기서주의해야 할 것은 인덱서 가 던지지 않는다는 것 KeyNotFoundException 입니다. 나는 그 협약을 절대적으로 싫어하고 그것이 내가이 구현에서 취한 1의 자유였다. 그게 중요한 경우 줄을 바꾸십시오 return default(TValue);. C # 6 사용 ( Visual Studio 2013과 호환 가능 )

/// <summary>
///     System.Collections.Specialized.OrderedDictionary is NOT generic.
///     This class is essentially a generic wrapper for OrderedDictionary.
/// </summary>
/// <remarks>
///     Indexer here will NOT throw KeyNotFoundException
/// </remarks>
public class OrderedDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary
{
    private readonly OrderedDictionary _privateDictionary;

    public OrderedDictionary()
    {
        _privateDictionary = new OrderedDictionary();
    }

    public OrderedDictionary(IDictionary<TKey, TValue> dictionary)
    {
        if (dictionary == null) return;

        _privateDictionary = new OrderedDictionary();

        foreach (var pair in dictionary)
        {
            _privateDictionary.Add(pair.Key, pair.Value);
        }
    }

    public bool IsReadOnly => false;
    public int Count => _privateDictionary.Count;
    int ICollection.Count => _privateDictionary.Count;
    object ICollection.SyncRoot => ((ICollection)_privateDictionary).SyncRoot;
    bool ICollection.IsSynchronized => ((ICollection)_privateDictionary).IsSynchronized;

    bool IDictionary.IsFixedSize => ((IDictionary)_privateDictionary).IsFixedSize;
    bool IDictionary.IsReadOnly => _privateDictionary.IsReadOnly;
    ICollection IDictionary.Keys => _privateDictionary.Keys;
    ICollection IDictionary.Values => _privateDictionary.Values;

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

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

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

    IDictionaryEnumerator IDictionary.GetEnumerator()
    {
        return _privateDictionary.GetEnumerator();
    }

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

    object IDictionary.this[object key]
    {
        get { return _privateDictionary[key]; }
        set { _privateDictionary[key] = value; }
    }

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

    public TValue this[TKey key]
    {
        get
        {
            if (key == null) throw new ArgumentNullException(nameof(key));

            if (_privateDictionary.Contains(key))
            {
                return (TValue) _privateDictionary[key];
            }

            return default(TValue);
        }
        set
        {
            if (key == null) throw new ArgumentNullException(nameof(key));

            _privateDictionary[key] = value;
        }
    }

    public ICollection<TKey> Keys
    {
        get
        {
            var keys = new List<TKey>(_privateDictionary.Count);

            keys.AddRange(_privateDictionary.Keys.Cast<TKey>());

            return keys.AsReadOnly();
        }
    }

    public ICollection<TValue> Values
    {
        get
        {
            var values = new List<TValue>(_privateDictionary.Count);

            values.AddRange(_privateDictionary.Values.Cast<TValue>());

            return values.AsReadOnly();
        }
    }

    public void Add(KeyValuePair<TKey, TValue> item)
    {
        Add(item.Key, item.Value);
    }

    public void Add(TKey key, TValue value)
    {
        if (key == null) throw new ArgumentNullException(nameof(key));

        _privateDictionary.Add(key, value);
    }

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

    public bool Contains(KeyValuePair<TKey, TValue> item)
    {
        if (item.Key == null || !_privateDictionary.Contains(item.Key))
        {
            return false;
        }

        return _privateDictionary[item.Key].Equals(item.Value);
    }

    public bool ContainsKey(TKey key)
    {
        if (key == null) throw new ArgumentNullException(nameof(key));

        return _privateDictionary.Contains(key);
    }

    public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
    {
        if (array == null) throw new ArgumentNullException(nameof(array));
        if (arrayIndex < 0) throw new ArgumentOutOfRangeException(nameof(arrayIndex));
        if (array.Rank > 1 || arrayIndex >= array.Length
                           || array.Length - arrayIndex < _privateDictionary.Count)
            throw new ArgumentException("Bad Copy ToArray", nameof(array));

        var index = arrayIndex;
        foreach (DictionaryEntry entry in _privateDictionary)
        {
            array[index] = 
                new KeyValuePair<TKey, TValue>((TKey) entry.Key, (TValue) entry.Value);
            index++;
        }
    }

    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
    {
        foreach (DictionaryEntry entry in _privateDictionary)
        {
            yield return 
                new KeyValuePair<TKey, TValue>((TKey) entry.Key, (TValue) entry.Value);
        }
    }

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

    public bool Remove(KeyValuePair<TKey, TValue> item)
    {
        if (false == Contains(item)) return false;

        _privateDictionary.Remove(item.Key);

        return true;
    }

    public bool Remove(TKey key)
    {
        if (key == null) throw new ArgumentNullException(nameof(key));

        if (false == _privateDictionary.Contains(key)) return false;

        _privateDictionary.Remove(key);

        return true;
    }

    public bool TryGetValue(TKey key, out TValue value)
    {
        if (key == null) throw new ArgumentNullException(nameof(key));

        var keyExists = _privateDictionary.Contains(key);
        value = keyExists ? (TValue) _privateDictionary[key] : default(TValue);

        return keyExists;
    }
}

GitHub에서 풀 요청 / 토론 허용


3

NuGet에서 "공식"패키지 옵션을 찾는 사람들을 위해 일반 OrderedDictionary의 구현이 .NET CoreFX Lab에 수용되었습니다. 모든 것이 잘 진행되면 결국 형식이 승인되어 기본 .NET CoreFX 저장소에 통합됩니다.

이 구현이 거부 될 가능성이 있습니다.

커밋 된 구현은 여기에서 참조 할 수 있습니다 https://github.com/dotnet/corefxlab/blob/57be99a176421992e29009701a99a370983329a6/src/Microsoft.Experimental.Collections/Microsoft/Collections/Extensions/OrderedDictionary.cs에서

이 유형을 사용할 수있는 NuGet 패키지는 https://www.nuget.org/packages/Microsoft.Experimental.Collections/1.0.6-e190117-3 에서 찾을 수 있습니다.

또는 Visual Studio 내에 패키지를 설치할 수 있습니다. "Microsoft.Experimental.Collections"패키지를 찾아서 "시험판 포함"확인란이 선택되어 있는지 확인하십시오.

형식이 공식적으로 제공되는 경우이 게시물을 업데이트합니다.


언제 출시 될지 예상 할 수 있습니까?
mvorisek

나는이 라이브러리의 개발에 참여하지 않으므로 불행히도 나는 모른다. "승인"되면 내장 된 프레임 워크 모음 일 가능성이 높습니다.
찰리

1

OrderedDictionary<TKey, TValue>랩핑 SortedList<TKey, TValue>하고 private을 추가 하여 제네릭 을 구현했습니다 Dictionary<TKey, int> _order. 그런 다음 Comparer<TKey>_order 사전에 대한 참조를 전달하여 의 내부 구현을 만들었습니다 . 그런 다음 내부 SortedList에이 비교기를 사용합니다. 이 클래스는 생성자에 전달 된 요소의 순서와 추가 순서를 유지합니다.

이 구현은 SortedList<TKey, TValue>_order에 추가 및 제거가 O (1)이므로 거의 동일한 O 특성 을 갖습니다. 각 요소는 ( 'C # 4 in Nutshell'책, 292 페이지, 표 7-1)에 따라 22 (오버 헤드) + 4 (int order) + TKey 크기 (8이라고 가정)의 추가 메모리 공간을 갖습니다. SortedList<TKey, TValue>2 바이트의 오버 헤드 와 함께 총 오버 헤드는 36 바이트이며, 같은 책에서는 제네릭 이외의OrderedDictionary 의 오버 헤드는 59 바이트라고합니다.

내가 전달하면 sorted=true생성자에 다음 _order 전혀 사용되지는이 OrderedDictionary<TKey, TValue>정확하게 SortedList<TKey, TValue>경우 모든 의미에서, 포장에 대한 약간의 오버 헤드.

에 많은 수의 큰 참조 객체를에 저장하려고 OrderedDictionary<TKey, TValue>하므로이 ca. 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) 품목의 수치 지수는 일정하게 유지되어야하고 (또는 적어도있을 수 있음), 수집 기간 동안 20 억 개 이하의 품목이 추가 될 것이므로 품목 # 7이 제거되면 다시는 아이템 # 7; (4) 아이템의 인덱스는 생존자에 대한 순위 여야합니다.
supercat

1
시나리오 1은와 병렬 배열을 사용하여 처리 할 수 ​​있습니다 Dictionary. 시나리오 # 2 및 # 3은 각 항목이 언제 추가되었는지, 그리고 이전 또는 이후에 추가 된 항목에 대한 색인을 유지하도록하여 처리 할 수 ​​있습니다. 시나리오 # 4는 임의의 순서로 연산에 대해 O (1) 성능을 달성 할 수없는 것처럼 보이는 유일한 방법입니다. 사용 패턴에 따라 # 4는 다양한 게으른 업데이트 전략을 사용하여 도움이 될 수 있습니다 (트리에 카운트를 유지하고 노드와 해당 부모를 업데이트하는 대신 노드가 변경됨).
supercat

1
내부 SortedList에는 사용자 지정 비교기를 사용하여 삽입 순서의 요소가 있습니다. 속도가 느릴 수 있지만 열거에 대한 의견이 잘못되었습니다. 열거에 대한 테스트를 보여주십시오.
VB

1
ToDictionary와 무슨 라인을 이야기하고 있습니까? 내부 목록에는 영향을 미치지 않지만 주문 사전에만 영향을 미칩니다.
VB

1
@ VB 사과, 나는 둘 다보고 싶었다. 따라서 테스트하지 않았습니다. 그런 다음 유일한 문제는 추가하는 것입니다. 사전 검색 2 개와 삽입 2 개 downvote를 취소하겠습니다.
nawfal
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.