Free cookie consent management tool by TermsFeed Policy Generator

source: branches/2886_SymRegGrammarEnumeration/HeuristicLab.Algorithms.DataAnalysis.SymRegGrammarEnumeration/GrammarEnumeration/LruCache.cs @ 16022

Last change on this file since 16022 was 15981, checked in by lkammere, 6 years ago

#2886: Refactor properties to comply with .NET 4.5.2

File size: 9.0 KB
Line 
1using System;
2using System.Collections;
3using System.Collections.Generic;
4using System.Linq;
5using HeuristicLab.Common;
6using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
7
8namespace HeuristicLab.Algorithms.DataAnalysis.SymRegGrammarEnumeration.GrammarEnumeration {
9  /// <summary>
10  /// A least-recently-used cache stored like a dictionary.
11  /// </summary>
12  /// <typeparam name="TKey">
13  /// The type of the key to the cached item
14  /// </typeparam>
15  /// <typeparam name="TValue">
16  /// The type of the cached item.
17  /// </typeparam>
18  /// <remarks>
19  /// Derived from https://stackoverflow.com/a/3719378/240845
20  /// </remarks>
21  [StorableClass]
22  public class LruCache<TKey, TValue> : DeepCloneable, IDictionary<TKey, TValue> {
23    private Dictionary<TKey, LinkedListNode<LruCacheItem>> cacheMap = new Dictionary<TKey, LinkedListNode<LruCacheItem>>();
24    private LinkedList<LruCacheItem> lruList = new LinkedList<LruCacheItem>();
25    private readonly Action<TValue> dispose;
26
27    [Storable]
28    // since a linked list is not directly serializable we use a list of { key, value, index } tuples
29    // the index is necessary to be able to restore the linked list with the correct order of nodes
30    private List<Tuple<TKey, TValue, int>> cacheItems {
31      get {
32        var indexedNodes = lruList.Select((lruCacheItem, index) => new { lruCacheItem, index }).ToDictionary(x => x.lruCacheItem, x => x.index);
33        return cacheMap.Select(x => Tuple.Create(x.Key, x.Value.Value.Value, indexedNodes[x.Value.Value])).ToList();
34      }
35      set {
36        lruList = new LinkedList<LruCacheItem>();
37        cacheMap = new Dictionary<TKey, LinkedListNode<LruCacheItem>>();
38        foreach (var t in value.OrderBy(x => x.Item3)) {
39          var key = t.Item1;
40          var val = t.Item2;
41          var item = new LruCacheItem(key, val);
42          var node = new LinkedListNode<LruCacheItem>(item);
43          lruList.AddLast(node);
44          cacheMap.Add(key, node);
45        }
46      }
47    }
48
49    /// <summary>
50    /// Initializes a new instance of the <see cref="LruCache{TKey, TValue}"/>
51    /// class.
52    /// </summary>
53    /// <param name="capacity">
54    /// Maximum number of elements to cache.
55    /// </param>
56    /// <param name="dispose">
57    /// When elements cycle out of the cache, disposes them. May be null.
58    /// </param>
59    public LruCache(int capacity, Action<TValue> dispose = null) {
60      this.Capacity = capacity;
61      this.dispose = dispose;
62    }
63
64    [StorableConstructor]
65    protected LruCache(bool deserializing) { }
66
67    protected LruCache(LruCache<TKey, TValue> original, Cloner cloner) : base(original, cloner) {
68      cacheMap = new Dictionary<TKey, LinkedListNode<LruCacheItem>>();
69      lruList = new LinkedList<LruCacheItem>(original.lruList.Select(cloner.Clone));
70
71      var nodeMap = new Dictionary<LruCacheItem, LinkedListNode<LruCacheItem>>();
72      for (var node = lruList.First; node != null; node = node.Next) {
73        nodeMap.Add(node.Value, node);
74      }
75
76      foreach (var t in original.cacheMap) {
77        var node = t.Value;
78        var item = node.Value;
79        var clone = (LruCacheItem)cloner.GetClone(item);
80        cacheMap.Add(t.Key, nodeMap[clone]);
81      }
82      Capacity = original.Capacity;
83    }
84
85    /// <summary>
86    /// Gets the capacity of the cache.
87    /// </summary>
88    [Storable]
89    public int Capacity { get; private set; }
90
91    public ICollection<TKey> Keys => cacheMap.Keys;
92
93    public ICollection<TValue> Values => (ICollection<TValue>)Keys.Select(x => cacheMap[x].Value.Value);
94
95    public int Count => cacheMap.Count;
96
97    public bool IsReadOnly => false;
98
99    public TValue this[TKey key] {
100      get { return Get(key, null); }
101      set { Add(key, value); }
102    }
103
104    /// <summary>Gets the value associated with the specified key.</summary>
105    /// <param name="key">
106    /// The key of the value to get.
107    /// </param>
108    /// <param name="value">
109    /// When this method returns, contains the value associated with the specified
110    /// key, if the key is found; otherwise, the default value for the type of the
111    /// <paramref name="value" /> parameter. This parameter is passed
112    /// uninitialized.
113    /// </param>
114    /// <returns>
115    /// true if the <see cref="T:System.Collections.Generic.Dictionary`2" />
116    /// contains an element with the specified key; otherwise, false.
117    /// </returns>
118    public bool TryGetValue(TKey key, out TValue value) {
119      lock (cacheMap) {
120        LinkedListNode<LruCacheItem> node;
121        if (cacheMap.TryGetValue(key, out node)) {
122          value = node.Value.Value;
123          lruList.Remove(node);
124          lruList.AddLast(node);
125          return true;
126        }
127
128        value = default(TValue);
129        return false;
130      }
131    }
132
133    /// <summary>
134    /// Looks for a value for the matching <paramref name="key"/>. If not found,
135    /// calls <paramref name="valueGenerator"/> to retrieve the value and add it to
136    /// the cache.
137    /// </summary>
138    /// <param name="key">
139    /// The key of the value to look up.
140    /// </param>
141    /// <param name="valueGenerator">
142    /// Generates a value if one isn't found.
143    /// </param>
144    /// <returns>
145    /// The requested value.
146    /// </returns>
147    public TValue Get(TKey key, Func<TValue> valueGenerator) {
148      lock (cacheMap) {
149        LinkedListNode<LruCacheItem> node;
150        TValue value;
151        if (cacheMap.TryGetValue(key, out node)) {
152          value = node.Value.Value;
153          lruList.Remove(node);
154          lruList.AddLast(node);
155        } else {
156          value = valueGenerator();
157          if (cacheMap.Count >= Capacity) {
158            RemoveFirst();
159          }
160
161          LruCacheItem cacheItem = new LruCacheItem(key, value);
162          node = new LinkedListNode<LruCacheItem>(cacheItem);
163          lruList.AddLast(node);
164          cacheMap.Add(key, node);
165        }
166
167        return value;
168      }
169    }
170
171    /// <summary>
172    /// Adds the specified key and value to the dictionary.
173    /// </summary>
174    /// <param name="key">
175    /// The key of the element to add.
176    /// </param>throw new NotImplementedException()
177    /// <param name="value">
178    /// The value of the element to add. The value can be null for reference types.
179    /// </param>
180    public void Add(TKey key, TValue value) {
181      lock (cacheMap) {
182        if (cacheMap.Count >= Capacity) {
183          RemoveFirst();
184        }
185
186        LruCacheItem cacheItem = new LruCacheItem(key, value);
187        LinkedListNode<LruCacheItem> node = new LinkedListNode<LruCacheItem>(cacheItem);
188        lruList.AddLast(node);
189        cacheMap.Add(key, node);
190      }
191    }
192
193    public void Remove(TKey key) {
194      LinkedListNode<LruCacheItem> node;
195      if (cacheMap.TryGetValue(key, out node)) {
196        lruList.Remove(node);
197        cacheMap.Remove(key);
198      }
199      dispose?.Invoke(node.Value.Value);
200    }
201
202    private void RemoveFirst() {
203      // Remove from LRUPriority
204      LinkedListNode<LruCacheItem> node = lruList.First;
205      lruList.RemoveFirst();
206
207      // Remove from cache
208      cacheMap.Remove(node.Value.Key);
209
210      // dispose
211      dispose?.Invoke(node.Value.Value);
212    }
213
214    private class LruCacheItem : DeepCloneable {
215      public LruCacheItem(TKey k, TValue v) {
216        Key = k;
217        Value = v;
218      }
219
220      protected LruCacheItem(LruCacheItem original, Cloner cloner) : base(original, cloner) {
221        Key = original.Key;
222        Value = original.Value;
223      }
224
225      public TKey Key { get; }
226
227      public TValue Value { get; }
228
229      public override IDeepCloneable Clone(Cloner cloner) {
230        return new LruCacheItem(this, cloner);
231      }
232    }
233
234    #region implement DeepCloneable abstract methods
235    public override IDeepCloneable Clone(Cloner cloner) {
236      return new LruCache<TKey, TValue>(this, cloner);
237    }
238
239    public bool ContainsKey(TKey key) {
240      return cacheMap.ContainsKey(key);
241    }
242
243    bool IDictionary<TKey, TValue>.Remove(TKey key) {
244      return cacheMap.Remove(key);
245    }
246
247    public void Add(KeyValuePair<TKey, TValue> item) {
248      Add(item.Key, item.Value);
249    }
250
251    public void Clear() {
252      cacheMap.Clear();
253      lruList.Clear();
254    }
255
256    public bool Contains(KeyValuePair<TKey, TValue> item) {
257      return cacheMap.Select(x => new KeyValuePair<TKey, TValue>(x.Key, x.Value.Value.Value)).Contains(item);
258    }
259
260    public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
261      throw new NotImplementedException();
262    }
263
264    public bool Remove(KeyValuePair<TKey, TValue> item) {
265      var ret = cacheMap.ContainsKey(item.Key);
266      Remove(item.Key);
267      return ret;
268    }
269
270    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
271      return cacheMap.Select(x => new KeyValuePair<TKey, TValue>(x.Key, x.Value.Value.Value)).GetEnumerator();
272    }
273
274    IEnumerator IEnumerable.GetEnumerator() {
275      return GetEnumerator();
276    }
277    #endregion
278  }
279}
Note: See TracBrowser for help on using the repository browser.