PageRenderTime 195ms CodeModel.GetById 80ms app.highlight 14ms RepoModel.GetById 97ms app.codeStats 0ms

/Utilities/Collections/MostRecentlyUsed.cs

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