| 1 |
// lrucache.d |
|---|
| 2 |
// to test for correctness, compile thus: |
|---|
| 3 |
// dmd -version="testLRUCache" lrucache.d |
|---|
| 4 |
// |
|---|
| 5 |
/** |
|---|
| 6 |
* Author: Charles Hixson, charleshixsn@earthlink.net |
|---|
| 7 |
* Date: Nov. 8, 2007 |
|---|
| 8 |
* Version: 0.2.b |
|---|
| 9 |
* (Note: Version 0.1 was lacking a version number) |
|---|
| 10 |
* License: MIT license |
|---|
| 11 |
* (Note: Some future version will switch to GPL...but this stuff |
|---|
| 12 |
* *SHOULD* be public domain.) |
|---|
| 13 |
* Copyright: Charles Hixson, all rights reserved. (But see the license.) |
|---|
| 14 |
*/ |
|---|
| 15 |
|
|---|
| 16 |
import std.conv; |
|---|
| 17 |
import std.stdio; |
|---|
| 18 |
|
|---|
| 19 |
/** |
|---|
| 20 |
* Key must implement opCmp and be hashable |
|---|
| 21 |
* Cache's should not be too large, so the size is a ushort. That's excessive. |
|---|
| 22 |
* If you want a cache nearly that large, use a database or something. |
|---|
| 23 |
*/ |
|---|
| 24 |
class LRUCache(Key, Body) |
|---|
| 25 |
{ |
|---|
| 26 |
private: |
|---|
| 27 |
typedef ushort NodeId; |
|---|
| 28 |
Body[Key] bodies; |
|---|
| 29 |
NodeId[Key] nodIds; |
|---|
| 30 |
Body _invalid; |
|---|
| 31 |
ushort _maxLength; |
|---|
| 32 |
NodeId maxNode; |
|---|
| 33 |
|
|---|
| 34 |
public: |
|---|
| 35 |
this (ushort maxLength, Body invalid) |
|---|
| 36 |
{ _invalid = invalid; |
|---|
| 37 |
_maxLength = maxLength; |
|---|
| 38 |
} |
|---|
| 39 |
|
|---|
| 40 |
|
|---|
| 41 |
const (Body) opIndex (Key k) |
|---|
| 42 |
{ if (auto b = k in bodies) |
|---|
| 43 |
{ nodIds[k] = ++maxNode; |
|---|
| 44 |
if (maxNode >= _maxLength - 2) renumber; |
|---|
| 45 |
return *b; |
|---|
| 46 |
} |
|---|
| 47 |
return _invalid; |
|---|
| 48 |
} |
|---|
| 49 |
|
|---|
| 50 |
const (Body) opIndexAssign (Body b, Key k) |
|---|
| 51 |
{ //Node n; |
|---|
| 52 |
if (b == _invalid) return _invalid; |
|---|
| 53 |
nodIds[k] = ++maxNode; |
|---|
| 54 |
bodies[k] = b; |
|---|
| 55 |
if (bodies.length > _maxLength) |
|---|
| 56 |
{ // cache is too large, so remove approx. the 1/4 least recently used |
|---|
| 57 |
//// N.B.: Expiration is based on access time. Creation time is (so far) unused. |
|---|
| 58 |
ulong sum = 0; |
|---|
| 59 |
NodeId maxT = 0; |
|---|
| 60 |
NodeId minT = maxNode; |
|---|
| 61 |
NodeId meanT; |
|---|
| 62 |
foreach (nId; nodIds) |
|---|
| 63 |
{ sum += nId; |
|---|
| 64 |
if (nId < minT) minT = nId; |
|---|
| 65 |
if (nId > maxT) maxT = nId; |
|---|
| 66 |
} |
|---|
| 67 |
meanT = cast(NodeId)(sum / nodIds.length); |
|---|
| 68 |
NodeId limit = cast(NodeId)(minT + (meanT - minT) / 2); |
|---|
| 69 |
foreach (Key key, NodeId nId; nodIds) |
|---|
| 70 |
{ if (nId <= limit) |
|---|
| 71 |
{ nodIds.remove(key); |
|---|
| 72 |
bodies.remove(key); |
|---|
| 73 |
} |
|---|
| 74 |
else nodIds[key] = cast(NodeId)(nodIds[key] - limit + 1); |
|---|
| 75 |
} |
|---|
| 76 |
maxNode = cast(NodeId)(maxNode - limit + 1); |
|---|
| 77 |
} |
|---|
| 78 |
if (maxNode >= _maxLength - 2) renumber; |
|---|
| 79 |
return b; |
|---|
| 80 |
} |
|---|
| 81 |
|
|---|
| 82 |
int length() { return bodies.length; } |
|---|
| 83 |
|
|---|
| 84 |
void remove (Key k) |
|---|
| 85 |
{ if (k in bodies) |
|---|
| 86 |
{ nodIds.remove(k); |
|---|
| 87 |
bodies.remove(k); |
|---|
| 88 |
} |
|---|
| 89 |
} |
|---|
| 90 |
|
|---|
| 91 |
bool contains (Key k) |
|---|
| 92 |
{ if (k in nodIds) |
|---|
| 93 |
{ nodIds[k] = ++maxNode; |
|---|
| 94 |
return true; |
|---|
| 95 |
} |
|---|
| 96 |
return false; |
|---|
| 97 |
} |
|---|
| 98 |
|
|---|
| 99 |
/// for diagnostic purposes only. Do NOT use this value. |
|---|
| 100 |
ulong nodeId(Key k) |
|---|
| 101 |
{ if (auto nId = k in nodIds) return *nId; return 0; } |
|---|
| 102 |
|
|---|
| 103 |
/** |
|---|
| 104 |
* renumber the nodes. This is a separate routine to allow different |
|---|
| 105 |
* algorithms to be easily inserted. I've picked quick & easy. |
|---|
| 106 |
*/ |
|---|
| 107 |
private void renumber() |
|---|
| 108 |
{ NodeId nId = 0; |
|---|
| 109 |
foreach (Key key, NodeId node; nodIds) nodIds[key] = ++nId; |
|---|
| 110 |
maxNode = cast(NodeId)((nId > 0) ? nId - 1 : 0); |
|---|
| 111 |
} |
|---|
| 112 |
version (testLRUCache) |
|---|
| 113 |
{ void dump() |
|---|
| 114 |
{ writef("there are %s entries in the cache\n", bodies.length); |
|---|
| 115 |
Key[] keys = bodies.keys; |
|---|
| 116 |
for (int i = 0; i < keys.length; i++) |
|---|
| 117 |
{ writef ("%2s", keys[i]); |
|---|
| 118 |
writef (": %10s", nodIds[keys[i]]); |
|---|
| 119 |
writef (": %s\n", bodies[keys[i]]); |
|---|
| 120 |
} |
|---|
| 121 |
} |
|---|
| 122 |
} |
|---|
| 123 |
} |
|---|
| 124 |
|
|---|
| 125 |
|
|---|
| 126 |
version (testLRUCache) |
|---|
| 127 |
{ |
|---|
| 128 |
void main() |
|---|
| 129 |
{ auto l = new LRUCache!(int, char)(10, cast(char)0); |
|---|
| 130 |
string s = "This is a rather long string of quasi-random text,"; |
|---|
| 131 |
int i = 0; |
|---|
| 132 |
foreach (char c; s) |
|---|
| 133 |
{ writef ("adding %d\n", i); |
|---|
| 134 |
l[i++] = c; |
|---|
| 135 |
} |
|---|
| 136 |
l.dump; |
|---|
| 137 |
for (i = 0; i < s.length; i++) |
|---|
| 138 |
{ if (l.contains(i)) writef ("l[%2s] = '%s', id = %s\n", i, l[i], l.nodeId(i)); |
|---|
| 139 |
//else writef ("l[%2s] = **missing**\n", i); |
|---|
| 140 |
} |
|---|
| 141 |
auto l2 = new LRUCache!(char, int)(10, -17); |
|---|
| 142 |
//string s = "This is a rather long string of quasi-random text,"; |
|---|
| 143 |
//int i = 0; |
|---|
| 144 |
i = 0; |
|---|
| 145 |
foreach (char c; s) |
|---|
| 146 |
{ writef ("adding %d\n", i); |
|---|
| 147 |
l2[c] = ++i; |
|---|
| 148 |
} |
|---|
| 149 |
l2.dump; |
|---|
| 150 |
// this next bit shows that accesses change the nodeId |
|---|
| 151 |
// remember that nodeId should not be used for any purpose outside of the class... |
|---|
| 152 |
// it's highly unstable. |
|---|
| 153 |
for (i = 0; i < s.length; i++) |
|---|
| 154 |
{ if (l2.contains(s[i])) |
|---|
| 155 |
writef ("l2[%2s] = '%s', id = %s\n", s[i], l2[s[i]], l2.nodeId(s[i])); |
|---|
| 156 |
//else |
|---|
| 157 |
// writef ("l2[%2s] = **missing**\n", s[i]); |
|---|
| 158 |
} |
|---|
| 159 |
} |
|---|
| 160 |
} |
|---|