Download Reference Manual
The Developer's Library for D
About Wiki Forums Source Search Contact

CollectionImprovements: Cache.d

File Cache.d, 5.0 kB (added by darrylb, 7 months ago)

Example of generic functionality that could be added to collection. (As a Cache, but could also be added simply as a MRU/LRU or BoundedList?)

Line 
1 /++
2     author: Darryl Bleau
3
4     Generic LRU Cache.
5
6     Makes a cache that has an upper limit, and the items least used will be removed. Putting an item doesn't gurantee that it will be in there later.
7
8     Example
9     ---
10         auto myCache = new Cache!(char[], char[])(3);
11         myCache["one"] = "1";
12         myCache["two"] = "2";
13         myCache["three"] = "3";
14         Stdout(myCache["one"], myCache["two"], myCache["three"]).newline;
15     ---
16 ++/
17
18 module tetra.util.Cache;
19
20 import tetra.util.container.Hash;
21 import tetra.util.container.List;
22
23 /++
24     Cache class...
25 ++/
26 public class Cache(K,V)
27 {
28     private struct cacheReference
29     {
30         K key;
31         V value;   
32     }   
33    
34     private Hash!(K,Item!(cacheReference)) chash;
35     private List!(cacheReference) mru;
36     private int _size;
37     /++ Property containing the maximum number of items that this cache will hold. ++/
38     int size()  { return this._size; }
39     void size(int size) { this._size = size; }
40    
41     /++
42         Add an item to the cache
43
44         Params:
45             key = the key to the item
46             value = the value to the item
47     ++/
48     final void add(K key, V value)
49     {
50         Item!(cacheReference) item = chash[key];
51         if (item !is null)
52             item.data.value = value;
53         else
54         {
55             if (mru.length == _size)
56             {
57                 Item!(cacheReference) lastItem = mru.lastItem;
58                 mru.remove(lastItem);
59                 chash.remove(lastItem.data.key);
60             }
61             cacheReference newReference;
62             newReference.key = key;
63             newReference.value = value;
64             Item!(cacheReference) newItem = new Item!(cacheReference)(newReference);
65             chash[key] = newItem;
66             mru.first = newItem; // Idea: perhaps newly added items should be added to the middle of the mru?
67         }
68     }
69     /++
70         Operator to allow blah[key] = value
71     ++/
72     final void opIndexAssign(V value, K key) { this.add(key, value); }
73
74     /++
75         The number of items currently in the cache
76     ++/
77     final int length()
78     {
79         return mru.length; 
80     }
81    
82     /++
83         remove an item from the cache based on the key
84
85         Params:
86             key = the key of the item to remove.
87     ++/
88     final void remove(K key)
89     {
90         Item!(cacheReference) item = chash[key];
91         if (item !is null)
92         {
93             mru.remove(item);
94             chash.remove(key);
95         }
96     }
97    
98     /++
99         Operator that allows foreach. Returns the key
100     ++/
101     int opApply(int delegate(inout K key) dg)
102     {
103         int result;
104         foreach(cacheReference cr; mru)
105         {
106             if ((result = dg(cr.key)) != 0)
107                 break;
108         }
109         return result;
110     }
111     /++
112         Operator that allows reverse foreach, returns the key
113     ++/
114     int opApplyReverse(int delegate(inout K key) dg)
115     {
116         int result;
117         foreach_reverse(cacheReference cr; mru)
118         {
119             if ((result = dg(cr.key)) != 0)
120                 break;
121         }
122         return result;
123     }
124     /++
125         Operator that allows foreach, returns key and value
126     ++/
127     int opApply(int delegate(inout K key, inout V value) dg)
128     {
129         int result;
130         foreach(cacheReference cr; mru)
131         {
132             if ((result = dg(cr.key, cr.value)) != 0)
133                 break;
134         }
135         return result;
136     }   
137     /++
138         Operator that allows reverse foreach, returns key and value
139     ++/
140     int opApplyReverse(int delegate(inout K key, inout V value) dg)
141     {
142         int result;
143         foreach_reverse(cacheReference cr; mru)
144         {
145             if ((result = dg(cr.key, cr.value)) != 0)
146                 break;
147         }
148         return result;
149     }
150    
151     /++
152         Returns a float representing how full the cache is.
153     ++/
154     float load()
155     {
156         return cast(float)(cast(float)this.length / cast(float)this.size); 
157     }
158    
159     /++
160         Operator to allow access of values via index: auto val = myCache["blah"];
161     ++/
162     final V opIndex(K key)
163     {
164         V rtn;
165         Item!(cacheReference) item = cast(Item!(cacheReference))cast(void*)chash[key];
166         if (item !is null)
167         {
168             mru.remove(item);
169             mru.first = item;
170             rtn = item.data.value;
171         }
172         return rtn;
173     }
174    
175     /++
176         Operator to check if the key exists in the cache: if ("blah" in myCache") {}
177     ++/
178     final bool opIn_r(K key)
179     {
180         return (key in chash);
181     }
182    
183     /++
184         Create a new cache
185
186         Params:
187             size = the initial size of the cache
188     ++/
189     this(int size)
190     {
191         if (size > 0)
192         {
193             chash = new Hash!(K,Item!(cacheReference));
194             mru = new List!(cacheReference);
195             _size = size;
196         }
197         else
198             throw new Exception("Cache size must be greater than zero.");
199     }
200 }
201
202 version(Test)
203 {
204     import tetra.util.Test;
205     import tango.io.Stdout;
206     unittest
207     {
208         Test.Status basicFunctionality(inout char[][] messages)
209         {
210             auto myCache = new Cache!(char[], char[])(3);
211             myCache["one"] = "1";
212             myCache["two"] = "2";
213             myCache["three"] = "3";
214             myCache["one"];
215             myCache["two"];
216             myCache.remove("two");
217             myCache["four"] = "4";
218             myCache["five"] = "5";
219             if (myCache["one"] == "1" && myCache["two"] == null && myCache["three"] == null && myCache["four"] == "4" && myCache["five"] == "5" && myCache.length == 3 && myCache.size == 3)           
220             {
221                 bool a, b, c;
222                 foreach(char[] key, char[] value; myCache)
223                 {
224                     if (key == "one" && value == "1")
225                         a = true;
226                     else if (key == "four" && value == "4")
227                         b = true;
228                     else if (key == "five" && value == "5")
229                         c = true;
230                     else
231                         break;
232                 }
233                 if (a && b && c)
234                 {
235                     if (myCache.load == 1.00)
236                         return Test.Status.Success;
237                 }
238             }
239             return Test.Status.Failure;
240         }
241
242         auto t = new Test("tetra.util.Cache");
243         t["Basic functionality"] = &basicFunctionality;
244         t.run();
245     }
246 }