Recipe11.5.Creating a One-to-Many Map (MultiMap)


Recipe 11.5. Creating a One-to-Many Map (MultiMap)

Problem

A Hashtable or a Dictionary<T,U> 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.

Solution

Use a Dictionary<T,U> with values that are a List<U>. This structure allows you to add multiple values (in the List<U>) for each key of the Dictionary<T,U>. The MultiMap<T,U> class shown in Example 11-8, which is used in practically the same manner as a Dictionary<T,U> class, does this.

Example 11-8. MultiMap class

 using System; using System Collections; using System.Collections.Generic; public class MultiMap<TKey, UValue> : IDictionary<TKey, IList<UValue>> {     private Dictionary<TKey, IList<UValue>> map =         new Dictionary<TKey, IList<UValue>>();     public IList<UValue> this[TKey key]     {         get {return (map[key]);}         set {map[key] = value;}     }     public void Add(TKey key, UValue item)     {         AddSingleMap(key, item);     }     public void Add(TKey key, IList<UValue> items)     {         foreach (UValue val in items)             AddSingleMap(key, val);     }     public void Add(KeyValuePair<TKey, IList<UValue>> keyValuePair)     {         foreach (UValue val in keyValuePair.Value)             AddSingleMap(keyValuePair.Key, val);     }     public void Clear()     {         map.Clear();     }     public int Count     {         get {return (map.Count);}     }     public bool IsReadOnly     {         get {throw(new NotSupportedException(              "This operation is not supported."));}     }     public bool Contains(TKey key)     {         return (map.ContainsKey(key));     }     public bool Contains(KeyValuePair<TKey, IList<UValue>> keyValuePair)     {         return (map.ContainsKey(keyValuePair.Key) &&                 map.ContainsValue(keyValuePair.Value));     }     public bool ContainsKey (TKey key)     {         return (map.ContainsKey(key));     }     public bool ContainsValue(UValue item)     {         if (item == null)         {             foreach (KeyValuePair<TKey, IList<UValue>> kvp in map)             {                 if (((List<UValue>)kvp.Value).Count == 0)                 {                     return (true);                 }             }             return (false);         }         else         {             foreach (KeyValuePair<TKey, IList<UValue>> kvp in map)             {                 if (((List<UValue>)kvp.Value).Contains(item))                 {                     return (true);                 }             }             return (false);         }     }     IEnumerator<KeyValuePair<TKey, IList<UValue>>> IEnumerable<KeyValuePair<TKey,                                    IList<UValue>>>.GetEnumerator()     {         return (map.GetEnumerator());     }     IEnumerator System.Collections.IEnumerable.GetEnumerator()     {         return (map.GetEnumerator());     }     public bool Remove(TKey key)     {         return (RemoveSingleMap(key));     }     public bool Remove(KeyValuePair<TKey, IList<UValue>> keyValuePair)     {         return (Remove(keyValuePair.Key));     }     protected virtual void AddSingleMap(TKey key, UValue item)     {         // Search for key in map Hashtable.         if (map.ContainsKey(key))         {             // Add value to List in map.             List<UValue> values = (List<UValue>)map[key];             // Add this value to this existing key.             values.Add(item);         }         else         {             if (item == null)             {                 // Create new key and mapping to an empty List.                 map.Add(key, new List<UValue>());             }             else             {                 List<UValue> values = new List<UValue>();                 values.Add(item);                 // Create new key and mapping to its value.                 map.Add(key, values);             }         }     }     protected virtual bool RemoveSingleMap(TKey key)     {         if (this.ContainsKey(key))         {             // Remove the key from KeysTable.             return (map.Remove(key));         }         else         {            throw (new ArgumentOutOfRangeException("key", key,                    "This key does not exists in the map."));         }     }     public bool TryGetValue(TKey key, out UValue item)     {         throw (new NotSupportedException(                "This operation is not supported, use " +                "TryGetValue(TKey, out IList<UValue>) instead."));     }     public bool TryGetValue(TKey key, out IList<UValue> items)     {         return (map.TryGetValue(key, out items));     }     public ICollection<TKey> Keys     {         get { return (map.Keys); }     }     public ICollection<IList<UValue>> Values     {         get { return (map.Values); }     }     public void CopyTo(TKey[] arr, int index)     {         int cntr = 0;         foreach (KeyValuePair<TKey, IList<UValue>> keyValuePair in map)         {             arr[cntr + index] = keyValuePair.Key;             cntr++;         }     }     public void CopyTo(KeyValuePair<TKey, IList<UValue>>[] arr, int index)     {         CopyTo(arr, index);     } } 

The methods defined in Table 11-3 are of particular interest to using a MultiMap<T,U> object.

Table 11-3. Members of the MultiMap class

Member

Description

Indexer

The get accessor obtains a List<U> of all values that are associated with a key. The set accessor adds an entire List<U> of values to a key. Its syntax is:

 public List<U> this[T key] 

where key is the key to be added to the MultiMap<T,U> through the set accessor, or it is the key with values that you want to retrieve via the get accessor.

Add method

Adds a key to the Dictionary<T,List<U>> and its associated value. Its syntax is:

 Add(T key, T value) 

where key is the key to be added to the MultiMap<T,U> and value is the value to add to the internal List<U> of the private map field.

Clear method

Removes all items from the MultiMap<T,U> object.

Count method

Returns a count of all keys in the MultiMap<T,U> object.

Clone method

Returns a deep copy of the MultiMap<T,U> object.

ContainsKey method

Returns a bool indicating whether the MultiMap<T,U> contains a particular value as its key. Its syntax is:

 ContainsKey(T key) 

where key is the key to be found in the MultiMap<T,U>.

ContainsValue method

Returns a bool indicating whether the MultiMap<T,U> contains a particular value. Its syntax is:

 ContainsValue(T value) 

where value is the object to be found in the MultiMap<T,U>.

Remove method

Removes a key from the MultiMap<T,U> and all its referent values in the internal map Dictionary<T, List<U>>. Its syntax is:

 Remove(T key) 

where key is the key to be removed.


Items may be added to a MultiMap<T,U> object by running the code shown in Example 11-9.

Example 11-9. Testing the MultiMap class

 public static void TestMultiMap() {     string s = "foo";     // Create and populate a MultiMap object.     MultiMap<int, string> myMap = new MultiMap<int, string>();     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 (KeyValuePair<int, List<string>> entry in myMap)     {         Console.Write("Key: " + entry.Key.ToString() + "\tValue: ");         foreach (string str in myMap[entry.Key])         {             Console.Write(str + " : ");         }         Console.WriteLine();     }     // 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 a List.     List<string> testArray = new List<string>();     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 (KeyValuePair<int, List<string>> entry in myMap) {         Console.Write("entry.Key: " + entry.Key.ToString() +                       "\tentry.Value(s): ");         foreach (string str in myMap[entry.Key])         {             if (str == null)             {                 Console.Write("null : ");             }             else             {                 Console.Write(str + " : ");             }         }         Console.WriteLine();     }     // Determine if the map contains the key or the value.     Console.WriteLine();     Console.WriteLine("myMap.ContainsKey(2): " + myMap.ContainsKey(2));     Console.WriteLine("myMap.ContainsValue(two): " +     myMap.ContainsValue("two"));     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"));     // Clear all items from MultiMap.     myMap.Clear(); } 

This code displays the following:

 Key: 0  Value: zero : Key: 1  Value: one : Key: 2  Value: two : Key: 3  Value: three : duplicate three : duplicate three : Key: 4  Value: Key: 5  Value: foo : Key: 6  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:  4    entry.Value(s): entry.Key:  5    entry.Value(s): foo : entry.Key:  6    entry.Value(s): foo : entry.Key:  10    entry.Value(s): BAR : BAZ : myMap.ContainsKey(2):  True myMap.ContainsValue(two):  True Contains Key  2:   True Contains Key  12:   False Contains Value  two:   True Contains Value  BAR:   True 

Discussion

A one-to-many map, or multimap, allows one object, a key, to be associated, or mapped, to zero or more objects. The MultiMap<T,U> class presented here operates similarly to a Dictionary<T,U>. The MultiMap<T,U> class contains a Dictionary<T, List<U>> field called map that contains the actual mapping of keys to values. Several of the MultiMap<T,U> methods are delegated to the methods on the map Dictionary<T, List<U>> object.

A Dictionary<T,U> 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<T,U> class. The private map field associates a key with a single List<U> 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<t,U> object:

  1. The MultiMap<T,U>.Add method is called with a key and value provided as parameters.

  2. The Add method checks to see whether key exists in the map Dictionary<T, List<U>> object.

  3. If key does not exist, it is added as a key in the map Dictionary<T, List<U>> object. This key is associated with a new List<U> as the value associated with key in this Hashtable.

  4. If the key does exist, the key is looked up in the map Dictionary<T, List<U>> object and the value is added to the key's List<U>.

To remove a key using the Remove method, the key and List<U> pair are removed from the map Dictionary<T, List<U>>. This allows removal of all values associated with a single key. The MultiMap<T,U>.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<T,U>.Clear method. This method removes all items from the map Dictionary<T, List<U>>.

The other major member of the MultiMap<T,U> class needing discussion is its indexer. The indexer returns the List<U> of values for a particular key through its get accessor. The set accessor simply adds the List<U> provided to a single key. This code creates an array of values and attempts to map them to key "5" in the myMap object:

 List<string> testArray = new List<string>(); 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 ([3] in the preceding examples) is used to pull the List<U> from the map Dictionary<T, List<U>>, and the second indexer is used to obtain the value in the List<U>. This code displays the following:

 three duplicate three duplicate three 

This MultiMap<T,U> 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 (KeyValuePair<int, List<string>> entry in myMap) {     Console.Write("Key: " + entry.Key.ToString() + "\tValue: ");     foreach (string str in myMap[entry.Key])     {         Console.Write(str + " : ");     }     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: 0 Value: zero : Key: 1 Value: one : Key: 2 Value: two : Key: 3 Value: three : duplicate three : duplicate three : Key: 4 Value: Key: 5 Value: foo : Key: 6 Value: foo : 

Two methods that allow searching of the MultiMap<T,U> object are ContainsKey and ContainsValue. The ContainsKey method searches for the specified key in the map Dictionary<T, List<U>>. The ContainsValue method searches for the specified value in a List<U> in the map Dictionary<T, List<U>>. 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.

See Also

See the "List<T> Class," "Dictionary<T,U> Class," and "IEnumerator Interface" topics in the MSDN documentation.



C# Cookbook
Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More
ISBN: 0596003943
EAN: 2147483647
Year: 2004
Pages: 424

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net