/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
- using System;
- using System.Collections;
- using NUnit.Framework;
-
- namespace Delta.Utilities.Collections
- {
- /// <summary>
- /// BidirHashtable is a simple, bidirectional data structure designed
- /// around Hashtables and accessed like a more robust Hashtable.
- /// Internally it just contains two hashtables:
- /// One maps from key to value, the other maps from value to key.
- /// Therefore, note that both types of objects must have reasonable
- /// GetHashCode() and Equals() implementations. Lookup in either direction
- /// is quick; changes take twice as long since two Hashtables are accessed.
- /// It is not currently serializable, but aside from that, it implements the
- /// same interfaces as a Hashtable. Forward lookup is just through the [] as
- /// in Hashtable. Reverse lookup is through ReverseLookup().
- /// </summary>
- [Serializable]
- public class BidirHashtable :
- IDictionary, ICollection, IEnumerable, ICloneable
- {
- #region FromHashtable (Static)
- /// <summary>
- /// Creates a Bidirectional hashtable from a normal Hashtable.
- /// </summary>
- /// <param name="setHashtable">Hashtable to initialize from</param>
- /// <returns>BidirHashtable with the same data</returns>
- public static BidirHashtable FromHashtable(Hashtable setHashtable)
- {
- return new BidirHashtable(setHashtable);
- }
- #endregion
-
- #region ToHashtable (Static)
- /// <summary>
- /// Hashtable
- /// </summary>
- /// <param name="bd">Bidirectional hashtable</param>
- /// <returns>A normal Hashtable</returns>
- public static Hashtable ToHashtable(BidirHashtable bd)
- {
- return (Hashtable)bd.hashtableForward.Clone();
- }
- #endregion
-
- #region Attach (Static)
- /// <summary>
- /// Creates a new BidirHashtable which is attached to
- /// the input Hashtable. The reverse mapping is set up
- /// automatically.
- /// Note that there is no need for a Detach(); just stop using
- /// this object and let it get garbage-collected aside from
- /// the attached table, which could still be used (though the
- /// user would have to be careful not to use the attached
- /// table while the BidirHashtable might still be used).
- /// While it is possible that someone might want to set up
- /// the reverse mapping by doing an Attach(), for that you
- /// must just use Attach() and then ReverseDirection().
- /// </summary>
- /// <param name="ht">Hastable</param>
- /// <returns>BidirHashtable with the same data</returns>
- public static BidirHashtable Attach(Hashtable ht)
- {
- return new BidirHashtable(ht, 0);
- }
- #endregion
-
- #region Count (Public)
- /// <summary>
- /// Count
- /// </summary>
- public int Count
- {
- get
- {
- return hashtableForward.Count;
- }
- }
- #endregion
-
- #region IsSynchronized (Public)
- /// <summary>
- /// Is synchronized
- /// </summary>
- public bool IsSynchronized
- {
- get
- {
- return hashtableForward.IsSynchronized;
- }
- }
- #endregion
-
- #region SyncRoot (Public)
- /// <summary>
- /// Sync root
- /// </summary>
- public object SyncRoot
- {
- get
- {
- return hashtableForward.SyncRoot;
- }
- }
- #endregion
-
- #region IsFixedSize (Public)
- /// <summary>
- /// Is fixed size
- /// </summary>
- public bool IsFixedSize
- {
- get
- {
- return hashtableForward.IsFixedSize;
- }
- }
- #endregion
-
- #region IsReadOnly (Public)
- /// <summary>
- /// Is hashtable read only? Will return IsReadOnly from the underlying
- /// hashtable (usually false).
- /// </summary>
- public bool IsReadOnly
- {
- get
- {
- return hashtableForward.IsReadOnly;
- }
- }
- #endregion
-
- #region Keys (Public)
- /// <summary>
- /// Note: Editing the keys would make this class inconsistent.
- /// </summary>
- public ICollection Keys
- {
- get
- {
- return hashtableForward.Keys;
- }
- }
- #endregion
-
- #region Values (Public)
- /// <summary>
- /// Note: Editing the values would make this class inconsistent,
- /// as they are used for the reverse mapping (and editing would change
- /// their hashcodes, plus make the forward and reverse hashmaps
- /// inconsistent.
- /// </summary>
- public ICollection Values
- {
- get
- {
- return hashtableForward.Values;
- }
- }
- #endregion
-
- #region Item (Public)
- /// <summary>
- /// Forward lookup and set.
- /// </summary>
- /// <param name="key">Key</param>
- /// <returns>Item at this key</returns>
- public object this[object key]
- {
- get
- {
- return hashtableForward[key];
- }
- set
- {
- // If the forward map contains this key, changing it
- // means removing it from the reverse map by its value,
- // which is the key for the reverse map.
- if (hashtableForward.ContainsKey(key))
- {
- hashtableBackward.Remove(hashtableForward[key]);
- }
- hashtableForward[key] = value;
- hashtableBackward[value] = key;
- }
- }
- #endregion
-
- #region ForwardHashtable (Public)
- /// <summary>
- /// Gives direct access to forward hashtable.
- /// Although provided as a "just-in-case" kind of convenience,
- /// it should be used carefully, if ever, as it gives direct
- /// access to internal data, and if altered, the state of this
- /// object will become inconsistent.
- /// Preferably, use (Hashtable) conversion instead, which
- /// returns a clone.
- /// </summary>
- public Hashtable ForwardHashtable
- {
- get
- {
- return hashtableForward;
- }
- }
- #endregion
-
- #region BackwardHashtable (Public)
- /// <summary>
- /// Gives direct access to backward hashtable.
- /// Although provided as a "just-in-case" kind of convenience,
- /// it should be used carefully, if ever, as it gives direct
- /// access to internal data, and if altered, the state of this
- /// object will become inconsistent.
- /// Preferably, use BackwardHashtableClone instead, which
- /// returns a clone.
- /// </summary>
- public Hashtable BackwardHashtable
- {
- get
- {
- return hashtableBackward;
- }
- }
- #endregion
-
- #region BackwardHashtableClone (Public)
- /// <summary>
- /// Returns a clone of the backward table which can be
- /// passed off and edited.
- /// </summary>
- public Hashtable BackwardHashtableClone
- {
- get
- {
- return (Hashtable)hashtableBackward.Clone();
- }
- }
- #endregion
-
- #region Private
-
- #region hashtableForward (Private)
- /// <summary>
- /// Hashtable forward
- /// </summary>
- private Hashtable hashtableForward;
- #endregion
-
- #region hashtableBackward (Private)
- /// <summary>
- /// Hashtable backward
- /// </summary>
- private Hashtable hashtableBackward;
- #endregion
-
- #endregion
-
- #region Constructors
- /// <summary>
- /// Standard constructor.
- /// Eventually it might be nice to add more, but in most cases
- /// the default should do fine.
- /// </summary>
- public BidirHashtable()
- {
- hashtableForward = new Hashtable();
- hashtableBackward = new Hashtable();
- }
-
- /// <summary>
- /// This constructor initializes from an existing
- /// IDictionary (such as another Hashtable).
- /// It sets up both the forward and backward tables.
- /// A use for this would be to take an existing Hashtable
- /// that you don't want rewritten as a BidirHashtable, and
- /// set it up for efficient reverse lookups.
- /// Obviously, it'str more efficient to use a BidirHashtable
- /// from the start, but the cost of converting a lot of code
- /// may be too high to justify.
- /// </summary>
- /// <param name="dict">Dictionary to create hashtable from</param>
- public BidirHashtable(IDictionary dict)
- {
- hashtableForward = new Hashtable();
- hashtableBackward = new Hashtable();
-
- foreach (object key in dict.Keys)
- {
- this[key] = dict[key];
- }
- }
-
- /// <summary>
- /// Private constructor used when explicitly attaching to
- /// an existing Hashtable and then setting up the reverse
- /// mapping from it.
- /// </summary>
- /// <param name="ht">Hashtable to attach to.</param>
- /// <param name="bytDummyIndicatesAttach">
- /// Dummy parameter (just to give it a different signature)</param>
- private BidirHashtable(Hashtable ht, byte bytDummyIndicatesAttach)
- {
- hashtableForward = ht;
- hashtableBackward = new Hashtable();
- // Use bytDummyIndicatesAttach to subpress CA1801 warning
- bytDummyIndicatesAttach++;
-
- foreach (object key in ht.Keys)
- {
- hashtableBackward[ht[key]] = key;
- }
- }
- #endregion
-
- #region ICloneable Members
- /// <summary>
- /// Clone the current BidirHashtable.
- /// </summary>
- /// <returns>Cloned BidirHashtable as an object</returns>
- public object Clone()
- {
- BidirHashtable bh = new BidirHashtable();
- bh.hashtableForward = (Hashtable)hashtableForward.Clone();
- bh.hashtableBackward = (Hashtable)hashtableBackward.Clone();
- return bh;
- }
- #endregion
-
- #region ICollection Members
- /// <summary>
- /// Copy to
- /// </summary>
- /// <param name="array">Array</param>
- /// <param name="index">Index</param>
- public void CopyTo(Array array, int index)
- {
- hashtableForward.CopyTo(array, index);
- }
- #endregion
-
- #region IDictionary Members
- /// <summary>
- /// Add
- /// </summary>
- /// <param name="key">Key</param>
- /// <param name="value">Values</param>
- public void Add(object key, object value)
- {
- hashtableForward.Add(key, value);
- hashtableBackward.Add(value, key);
- }
-
- /// <summary>
- /// Clear
- /// </summary>
- public void Clear()
- {
- hashtableForward.Clear();
- hashtableBackward.Clear();
- }
-
- /// <summary>
- /// Contains
- /// </summary>
- /// <param name="key">Key to search for</param>
- /// <returns>True if an entry with the key was found, false otherwise.
- /// </returns>
- public bool Contains(object key)
- {
- return hashtableForward.Contains(key);
- }
-
- /// <summary>
- /// Remove
- /// </summary>
- /// <param name="key">Key</param>
- public void Remove(object key)
- {
- object val = hashtableForward[key];
- hashtableForward.Remove(key);
- hashtableBackward.Remove(val);
- }
-
- //public
- IDictionaryEnumerator IDictionary.GetEnumerator()
- {
- return hashtableForward.GetEnumerator();
- }
- #endregion
-
- #region IEnumerable Members
- IEnumerator IEnumerable.GetEnumerator()
- {
- return hashtableForward.GetEnumerator();
- }
- #endregion
-
- #region op_Explicit (Operator)
- /// <summary>
- /// Bidir hashtable
- /// </summary>
- /// <param name="ht">Hashtable</param>
- /// <returns>New BidirHashtable with the same data</returns>
- public static explicit operator BidirHashtable(Hashtable ht)
- {
- return new BidirHashtable(ht);
- }
-
- /// <summary>
- /// Hashtable
- /// </summary>
- /// <param name="bd">BidirHashtable</param>
- /// <returns>Cloned hashtable with the same data</returns>
- public static explicit operator Hashtable(BidirHashtable bd)
- {
- return (Hashtable)bd.hashtableForward.Clone();
- }
- #endregion
-
- #region CopyValuesTo (Public)
- /// <summary>
- /// Copy values to
- /// </summary>
- /// <param name="array">Array</param>
- /// <param name="index">Index</param>
- public void CopyValuesTo(Array array, int index)
- {
- hashtableBackward.CopyTo(array, index);
- }
- #endregion
-
- #region ReverseLookup (Public)
- /// <summary>
- /// This is the key addition in this class--a reverse lookup
- /// which operates at the speed of a regular Hashtable.
- /// </summary>
- /// <param name="val">Value for lookup</param>
- /// <returns>Object at lookup location</returns>
- public object ReverseLookup(object val)
- {
- return hashtableBackward[val];
- }
- #endregion
-
- #region ContainsValue (Public)
- /// <summary>
- /// Contains value
- /// </summary>
- /// <param name="val">Value to check for</param>
- /// <returns>True if the value was found, false otherwise.</returns>
- public bool ContainsValue(object val)
- {
- return hashtableBackward.Contains(val);
- }
- #endregion
-
- #region ReverseDirection (Public)
- /// <summary>
- /// Reverses the direction of the BidirHashtable.
- /// This just swaps the two internal Hashtables.
- /// </summary>
- public void ReverseDirection()
- {
- Hashtable htTemp = hashtableForward;
- hashtableForward = hashtableBackward;
- hashtableBackward = htTemp;
- }
- #endregion
-
- /// <summary>
- /// Bidir hashtable tests
- /// </summary>
- internal class BidirHashtableTests
- {
- #region TestBidirHashtable
- /// <summary>
- /// Test bidir hashtable
- /// </summary>
- [Test]
- public void TestBidirHashtable()
- {
- BidirHashtable hash = new BidirHashtable();
- hash.Add("test item", 10);
- hash.Add("yoyo", 20);
- hash.Add("wazzzap", 30);
- // We can't add "yoyo" twice, use another name!
- hash.Add("yoyo2", 40);
- Assert.Equal(4, hash.Keys.Count);
- Assert.Equal(10, (int)hash["test item"]);
- Assert.Equal(40, (int)hash["yoyo2"]);
- Assert.Equal("yoyo", (string)hash.ReverseLookup(20));
- Assert.Equal("yoyo2", (string)hash.ReverseLookup(40));
- Assert.True(hash.Contains("wazzzap"));
- Assert.True(hash.ContainsValue(20));
- Assert.False(hash.Contains("yoyo3"));
- Assert.False(hash.ContainsValue(50));
- }
- #endregion
- }
- }
- }