PageRenderTime 43ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/Utilities/Collections/BidirHashtable.cs

#
C# | 504 lines | 277 code | 38 blank | 189 comment | 1 complexity | 2604aa376840e06eef1c0d7ade956862 MD5 | raw file
Possible License(s): Apache-2.0
  1. using System;
  2. using System.Collections;
  3. using NUnit.Framework;
  4. namespace Delta.Utilities.Collections
  5. {
  6. /// <summary>
  7. /// BidirHashtable is a simple, bidirectional data structure designed
  8. /// around Hashtables and accessed like a more robust Hashtable.
  9. /// Internally it just contains two hashtables:
  10. /// One maps from key to value, the other maps from value to key.
  11. /// Therefore, note that both types of objects must have reasonable
  12. /// GetHashCode() and Equals() implementations. Lookup in either direction
  13. /// is quick; changes take twice as long since two Hashtables are accessed.
  14. /// It is not currently serializable, but aside from that, it implements the
  15. /// same interfaces as a Hashtable. Forward lookup is just through the [] as
  16. /// in Hashtable. Reverse lookup is through ReverseLookup().
  17. /// </summary>
  18. [Serializable]
  19. public class BidirHashtable :
  20. IDictionary, ICollection, IEnumerable, ICloneable
  21. {
  22. #region FromHashtable (Static)
  23. /// <summary>
  24. /// Creates a Bidirectional hashtable from a normal Hashtable.
  25. /// </summary>
  26. /// <param name="setHashtable">Hashtable to initialize from</param>
  27. /// <returns>BidirHashtable with the same data</returns>
  28. public static BidirHashtable FromHashtable(Hashtable setHashtable)
  29. {
  30. return new BidirHashtable(setHashtable);
  31. }
  32. #endregion
  33. #region ToHashtable (Static)
  34. /// <summary>
  35. /// Hashtable
  36. /// </summary>
  37. /// <param name="bd">Bidirectional hashtable</param>
  38. /// <returns>A normal Hashtable</returns>
  39. public static Hashtable ToHashtable(BidirHashtable bd)
  40. {
  41. return (Hashtable)bd.hashtableForward.Clone();
  42. }
  43. #endregion
  44. #region Attach (Static)
  45. /// <summary>
  46. /// Creates a new BidirHashtable which is attached to
  47. /// the input Hashtable. The reverse mapping is set up
  48. /// automatically.
  49. /// Note that there is no need for a Detach(); just stop using
  50. /// this object and let it get garbage-collected aside from
  51. /// the attached table, which could still be used (though the
  52. /// user would have to be careful not to use the attached
  53. /// table while the BidirHashtable might still be used).
  54. /// While it is possible that someone might want to set up
  55. /// the reverse mapping by doing an Attach(), for that you
  56. /// must just use Attach() and then ReverseDirection().
  57. /// </summary>
  58. /// <param name="ht">Hastable</param>
  59. /// <returns>BidirHashtable with the same data</returns>
  60. public static BidirHashtable Attach(Hashtable ht)
  61. {
  62. return new BidirHashtable(ht, 0);
  63. }
  64. #endregion
  65. #region Count (Public)
  66. /// <summary>
  67. /// Count
  68. /// </summary>
  69. public int Count
  70. {
  71. get
  72. {
  73. return hashtableForward.Count;
  74. }
  75. }
  76. #endregion
  77. #region IsSynchronized (Public)
  78. /// <summary>
  79. /// Is synchronized
  80. /// </summary>
  81. public bool IsSynchronized
  82. {
  83. get
  84. {
  85. return hashtableForward.IsSynchronized;
  86. }
  87. }
  88. #endregion
  89. #region SyncRoot (Public)
  90. /// <summary>
  91. /// Sync root
  92. /// </summary>
  93. public object SyncRoot
  94. {
  95. get
  96. {
  97. return hashtableForward.SyncRoot;
  98. }
  99. }
  100. #endregion
  101. #region IsFixedSize (Public)
  102. /// <summary>
  103. /// Is fixed size
  104. /// </summary>
  105. public bool IsFixedSize
  106. {
  107. get
  108. {
  109. return hashtableForward.IsFixedSize;
  110. }
  111. }
  112. #endregion
  113. #region IsReadOnly (Public)
  114. /// <summary>
  115. /// Is hashtable read only? Will return IsReadOnly from the underlying
  116. /// hashtable (usually false).
  117. /// </summary>
  118. public bool IsReadOnly
  119. {
  120. get
  121. {
  122. return hashtableForward.IsReadOnly;
  123. }
  124. }
  125. #endregion
  126. #region Keys (Public)
  127. /// <summary>
  128. /// Note: Editing the keys would make this class inconsistent.
  129. /// </summary>
  130. public ICollection Keys
  131. {
  132. get
  133. {
  134. return hashtableForward.Keys;
  135. }
  136. }
  137. #endregion
  138. #region Values (Public)
  139. /// <summary>
  140. /// Note: Editing the values would make this class inconsistent,
  141. /// as they are used for the reverse mapping (and editing would change
  142. /// their hashcodes, plus make the forward and reverse hashmaps
  143. /// inconsistent.
  144. /// </summary>
  145. public ICollection Values
  146. {
  147. get
  148. {
  149. return hashtableForward.Values;
  150. }
  151. }
  152. #endregion
  153. #region Item (Public)
  154. /// <summary>
  155. /// Forward lookup and set.
  156. /// </summary>
  157. /// <param name="key">Key</param>
  158. /// <returns>Item at this key</returns>
  159. public object this[object key]
  160. {
  161. get
  162. {
  163. return hashtableForward[key];
  164. }
  165. set
  166. {
  167. // If the forward map contains this key, changing it
  168. // means removing it from the reverse map by its value,
  169. // which is the key for the reverse map.
  170. if (hashtableForward.ContainsKey(key))
  171. {
  172. hashtableBackward.Remove(hashtableForward[key]);
  173. }
  174. hashtableForward[key] = value;
  175. hashtableBackward[value] = key;
  176. }
  177. }
  178. #endregion
  179. #region ForwardHashtable (Public)
  180. /// <summary>
  181. /// Gives direct access to forward hashtable.
  182. /// Although provided as a "just-in-case" kind of convenience,
  183. /// it should be used carefully, if ever, as it gives direct
  184. /// access to internal data, and if altered, the state of this
  185. /// object will become inconsistent.
  186. /// Preferably, use (Hashtable) conversion instead, which
  187. /// returns a clone.
  188. /// </summary>
  189. public Hashtable ForwardHashtable
  190. {
  191. get
  192. {
  193. return hashtableForward;
  194. }
  195. }
  196. #endregion
  197. #region BackwardHashtable (Public)
  198. /// <summary>
  199. /// Gives direct access to backward hashtable.
  200. /// Although provided as a "just-in-case" kind of convenience,
  201. /// it should be used carefully, if ever, as it gives direct
  202. /// access to internal data, and if altered, the state of this
  203. /// object will become inconsistent.
  204. /// Preferably, use BackwardHashtableClone instead, which
  205. /// returns a clone.
  206. /// </summary>
  207. public Hashtable BackwardHashtable
  208. {
  209. get
  210. {
  211. return hashtableBackward;
  212. }
  213. }
  214. #endregion
  215. #region BackwardHashtableClone (Public)
  216. /// <summary>
  217. /// Returns a clone of the backward table which can be
  218. /// passed off and edited.
  219. /// </summary>
  220. public Hashtable BackwardHashtableClone
  221. {
  222. get
  223. {
  224. return (Hashtable)hashtableBackward.Clone();
  225. }
  226. }
  227. #endregion
  228. #region Private
  229. #region hashtableForward (Private)
  230. /// <summary>
  231. /// Hashtable forward
  232. /// </summary>
  233. private Hashtable hashtableForward;
  234. #endregion
  235. #region hashtableBackward (Private)
  236. /// <summary>
  237. /// Hashtable backward
  238. /// </summary>
  239. private Hashtable hashtableBackward;
  240. #endregion
  241. #endregion
  242. #region Constructors
  243. /// <summary>
  244. /// Standard constructor.
  245. /// Eventually it might be nice to add more, but in most cases
  246. /// the default should do fine.
  247. /// </summary>
  248. public BidirHashtable()
  249. {
  250. hashtableForward = new Hashtable();
  251. hashtableBackward = new Hashtable();
  252. }
  253. /// <summary>
  254. /// This constructor initializes from an existing
  255. /// IDictionary (such as another Hashtable).
  256. /// It sets up both the forward and backward tables.
  257. /// A use for this would be to take an existing Hashtable
  258. /// that you don't want rewritten as a BidirHashtable, and
  259. /// set it up for efficient reverse lookups.
  260. /// Obviously, it'str more efficient to use a BidirHashtable
  261. /// from the start, but the cost of converting a lot of code
  262. /// may be too high to justify.
  263. /// </summary>
  264. /// <param name="dict">Dictionary to create hashtable from</param>
  265. public BidirHashtable(IDictionary dict)
  266. {
  267. hashtableForward = new Hashtable();
  268. hashtableBackward = new Hashtable();
  269. foreach (object key in dict.Keys)
  270. {
  271. this[key] = dict[key];
  272. }
  273. }
  274. /// <summary>
  275. /// Private constructor used when explicitly attaching to
  276. /// an existing Hashtable and then setting up the reverse
  277. /// mapping from it.
  278. /// </summary>
  279. /// <param name="ht">Hashtable to attach to.</param>
  280. /// <param name="bytDummyIndicatesAttach">
  281. /// Dummy parameter (just to give it a different signature)</param>
  282. private BidirHashtable(Hashtable ht, byte bytDummyIndicatesAttach)
  283. {
  284. hashtableForward = ht;
  285. hashtableBackward = new Hashtable();
  286. // Use bytDummyIndicatesAttach to subpress CA1801 warning
  287. bytDummyIndicatesAttach++;
  288. foreach (object key in ht.Keys)
  289. {
  290. hashtableBackward[ht[key]] = key;
  291. }
  292. }
  293. #endregion
  294. #region ICloneable Members
  295. /// <summary>
  296. /// Clone the current BidirHashtable.
  297. /// </summary>
  298. /// <returns>Cloned BidirHashtable as an object</returns>
  299. public object Clone()
  300. {
  301. BidirHashtable bh = new BidirHashtable();
  302. bh.hashtableForward = (Hashtable)hashtableForward.Clone();
  303. bh.hashtableBackward = (Hashtable)hashtableBackward.Clone();
  304. return bh;
  305. }
  306. #endregion
  307. #region ICollection Members
  308. /// <summary>
  309. /// Copy to
  310. /// </summary>
  311. /// <param name="array">Array</param>
  312. /// <param name="index">Index</param>
  313. public void CopyTo(Array array, int index)
  314. {
  315. hashtableForward.CopyTo(array, index);
  316. }
  317. #endregion
  318. #region IDictionary Members
  319. /// <summary>
  320. /// Add
  321. /// </summary>
  322. /// <param name="key">Key</param>
  323. /// <param name="value">Values</param>
  324. public void Add(object key, object value)
  325. {
  326. hashtableForward.Add(key, value);
  327. hashtableBackward.Add(value, key);
  328. }
  329. /// <summary>
  330. /// Clear
  331. /// </summary>
  332. public void Clear()
  333. {
  334. hashtableForward.Clear();
  335. hashtableBackward.Clear();
  336. }
  337. /// <summary>
  338. /// Contains
  339. /// </summary>
  340. /// <param name="key">Key to search for</param>
  341. /// <returns>True if an entry with the key was found, false otherwise.
  342. /// </returns>
  343. public bool Contains(object key)
  344. {
  345. return hashtableForward.Contains(key);
  346. }
  347. /// <summary>
  348. /// Remove
  349. /// </summary>
  350. /// <param name="key">Key</param>
  351. public void Remove(object key)
  352. {
  353. object val = hashtableForward[key];
  354. hashtableForward.Remove(key);
  355. hashtableBackward.Remove(val);
  356. }
  357. //public
  358. IDictionaryEnumerator IDictionary.GetEnumerator()
  359. {
  360. return hashtableForward.GetEnumerator();
  361. }
  362. #endregion
  363. #region IEnumerable Members
  364. IEnumerator IEnumerable.GetEnumerator()
  365. {
  366. return hashtableForward.GetEnumerator();
  367. }
  368. #endregion
  369. #region op_Explicit (Operator)
  370. /// <summary>
  371. /// Bidir hashtable
  372. /// </summary>
  373. /// <param name="ht">Hashtable</param>
  374. /// <returns>New BidirHashtable with the same data</returns>
  375. public static explicit operator BidirHashtable(Hashtable ht)
  376. {
  377. return new BidirHashtable(ht);
  378. }
  379. /// <summary>
  380. /// Hashtable
  381. /// </summary>
  382. /// <param name="bd">BidirHashtable</param>
  383. /// <returns>Cloned hashtable with the same data</returns>
  384. public static explicit operator Hashtable(BidirHashtable bd)
  385. {
  386. return (Hashtable)bd.hashtableForward.Clone();
  387. }
  388. #endregion
  389. #region CopyValuesTo (Public)
  390. /// <summary>
  391. /// Copy values to
  392. /// </summary>
  393. /// <param name="array">Array</param>
  394. /// <param name="index">Index</param>
  395. public void CopyValuesTo(Array array, int index)
  396. {
  397. hashtableBackward.CopyTo(array, index);
  398. }
  399. #endregion
  400. #region ReverseLookup (Public)
  401. /// <summary>
  402. /// This is the key addition in this class--a reverse lookup
  403. /// which operates at the speed of a regular Hashtable.
  404. /// </summary>
  405. /// <param name="val">Value for lookup</param>
  406. /// <returns>Object at lookup location</returns>
  407. public object ReverseLookup(object val)
  408. {
  409. return hashtableBackward[val];
  410. }
  411. #endregion
  412. #region ContainsValue (Public)
  413. /// <summary>
  414. /// Contains value
  415. /// </summary>
  416. /// <param name="val">Value to check for</param>
  417. /// <returns>True if the value was found, false otherwise.</returns>
  418. public bool ContainsValue(object val)
  419. {
  420. return hashtableBackward.Contains(val);
  421. }
  422. #endregion
  423. #region ReverseDirection (Public)
  424. /// <summary>
  425. /// Reverses the direction of the BidirHashtable.
  426. /// This just swaps the two internal Hashtables.
  427. /// </summary>
  428. public void ReverseDirection()
  429. {
  430. Hashtable htTemp = hashtableForward;
  431. hashtableForward = hashtableBackward;
  432. hashtableBackward = htTemp;
  433. }
  434. #endregion
  435. /// <summary>
  436. /// Bidir hashtable tests
  437. /// </summary>
  438. internal class BidirHashtableTests
  439. {
  440. #region TestBidirHashtable
  441. /// <summary>
  442. /// Test bidir hashtable
  443. /// </summary>
  444. [Test]
  445. public void TestBidirHashtable()
  446. {
  447. BidirHashtable hash = new BidirHashtable();
  448. hash.Add("test item", 10);
  449. hash.Add("yoyo", 20);
  450. hash.Add("wazzzap", 30);
  451. // We can't add "yoyo" twice, use another name!
  452. hash.Add("yoyo2", 40);
  453. Assert.Equal(4, hash.Keys.Count);
  454. Assert.Equal(10, (int)hash["test item"]);
  455. Assert.Equal(40, (int)hash["yoyo2"]);
  456. Assert.Equal("yoyo", (string)hash.ReverseLookup(20));
  457. Assert.Equal("yoyo2", (string)hash.ReverseLookup(40));
  458. Assert.True(hash.Contains("wazzzap"));
  459. Assert.True(hash.ContainsValue(20));
  460. Assert.False(hash.Contains("yoyo3"));
  461. Assert.False(hash.ContainsValue(50));
  462. }
  463. #endregion
  464. }
  465. }
  466. }