1 /// 2 module cachetools.containers.orderedhashmap; 3 4 private import std.experimental.allocator.mallocator : Mallocator; 5 private import std.experimental.allocator.gc_allocator; 6 private import std.typecons; 7 private import std.traits; 8 9 import cachetools.internal; 10 import cachetools.containers.hashmap; 11 import cachetools.containers.lists; 12 13 /// 14 struct OrderedHashMap(K, V, Allocator = Mallocator, bool GCRangesAllowed = true) 15 { 16 17 private 18 { 19 alias StoredKeyType = StoredType!K; 20 alias StoredValueType = StoredType!V; 21 alias KeysType = CompressedList!(K, Allocator, GCRangesAllowed); 22 alias HashesType = HashMap!(K, HashMapElement, Allocator, GCRangesAllowed); 23 24 struct HashMapElement 25 { 26 StoredValueType value; 27 KeysType.NodePointer kptr; 28 } 29 30 KeysType __keys; 31 HashesType __hashes; 32 } 33 this(this) { 34 import std.algorithm.mutation; 35 KeysType keys; 36 HashesType hashes; 37 foreach(k; __keys) { 38 auto p = keys.insertBack(k); 39 if (auto vp = k in __hashes) { 40 hashes.put(k, HashMapElement(vp.value, p)); 41 } 42 } 43 __keys.clear; 44 __hashes.clear; 45 swap(__keys,keys); 46 swap(__hashes, hashes); 47 } 48 void opAssign(ref typeof(this) rhs) { 49 if ( rhs is this ) return; 50 foreach (k; rhs.__keys) { 51 auto p = __keys.insertBack(k); 52 if (auto vp = k in rhs.__hashes) { 53 __hashes.put(k, HashMapElement(vp.value, p)); 54 } 55 } 56 } 57 string toString() { 58 import std.algorithm, std.array, std.format; 59 60 auto pairs = byPair; 61 return "[%s]".format(pairs.map!(p => "%s:%s".format(p.key, p.value)).array.join(", ")); 62 } 63 64 /// 65 auto put(K k, V v) 66 { 67 auto hashesptr = k in __hashes; 68 if (hashesptr is null) 69 { 70 // append to list and store in hashes 71 auto keysptr = __keys.insertBack(k); 72 __hashes.put(k, HashMapElement(v, keysptr)); 73 return Nullable!V(); 74 } 75 else 76 { 77 auto o = hashesptr.value; 78 hashesptr.value = v; 79 return nullable(o); 80 } 81 } 82 /// 83 /// map[key] 84 /// Attention: you can't use this method in @nogc code. 85 /// Usual aa[key] method. 86 /// Throws exception if key not found 87 /// Returns: value for given key 88 /// 89 ref V opIndex(in K k) @safe 90 { 91 V* v = k in this; 92 if (v !is null) 93 { 94 return *v; 95 } 96 throw new KeyNotFound(); 97 } 98 99 /// 100 /// map[k] = v; 101 /// 102 void opIndexAssign(V v, K k) @safe 103 { 104 put(k, v); 105 } 106 /// 107 bool remove(K k) @safe 108 { 109 auto hashesptr = k in __hashes; 110 if (hashesptr is null) 111 { 112 return false; 113 } 114 auto keysptr = hashesptr.kptr; 115 () @trusted { __keys.remove(keysptr); }(); 116 __hashes.remove(k); 117 return true; 118 } 119 /// 120 void clear() @safe 121 { 122 __hashes.clear(); 123 __keys.clear(); 124 } 125 126 /// get numter of keys in table 127 auto length() const pure nothrow @nogc @safe 128 { 129 return __keys.length; 130 } 131 132 /// key in table 133 /// Returns: pointer to stored value (if key in table) or null 134 /// 135 V* opBinaryRight(string op)(in K k) @safe if (op == "in") 136 { 137 auto hashesptr = k in __hashes; 138 if (hashesptr) 139 { 140 return &hashesptr.value; 141 } 142 return null; 143 } 144 145 /// 146 /// get 147 /// Returns: value from hash, or defaultValue if key not found (see also getOrAdd). 148 /// defaultValue can be callable. 149 /// 150 V get(T)(K k, T defaultValue) @safe 151 { 152 auto v = k in __hashes; 153 if (v) 154 { 155 return v.value; 156 } 157 static if (isAssignable!(V, T)) 158 { 159 return defaultValue; 160 } 161 else static if (isCallable!T && isAssignable!(V, ReturnType!T)) 162 { 163 return defaultValue(); 164 } 165 else 166 { 167 static assert(0, "You must call 'get' with default value of HashMap 'value' type, or with callable, returning HashMap 'value'"); 168 } 169 } 170 /// 171 V getOrAdd(T)(K k, T defaultValue) @safe 172 { 173 auto v = k in __hashes; 174 if (v) 175 { 176 return v.value; 177 } 178 static if (isAssignable!(V, T)) 179 { 180 put(k, defaultValue); 181 return defaultValue; 182 } 183 else static if (isCallable!T && isAssignable!(V, ReturnType!T)) 184 { 185 auto value = defaultValue(); 186 return put(k, value); 187 return value; 188 } 189 else 190 { 191 static assert(0, "what?"); 192 } 193 } 194 195 /// 196 bool addIfMissed(T)(K k, T value) @safe { 197 auto v = k in __hashes; 198 if (v) { 199 return false; 200 } 201 static if (isAssignable!(V, T)) { 202 put(k, value); 203 return true; 204 } 205 else static if (isCallable!T && isAssignable!(V, ReturnType!T)) { 206 put(k, value()); 207 return true; 208 } 209 else { 210 static assert(0, "what?"); 211 } 212 } 213 214 /// iterator by keys 215 auto byKey() pure @safe @nogc 216 { 217 return __keys.range(); 218 } 219 /// iterator by value 220 auto byValue() pure @safe @nogc 221 { 222 struct _range 223 { 224 private KeysType.Range _keysIter; 225 private HashesType* _hashes; 226 V front() @safe 227 { 228 auto p = _keysIter.front in *_hashes; 229 return p.value; 230 } 231 232 bool empty() @safe 233 { 234 return _keysIter.empty(); 235 } 236 237 void popFront() @safe 238 { 239 _keysIter.popFront; 240 } 241 } 242 243 return _range(__keys.range, &__hashes); 244 } 245 246 /// 247 auto byPair() pure @safe @nogc 248 { 249 struct _range 250 { 251 private KeysType.Range _keysIter; 252 private HashesType* _hashes; 253 auto front() @safe 254 { 255 auto p = _keysIter.front in *_hashes; 256 return Tuple!(K, "key", V, "value")(_keysIter.front, p.value); 257 } 258 259 bool empty() @safe 260 { 261 return _keysIter.empty(); 262 } 263 264 void popFront() @safe 265 { 266 _keysIter.popFront; 267 } 268 } 269 270 return _range(__keys.range, &__hashes); 271 } 272 } 273 274 unittest { 275 import std.experimental.logger; 276 globalLogLevel = LogLevel.info; 277 info("Testing OrderedHashMap"); 278 } 279 280 @safe @nogc unittest 281 { 282 import std.algorithm; 283 import std.range; 284 import std.stdio; 285 OrderedHashMap!(int, int) dict; 286 dict.put(1, 1); 287 int* v = 1 in dict; 288 assert(v !is null && *v == 1); 289 assert(dict.remove(1)); 290 assert(1 !in dict); 291 assert(!dict.remove(2)); 292 assert(dict.length == 0); 293 iota(100).each!(a => dict.put(a, a)); 294 assert(dict.length == 100); 295 assert(equal(dict.byKey, iota(100))); 296 assert(equal(dict.byValue, iota(100))); 297 assert(dict.getOrAdd(100, 100) == 100); 298 } 299 300 @safe @nogc unittest { 301 import std.algorithm; 302 import std.range; 303 304 OrderedHashMap!(int, int) dict0, dict1; 305 iota(100).each!(a => dict0.put(a, a)); 306 dict1 = dict0; 307 assert(0 in dict1); 308 dict1.remove(0); 309 assert(equal(dict1.byKey, iota(1,100))); 310 } 311 312 @safe unittest { 313 class C { 314 hash_t c; 315 override hash_t toHash() const @safe { 316 return c; 317 } 318 319 bool opEquals(const C other) const @safe { 320 return c == other.c; 321 } 322 323 this(hash_t i) { 324 c = i; 325 } 326 // void opCast(const C from) { 327 // c = from.c; 328 // } 329 } 330 331 OrderedHashMap!(C, int) map; 332 int* f(const C c) { 333 auto v = map[c]; 334 // can't do this with classes because put require key assignment which can't convert const object to mutable 335 //map.put(c, 2); 336 return c in map; 337 } 338 339 C c = new C(1); 340 map[c] = 1; 341 f(c); 342 }