/Utilities/Collections/MostRecentlyUsed.cs
C# | 451 lines | 261 code | 45 blank | 145 comment | 18 complexity | 4041269adf9f707b94749be01907b24b MD5 | raw file
Possible License(s): Apache-2.0
- using System;
- using System.Collections.Generic;
- using System.Text;
- using NUnit.Framework;
-
- namespace Delta.Utilities.Collections
- {
-
- #region Remarks and Summary
- /// <summary>
- /// The usage of the class is the same of a hash table. Keys and values are
- /// insert into the table. If a new key is inserted into the table and the
- /// cache has exceeded the set size, then the least recently used item is
- /// removed from the table, and an event is fired to signal that the element
- /// has fallen off the list.
- /// </summary>
- /// <remarks>
- /// Many times during the usage of an algorithm, a list of the last (n) most
- /// recently used items comes to be useful. Sometimes this is referred to
- /// the least recently used (LRU) cache, but this simply implies which e
- /// elements that fall out of the list (i.e. the least recently used ones).
- /// <para />
- /// Basically, as items are added to the list, they are appended to a
- /// doubly linked list. This list is usefull in the fact that it allows
- /// deletion and insertion at addObj(1) time. Since if a reference element
- /// (n) is not in the list of most recently used items, then at least one
- /// value falls out of the cache.
- /// <para />
- /// For examples see the unit tests in <see cref="MostRecentlyUsedTests"/>
- /// </remarks>
- /// <typeparam name="TKey">Key type</typeparam>
- /// <typeparam name="TValue">Value type</typeparam>
- #endregion
-
- public class MostRecentlyUsed<TKey, TValue>
- : DictionaryBase<TKey, TValue>
- {
- #region Item (Public)
- /// <summary>
- /// Indexer. Can't do this because of base class:
- /// public TValue this[TKey key]
- /// </summary>
- /// <param name="key">Key</param>
- /// <returns>Item at this key</returns>
- public override TValue this[TKey key]
- {
- get
- {
- int index = keys.IndexOf(key);
- LinkedListNode<TValue> item;
- if (index < 0)
- {
- item = default(LinkedListNode<TValue>);
- }
- else
- {
- item = values[index];
- // Move item to head
- if (item != null)
- {
- internalLinkedList.Remove(item);
- internalLinkedList.AddFirst(item);
- }
- }
-
- // Return value of item
- return item.Value; //.current;
- }
- set
- {
- int index = keys.IndexOf(key);
- if (index < 0)
- {
- Add(key, value);
- }
- else
- {
- LinkedListNode<TValue> link = values[index];
- link.Value = value;
-
- // Move item to head
- internalLinkedList.Remove(link);
- internalLinkedList.AddFirst(link);
-
- // Update link
- values[index] = link;
-
- // Keep a reverse index from the link to the key
- linkToKeyDictionary[link] = key;
- }
- }
- }
- #endregion
-
- #region Capacity (Public)
- /// <summary>
- /// The maximum capacity of the list
- /// </summary>
- /// <typeparam name="TKey">TKey</typeparam>
- /// <typeparam name="TValue">TValue</typeparam>
- public uint Capacity
- {
- get
- {
- return maxCapacity;
- }
- set
- {
- maxCapacity = value;
- }
- }
- #endregion
-
- #region Count (Public)
- /// <summary>
- /// Count
- /// </summary>
- /// <returns>Int</returns>
- public override int Count
- {
- get
- {
- return keys.Count;
- } // get
- }
- #endregion
-
- #region Private
-
- #region keys (Private)
- /// <summary>
- /// Keys
- /// </summary>
- /// <typeparam name="TKey">TKey</typeparam>
- /// <typeparam name="TValue">VTalue</typeparam>
- private readonly List<TKey> keys = new List<TKey>();
- #endregion
-
- #region values (Private)
- /// <summary>
- /// Values
- /// </summary>
- /// <typeparam name="TKey">TKey</typeparam>
- /// <typeparam name="TValue">VTalue</typeparam>
- private readonly List<LinkedListNode<TValue>> values =
- new List<LinkedListNode<TValue>>();
- #endregion
-
- #region internalLinkedList (Private)
- /// <summary>
- /// Internal linked list
- /// </summary>
- /// <typeparam name="TKey">TKey</typeparam>
- /// <typeparam name="TValue">TValue</typeparam>
- private readonly LinkedList<TValue> internalLinkedList =
- new LinkedList<TValue>();
- #endregion
-
- #region linkToKeyDictionary (Private)
- /// <summary>
- /// Link to key dictionary. LinkItem -> key relationship (inverse
- /// dictionary). In non generic version we used a HybridDictionary,
- /// which is not available for generics in .NET 2.0. We have to use
- /// a generic Dictionary instead!
- /// </summary>
- /// <typeparam name="TKey">TKey</typeparam>
- /// <typeparam name="TValue">TValue</typeparam>
- private readonly Dictionary<LinkedListNode<TValue>, TKey> linkToKeyDictionary
- =
- new Dictionary<LinkedListNode<TValue>, TKey>();
- #endregion
-
- #region maxCapacity (Private)
- /// <summary>
- /// Maximum capacity
- /// </summary>
- /// <typeparam name="TKey">TKey</typeparam>
- /// <typeparam name="TValue">TValue</typeparam>
- private uint maxCapacity = 50;
- #endregion
-
- #endregion
-
- #region Constructors
- /// <summary>
- /// Default constructor for the most recently used items using the
- /// default size (50).
- /// </summary>
- public MostRecentlyUsed()
- {
- }
-
- /// <summary>
- /// Construct a most recently used items list with the maximum number
- /// of items allowed in the list.
- /// </summary>
- /// <param name="maxItems">
- /// Maximum number of items allowed, should be some small number
- /// (maybe max. 1000) because the whole point of this class is to manage
- /// recently used items and not everthing ever added.
- /// </param>
- public MostRecentlyUsed(uint maxItems)
- {
- maxCapacity = maxItems;
- }
- #endregion
-
- #region Add (Public)
- /// <summary>
- /// Add
- /// </summary>
- public override void Add(TKey key, TValue value)
- {
- if (ContainsKey(key))
- {
- throw new ArgumentException(
- "The key was already present in the dictionary.", "key");
- }
- else
- {
- // Insert at head of list
- internalLinkedList.AddFirst(value);
- LinkedListNode<TValue> link = internalLinkedList.First;
-
- // Add the dictionary entry
- keys.Add(key);
- values.Add(link);
-
- // Keep a reverse index from the link to the key
- linkToKeyDictionary[link] = key;
-
- // Now also check if we reached our max. capacity
- if (keys.Count > maxCapacity)
- {
- // Get the least used item
- LinkedListNode<TValue> lastUsed = internalLinkedList.Last;
-
- // And remove it if possible
- if (lastUsed != null)
- {
- Remove(linkToKeyDictionary[lastUsed]);
- }
- }
- }
- }
- #endregion
-
- #region Contains (Public)
- /// <summary>
- /// Does this collection contains the given key?
- /// </summary>
- /// <param name="key">Key</param>
- /// <returns>True if there is something at this key entry</returns>
- public bool Contains(TKey key)
- {
- bool hasKey = base.ContainsKey(key);
-
- // Update the reference for this link
- if (hasKey)
- {
- // Move to head
- LinkedListNode<TValue> link = GetValueNode(key);
- internalLinkedList.Remove(link);
- internalLinkedList.AddFirst(link);
- }
-
- return hasKey;
- }
- #endregion
-
- #region Remove (Public)
- /// <summary>
- /// Remove
- /// </summary>
- /// <param name="key">Key</param>
- /// <returns>bool</returns>
- public override bool Remove(TKey key)
- {
- int index = keys.IndexOf(key);
- if (index < 0)
- {
- return false;
- }
- else
- {
- keys.RemoveAt(index);
-
- LinkedListNode<TValue> link = values[index];
- if (link != null)
- {
- // Remove from linked list
- internalLinkedList.Remove(link);
-
- // Remove reverse index from the link to the key
- linkToKeyDictionary.Remove(link);
- }
-
- // And finally remove from values list
- values.RemoveAt(index);
- return true;
- }
- }
- #endregion
-
- #region Clear (Public)
- /// <summary>
- /// Clear
- /// </summary>
- public override void Clear()
- {
- // Kill everything
- internalLinkedList.Clear();
- linkToKeyDictionary.Clear();
- keys.Clear();
- values.Clear();
- }
- #endregion
-
- #region TryGetValue (Public)
- /// <summary>
- /// Try get value
- /// </summary>
- /// <param name="key">Key</param>
- /// <param name="value">Value</param>
- /// <returns>True if the key was found and the value was filled.</returns>
- public override bool TryGetValue(
- TKey key, out TValue value)
- {
- int index = keys.IndexOf(key);
- if (index < 0)
- {
- value = default(TValue);
- return false;
- }
- else
- {
- value = values[index].Value;
- return true;
- }
- }
- #endregion
-
- #region GetEnumerator (Public)
- /// <summary>
- /// Get enumerator
- /// </summary>
- /// <returns>IEnumerator</returns>
- public override IEnumerator<KeyValuePair<TKey, TValue>>
- GetEnumerator()
- {
- for (int i = 0; i < keys.Count; ++i)
- {
- yield return new KeyValuePair<TKey, TValue>(
- keys[i], values[i].Value);
- }
- }
- #endregion
-
- #region ToString (Public)
- /// <summary>
- /// To string
- /// </summary>
- /// <returns>string</returns>
- public override string ToString()
- {
- StringBuilder buffer = new StringBuilder(Convert.ToInt32(maxCapacity));
- foreach (TValue item in internalLinkedList)
- {
- if (buffer.Length > 0)
- {
- buffer.Append(", ");
- }
-
- buffer.Append(item.ToString());
- }
-
- return buffer.ToString();
- }
- #endregion
-
- #region Methods (Private)
-
- #region GetValueNode
- /// <summary>
- /// Get value node
- /// </summary>
- /// <param name="key">Key</param>
- /// <returns>Node</returns>
- private LinkedListNode<TValue> GetValueNode(TKey key)
- {
- int index = keys.IndexOf(key);
- LinkedListNode<TValue> item;
- if (index < 0)
- {
- item = default(LinkedListNode<TValue>); // null;
- }
- else
- {
- item = values[index];
- }
-
- return item;
- }
- #endregion
-
- #endregion
- }
-
- /// <summary>
- /// MostRecentlyUsed tests. Can't execute unit test in generics class.
- /// </summary>
- internal class MostRecentlyUsedTests
- {
- #region TestMostRecentlyUsed (Static)
- /// <summary>
- /// Test most recently used. Note: Too slow for a dynamic unit test.
- /// </summary>
- [Test]
- public static void TestMostRecentlyUsed()
- {
- MostRecentlyUsed<int, string> mru =
- new MostRecentlyUsed<int, string>(3);
-
- Assert.Equal("", mru.ToString());
- for (int i = 0; i < 5; i++)
- {
- //Console.WriteLine("Adding " + i);
- mru[i] = "Bla " + i;
- //Console.WriteLine(">> State: " + mru);
- }
-
- // We must have the last 3 entries now
- Assert.Equal("Bla 4, Bla 3, Bla 2", mru.ToString());
- // When accessing 3, it must be first then
- Assert.Equal("Bla 3", mru[3]);
- Assert.Equal("Bla 3, Bla 4, Bla 2", mru.ToString());
-
- // Add a few more entries
- for (int i = 5; i < 7; i++)
- {
- //Console.WriteLine("Adding " + i);
- //Console.WriteLine(">> State: " + mru);
- mru[i] = "Honk " + i;
- }
-
- // Check entries again
- Assert.Equal("Honk 6, Honk 5, Bla 3", mru.ToString());
- }
- #endregion
- }
- }