using System; using System.Collections; using System.Collections.Generic; namespace SlatedGameToolkit.Framework.Utilities.Collections { public class LRUCache : ICollection { public V this[K key] { get { return Get(key); } set { Set(key, value); } } private Node first, last; private int maxLength; public int MaxLength { get { return maxLength; } set { maxLength = value; } } Dictionary hashmap; public int Count => hashmap.Count; public bool IsSynchronized => false; public object SyncRoot => this; public LRUCache(int initialSize = 1024) { this.maxLength = initialSize; if (initialSize < 1) throw new ArgumentException("size cannot be less than 1."); this.hashmap = new Dictionary(maxLength + 1); } /// /// Sets the value given the key. If the key already registered with a value, the previous value is replaced. /// If the key did not exist, it is registered with the value. /// /// The key to associate the value with. /// The value to cache. public void Set(K key, V value) { if (!hashmap.ContainsKey(key)) { Node node = new Node(key, value); hashmap.Add(key, node); AddToStack(node); if (hashmap.Count > maxLength) { hashmap.Remove(RemoveLastFromStack().key); } } else { Node node = hashmap[key]; node.value = value; MoveToFrontOfStack(node); } hashmap[key].value = value; } /// /// Retrieves the key, and increases the pair's usage ranking. /// If the key doesn't exist, a KeyNotFoundException is thrown. /// /// The key to retrieve the value for. /// The value. public V Get(K key) { if (ContainsKey(key)) { Node node = hashmap[key]; MoveToFrontOfStack(node); return node.value; } throw new KeyNotFoundException(); } /// /// Checks if the key exists and returns it if it does, otherwise, /// uses the function to calculate the value and cache it. /// /// The key to retrieve the value for. /// The function to calculate the value in the case the key doesn't have a associated with it. /// The value. public V ComputeIfNonExistent(K key, Func func) { if (ContainsKey(key)) { return hashmap[key].value; } V val = func(key); Set(key, val); return val; } /// /// If the cache currently holds a value for the key. /// The value for the key can be null. /// /// The key to verify for a value. /// True if there does exist a value for the key in the cache. public bool ContainsKey(K key) { return hashmap.ContainsKey(key); } private void AddToStack(Node node) { node.left = null; if (first != null) { node.right = first; first.left = node; } else { last = node; } first = node; } private Node RemoveLastFromStack() { if (last != null) { Node last = this.last; RemoveFromStack(last); return last; } return null; } private void MoveToFrontOfStack(Node node) { RemoveFromStack(node); AddToStack(node); } private void RemoveFromStack(Node node) { if (node.left == null) { first = node.right; } else { node.left.right = node.right; } if (node.right == null) { last = node.left; } else { node.right.left = node.left; } node.left = null; node.right = null; } public void CopyTo(Array array, int index) { Node currentNode = first; for (int i = 0; i < array.Length && i < Count; i++) { array.SetValue(currentNode.value, i + index); currentNode = currentNode.right; } } public IEnumerator GetEnumerator() { return new LinkedHashmapEnumerable(first); } private class LinkedHashmapEnumerable : IEnumerator { Node start, current; public LinkedHashmapEnumerable(Node start) { this.start = start; this.current = start; } public V Current => current.value; object IEnumerator.Current => current.value; public void Dispose() { } public bool MoveNext() { current = current.right; return current != null; } public void Reset() { current = start; } } private class Node { public Node left, right; public readonly K key; public V value; public Node(K key, V value) { this.key = key; this.value = value; } public override int GetHashCode() { return value.GetHashCode(); } public override bool Equals(object obj) { if ((obj == null && value == null) || GetType() != obj.GetType()) { return false; } return value.Equals(obj); } } } }