PageRenderTime 44ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/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
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using NUnit.Framework;
  5. namespace Delta.Utilities.Collections
  6. {
  7. #region Remarks and Summary
  8. /// <summary>
  9. /// The usage of the class is the same of a hash table. Keys and values are
  10. /// insert into the table. If a new key is inserted into the table and the
  11. /// cache has exceeded the set size, then the least recently used item is
  12. /// removed from the table, and an event is fired to signal that the element
  13. /// has fallen off the list.
  14. /// </summary>
  15. /// <remarks>
  16. /// Many times during the usage of an algorithm, a list of the last (n) most
  17. /// recently used items comes to be useful. Sometimes this is referred to
  18. /// the least recently used (LRU) cache, but this simply implies which e
  19. /// elements that fall out of the list (i.e. the least recently used ones).
  20. /// <para />
  21. /// Basically, as items are added to the list, they are appended to a
  22. /// doubly linked list. This list is usefull in the fact that it allows
  23. /// deletion and insertion at addObj(1) time. Since if a reference element
  24. /// (n) is not in the list of most recently used items, then at least one
  25. /// value falls out of the cache.
  26. /// <para />
  27. /// For examples see the unit tests in <see cref="MostRecentlyUsedTests"/>
  28. /// </remarks>
  29. /// <typeparam name="TKey">Key type</typeparam>
  30. /// <typeparam name="TValue">Value type</typeparam>
  31. #endregion
  32. public class MostRecentlyUsed<TKey, TValue>
  33. : DictionaryBase<TKey, TValue>
  34. {
  35. #region Item (Public)
  36. /// <summary>
  37. /// Indexer. Can't do this because of base class:
  38. /// public TValue this[TKey key]
  39. /// </summary>
  40. /// <param name="key">Key</param>
  41. /// <returns>Item at this key</returns>
  42. public override TValue this[TKey key]
  43. {
  44. get
  45. {
  46. int index = keys.IndexOf(key);
  47. LinkedListNode<TValue> item;
  48. if (index < 0)
  49. {
  50. item = default(LinkedListNode<TValue>);
  51. }
  52. else
  53. {
  54. item = values[index];
  55. // Move item to head
  56. if (item != null)
  57. {
  58. internalLinkedList.Remove(item);
  59. internalLinkedList.AddFirst(item);
  60. }
  61. }
  62. // Return value of item
  63. return item.Value; //.current;
  64. }
  65. set
  66. {
  67. int index = keys.IndexOf(key);
  68. if (index < 0)
  69. {
  70. Add(key, value);
  71. }
  72. else
  73. {
  74. LinkedListNode<TValue> link = values[index];
  75. link.Value = value;
  76. // Move item to head
  77. internalLinkedList.Remove(link);
  78. internalLinkedList.AddFirst(link);
  79. // Update link
  80. values[index] = link;
  81. // Keep a reverse index from the link to the key
  82. linkToKeyDictionary[link] = key;
  83. }
  84. }
  85. }
  86. #endregion
  87. #region Capacity (Public)
  88. /// <summary>
  89. /// The maximum capacity of the list
  90. /// </summary>
  91. /// <typeparam name="TKey">TKey</typeparam>
  92. /// <typeparam name="TValue">TValue</typeparam>
  93. public uint Capacity
  94. {
  95. get
  96. {
  97. return maxCapacity;
  98. }
  99. set
  100. {
  101. maxCapacity = value;
  102. }
  103. }
  104. #endregion
  105. #region Count (Public)
  106. /// <summary>
  107. /// Count
  108. /// </summary>
  109. /// <returns>Int</returns>
  110. public override int Count
  111. {
  112. get
  113. {
  114. return keys.Count;
  115. } // get
  116. }
  117. #endregion
  118. #region Private
  119. #region keys (Private)
  120. /// <summary>
  121. /// Keys
  122. /// </summary>
  123. /// <typeparam name="TKey">TKey</typeparam>
  124. /// <typeparam name="TValue">VTalue</typeparam>
  125. private readonly List<TKey> keys = new List<TKey>();
  126. #endregion
  127. #region values (Private)
  128. /// <summary>
  129. /// Values
  130. /// </summary>
  131. /// <typeparam name="TKey">TKey</typeparam>
  132. /// <typeparam name="TValue">VTalue</typeparam>
  133. private readonly List<LinkedListNode<TValue>> values =
  134. new List<LinkedListNode<TValue>>();
  135. #endregion
  136. #region internalLinkedList (Private)
  137. /// <summary>
  138. /// Internal linked list
  139. /// </summary>
  140. /// <typeparam name="TKey">TKey</typeparam>
  141. /// <typeparam name="TValue">TValue</typeparam>
  142. private readonly LinkedList<TValue> internalLinkedList =
  143. new LinkedList<TValue>();
  144. #endregion
  145. #region linkToKeyDictionary (Private)
  146. /// <summary>
  147. /// Link to key dictionary. LinkItem -> key relationship (inverse
  148. /// dictionary). In non generic version we used a HybridDictionary,
  149. /// which is not available for generics in .NET 2.0. We have to use
  150. /// a generic Dictionary instead!
  151. /// </summary>
  152. /// <typeparam name="TKey">TKey</typeparam>
  153. /// <typeparam name="TValue">TValue</typeparam>
  154. private readonly Dictionary<LinkedListNode<TValue>, TKey> linkToKeyDictionary
  155. =
  156. new Dictionary<LinkedListNode<TValue>, TKey>();
  157. #endregion
  158. #region maxCapacity (Private)
  159. /// <summary>
  160. /// Maximum capacity
  161. /// </summary>
  162. /// <typeparam name="TKey">TKey</typeparam>
  163. /// <typeparam name="TValue">TValue</typeparam>
  164. private uint maxCapacity = 50;
  165. #endregion
  166. #endregion
  167. #region Constructors
  168. /// <summary>
  169. /// Default constructor for the most recently used items using the
  170. /// default size (50).
  171. /// </summary>
  172. public MostRecentlyUsed()
  173. {
  174. }
  175. /// <summary>
  176. /// Construct a most recently used items list with the maximum number
  177. /// of items allowed in the list.
  178. /// </summary>
  179. /// <param name="maxItems">
  180. /// Maximum number of items allowed, should be some small number
  181. /// (maybe max. 1000) because the whole point of this class is to manage
  182. /// recently used items and not everthing ever added.
  183. /// </param>
  184. public MostRecentlyUsed(uint maxItems)
  185. {
  186. maxCapacity = maxItems;
  187. }
  188. #endregion
  189. #region Add (Public)
  190. /// <summary>
  191. /// Add
  192. /// </summary>
  193. public override void Add(TKey key, TValue value)
  194. {
  195. if (ContainsKey(key))
  196. {
  197. throw new ArgumentException(
  198. "The key was already present in the dictionary.", "key");
  199. }
  200. else
  201. {
  202. // Insert at head of list
  203. internalLinkedList.AddFirst(value);
  204. LinkedListNode<TValue> link = internalLinkedList.First;
  205. // Add the dictionary entry
  206. keys.Add(key);
  207. values.Add(link);
  208. // Keep a reverse index from the link to the key
  209. linkToKeyDictionary[link] = key;
  210. // Now also check if we reached our max. capacity
  211. if (keys.Count > maxCapacity)
  212. {
  213. // Get the least used item
  214. LinkedListNode<TValue> lastUsed = internalLinkedList.Last;
  215. // And remove it if possible
  216. if (lastUsed != null)
  217. {
  218. Remove(linkToKeyDictionary[lastUsed]);
  219. }
  220. }
  221. }
  222. }
  223. #endregion
  224. #region Contains (Public)
  225. /// <summary>
  226. /// Does this collection contains the given key?
  227. /// </summary>
  228. /// <param name="key">Key</param>
  229. /// <returns>True if there is something at this key entry</returns>
  230. public bool Contains(TKey key)
  231. {
  232. bool hasKey = base.ContainsKey(key);
  233. // Update the reference for this link
  234. if (hasKey)
  235. {
  236. // Move to head
  237. LinkedListNode<TValue> link = GetValueNode(key);
  238. internalLinkedList.Remove(link);
  239. internalLinkedList.AddFirst(link);
  240. }
  241. return hasKey;
  242. }
  243. #endregion
  244. #region Remove (Public)
  245. /// <summary>
  246. /// Remove
  247. /// </summary>
  248. /// <param name="key">Key</param>
  249. /// <returns>bool</returns>
  250. public override bool Remove(TKey key)
  251. {
  252. int index = keys.IndexOf(key);
  253. if (index < 0)
  254. {
  255. return false;
  256. }
  257. else
  258. {
  259. keys.RemoveAt(index);
  260. LinkedListNode<TValue> link = values[index];
  261. if (link != null)
  262. {
  263. // Remove from linked list
  264. internalLinkedList.Remove(link);
  265. // Remove reverse index from the link to the key
  266. linkToKeyDictionary.Remove(link);
  267. }
  268. // And finally remove from values list
  269. values.RemoveAt(index);
  270. return true;
  271. }
  272. }
  273. #endregion
  274. #region Clear (Public)
  275. /// <summary>
  276. /// Clear
  277. /// </summary>
  278. public override void Clear()
  279. {
  280. // Kill everything
  281. internalLinkedList.Clear();
  282. linkToKeyDictionary.Clear();
  283. keys.Clear();
  284. values.Clear();
  285. }
  286. #endregion
  287. #region TryGetValue (Public)
  288. /// <summary>
  289. /// Try get value
  290. /// </summary>
  291. /// <param name="key">Key</param>
  292. /// <param name="value">Value</param>
  293. /// <returns>True if the key was found and the value was filled.</returns>
  294. public override bool TryGetValue(
  295. TKey key, out TValue value)
  296. {
  297. int index = keys.IndexOf(key);
  298. if (index < 0)
  299. {
  300. value = default(TValue);
  301. return false;
  302. }
  303. else
  304. {
  305. value = values[index].Value;
  306. return true;
  307. }
  308. }
  309. #endregion
  310. #region GetEnumerator (Public)
  311. /// <summary>
  312. /// Get enumerator
  313. /// </summary>
  314. /// <returns>IEnumerator</returns>
  315. public override IEnumerator<KeyValuePair<TKey, TValue>>
  316. GetEnumerator()
  317. {
  318. for (int i = 0; i < keys.Count; ++i)
  319. {
  320. yield return new KeyValuePair<TKey, TValue>(
  321. keys[i], values[i].Value);
  322. }
  323. }
  324. #endregion
  325. #region ToString (Public)
  326. /// <summary>
  327. /// To string
  328. /// </summary>
  329. /// <returns>string</returns>
  330. public override string ToString()
  331. {
  332. StringBuilder buffer = new StringBuilder(Convert.ToInt32(maxCapacity));
  333. foreach (TValue item in internalLinkedList)
  334. {
  335. if (buffer.Length > 0)
  336. {
  337. buffer.Append(", ");
  338. }
  339. buffer.Append(item.ToString());
  340. }
  341. return buffer.ToString();
  342. }
  343. #endregion
  344. #region Methods (Private)
  345. #region GetValueNode
  346. /// <summary>
  347. /// Get value node
  348. /// </summary>
  349. /// <param name="key">Key</param>
  350. /// <returns>Node</returns>
  351. private LinkedListNode<TValue> GetValueNode(TKey key)
  352. {
  353. int index = keys.IndexOf(key);
  354. LinkedListNode<TValue> item;
  355. if (index < 0)
  356. {
  357. item = default(LinkedListNode<TValue>); // null;
  358. }
  359. else
  360. {
  361. item = values[index];
  362. }
  363. return item;
  364. }
  365. #endregion
  366. #endregion
  367. }
  368. /// <summary>
  369. /// MostRecentlyUsed tests. Can't execute unit test in generics class.
  370. /// </summary>
  371. internal class MostRecentlyUsedTests
  372. {
  373. #region TestMostRecentlyUsed (Static)
  374. /// <summary>
  375. /// Test most recently used. Note: Too slow for a dynamic unit test.
  376. /// </summary>
  377. [Test]
  378. public static void TestMostRecentlyUsed()
  379. {
  380. MostRecentlyUsed<int, string> mru =
  381. new MostRecentlyUsed<int, string>(3);
  382. Assert.Equal("", mru.ToString());
  383. for (int i = 0; i < 5; i++)
  384. {
  385. //Console.WriteLine("Adding " + i);
  386. mru[i] = "Bla " + i;
  387. //Console.WriteLine(">> State: " + mru);
  388. }
  389. // We must have the last 3 entries now
  390. Assert.Equal("Bla 4, Bla 3, Bla 2", mru.ToString());
  391. // When accessing 3, it must be first then
  392. Assert.Equal("Bla 3", mru[3]);
  393. Assert.Equal("Bla 3, Bla 4, Bla 2", mru.ToString());
  394. // Add a few more entries
  395. for (int i = 5; i < 7; i++)
  396. {
  397. //Console.WriteLine("Adding " + i);
  398. //Console.WriteLine(">> State: " + mru);
  399. mru[i] = "Honk " + i;
  400. }
  401. // Check entries again
  402. Assert.Equal("Honk 6, Honk 5, Bla 3", mru.ToString());
  403. }
  404. #endregion
  405. }
  406. }