ObjectMap.hx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. /*
  2. * Copyright (C)2005-2012 Haxe Foundation
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a
  5. * copy of this software and associated documentation files (the "Software"),
  6. * to deal in the Software without restriction, including without limitation
  7. * the rights to use, copy, modify, merge, publish, distribute, sublicense,
  8. * and/or sell copies of the Software, and to permit persons to whom the
  9. * Software is furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  20. * DEALINGS IN THE SOFTWARE.
  21. */
  22. package haxe.ds;
  23. import cs.NativeArray;
  24. @:coreApi class ObjectMap<K:haxe.Constraints.ObjectMapKey, V> implements Map.IMap<K,V>
  25. {
  26. @:extern private static inline var HASH_UPPER = 0.77;
  27. @:extern private static inline var FLAG_EMPTY = 0;
  28. @:extern private static inline var FLAG_DEL = 1;
  29. /**
  30. * This is the most important structure here and the reason why it's so fast.
  31. * It's an array of all the hashes contained in the table. These hashes cannot be 0 nor 1,
  32. * which stand for "empty" and "deleted" states.
  33. *
  34. * The lookup algorithm will keep looking until a 0 or the key wanted is found;
  35. * The insertion algorithm will do the same but will also break when FLAG_DEL is found;
  36. */
  37. private var hashes:NativeArray<HashType>;
  38. private var _keys:NativeArray<K>;
  39. private var vals:NativeArray<V>;
  40. private var nBuckets:Int;
  41. private var size:Int;
  42. private var nOccupied:Int;
  43. private var upperBound:Int;
  44. private var cachedKey:K;
  45. private var cachedIndex:Int;
  46. #if DEBUG_HASHTBL
  47. private var totalProbes:Int;
  48. private var probeTimes:Int;
  49. private var sameHash:Int;
  50. private var maxProbe:Int;
  51. #end
  52. public function new() : Void
  53. {
  54. cachedIndex = -1;
  55. }
  56. public function set( key : K, value : V ) : Void
  57. {
  58. var x:Int, k:Int;
  59. if (nOccupied >= upperBound)
  60. {
  61. if (nBuckets > (size << 1))
  62. resize(nBuckets - 1); //clear "deleted" elements
  63. else
  64. resize(nBuckets + 2);
  65. }
  66. var hashes = hashes, keys = _keys, hashes = hashes;
  67. {
  68. var mask = (nBuckets == 0) ? 0 : nBuckets - 1;
  69. var site = x = nBuckets;
  70. k = hash(key);
  71. var i = k & mask, nProbes = 0;
  72. //for speed up
  73. if (isEither(hashes[i])) {
  74. x = i;
  75. } else {
  76. //var inc = getInc(k, mask);
  77. var last = i, flag;
  78. while(! (isEither(flag = hashes[i]) || (flag == k && _keys[i] == key)) )
  79. {
  80. i = (i + ++nProbes) & mask;
  81. #if DEBUG_HASHTBL
  82. probeTimes++;
  83. if (i == last)
  84. throw "assert";
  85. #end
  86. }
  87. x = i;
  88. }
  89. #if DEBUG_HASHTBL
  90. if (nProbes > maxProbe)
  91. maxProbe = nProbes;
  92. totalProbes++;
  93. #end
  94. }
  95. var flag = hashes[x];
  96. if (isEmpty(flag))
  97. {
  98. keys[x] = key;
  99. vals[x] = value;
  100. hashes[x] = k;
  101. size++;
  102. nOccupied++;
  103. } else if (isDel(flag)) {
  104. keys[x] = key;
  105. vals[x] = value;
  106. hashes[x] = k;
  107. size++;
  108. } else {
  109. assert(_keys[x] == key);
  110. vals[x] = value;
  111. }
  112. cachedIndex = x;
  113. cachedKey = key;
  114. }
  115. @:final private function lookup( key : K ) : Int
  116. {
  117. if (nBuckets != 0)
  118. {
  119. var hashes = hashes, keys = _keys;
  120. var mask = nBuckets - 1, hash = hash(key), k = hash, nProbes = 0;
  121. var i = k & mask;
  122. var last = i, flag;
  123. //var inc = getInc(k, mask);
  124. while (!isEmpty(flag = hashes[i]) && (isDel(flag) || flag != k || keys[i] != key))
  125. {
  126. i = (i + ++nProbes) & mask;
  127. #if DEBUG_HASHTBL
  128. probeTimes++;
  129. if (i == last)
  130. throw "assert";
  131. #end
  132. }
  133. #if DEBUG_HASHTBL
  134. if (nProbes > maxProbe)
  135. maxProbe = nProbes;
  136. totalProbes++;
  137. #end
  138. return isEither(flag) ? -1 : i;
  139. }
  140. return -1;
  141. }
  142. @:final @:private function resize(newNBuckets:Int) : Void
  143. {
  144. //This function uses 0.25*n_bucktes bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets.
  145. var newHash = null;
  146. var j = 1;
  147. {
  148. newNBuckets = roundUp(newNBuckets);
  149. if (newNBuckets < 4) newNBuckets = 4;
  150. if (size >= (newNBuckets * HASH_UPPER + 0.5)) /* requested size is too small */
  151. {
  152. j = 0;
  153. } else { /* hash table size to be changed (shrink or expand); rehash */
  154. var nfSize = newNBuckets;
  155. newHash = new NativeArray( nfSize );
  156. if (nBuckets < newNBuckets) //expand
  157. {
  158. var k = new NativeArray(newNBuckets);
  159. if (_keys != null)
  160. arrayCopy(_keys, 0, k, 0, nBuckets);
  161. _keys = k;
  162. var v = new NativeArray(newNBuckets);
  163. if (vals != null)
  164. arrayCopy(vals, 0, v, 0, nBuckets);
  165. vals = v;
  166. } //otherwise shrink
  167. }
  168. }
  169. if (j != 0)
  170. { //rehashing is required
  171. //resetting cache
  172. cachedKey = null;
  173. cachedIndex = -1;
  174. j = -1;
  175. var nBuckets = nBuckets, _keys = _keys, vals = vals, hashes = hashes;
  176. var newMask = newNBuckets - 1;
  177. while (++j < nBuckets)
  178. {
  179. var k;
  180. if (!isEither(k = hashes[j]))
  181. {
  182. var key = _keys[j];
  183. var val = vals[j];
  184. hashes[j] = FLAG_DEL;
  185. while (true) /* kick-out process; sort of like in Cuckoo hashing */
  186. {
  187. var nProbes = 0;
  188. //var inc = getInc(k, newMask);
  189. var i = k & newMask;
  190. while (!isEmpty(newHash[i]))
  191. i = (i + ++nProbes) & newMask;
  192. newHash[i] = k;
  193. if (i < nBuckets && !isEither(k = hashes[i])) /* kick out the existing element */
  194. {
  195. {
  196. var tmp = _keys[i];
  197. _keys[i] = key;
  198. key = tmp;
  199. }
  200. {
  201. var tmp = vals[i];
  202. vals[i] = val;
  203. val = tmp;
  204. }
  205. hashes[i] = FLAG_DEL; /* mark it as deleted in the old hash table */
  206. } else { /* write the element and jump out of the loop */
  207. _keys[i] = key;
  208. vals[i] = val;
  209. break;
  210. }
  211. }
  212. }
  213. }
  214. if (nBuckets > newNBuckets) /* shrink the hash table */
  215. {
  216. {
  217. var k = new NativeArray(newNBuckets);
  218. arrayCopy(_keys, 0, k, 0, newNBuckets);
  219. this._keys = k;
  220. }
  221. {
  222. var v = new NativeArray(newNBuckets);
  223. arrayCopy(vals, 0, v, 0, newNBuckets);
  224. this.vals = v;
  225. }
  226. }
  227. this.hashes = newHash;
  228. this.nBuckets = newNBuckets;
  229. this.nOccupied = size;
  230. this.upperBound = Std.int(newNBuckets * HASH_UPPER + .5);
  231. }
  232. }
  233. public function get( key : K ) : Null<V>
  234. {
  235. var idx = -1;
  236. if (cachedKey == key && ( (idx = cachedIndex) != -1 ))
  237. {
  238. return vals[idx];
  239. }
  240. idx = lookup(key);
  241. if (idx != -1)
  242. {
  243. cachedKey = key;
  244. cachedIndex = idx;
  245. return vals[idx];
  246. }
  247. return null;
  248. }
  249. private function getDefault( key : K, def : V ) : V
  250. {
  251. var idx = -1;
  252. if (cachedKey == key && ( (idx = cachedIndex) != -1 ))
  253. {
  254. return vals[idx];
  255. }
  256. idx = lookup(key);
  257. if (idx != -1)
  258. {
  259. cachedKey = key;
  260. cachedIndex = idx;
  261. return vals[idx];
  262. }
  263. return def;
  264. }
  265. public function exists( key : K ) : Bool
  266. {
  267. var idx = -1;
  268. if (cachedKey == key && ( (idx = cachedIndex) != -1 ))
  269. {
  270. return true;
  271. }
  272. idx = lookup(key);
  273. if (idx != -1)
  274. {
  275. cachedKey = key;
  276. cachedIndex = idx;
  277. return true;
  278. }
  279. return false;
  280. }
  281. public function remove( key : K ) : Bool
  282. {
  283. var idx = -1;
  284. if (! (cachedKey == key && ( (idx = cachedIndex) != -1 )))
  285. {
  286. idx = lookup(key);
  287. }
  288. if (idx == -1)
  289. {
  290. return false;
  291. } else {
  292. if (cachedKey == key)
  293. cachedIndex = -1;
  294. hashes[idx] = FLAG_DEL;
  295. _keys[idx] = null;
  296. vals[idx] = null;
  297. --size;
  298. return true;
  299. }
  300. }
  301. /**
  302. Returns an iterator of all keys in the hashtable.
  303. Implementation detail: Do not set() any new value while iterating, as it may cause a resize, which will break iteration
  304. **/
  305. public function keys() : Iterator<K>
  306. {
  307. var i = 0;
  308. var len = nBuckets;
  309. return {
  310. hasNext: function() {
  311. for (j in i...len)
  312. {
  313. if (!isEither(hashes[j]))
  314. {
  315. i = j;
  316. return true;
  317. }
  318. }
  319. return false;
  320. },
  321. next: function() {
  322. var ret = _keys[i];
  323. cachedIndex = i;
  324. cachedKey = ret;
  325. i = i + 1;
  326. return ret;
  327. }
  328. };
  329. }
  330. /**
  331. Returns an iterator of all values in the hashtable.
  332. Implementation detail: Do not set() any new value while iterating, as it may cause a resize, which will break iteration
  333. **/
  334. public function iterator() : Iterator<V>
  335. {
  336. var i = 0;
  337. var len = nBuckets;
  338. return {
  339. hasNext: function() {
  340. for (j in i...len)
  341. {
  342. if (!isEither(hashes[j]))
  343. {
  344. i = j;
  345. return true;
  346. }
  347. }
  348. return false;
  349. },
  350. next: function() {
  351. var ret = vals[i];
  352. i = i + 1;
  353. return ret;
  354. }
  355. };
  356. }
  357. /**
  358. Returns an displayable representation of the hashtable content.
  359. **/
  360. public function toString() : String {
  361. var s = new StringBuf();
  362. s.add("{");
  363. var it = keys();
  364. for( i in it ) {
  365. s.add(Std.string(i));
  366. s.add(" => ");
  367. s.add(Std.string(get(i)));
  368. if( it.hasNext() )
  369. s.add(", ");
  370. }
  371. s.add("}");
  372. return s.toString();
  373. }
  374. @:extern private static inline function roundUp(x:Int):Int
  375. {
  376. --x;
  377. x |= (x) >>> 1;
  378. x |= (x) >>> 2;
  379. x |= (x) >>> 4;
  380. x |= (x) >>> 8;
  381. x |= (x) >>> 16;
  382. return ++x;
  383. }
  384. @:extern private static inline function getInc(k:Int, mask:Int):Int //return 1 for linear probing
  385. return (((k) >> 3 ^ (k) << 3) | 1) & (mask);
  386. @:extern private static inline function isEither(v:HashType):Bool
  387. return (v & 0xFFFFFFFE) == 0;
  388. @:extern private static inline function isEmpty(v:HashType):Bool
  389. return v == FLAG_EMPTY;
  390. @:extern private static inline function isDel(v:HashType):Bool
  391. return v == FLAG_DEL;
  392. //guarantee: Whatever this function is, it will never return 0 nor 1
  393. @:extern private static inline function hash<K>(s:K):HashType
  394. {
  395. var k:Int = untyped s.GetHashCode();
  396. //k *= 357913941;
  397. //k ^= k << 24;
  398. //k += ~357913941;
  399. //k ^= k >> 31;
  400. //k ^= k << 31;
  401. k = (k+0x7ed55d16) + (k<<12);
  402. k = (k^0xc761c23c) ^ (k>>19);
  403. k = (k+0x165667b1) + (k<<5);
  404. k = (k+0xd3a2646c) ^ (k<<9);
  405. k = (k+0xfd7046c5) + (k<<3);
  406. k = (k^0xb55a4f09) ^ (k>>16);
  407. var ret = k;
  408. if (isEither(ret))
  409. {
  410. if (ret == 0)
  411. ret = 2;
  412. else
  413. ret = 0xFFFFFFFF;
  414. }
  415. return ret;
  416. }
  417. @:extern private static inline function arrayCopy(sourceArray:cs.system.Array, sourceIndex:Int, destinationArray:cs.system.Array, destinationIndex:Int, length:Int):Void
  418. cs.system.Array.Copy(sourceArray, sourceIndex, destinationArray, destinationIndex, length);
  419. @:extern private static inline function assert(x:Bool):Void
  420. {
  421. #if DEBUG_HASHTBL
  422. if (!x) throw "assert failed";
  423. #end
  424. }
  425. }
  426. private typedef HashType = Int;