A Hashtable
can
map only a single key to a single value, but you need to map a key to
one or more values. In addition, it may also be possible to map a key
to null
.
Use a Hashtable
whose values are
ArrayList
s. This structure allows you to add
multiple values (in the ArrayList
) for each key of
the Hashtable
. The MultiMap
class, which is used in practically the same manner as a
Hashtable
class, does
this:
using System; using System.Collections; public class MultiMap { private Hashtable map = new Hashtable( ); public ArrayList this[object key] { get {return ((ArrayList)map[key]);} set {map[key] = value;} } public void Add(object key, object item) { AddSingleMap(key, item); } public void Clear( ) { map.Clear( ); } public int Count { get {return (map.Count);} } public bool ContainsKey (object key) { return (map.ContainsKey(key)); } public bool ContainsValue(object item) { if (item == null) { foreach (DictionaryEntry de in map) { if (((ArrayList)de.Value).Count == 0) { return (true); } } return (false); } else { foreach (DictionaryEntry de in map) { if (((ArrayList)de.Value).Contains(item)) { return (true); } } return (false); } } public IEnumerator GetEnumerator( ) { return (map.GetEnumerator( )); } public void Remove(object key) { RemoveSingleMap(key); } public object Clone( ) { MultiMap clone = new MultiMap( ); foreach (DictionaryEntry de in map) { clone[de.Key] = (ArrayList)((ArrayList)de.Value).Clone( ); } return (clone); } protected virtual void AddSingleMap(object key, object item) { // Search for key in map Hashtable if (map.ContainsKey(key)) { if (item == null) { throw (new ArgumentNullException("value", "Cannot map a null to this key")); } else { // Add value to ArrayList in map ArrayList values = (ArrayList)map[key]; // Add this value to this existing key values.Add(item); } } else { if (item == null) { // Create new key and mapping to an empty ArrayList map.Add(key, new ArrayList( )); } else { ArrayList values = new ArrayList( ); values.Add(item); // Create new key and mapping to its value map.Add(key, values); } } } protected virtual void RemoveSingleMap(object key) { if (this.ContainsKey(key)) { // Remove the key from KeysTable map.Remove(key); } else { throw (new ArgumentOutOfRangeException("key", key.ToString( ), "This key does not exists in the map.")); } } }
The methods defined in Table 10-3 are of particular
interest to using a MultiMap
object.
Table 10-3. Members of the MultiMap class
Items may be added to a MultiMap
object in the
following manner:
public static void TestMultiMap( ) { string s = "foo"; // Create and populate a MultiMap object MultiMap myMap = new MultiMap( ); myMap.Add("0", "zero"); myMap.Add("1", "one"); myMap.Add("2", "two"); myMap.Add("3", "three"); myMap.Add("3", "duplicate three"); myMap.Add("3", "duplicate three"); myMap.Add("4", null); myMap.Add("5", s); myMap.Add("6", s); // Display contents foreach (DictionaryEntry entry in myMap) { Console.Write("Key: " + entry.Key.ToString( ) + " Value: "); foreach (object o in myMap[entry.Key]) { Console.Write(o.ToString( ) + " : "); } Console.WriteLine( ); } MultiMap otherMap = (MultiMap)myMap.Clone( ); // Obtain values through the indexer Console.WriteLine( ); Console.WriteLine("((ArrayList) myMap[3])[0]: " + myMap["3"][0]); Console.WriteLine("((ArrayList) myMap[3])[1]: " + myMap["3"][1]); // Add items to MultiMap using an ArrayList ArrayList testArray = new ArrayList( ); testArray.Add("BAR"); testArray.Add("BAZ"); myMap["10"] = testArray; myMap["10"] = testArray; // Remove items from MultiMap myMap.Remove("0"); myMap.Remove("1"); // Display MultiMap Console.WriteLine( ); Console.WriteLine("myMap.Count: " + myMap.Count); foreach (DictionaryEntry entry in myMap) { Console.Write("entry.Key: " + entry.Key.ToString( ) + " entry.Value(s): "); foreach (object o in myMap[entry.Key]) { if (o == null) { Console.Write("null : "); } else { Console.Write(o.ToString( ) + " : "); } } Console.WriteLine( ); } // Determine if the map contains the key("2") or the value("two") Console.WriteLine( ); Console.WriteLine("myMap.ContainsKey(2): " + myMap.ContainsKey("2")); Console.WriteLine("myMap.ContainsValue(two): " + myMap.ContainsValue("two")); // Clear all items from MultiMap myMap.Clear( ); }
This code displays the following:
Key: 2 Value: two : Key: 3 Value: three : duplicate three : duplicate three : Key: 0 Value: zero : Key: 1 Value: one : Key: 6 Value: foo : Key: 4 Value: Key: 5 Value: foo : ((ArrayList) myMap[3])[0]: three ((ArrayList) myMap[3])[1]: duplicate three myMap.Count: 6 entry.Key: 2 entry.Value(s): two : entry.Key: 3 entry.Value(s): three : duplicate three : duplicate three : entry.Key: 6 entry.Value(s): foo : entry.Key: 4 entry.Value(s): entry.Key: 5 entry.Value(s): foo : entry.Key: 10 entry.Value(s): BAR : BAZ : myMap.ContainsKey(2): True myMap.ContainsValue(two): True
A one-to-many map, or multimap, allows one object, a key, to be
associated, or mapped, to zero or more objects.
The
MultiMap
class presented here operates similarly
to a Hashtable
. The MultiMap
class contains a Hashtable
field called
map
that contains the actual mapping of keys to
values. Several of the MultiMap
methods are
delegated to the methods on the map
Hashtable
object.
A Hashtable
operates on a one-to-one principle:
only one key may be associated with one value at any time. However,
if you need to associate multiple values with a single key, you must
use the approach used by the MultiMap
class. The
private map
field associates a key with a single
ArrayList
of values, which allows multiple
mappings of values to a single key and mappings of a single value to
multiple keys. As an added feature, a key can also be mapped to a
null
value.
Here’s what happens when key/value pairs are added
to a MultiMap
object:
The MultiMap.Add
method is called with a key
and
value
provided as parameters.
The Add
method checks to see whether
key
exists in the map
Hashtable
object.
If key
does not exist, it is added as a
key in the map
Hashtable
object. This key is associated with a new
ArrayList
as the value associated with
key
in this Hashtable
.
If the key
does exist, the
key
is looked up in the
map
Hashtable
object and the
value
is added to the
key
’s
ArrayList
.
To remove a key using the
Remove
method, the key and
ArrayList
pair are removed from the
map
Hashtable
. This allows
removal of all values associated with a single key. The
MultiMap.Remove
method calls the
RemoveSingleMap
method, which encapsulates this
behavior. Removal of key “0”, and
all values mapped to this key, is performed with the following code:
myMap.Remove(1);
To remove all keys and their associated
values, use the MultiMap.Clear
method. This method
removes all items from the map
Hashtable
.
The other major member of the
MultiMap
class to discuss is its indexer. The
indexer returns the ArrayList
of values for a
particular key through its get
accessor. The
set
accessor simply adds the
ArrayList
provided to a single key. This code
creates an array of values and attempts to map them to key
“5” in the myMap
object:
ArrayList testArray = new ArrayList( ); testArray.Add("BAR"); testArray.Add("BAZ"); myMap["5"] = testArray;
The following code makes use of the get
accessor
to access each value associated with key
“3”:
Console.WriteLine(myMap["3"][0]); Console.WriteLine(myMap["3"][1]); Console.WriteLine(myMap["3"][2]);
This looks somewhat similar to using a jagged array. The first
indexer is used to pull the ArrayList
from the
map
Hashtable
and the second
indexer is used to obtain the value in the
ArrayList
. This code displays the following:
three duplicate three duplicate three
This MultiMap
class also allows the use of the
foreach
loop to enumerate its key/value pairs. The
following code displays each key/value pair in the
MyMap
object:
foreach (DictionaryEntry entry in myMap) { Console.Write("Key: " + entry.Key.ToString( ) + " Value: "); foreach (object o in myMap[entry.Key]) { Console.Write(o.ToString( ) + " : "); } Console.WriteLine( ); }
The outer foreach
loop is used to retrieve all the
keys and the inner foreach
loop is used to display
each value mapped to a particular key. This code displays the
following for the initial MyMap
object:
Key: 2 Value: two : Key: 3 Value: three : duplicate three : duplicate three : Key: 0 Value: zero : Key: 1 Value: one : Key: 4 Value:
There are two methods that allow
searching of the MultiMap
object:
ContainsKey
and ContainsValue
.
The ContainsKey
method searches for the specified
key in the map
Hashtable
. The
ContainsValue
method searches for the specified
value in an ArrayList
in the
map
Hashtable
. Both methods
return true
if the key/value was found or
false
otherwise:
Console.WriteLine("Contains Key 2: " + myMap.ContainsKey("2")); Console.WriteLine("Contains Key 12: " + myMap.ContainsKey("12")); Console.WriteLine("Contains Value two: " + myMap.ContainsValue("two")); Console.WriteLine("Contains Value BAR: " + myMap.ContainsValue("BAR"));
Note that the ContainsKey
and
ContainsValue
methods are both case-sensitive.