From 5b6324eb0f78e407ec540021e3361dd648b2fb18 Mon Sep 17 00:00:00 2001 From: kvr Date: Fri, 17 Jul 2015 20:25:37 +0200 Subject: [PATCH] SPRNET-1581: LinkedHashDictionary implementation --- .../Collections/Generic/AbstractDictionary.cs | 110 ++++++++++++++ .../Generic/LinkedHashDictionary.cs | 137 ++++++++++++++++++ .../Generic/Test/LinkedHashDictionaryTest.cs | 71 +++++++++ 3 files changed, 318 insertions(+) create mode 100644 src/Spring/Spring.Core/Collections/Generic/AbstractDictionary.cs create mode 100644 src/Spring/Spring.Core/Collections/Generic/LinkedHashDictionary.cs create mode 100644 test/Spring/Spring.Core.Tests/Collections/Generic/Test/LinkedHashDictionaryTest.cs diff --git a/src/Spring/Spring.Core/Collections/Generic/AbstractDictionary.cs b/src/Spring/Spring.Core/Collections/Generic/AbstractDictionary.cs new file mode 100644 index 00000000..d59470d9 --- /dev/null +++ b/src/Spring/Spring.Core/Collections/Generic/AbstractDictionary.cs @@ -0,0 +1,110 @@ +#region License + +/* + * Copyright © 2015-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Spring.Collections.Generic +{ + /// + /// Partial IDictionary implementation which serves as a common base for final implementations. + /// + /// Zbynek Vyskovsky, kvr@centrum.cz + public abstract class AbstractDictionary: System.Collections.Generic.IDictionary + { + public abstract void Add(TKey key, TValue value); + + public abstract bool ContainsKey(TKey key); + + public abstract bool Remove(TKey key); + + public abstract bool TryGetValue(TKey key, out TValue value); + + public abstract void Clear(); + + public bool Contains(KeyValuePair item) + { + TValue value; + return TryGetValue(item.Key, out value) && (item.Value == null ? value == null : item.Value.Equals(value)); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + } + + public abstract int Count { get; } + + public bool IsReadOnly + { + get { + return false; + } + } + + public abstract bool Remove(KeyValuePair item); + + public IEnumerator> GetEnumerator() + { + return EntriesSet().GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return EntriesSet().GetEnumerator(); + } + + public ICollection Keys + { + get { + return EntriesSet().Select((KeyValuePair entry) => entry.Key).ToList(); + } + } + + public ICollection Values + { + get { + return EntriesSet().Select((KeyValuePair entry) => entry.Value).ToList(); + } + } + + public TValue this[TKey key] + { + get { + TValue value; + if (!TryGetValue(key, out value)) + throw new KeyNotFoundException("Key not found in map: "+key); + return value; + } + set { + Add(key, value); + } + } + + public void Add(KeyValuePair item) + { + Add(item.Key, item.Value); + } + + protected abstract IEnumerable> EntriesSet(); + } +} diff --git a/src/Spring/Spring.Core/Collections/Generic/LinkedHashDictionary.cs b/src/Spring/Spring.Core/Collections/Generic/LinkedHashDictionary.cs new file mode 100644 index 00000000..f29e5d28 --- /dev/null +++ b/src/Spring/Spring.Core/Collections/Generic/LinkedHashDictionary.cs @@ -0,0 +1,137 @@ +#region License + +/* + * Copyright © 2015-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Spring.Collections.Generic +{ + /// + /// IDictionary implementation which preserves the order of inserted items. + /// + /// Zbynek Vyskovsky, kvr@centrum.cz + public class LinkedHashDictionary: AbstractDictionary + { + public override void Add(TKey key, TValue value) + { + Node node; + if (items.TryGetValue(key, out node)) { + node.value = value; + } + else { + node = new Node(); + node.key = key; + node.value = value; + if ((node.previousLinked = linkedTail) != null) + node.previousLinked.nextLinked = node; + node.nextLinked = null; + linkedTail = node; + if (linkedHead == null) + linkedHead = node; + items.Add(key, node); + } + } + + public override bool ContainsKey(TKey key) + { + return items.ContainsKey(key); + } + + public override bool Remove(TKey key) + { + Node node; + if (!items.TryGetValue(key, out node)) + return false; + + if (node.previousLinked != null) { + node.previousLinked.nextLinked = node.nextLinked; + } + else { + linkedHead = node.nextLinked; + } + + if (node.nextLinked != null) { + node.nextLinked.previousLinked = node.previousLinked; + } + else { + linkedTail = node.previousLinked; + } + + return true; + } + + public override bool TryGetValue(TKey key, out TValue value) + { + Node node; + if (!items.TryGetValue(key, out node)) { + value = default(TValue); + return false; + } + value = node.value; + return true; + } + + public override void Clear() + { + items.Clear(); + linkedHead = null; + linkedTail = null; + } + + public override bool Remove(KeyValuePair item) + { + Node node; + if (!items.TryGetValue(item.Key, out node)) + return false; + if (!node.value.Equals(item.Value)) + return false; + return Remove(item.Key); + } + + public override int Count + { + get { return items.Count; } + } + + protected class Node + { + public TKey key; + public TValue value; + + public Node previousLinked; + public Node nextLinked; + } + + protected override IEnumerable> EntriesSet() + { + List> entries = new List>(); + for (Node node = linkedHead; node != null; node = node.nextLinked) + entries.Add(new KeyValuePair(node.key, node.value)); + return entries; + } + + private Node linkedHead = null; + private Node linkedTail = null; + + private Dictionary items = new Dictionary(); + } +} diff --git a/test/Spring/Spring.Core.Tests/Collections/Generic/Test/LinkedHashDictionaryTest.cs b/test/Spring/Spring.Core.Tests/Collections/Generic/Test/LinkedHashDictionaryTest.cs new file mode 100644 index 00000000..7c6536e0 --- /dev/null +++ b/test/Spring/Spring.Core.Tests/Collections/Generic/Test/LinkedHashDictionaryTest.cs @@ -0,0 +1,71 @@ +#region License + +/* + * Copyright © 2015-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using Spring.Collections.Generic +using NUnit.Framework; + +namespace Spring.Collections.Generic.Test +{ + /// + /// This class contains tests for LinkedHashDictionary + /// + /// Zbynek Vyskovsky, kvr@centrum.cz + [TestFixture] + public class LinkedHashDictionaryTest + { + [Test] + public void TestSunnyDay() + { + IDictionary td = new LinkedHashDictionary(); + td.Add(0, "a"); + td.Add(1, "b"); + td.Add(2, "c"); + td.Add(1, "b2"); + td.Add(3, "d"); + td.Add(4, "e"); + Assert.IsTrue(td.ContainsKey(0)); + Assert.IsTrue(td.ContainsKey(1)); + Assert.IsTrue(td.ContainsKey(2)); + Assert.IsTrue(td.ContainsKey(3)); + Assert.IsTrue(td.ContainsKey(4)); + Assert.AreEqual("a", td[0]); + Assert.AreEqual("b2", td[1]); + int[] keys = td.Keys.ToArray(); + Assert.AreEqual(5, keys.Length); + Assert.AreEqual(0, keys[0]); + Assert.AreEqual(1, keys[1]); + Assert.AreEqual(2, keys[2]); + Assert.AreEqual(3, keys[3]); + Assert.AreEqual(4, keys[4]); + td.Remove(3); + td.Remove(2); + Assert.AreEqual(3, td.Count); + td.Remove(1); + Assert.AreEqual(2, td.Count); + td.Remove(4); + td.Remove(0); + Assert.AreEqual(0, td.Count); + Assert.AreEqual(0, td.Keys.Count); + } + } +}