StringMap.hx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. /*
  2. * Copyright (C)2005-2018 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 java.NativeArray;
  24. @:coreApi class StringMap<T> implements haxe.Constraints.IMap<String,T>
  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<String>;
  39. private var vals:NativeArray<T>;
  40. private var nBuckets:Int;
  41. private var size:Int;
  42. private var nOccupied:Int;
  43. private var upperBound:Int;
  44. #if !no_map_cache
  45. private var cachedKey:String;
  46. private var cachedIndex:Int;
  47. #end
  48. #if DEBUG_HASHTBL
  49. private var totalProbes:Int;
  50. private var probeTimes:Int;
  51. private var sameHash:Int;
  52. private var maxProbe:Int;
  53. #end
  54. public function new() : Void
  55. {
  56. #if !no_map_cache
  57. cachedIndex = -1;
  58. #end
  59. }
  60. public function set( key : String, value : T ) : Void
  61. {
  62. var x:Int, k:Int;
  63. if (nOccupied >= upperBound)
  64. {
  65. if (nBuckets > (size << 1))
  66. {
  67. resize(nBuckets - 1); //clear "deleted" elements
  68. } else {
  69. resize(nBuckets + 2);
  70. }
  71. }
  72. var hashes = hashes, keys = _keys, hashes = hashes;
  73. {
  74. var mask = (nBuckets == 0) ? 0 : nBuckets - 1;
  75. var site = x = nBuckets;
  76. k = hash(key);
  77. var i = k & mask, nProbes = 0;
  78. var delKey = -1;
  79. // to speed things up, don't loop if the first bucket is already free
  80. if (isEmpty(hashes[i])) {
  81. x = i;
  82. } else {
  83. var last = i, flag;
  84. while(! (isEmpty(flag = hashes[i]) || (flag == k && _keys[i] == key)) )
  85. {
  86. if (isDel(flag) && delKey == -1)
  87. {
  88. delKey = i;
  89. }
  90. i = (i + ++nProbes) & mask;
  91. #if DEBUG_HASHTBL
  92. probeTimes++;
  93. if (i == last)
  94. throw "assert";
  95. #end
  96. }
  97. if (isEmpty(flag) && delKey != -1)
  98. {
  99. x = delKey;
  100. } else {
  101. x = i;
  102. }
  103. }
  104. #if DEBUG_HASHTBL
  105. if (nProbes > maxProbe)
  106. maxProbe = nProbes;
  107. totalProbes++;
  108. #end
  109. }
  110. var flag = hashes[x];
  111. if (isEmpty(flag))
  112. {
  113. keys[x] = key;
  114. vals[x] = value;
  115. hashes[x] = k;
  116. size++;
  117. nOccupied++;
  118. } else if (isDel(flag)) {
  119. keys[x] = key;
  120. vals[x] = value;
  121. hashes[x] = k;
  122. size++;
  123. } else {
  124. assert(_keys[x] == key);
  125. vals[x] = value;
  126. }
  127. #if !no_map_cache
  128. cachedIndex = x;
  129. cachedKey = key;
  130. #end
  131. }
  132. @:final private function lookup( key : String ) : Int
  133. {
  134. if (nBuckets != 0)
  135. {
  136. var hashes = hashes, keys = _keys;
  137. var mask = nBuckets - 1, hash = hash(key), k = hash, nProbes = 0;
  138. var i = k & mask;
  139. var last = i, flag;
  140. // if we hit an empty bucket, it means we're done
  141. while (!isEmpty(flag = hashes[i]) && (isDel(flag) || flag != k || keys[i] != key))
  142. {
  143. i = (i + ++nProbes) & mask;
  144. #if DEBUG_HASHTBL
  145. probeTimes++;
  146. if (i == last)
  147. throw "assert";
  148. #end
  149. }
  150. #if DEBUG_HASHTBL
  151. if (nProbes > maxProbe)
  152. maxProbe = nProbes;
  153. totalProbes++;
  154. #end
  155. return isEither(flag) ? -1 : i;
  156. }
  157. return -1;
  158. }
  159. @:final @:private function resize(newNBuckets:Int) : Void
  160. {
  161. //This function uses 0.25*n_bucktes bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets.
  162. var newHash = null;
  163. var j = 1;
  164. {
  165. newNBuckets = roundUp(newNBuckets);
  166. if (newNBuckets < 4) newNBuckets = 4;
  167. if (size >= (newNBuckets * HASH_UPPER + 0.5)) /* requested size is too small */
  168. {
  169. j = 0;
  170. } else { /* hash table size to be changed (shrink or expand); rehash */
  171. var nfSize = newNBuckets;
  172. newHash = new NativeArray( nfSize );
  173. if (nBuckets < newNBuckets) //expand
  174. {
  175. var k = new NativeArray(newNBuckets);
  176. if (_keys != null)
  177. arrayCopy(_keys, 0, k, 0, nBuckets);
  178. _keys = k;
  179. var v = new NativeArray(newNBuckets);
  180. if (vals != null)
  181. arrayCopy(vals, 0, v, 0, nBuckets);
  182. vals = v;
  183. } //otherwise shrink
  184. }
  185. }
  186. if (j != 0)
  187. { //rehashing is required
  188. //resetting cache
  189. #if !no_map_cache
  190. cachedKey = null;
  191. cachedIndex = -1;
  192. #end
  193. j = -1;
  194. var nBuckets = nBuckets, _keys = _keys, vals = vals, hashes = hashes;
  195. var newMask = newNBuckets - 1;
  196. while (++j < nBuckets)
  197. {
  198. var k;
  199. if (!isEither(k = hashes[j]))
  200. {
  201. var key = _keys[j];
  202. var val = vals[j];
  203. _keys[j] = null;
  204. vals[j] = cast null;
  205. hashes[j] = FLAG_DEL;
  206. while (true) /* kick-out process; sort of like in Cuckoo hashing */
  207. {
  208. var nProbes = 0;
  209. var i = k & newMask;
  210. while (!isEmpty(newHash[i]))
  211. {
  212. i = (i + ++nProbes) & newMask;
  213. }
  214. newHash[i] = k;
  215. if (i < nBuckets && !isEither(k = hashes[i])) /* kick out the existing element */
  216. {
  217. {
  218. var tmp = _keys[i];
  219. _keys[i] = key;
  220. key = tmp;
  221. }
  222. {
  223. var tmp = vals[i];
  224. vals[i] = val;
  225. val = tmp;
  226. }
  227. hashes[i] = FLAG_DEL; /* mark it as deleted in the old hash table */
  228. } else { /* write the element and jump out of the loop */
  229. _keys[i] = key;
  230. vals[i] = val;
  231. break;
  232. }
  233. }
  234. }
  235. }
  236. if (nBuckets > newNBuckets) /* shrink the hash table */
  237. {
  238. {
  239. var k = new NativeArray(newNBuckets);
  240. arrayCopy(_keys, 0, k, 0, newNBuckets);
  241. this._keys = k;
  242. }
  243. {
  244. var v = new NativeArray(newNBuckets);
  245. arrayCopy(vals, 0, v, 0, newNBuckets);
  246. this.vals = v;
  247. }
  248. }
  249. this.hashes = newHash;
  250. this.nBuckets = newNBuckets;
  251. this.nOccupied = size;
  252. this.upperBound = Std.int(newNBuckets * HASH_UPPER + .5);
  253. }
  254. }
  255. public function get( key : String ) : Null<T>
  256. {
  257. var idx = -1;
  258. #if !no_map_cache
  259. if (cachedKey == key && ( (idx = cachedIndex) != -1 ))
  260. {
  261. return vals[idx];
  262. }
  263. #end
  264. idx = lookup(key);
  265. if (idx != -1)
  266. {
  267. #if !no_map_cache
  268. cachedKey = key;
  269. cachedIndex = idx;
  270. #end
  271. return vals[idx];
  272. }
  273. return null;
  274. }
  275. private function getDefault( key : String, def : T ) : T
  276. {
  277. var idx = -1;
  278. #if !no_map_cache
  279. if (cachedKey == key && ( (idx = cachedIndex) != -1 ))
  280. {
  281. return vals[idx];
  282. }
  283. #end
  284. idx = lookup(key);
  285. if (idx != -1)
  286. {
  287. #if !no_map_cache
  288. cachedKey = key;
  289. cachedIndex = idx;
  290. #end
  291. return vals[idx];
  292. }
  293. return def;
  294. }
  295. public function exists( key : String ) : Bool
  296. {
  297. var idx = -1;
  298. #if !no_map_cache
  299. if (cachedKey == key && ( (idx = cachedIndex) != -1 ))
  300. {
  301. return true;
  302. }
  303. #end
  304. idx = lookup(key);
  305. if (idx != -1)
  306. {
  307. #if !no_map_cache
  308. cachedKey = key;
  309. cachedIndex = idx;
  310. #end
  311. return true;
  312. }
  313. return false;
  314. }
  315. public function remove( key : String ) : Bool
  316. {
  317. var idx = -1;
  318. #if !no_map_cache
  319. if (! (cachedKey == key && ( (idx = cachedIndex) != -1 )))
  320. #end
  321. {
  322. idx = lookup(key);
  323. }
  324. if (idx == -1)
  325. {
  326. return false;
  327. } else {
  328. #if !no_map_cache
  329. if (cachedKey == key)
  330. {
  331. cachedIndex = -1;
  332. }
  333. #end
  334. hashes[idx] = FLAG_DEL;
  335. _keys[idx] = null;
  336. vals[idx] = null;
  337. --size;
  338. return true;
  339. }
  340. }
  341. /**
  342. Returns an iterator of all keys in the hashtable.
  343. Implementation detail: Do not set() any new value while iterating, as it may cause a resize, which will break iteration
  344. **/
  345. public inline function keys() : Iterator<String>
  346. {
  347. return new StringMapKeyIterator(this);
  348. }
  349. @:runtime public inline function keyValueIterator() : KeyValueIterator<String, T> {
  350. return new haxe.iterators.MapKeyValueIterator(this);
  351. }
  352. /**
  353. Returns an iterator of all values in the hashtable.
  354. Implementation detail: Do not set() any new value while iterating, as it may cause a resize, which will break iteration
  355. **/
  356. public inline function iterator() : Iterator<T>
  357. {
  358. return new StringMapValueIterator(this);
  359. }
  360. public function copy() : StringMap<T> {
  361. var copied = new StringMap();
  362. for(key in keys()) copied.set(key, get(key));
  363. return copied;
  364. }
  365. /**
  366. Returns an displayable representation of the hashtable content.
  367. **/
  368. public function toString() : String {
  369. var s = new StringBuf();
  370. s.add("{");
  371. var it = keys();
  372. for( i in it ) {
  373. s.add(i);
  374. s.add(" => ");
  375. s.add(Std.string(get(i)));
  376. if( it.hasNext() )
  377. s.add(", ");
  378. }
  379. s.add("}");
  380. return s.toString();
  381. }
  382. extern private static inline function roundUp(x:Int):Int
  383. {
  384. --x;
  385. x |= (x) >>> 1;
  386. x |= (x) >>> 2;
  387. x |= (x) >>> 4;
  388. x |= (x) >>> 8;
  389. x |= (x) >>> 16;
  390. return ++x;
  391. }
  392. extern private static inline function getInc(k:Int, mask:Int):Int //return 1 for linear probing
  393. return (((k) >> 3 ^ (k) << 3) | 1) & (mask);
  394. extern private static inline function isEither(v:HashType):Bool
  395. return (v & 0xFFFFFFFE) == 0;
  396. extern private static inline function isEmpty(v:HashType):Bool
  397. return v == FLAG_EMPTY;
  398. extern private static inline function isDel(v:HashType):Bool
  399. return v == FLAG_DEL;
  400. //guarantee: Whatever this function is, it will never return 0 nor 1
  401. extern private static inline function hash(s:String):HashType
  402. {
  403. var k:Int = untyped s.hashCode();
  404. //k *= 357913941;
  405. //k ^= k << 24;
  406. //k += ~357913941;
  407. //k ^= k >> 31;
  408. //k ^= k << 31;
  409. k = (k+0x7ed55d16) + (k<<12);
  410. k = (k^0xc761c23c) ^ (k>>19);
  411. k = (k+0x165667b1) + (k<<5);
  412. k = (k+0xd3a2646c) ^ (k<<9);
  413. k = (k+0xfd7046c5) + (k<<3);
  414. k = (k^0xb55a4f09) ^ (k>>16);
  415. var ret = k;
  416. if (isEither(ret))
  417. {
  418. if (ret == 0)
  419. ret = 2;
  420. else
  421. ret = 0xFFFFFFFF;
  422. }
  423. return ret;
  424. }
  425. extern private static inline function arrayCopy(sourceArray:Dynamic, sourceIndex:Int, destinationArray:Dynamic, destinationIndex:Int, length:Int):Void
  426. java.lang.System.arraycopy(sourceArray, sourceIndex, destinationArray, destinationIndex, length);
  427. extern private static inline function assert(x:Bool):Void
  428. {
  429. #if DEBUG_HASHTBL
  430. if (!x) throw "assert failed";
  431. #end
  432. }
  433. }
  434. private typedef HashType = Int;
  435. @:final
  436. @:access(haxe.ds.StringMap)
  437. private class StringMapKeyIterator<T>
  438. {
  439. var m:StringMap<T>;
  440. var i:Int;
  441. var len:Int;
  442. public function new(m:StringMap<T>)
  443. {
  444. this.m = m;
  445. this.i = 0;
  446. this.len = m.nBuckets;
  447. }
  448. public function hasNext():Bool
  449. {
  450. for (j in i...len)
  451. {
  452. if (!StringMap.isEither(m.hashes[j]))
  453. {
  454. i = j;
  455. return true;
  456. }
  457. }
  458. return false;
  459. }
  460. public function next():String
  461. {
  462. var ret = m._keys[i];
  463. #if !no_map_cache
  464. m.cachedIndex = i;
  465. m.cachedKey = ret;
  466. #end
  467. i++;
  468. return ret;
  469. }
  470. }
  471. @:final
  472. @:access(haxe.ds.StringMap)
  473. private class StringMapValueIterator<T>
  474. {
  475. var m:StringMap<T>;
  476. var i:Int;
  477. var len:Int;
  478. public function new(m:StringMap<T>)
  479. {
  480. this.m = m;
  481. this.i = 0;
  482. this.len = m.nBuckets;
  483. }
  484. public function hasNext():Bool
  485. {
  486. for (j in i...len)
  487. {
  488. if (!StringMap.isEither(m.hashes[j]))
  489. {
  490. i = j;
  491. return true;
  492. }
  493. }
  494. return false;
  495. }
  496. public inline function next():T
  497. {
  498. return m.vals[i++];
  499. }
  500. }