PageRenderTime 50ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/Utilities/Collections/Hash.cs

#
C# | 962 lines | 551 code | 94 blank | 317 comment | 69 complexity | 5fd2ac21b50d7dfe2f0f24b965f07ee6 MD5 | raw file
Possible License(s): Apache-2.0
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.Runtime.Serialization;
  6. using Delta.Utilities.Helpers;
  7. using NUnit.Framework;
  8. namespace Delta.Utilities.Collections
  9. {
  10. /// <summary>
  11. /// The base implementation for various collections classes that use hash
  12. /// tables as part of their implementation. This class should not (and can
  13. /// not) be used directly by end users; it's only for internal use by the
  14. /// collections package. The Hash does not handle duplicate values.
  15. /// </summary>
  16. /// <remarks>
  17. /// The Hash manages items of type T, and uses a IComparer&lt;ItemTYpe&gt;
  18. /// that hashes compares items to hash items into the table.
  19. /// </remarks>
  20. /// <typeparam name="T">Data type</typeparam>
  21. internal class Hash<T>
  22. : IEnumerable<T>, ISerializable, IDeserializationCallback
  23. {
  24. #region Public
  25. /// <summary>
  26. /// Get the number of items in the hash table.
  27. /// </summary>
  28. /// <typeparam name="T">T</typeparam>
  29. /// <value>The number of items stored in the hash table.</value>
  30. public int ElementCount
  31. {
  32. get
  33. {
  34. return count;
  35. }
  36. }
  37. /// <summary>
  38. /// Get the number of slots in the hash table. Exposed internally
  39. /// for testing purposes.
  40. /// </summary>
  41. /// <typeparam name="T">T</typeparam>
  42. /// <value>The number of slots in the hash table.</value>
  43. internal int SlotCount
  44. {
  45. get
  46. {
  47. return totalSlots;
  48. }
  49. }
  50. /// <summary>
  51. /// Get or change the load factor. Changing the load factor may cause
  52. /// the size of the table to grow or shrink accordingly.
  53. /// </summary>
  54. /// <typeparam name="T">T</typeparam>
  55. /// <value></value>
  56. public float LoadFactor
  57. {
  58. get
  59. {
  60. return loadFactor;
  61. }
  62. set
  63. {
  64. // Don't allow hopelessly inefficient load factors.
  65. if (value < 0.25 ||
  66. value > 0.95)
  67. {
  68. throw new ArgumentOutOfRangeException(
  69. "value", "The load factor must be between 0.25 and 0.95: " + value);
  70. }
  71. StopEnumerations();
  72. // May need to expand or shrink the table -- which?
  73. bool maybeExpand = value < loadFactor;
  74. // Update loadFactor and thresholds.
  75. loadFactor = value;
  76. thresholdGrow = (int)(totalSlots * loadFactor);
  77. thresholdShrink = thresholdGrow / 3;
  78. if (thresholdShrink <= MINSIZE)
  79. {
  80. thresholdShrink = 1;
  81. }
  82. // Possibly expand or shrink the table.
  83. if (maybeExpand)
  84. {
  85. EnsureEnoughSlots(0);
  86. }
  87. else
  88. {
  89. ShrinkIfNeeded();
  90. }
  91. }
  92. }
  93. #endregion
  94. #region Private
  95. // NOTE: If you add new member variables, you very well may need to change
  96. // the serialization code to serialize that member.
  97. /// <summary>
  98. /// Interface for comparing elements
  99. /// </summary>
  100. /// <typeparam name="T">T</typeparam>
  101. private IEqualityComparer<T> equalityComparer;
  102. /// <summary>
  103. /// Count
  104. /// </summary>
  105. /// <typeparam name="T">T</typeparam>
  106. /// <summary>
  107. /// The count of elements in the table.
  108. /// </summary>
  109. private int count;
  110. /// <summary>
  111. /// Includes real elements and deleted elements with the collision bit on.
  112. /// Used to determine when we need to resize.
  113. /// </summary>
  114. /// <typeparam name="T">T</typeparam>
  115. private int usedSlots;
  116. /// <summary>
  117. /// Size of the table. Always a power of two.
  118. /// </summary>
  119. /// <typeparam name="T">T</typeparam>
  120. private int totalSlots;
  121. /// <summary>
  122. /// maximal load factor for the table.
  123. /// </summary>
  124. /// <typeparam name="T">T</typeparam>
  125. private float loadFactor;
  126. /// <summary>
  127. /// floor(totalSlots * loadFactor);
  128. /// </summary>
  129. /// <typeparam name="T">T</typeparam>
  130. private int thresholdGrow;
  131. /// <summary>
  132. /// thresholdGrow / 3.
  133. /// </summary>
  134. /// <typeparam name="T">T</typeparam>
  135. private int thresholdShrink;
  136. /// <summary>
  137. /// Table
  138. /// </summary>
  139. /// <typeparam name="T">T</typeparam>
  140. /// <summary>
  141. /// Mask to convert hash values to the size of the table.
  142. /// </summary>
  143. private int hashMask;
  144. /// <summary>
  145. /// Shift to get the secondary skip value.
  146. /// </summary>
  147. /// <typeparam name="T">T</typeparam>
  148. private int secondaryShift;
  149. /// <summary>
  150. /// The hash table.
  151. /// </summary>
  152. /// <typeparam name="T">T</typeparam>
  153. private Slot[] table;
  154. /// <summary>
  155. /// An integer that is changed every time the table structurally changes.
  156. /// Used so that enumerations throw an exception if the tree is changed
  157. /// during enumeration.
  158. /// </summary>
  159. /// <typeparam name="T">T</typeparam>
  160. private int changeStamp;
  161. // minimum number of slots.
  162. private const int MINSIZE = 16;
  163. /// <summary>
  164. /// Info used during deserialization.
  165. /// </summary>
  166. /// <typeparam name="T">T</typeparam>
  167. private SerializationInfo serializationInfo;
  168. #endregion
  169. #region Slot helper structure
  170. /// <summary>
  171. /// The structure that has each slot in the hash table. Each slot has three
  172. /// parts:
  173. /// 1. The collision bit. Indicates whether some item visited this slot but
  174. /// had to keep looking because the slot was full.
  175. /// 2. 31-bit full hash value of the item. If zero, the slot is empty.
  176. /// 3. The item itself.
  177. /// </summary>
  178. private struct Slot
  179. {
  180. #region item (Public)
  181. /// <summary>
  182. ///
  183. /// </summary>
  184. /// <summary>
  185. /// The item.
  186. /// </summary>
  187. public T item;
  188. #endregion
  189. #region HashValue (Public)
  190. /// <summary>
  191. /// The full hash value associated with the value in this slot, or zero
  192. /// if the slot is empty.
  193. /// </summary>
  194. public int HashValue
  195. {
  196. get
  197. {
  198. return (int)(hash_collision & 0x7FFFFFFF);
  199. }
  200. set
  201. {
  202. // make sure sign bit isn't set.
  203. Debug.Assert((value & 0x80000000) == 0);
  204. hash_collision = (uint)value | (hash_collision & 0x80000000);
  205. }
  206. }
  207. #endregion
  208. #region Empty (Public)
  209. /// <summary>
  210. /// Is this slot empty?
  211. /// </summary>
  212. public bool Empty
  213. {
  214. get
  215. {
  216. return HashValue == 0;
  217. }
  218. }
  219. #endregion
  220. #region Collision (Public)
  221. /// <summary>
  222. /// The "Collision" bit indicates that some value hit this slot and
  223. /// collided, so had to try another slot.
  224. /// </summary>
  225. public bool Collision
  226. {
  227. get
  228. {
  229. return (hash_collision & 0x80000000) != 0;
  230. }
  231. set
  232. {
  233. if (value)
  234. {
  235. hash_collision |= 0x80000000;
  236. }
  237. else
  238. {
  239. hash_collision &= 0x7FFFFFFF;
  240. }
  241. }
  242. }
  243. #endregion
  244. #region Private
  245. #region hash_collision (Private)
  246. /// <summary>
  247. /// Lower 31 bits: the hash value. Top bit: the collision bit.
  248. /// </summary>
  249. private uint hash_collision;
  250. #endregion
  251. #endregion
  252. #region Clear (Public)
  253. /// <summary>
  254. /// Clear this slot, leaving the collision bit alone.
  255. /// </summary>
  256. public void Clear()
  257. {
  258. HashValue = 0;
  259. // Done to avoid keeping things alive that shouldn't be.
  260. item = default(T);
  261. }
  262. #endregion
  263. #region ToString (Public)
  264. /// <summary>
  265. /// To string
  266. /// </summary>
  267. /// <returns>String</returns>
  268. public override string ToString()
  269. {
  270. return item != null
  271. ? item.ToString()
  272. : "";
  273. }
  274. #endregion
  275. }
  276. #endregion
  277. #region Constructor
  278. /// <summary>
  279. /// Constructor. Create a new hash table.
  280. /// </summary>
  281. /// <param name="equalityComparer">The comparer to use to compare items.
  282. /// </param>
  283. public Hash(IEqualityComparer<T> equalityComparer)
  284. {
  285. this.equalityComparer = equalityComparer;
  286. // default load factor.
  287. loadFactor = 0.70F;
  288. }
  289. #endregion
  290. #region Enumeration helpers
  291. /// <summary>
  292. /// Gets the current enumeration stamp. Call CheckEnumerationStamp later
  293. /// with this value to throw an exception if the hash table is changed.
  294. /// </summary>
  295. /// <returns>The current enumeration stamp.</returns>
  296. internal int GetEnumerationStamp()
  297. {
  298. return changeStamp;
  299. }
  300. /// <summary>
  301. /// Must be called whenever there is a structural change in the tree.
  302. /// Causes changeStamp to be changed, which causes any in-progress
  303. /// enumerations to throw exceptions.
  304. /// </summary>
  305. internal void StopEnumerations()
  306. {
  307. ++changeStamp;
  308. }
  309. /// <summary>
  310. /// Checks the given stamp against the current change stamp. If different,
  311. /// the collection has changed during enumeration and an
  312. /// InvalidOperationException must be thrown
  313. /// </summary>
  314. /// <param name="startStamp">changeStamp at the start of the enumeration.
  315. /// </param>
  316. internal void CheckEnumerationStamp(int startStamp)
  317. {
  318. if (startStamp != changeStamp)
  319. {
  320. throw new InvalidOperationException(
  321. "Collection was modified during an enumeration.");
  322. }
  323. }
  324. /// <summary>
  325. /// Enumerate all of the items in the hash table. The items
  326. /// are enumerated in a haphazard, unpredictable order.
  327. /// </summary>
  328. /// <returns>An IEnumerator&lt;T&gt; that enumerates the items
  329. /// in the hash table.</returns>
  330. public IEnumerator<T> GetEnumerator()
  331. {
  332. if (count > 0)
  333. {
  334. int startStamp = changeStamp;
  335. foreach (Slot slot in table)
  336. {
  337. if (!slot.Empty)
  338. {
  339. yield return slot.item;
  340. CheckEnumerationStamp(startStamp);
  341. }
  342. }
  343. }
  344. }
  345. /// <summary>
  346. /// Enumerate all of the items in the hash table. The items
  347. /// are enumerated in a haphazard, unpredictable order.
  348. /// </summary>
  349. /// <returns>An IEnumerator that enumerates the items
  350. /// in the hash table.</returns>
  351. IEnumerator
  352. IEnumerable.GetEnumerator()
  353. {
  354. return GetEnumerator();
  355. }
  356. #endregion
  357. #region Get hash values
  358. /// <summary>
  359. /// Gets the full hash code for an item.
  360. /// </summary>
  361. /// <param name="item">Item to get hash code for.</param>
  362. /// <returns>The full hash code. It is never zero.</returns>
  363. private int GetFullHash(T item)
  364. {
  365. uint hash;
  366. hash = (uint)(item == null
  367. ? 0x1786E23C
  368. : equalityComparer.GetHashCode(item));
  369. // The .NET framework tends to produce pretty bad hash codes.
  370. // Scramble them up to be much more random!
  371. hash += ~(hash << 15);
  372. hash ^= (hash >> 10);
  373. hash += (hash << 3);
  374. hash ^= (hash >> 6);
  375. hash += ~(hash << 11);
  376. hash ^= (hash >> 16);
  377. hash &= 0x7FFFFFFF;
  378. if (hash == 0)
  379. {
  380. hash = 0x7FFFFFFF; // Make sure it isn't zero.
  381. }
  382. return (int)hash;
  383. }
  384. /// <summary>
  385. /// Get the initial bucket number and skip amount from the full hash value.
  386. /// </summary>
  387. /// <param name="hash">The full hash value.</param>
  388. /// <param name="initialBucket">Returns the initial bucket. Always in the
  389. /// range 0..(totalSlots - 1).</param>
  390. /// <param name="skip">Returns the skip values. Always odd in the range
  391. /// 0..(totalSlots - 1).</param>
  392. private void GetHashValuesFromFullHash(
  393. int hash, out int initialBucket, out int skip)
  394. {
  395. initialBucket = hash & hashMask;
  396. // The skip value must be relatively prime to the table size. Since the
  397. // table size is a power of two, any odd number is relatively prime, so
  398. // oring in 1 will do it.
  399. skip = ((hash >> secondaryShift) & hashMask) | 1;
  400. }
  401. /// <summary>
  402. /// Gets the full hash value, initial bucket number, and skip amount for an
  403. /// item.
  404. /// </summary>
  405. /// <param name="item">Item to get hash value of.</param>
  406. /// <param name="initialBucket">Returns the initial bucket. Always in the
  407. /// range 0..(totalSlots - 1).</param>
  408. /// <param name="skip">Returns the skip values. Always odd in the range
  409. /// 0..(totalSlots - 1).</param>
  410. /// <returns>The full hash value. This is never zero.</returns>
  411. private int GetHashValues(T item, out int initialBucket, out int skip)
  412. {
  413. int hash = GetFullHash(item);
  414. GetHashValuesFromFullHash(hash, out initialBucket, out skip);
  415. return hash;
  416. }
  417. #endregion
  418. #region Helper methods
  419. /// <summary>
  420. /// Make sure there are enough slots in the hash table that
  421. /// <paramref name="additionalItems"/> items can be inserted into the
  422. /// table.
  423. /// </summary>
  424. /// <param name="additionalItems">Number of additional items we are
  425. /// inserting.</param>
  426. private void EnsureEnoughSlots(int additionalItems)
  427. {
  428. StopEnumerations();
  429. if (usedSlots + additionalItems > thresholdGrow)
  430. {
  431. // We need to expand the table. Figure out to what size.
  432. int newSize;
  433. newSize = Math.Max(totalSlots, MINSIZE);
  434. while ((int)(newSize * loadFactor) <
  435. usedSlots + additionalItems)
  436. {
  437. newSize *= 2;
  438. if (newSize <= 0)
  439. {
  440. // Must have overflowed the size of an int.
  441. // Hard to believe we didn't run out of memory first.
  442. throw new InvalidOperationException(
  443. "The collection has become too large.");
  444. }
  445. }
  446. ResizeTable(newSize);
  447. }
  448. }
  449. /// <summary>
  450. /// Check if the number of items in the table is small enough that
  451. /// we should shrink the table again.
  452. /// </summary>
  453. private void ShrinkIfNeeded()
  454. {
  455. if (count < thresholdShrink)
  456. {
  457. int newSize;
  458. if (count > 0)
  459. {
  460. newSize = MINSIZE;
  461. while ((int)(newSize * loadFactor) < count)
  462. {
  463. newSize *= 2;
  464. }
  465. }
  466. else
  467. {
  468. // We've removed all the elements. Shrink to zero.
  469. newSize = 0;
  470. }
  471. ResizeTable(newSize);
  472. }
  473. }
  474. /// <summary>
  475. /// Given the size of a hash table, compute the "secondary shift" value.
  476. /// The shift that is used to determine the skip amount for collision
  477. /// resolution.
  478. /// </summary>
  479. /// <param name="newSize">The new size of the table.</param>
  480. /// <returns>The secondary skip amount.</returns>
  481. private int GetSecondaryShift(int newSize)
  482. {
  483. // x is of the form 0000111110 -- a single string of 1's followed by a
  484. // single zero.
  485. int x = newSize - 2;
  486. int secondaryShiftValue = 0;
  487. // Keep shifting x until it is the set of bits we want to extract:
  488. // it be the highest bits possible, but can't overflow into the sign bit.
  489. while ((x & 0x40000000) == 0)
  490. {
  491. x <<= 1;
  492. ++secondaryShiftValue;
  493. }
  494. return secondaryShiftValue;
  495. }
  496. #endregion
  497. #region Resize table
  498. /// <summary>
  499. /// Resize the hash table to the given new size, moving all items into the
  500. /// new hash table.
  501. /// </summary>
  502. /// <param name="newSize">The new size of the hash table. Must be a power
  503. /// of two.</param>
  504. private void ResizeTable(int newSize)
  505. {
  506. // Move all the items from this table to the new table.
  507. Slot[] oldTable = table;
  508. // Check newSize is a power of two.
  509. Debug.Assert((newSize & (newSize - 1)) == 0);
  510. totalSlots = newSize;
  511. thresholdGrow = (int)(totalSlots * loadFactor);
  512. thresholdShrink = thresholdGrow / 3;
  513. if (thresholdShrink <= MINSIZE)
  514. {
  515. thresholdShrink = 1;
  516. }
  517. hashMask = newSize - 1;
  518. secondaryShift = GetSecondaryShift(newSize);
  519. if (totalSlots > 0)
  520. {
  521. table = new Slot[totalSlots];
  522. }
  523. else
  524. {
  525. table = null;
  526. }
  527. if (oldTable != null &&
  528. table != null)
  529. {
  530. foreach (Slot oldSlot in oldTable)
  531. {
  532. int hash, bucket, skip;
  533. hash = oldSlot.HashValue;
  534. GetHashValuesFromFullHash(hash, out bucket, out skip);
  535. // Find an empty bucket.
  536. while (!table[bucket].Empty)
  537. {
  538. // The slot is used, but isn't our item. Set the collision bit
  539. // and keep looking.
  540. table[bucket].Collision = true;
  541. bucket = (bucket + skip) & hashMask;
  542. }
  543. // We found an empty bucket.
  544. table[bucket].HashValue = hash;
  545. table[bucket].item = oldSlot.item;
  546. }
  547. }
  548. // no deleted elements have the collision bit on now.
  549. usedSlots = count;
  550. }
  551. #endregion
  552. #region Insert
  553. /// <summary>
  554. /// Insert a new item into the hash table. If a duplicate item exists,
  555. /// can replace or do nothing.
  556. /// </summary>
  557. /// <param name="item">The item to insert.</param>
  558. /// <param name="replaceOnDuplicate">If true, duplicate items are replaced.
  559. /// If false, nothing is done if a duplicate already exists.</param>
  560. /// <param name="previous">If a duplicate was found, returns it (whether
  561. /// replaced or not).</param>
  562. /// <returns>True if no duplicate existed, false if a duplicate was found.
  563. /// </returns>
  564. public bool Insert(T item, bool replaceOnDuplicate, out T previous)
  565. {
  566. int hash, bucket, skip;
  567. // If >= 0, an empty bucket we can use for a true insert
  568. int emptyBucket = -1;
  569. // If true, still the possibility that a duplicate exists.
  570. bool duplicateMightExist = true;
  571. // Ensure enough room to insert. Also stops enumerations.
  572. EnsureEnoughSlots(1);
  573. hash = GetHashValues(item, out bucket, out skip);
  574. for (;;)
  575. {
  576. if (table[bucket].Empty)
  577. {
  578. // Record the location of the first empty bucket seen.
  579. // This is where the item will go if no duplicate exists.
  580. if (emptyBucket == -1)
  581. {
  582. emptyBucket = bucket;
  583. }
  584. if (!duplicateMightExist ||
  585. !table[bucket].Collision)
  586. {
  587. // There can't be a duplicate further on, because a bucket with
  588. // the collision bit clear was found (here or earlier). We have
  589. // the place to insert.
  590. break;
  591. }
  592. }
  593. else if (table[bucket].HashValue == hash &&
  594. equalityComparer.Equals(table[bucket].item, item))
  595. {
  596. // We found a duplicate item. Replace it if requested to.
  597. previous = table[bucket].item;
  598. if (replaceOnDuplicate)
  599. {
  600. table[bucket].item = item;
  601. }
  602. return false;
  603. }
  604. else
  605. {
  606. // The slot is used, but isn't our item.
  607. if (!table[bucket].Collision)
  608. {
  609. // Since the collision bit is off, we can't have a duplicate.
  610. if (emptyBucket >= 0)
  611. {
  612. // We already have an empty bucket to use.
  613. break;
  614. }
  615. else
  616. {
  617. // Keep searching for an empty bucket to place the item.
  618. table[bucket].Collision = true;
  619. duplicateMightExist = false;
  620. }
  621. }
  622. }
  623. bucket = (bucket + skip) & hashMask;
  624. }
  625. // We found an empty bucket. Insert the new item.
  626. table[emptyBucket].HashValue = hash;
  627. table[emptyBucket].item = item;
  628. ++count;
  629. if (!table[emptyBucket].Collision)
  630. {
  631. ++usedSlots;
  632. }
  633. previous = default(T);
  634. return true;
  635. }
  636. #endregion
  637. #region Delete
  638. /// <summary>
  639. /// Deletes an item from the hash table.
  640. /// </summary>
  641. /// <param name="item">Item to search for and delete.</param>
  642. /// <param name="itemDeleted">If true returned, the actual item stored in
  643. /// the hash table (must be equal to <paramref name="item"/>, but may not
  644. /// be identical.</param>
  645. /// <returns>True if item was found and deleted, false if item wasn't
  646. /// found.</returns>
  647. public bool Delete(T item, out T itemDeleted)
  648. {
  649. int hash, bucket, skip;
  650. StopEnumerations();
  651. if (count == 0)
  652. {
  653. itemDeleted = default(T);
  654. return false;
  655. }
  656. hash = GetHashValues(item, out bucket, out skip);
  657. for (;;)
  658. {
  659. if (table[bucket].HashValue == hash &&
  660. equalityComparer.Equals(table[bucket].item, item))
  661. {
  662. // Found the item. Remove it.
  663. itemDeleted = table[bucket].item;
  664. table[bucket].Clear();
  665. --count;
  666. if (!table[bucket].Collision)
  667. {
  668. --usedSlots;
  669. }
  670. ShrinkIfNeeded();
  671. return true;
  672. }
  673. else if (!table[bucket].Collision)
  674. {
  675. // No collision bit, so we can stop searching. No such element.
  676. itemDeleted = default(T);
  677. return false;
  678. }
  679. bucket = (bucket + skip) & hashMask;
  680. }
  681. }
  682. #endregion
  683. #region Find
  684. /// <summary>
  685. /// Find an item in the hash table. If found, optionally replace it with
  686. /// the finding item.
  687. /// </summary>
  688. /// <param name="find">Item to find.</param>
  689. /// <param name="replace">If true, replaces the equal item in the hash
  690. /// table with <paramref name="item"/>.</param>
  691. /// <param name="item">Returns the equal item found in the table, if true
  692. /// was returned.</param>
  693. /// <returns>True if the item was found, false otherwise.</returns>
  694. public bool Find(T find, bool replace, out T item)
  695. {
  696. int hash, bucket, skip;
  697. if (count == 0)
  698. {
  699. item = default(T);
  700. return false;
  701. }
  702. hash = GetHashValues(find, out bucket, out skip);
  703. for (;;)
  704. {
  705. if (table[bucket].HashValue == hash &&
  706. equalityComparer.Equals(table[bucket].item, find))
  707. {
  708. // Found the item.
  709. item = table[bucket].item;
  710. if (replace)
  711. {
  712. table[bucket].item = find;
  713. }
  714. return true;
  715. }
  716. else if (!table[bucket].Collision)
  717. {
  718. // No collision bit, so we can stop searching. No such element.
  719. item = default(T);
  720. return false;
  721. }
  722. bucket = (bucket + skip) & hashMask;
  723. }
  724. }
  725. #endregion
  726. #region Clone
  727. /// <summary>
  728. /// Creates a clone of this hash table.
  729. /// </summary>
  730. /// <param name="cloneItem">If non-null, this function is applied to each
  731. /// item when cloning. It must be the case that this function does not
  732. /// modify the hash code or equality function.</param>
  733. /// <returns>A shallow clone that contains the same items.</returns>
  734. public Hash<T> Clone(Converter<T, T> cloneItem)
  735. {
  736. Hash<T> clone = new Hash<T>(equalityComparer);
  737. clone.count = count;
  738. clone.usedSlots = usedSlots;
  739. clone.totalSlots = totalSlots;
  740. clone.loadFactor = loadFactor;
  741. clone.thresholdGrow = thresholdGrow;
  742. clone.thresholdShrink = thresholdShrink;
  743. clone.hashMask = hashMask;
  744. clone.secondaryShift = secondaryShift;
  745. if (table != null)
  746. {
  747. clone.table = (Slot[])table.Clone();
  748. if (cloneItem != null)
  749. {
  750. for (int i = 0; i < table.Length; ++i)
  751. {
  752. if (!table[i].Empty)
  753. {
  754. table[i].item = cloneItem(table[i].item);
  755. }
  756. }
  757. }
  758. }
  759. return clone;
  760. }
  761. #endregion
  762. #region Serialization
  763. /// <summary>
  764. /// Serialize the hash table. Called from the serialization infrastructure.
  765. /// </summary>
  766. /// <param name="context">Context</param>
  767. /// <param name="info">Info</param>
  768. void ISerializable.GetObjectData(
  769. SerializationInfo info, StreamingContext context)
  770. {
  771. if (info == null)
  772. {
  773. throw new ArgumentNullException("info");
  774. }
  775. info.AddValue("equalityComparer", equalityComparer,
  776. typeof(IEqualityComparer<T>));
  777. info.AddValue("loadFactor", loadFactor, typeof(float));
  778. T[] items = new T[count];
  779. int i = 0;
  780. foreach (Slot slot in table)
  781. {
  782. if (!slot.Empty)
  783. {
  784. items[i++] = slot.item;
  785. }
  786. }
  787. info.AddValue("items", items, typeof(T[]));
  788. }
  789. /// <summary>
  790. /// Called on deserialization. We cannot deserialize now, because hash
  791. /// codes might not be correct now. We do real deserialization in the
  792. /// OnDeserialization call.
  793. /// </summary>
  794. /// <param name="context">Context</param>
  795. /// <param name="serInfo">serInfo</param>
  796. protected Hash(SerializationInfo serInfo, StreamingContext context)
  797. {
  798. // Save away the serialization info for use later. We can't be sure of
  799. // hash codes being stable until the entire object graph is deserialized,
  800. // so we wait until then to deserialize.
  801. serializationInfo = serInfo;
  802. }
  803. /// <summary>
  804. /// Deserialize the hash table. Called from the serialization
  805. /// infrastructure when the object graph has finished deserializing.
  806. /// </summary>
  807. /// <param name="sender">sender</param>
  808. void IDeserializationCallback.OnDeserialization(object sender)
  809. {
  810. if (serializationInfo == null)
  811. {
  812. return;
  813. }
  814. loadFactor = serializationInfo.GetSingle("loadFactor");
  815. equalityComparer = (IEqualityComparer<T>)serializationInfo.GetValue(
  816. "equalityComparer", typeof(IEqualityComparer<T>));
  817. T[] items = (T[])serializationInfo.GetValue("items", typeof(T[]));
  818. T dummy;
  819. EnsureEnoughSlots(items.Length);
  820. foreach (T item in items)
  821. {
  822. Insert(item, true, out dummy);
  823. }
  824. serializationInfo = null;
  825. }
  826. #endregion
  827. #region To string
  828. /// <summary>
  829. /// To string
  830. /// </summary>
  831. /// <returns>String</returns>
  832. public override string ToString()
  833. {
  834. return "Hash: " + table.Write();
  835. }
  836. #endregion
  837. #region Debug helpers
  838. #endregion
  839. }
  840. /// <summary>
  841. /// Hash tests, needs to be an extra class because Hash is generic.
  842. /// </summary>
  843. internal class HashTests
  844. {
  845. #region TestHash (Static)
  846. /// <summary>
  847. /// Test hash. Note: Too slow for a dynamic unit test.
  848. /// </summary>
  849. [Test]
  850. public static void TestHash()
  851. {
  852. Hash<string> hash = new Hash<string>(StringComparer.InvariantCulture);
  853. string dummyPrevious;
  854. hash.Insert("test item", true, out dummyPrevious);
  855. hash.Insert("yo mama", true, out dummyPrevious);
  856. hash.Insert("test item", true, out dummyPrevious);
  857. Assert.Equal(2, hash.ElementCount);
  858. // Thats just the way the data is used internally ^^
  859. Assert.Equal("Hash: yo mama, , , , test item, , , , , ",
  860. hash.ToString());
  861. string item;
  862. Assert.Equal(false, hash.Find("ne ne", false, out item));
  863. Assert.Equal(true, hash.Find("test item", false, out item));
  864. Assert.Equal("test item", item);
  865. }
  866. #endregion
  867. }
  868. }