//******************************
// Written by Peter Golde
// Copyright (c) 2004-2005, Wintellect
//
// Use and restribution of this code is subject to the license agreement
// contained in the file "License.txt" accompanying this file.
//******************************
using System;
using System.Collections;
using System.Collections.Generic;
namespace Wintellect.PowerCollections
{
///
/// The MultiDictionary class that associates values with a key. Unlike an Dictionary,
/// each key can have multiple values associated with it. When indexing an MultiDictionary, instead
/// of a single value associated with a key, you retrieve an enumeration of values.
/// When constructed, you can chose to allow the same value to be associated with a key multiple
/// times, or only one time.
///
/// The type of the keys.
/// The of values associated with the keys.
///
///
[Serializable]
public class MultiDictionary : MultiDictionaryBase, ICloneable
{
// The comparer for comparing keys
private IEqualityComparer keyEqualityComparer;
// The comparer for comparing values;
private IEqualityComparer valueEqualityComparer;
// The comparer for compaing keys and values.
private IEqualityComparer equalityComparer;
// The hash that holds the keys and values.
private Hash hash;
// Whether duplicate values for the same key are allowed.
private bool allowDuplicateValues;
///
/// A structure to hold the key and the values associated with the key.
/// The number of values must always be 1 or greater in a version that is stored, but
/// can be zero in a dummy version used only for lookups.
///
[Serializable]
private struct KeyAndValues
{
///
/// The key.
///
public TKey Key;
///
/// The number of values. Always at least 1 except in a dummy version for lookups.
///
public int Count;
///
/// An array of values.
///
public TValue[] Values;
///
/// Create a dummy KeyAndValues with just the key, for lookups.
///
/// The key to use.
public KeyAndValues(TKey key)
{
this.Key = key;
this.Count = 0;
this.Values = null;
}
///
/// Make a copy of a KeyAndValues, copying the array.
///
/// KeyAndValues to copy.
/// A copied version.
public static KeyAndValues Copy(KeyAndValues x)
{
KeyAndValues result;
result.Key = x.Key;
result.Count = x.Count;
if (x.Values != null)
result.Values = (TValue[])x.Values.Clone();
else
result.Values = null;
return result;
}
}
///
/// This class implements IEqualityComparer for KeysAndValues, allowing them to be
/// compared by their keys. An IEqualityComparer on keys is required.
///
[Serializable]
private class KeyAndValuesEqualityComparer : IEqualityComparer
{
private IEqualityComparer keyEqualityComparer;
public KeyAndValuesEqualityComparer(IEqualityComparer keyEqualityComparer)
{
this.keyEqualityComparer = keyEqualityComparer;
}
public bool Equals(KeyAndValues x, KeyAndValues y)
{
return keyEqualityComparer.Equals(x.Key, y.Key);
}
public int GetHashCode(KeyAndValues obj)
{
return Util.GetHashCode(obj.Key, keyEqualityComparer);
}
}
#region Constructors
///
/// Create a new MultiDictionary. The default ordering of keys and values are used. If duplicate values
/// are allowed, multiple copies of the same value can be associated with the same key. For example, the key "foo"
/// could have "a", "a", and "b" associated with it. If duplicate values are not allowed, only one copies of a given value can
/// be associated with the same key, although different keys can have the same value. For example, the key "foo" could
/// have "a" and "b" associated with it, which key "bar" has values "b" and "c" associated with it.
///
/// The default ordering of keys and values will be used, as defined by TKey and TValue's implementation
/// of IComparable<T> (or IComparable if IComparable<T> is not implemented). If a different ordering should be
/// used, other constructors allow a custom Comparer or IComparer to be passed to changed the ordering.
/// Can the same value be associated with a key multiple times?
/// TKey or TValue does not implement either IComparable<T> or IComparable.
public MultiDictionary(bool allowDuplicateValues)
: this(allowDuplicateValues, EqualityComparer.Default, EqualityComparer.Default)
{
}
///
/// Create a new MultiDictionary. If duplicate values
/// are allowed, multiple copies of the same value can be associated with the same key. For example, the key "foo"
/// could have "a", "a", and "b" associated with it. If duplicate values are not allowed, only one copies of a given value can
/// be associated with the same key, although different keys can have the same value. For example, the key "foo" could
/// have "a" and "b" associated with it, which key "bar" has values "b" and "c" associated with it.
///
/// Can the same value be associated with a key multiple times?
/// An IEqualityComparer<TKey> instance that will be used to compare keys.
/// TValue does not implement either IComparable<TValue> or IComparable.
public MultiDictionary(bool allowDuplicateValues, IEqualityComparer keyEqualityComparer)
: this(allowDuplicateValues, keyEqualityComparer, EqualityComparer.Default)
{
}
///
/// Create a new MultiDictionary. If duplicate values
/// are allowed, multiple copies of the same value can be associated with the same key. For example, the key "foo"
/// could have "a", "a", and "b" associated with it. If duplicate values are not allowed, only one copies of a given value can
/// be associated with the same key, although different keys can have the same value. For example, the key "foo" could
/// have "a" and "b" associated with it, which key "bar" has values "b" and "c" associated with it.
///
/// Can the same value be associated with a key multiple times?
/// An IEqualityComparer<TKey> instance that will be used to compare keys.
/// An IEqualityComparer<TValue> instance that will be used to compare values.
public MultiDictionary(bool allowDuplicateValues, IEqualityComparer keyEqualityComparer, IEqualityComparer valueEqualityComparer)
{
if (keyEqualityComparer == null)
throw new ArgumentNullException("keyEqualityComparer");
if (valueEqualityComparer == null)
throw new ArgumentNullException("valueEqualityComparer");
this.allowDuplicateValues = allowDuplicateValues;
this.keyEqualityComparer = keyEqualityComparer;
this.valueEqualityComparer = valueEqualityComparer;
this.equalityComparer = new KeyAndValuesEqualityComparer(keyEqualityComparer);
this.hash = new Hash(equalityComparer);
}
///
/// Create a new MultiDictionary. Private constructor, for use by Clone().
///
private MultiDictionary(bool allowDuplicateValues, IEqualityComparer keyEqualityComparer, IEqualityComparer valueEqualityComparer, IEqualityComparer equalityComparer, Hash hash)
{
if (keyEqualityComparer == null)
throw new ArgumentNullException("keyEqualityComparer");
if (valueEqualityComparer == null)
throw new ArgumentNullException("valueEqualityComparer");
this.allowDuplicateValues = allowDuplicateValues;
this.keyEqualityComparer = keyEqualityComparer;
this.valueEqualityComparer = valueEqualityComparer;
this.equalityComparer = equalityComparer;
this.hash = hash;
}
#endregion Constructors
#region Add or remove items
///
/// Adds a new value to be associated with a key. If duplicate values are permitted, this
/// method always adds a new key-value pair to the dictionary.
/// If duplicate values are not permitted, and already has a value
/// equal to associated with it, then that value is replaced with ,
/// and the number of values associate with is unchanged.
///
/// The key to associate with.
/// The value to associated with .
public sealed override void Add(TKey key, TValue value)
{
KeyAndValues keyValues = new KeyAndValues(key);
KeyAndValues existing;
if (hash.Find(keyValues, false, out existing)) {
// There already is an item in the hash table equal to this key. Add the new value,
// taking into account duplicates if needed.
int existingCount = existing.Count;
if (!allowDuplicateValues) {
int valueHash = Util.GetHashCode(value, valueEqualityComparer);
for (int i = 0; i < existingCount; ++i) {
if (Util.GetHashCode(existing.Values[i], valueEqualityComparer) == valueHash &&
valueEqualityComparer.Equals(existing.Values[i], value)) {
// Found an equal existing value. Replace it and we're done.
existing.Values[i] = value;
return;
}
}
}
// Add a new value to an existing key.
if (existingCount == existing.Values.Length) {
// Grow the array to make room.
TValue[] newValues = new TValue[existingCount * 2];
Array.Copy(existing.Values, newValues, existingCount);
existing.Values = newValues;
}
existing.Values[existingCount] = value;
existing.Count = existingCount + 1;
// Update the hash table.
hash.Find(existing, true, out keyValues);
return;
}
else {
// No item with this key. Add it.
keyValues.Count = 1;
keyValues.Values = new TValue[1] { value };
hash.Insert(keyValues, true, out existing);
return;
}
}
///
/// Removes a given value from the values associated with a key. If the
/// last value is removed from a key, the key is removed also.
///
/// A key to remove a value from.
/// The value to remove.
/// True if was associated with (and was
/// therefore removed). False if was not associated with .
public sealed override bool Remove(TKey key, TValue value)
{
KeyAndValues keyValues = new KeyAndValues(key);
KeyAndValues existing;
if (hash.Find(keyValues, false, out existing)) {
// There is an item in the hash table equal to this key. Find the value.
int existingCount = existing.Count;
int valueHash = Util.GetHashCode(value, valueEqualityComparer);
int indexFound = -1;
for (int i = 0; i < existingCount; ++i) {
if (Util.GetHashCode(existing.Values[i], valueEqualityComparer) == valueHash &&
valueEqualityComparer.Equals(existing.Values[i], value))
{
// Found an equal existing value
indexFound = i;
}
}
if (existingCount == 1) {
// Removing the last value. Remove the key.
hash.Delete(existing, out keyValues);
return true;
}
else if (indexFound >= 0) {
// Found a value. Remove it.
if (indexFound < existingCount - 1)
Array.Copy(existing.Values, indexFound + 1, existing.Values, indexFound, existingCount - indexFound - 1);
existing.Count = existingCount - 1;
// Update the hash.
hash.Find(existing, true, out keyValues);
return true;
}
else {
// Value was not found.
return false;
}
}
else {
return false; // key not found.
}
}
///
/// Removes a key and all associated values from the dictionary. If the
/// key is not present in the dictionary, it is unchanged and false is returned.
///
/// The key to remove.
/// True if the key was present and was removed. Returns
/// false if the key was not present.
public sealed override bool Remove(TKey key)
{
KeyAndValues dummy;
return hash.Delete(new KeyAndValues(key), out dummy);
}
///
/// Removes all keys and values from the dictionary.
///
public sealed override void Clear()
{
hash.StopEnumerations(); // Invalidate any enumerations.
// The simplest and fastest way is simply to throw away the old hash and create a new one.
hash = new Hash(equalityComparer);
}
#endregion Add or remove items
#region Query items
///
/// Returns the IEqualityComparer<T> used to compare keys in this dictionary.
///
/// If the dictionary was created using a comparer, that comparer is returned. Otherwise
/// the default comparer for TKey (EqualityComparer<TKey>.Default) is returned.
public IEqualityComparer KeyComparer
{
get
{
return this.keyEqualityComparer;
}
}
///
/// Returns the IEqualityComparer<T> used to compare values in this dictionary.
///
/// If the dictionary was created using a comparer, that comparer is returned. Otherwise
/// the default comparer for TValue (EqualityComparer<TValue>.Default) is returned.
public IEqualityComparer ValueComparer
{
get
{
return this.valueEqualityComparer;
}
}
///
/// Determine if two values are equal.
///
/// First value to compare.
/// Second value to compare.
/// True if the values are equal.
protected sealed override bool EqualValues(TValue value1, TValue value2)
{
return valueEqualityComparer.Equals(value1, value2);
}
///
/// Gets the number of key-value pairs in the dictionary. Each value associated
/// with a given key is counted. If duplicate values are permitted, each duplicate
/// value is included in the count.
///
/// The number of key-value pairs in the dictionary.
public sealed override int Count
{
get
{
return hash.ElementCount;
}
}
///
/// Checks to see if is associated with
/// in the dictionary.
///
/// The key to check.
/// The value to check.
/// True if is associated with .
public sealed override bool Contains(TKey key, TValue value)
{
KeyAndValues find = new KeyAndValues(key);
KeyAndValues item;
if (hash.Find(find, false, out item)) {
int existingCount = item.Count;
int valueHash = Util.GetHashCode(value, valueEqualityComparer);
for (int i = 0; i < existingCount; ++i) {
if (Util.GetHashCode(item.Values[i], valueEqualityComparer) == valueHash &&
valueEqualityComparer.Equals(item.Values[i], value)) {
// Found an equal existing value.
return true;
}
}
}
return false;
}
///
/// Checks to see if the key is present in the dictionary and has
/// at least one value associated with it.
///
/// The key to check.
/// True if is present and has at least
/// one value associated with it. Returns false otherwise.
public sealed override bool ContainsKey(TKey key)
{
KeyAndValues find = new KeyAndValues(key);
KeyAndValues temp;
return hash.Find(find, false, out temp);
}
///
/// Enumerate all the keys in the dictionary.
///
/// An IEnumerator<TKey> that enumerates all of the keys in the dictionary that
/// have at least one value associated with them.
protected sealed override IEnumerator EnumerateKeys()
{
foreach (KeyAndValues item in hash) {
yield return item.Key;
}
}
///
/// Enumerate the values in the a KeyAndValues structure. Can't return
/// the array directly because:
/// a) The array might be larger than the count.
/// b) We can't allow clients to down-cast to the array and modify it.
/// c) We have to abort enumeration if the hash changes.
///
/// Item with the values to enumerate..
/// An enumerable that enumerates the items in the KeyAndValues structure.
private IEnumerator EnumerateValues(KeyAndValues keyAndValues)
{
int count = keyAndValues.Count;
int stamp = hash.GetEnumerationStamp();
for (int i = 0; i < count; ++i) {
yield return keyAndValues.Values[i];
hash.CheckEnumerationStamp(stamp);
}
}
///
/// Determines if this dictionary contains a key equal to . If so, all the values
/// associated with that key are returned through the values parameter.
///
/// The key to search for.
/// Returns all values associated with key, if true was returned.
/// True if the dictionary contains key. False if the dictionary does not contain key.
protected sealed override bool TryEnumerateValuesForKey(TKey key, out IEnumerator values)
{
KeyAndValues find = new KeyAndValues(key);
KeyAndValues item;
if (hash.Find(find, false, out item)) {
values = EnumerateValues(item);
return true;
}
else {
values = null;
return false;
}
}
///
/// Gets the number of values associated with a given key.
///
/// The key to count values of.
/// The number of values associated with . If
/// is not present in the dictionary, zero is returned.
protected sealed override int CountValues(TKey key)
{
KeyAndValues find = new KeyAndValues(key);
KeyAndValues item;
if (hash.Find(find, false, out item)) {
return item.Count;
}
else {
return 0;
}
}
#endregion Query items
#region Cloning
///
/// Makes a shallow clone of this dictionary; i.e., if keys or values of the
/// dictionary are reference types, then they are not cloned. If TKey or TValue is a value type,
/// then each element is copied as if by simple assignment.
///
/// Cloning the dictionary takes time O(N), where N is the number of key-value pairs in the dictionary.
/// The cloned dictionary.
public MultiDictionary Clone()
{
return new MultiDictionary(allowDuplicateValues, keyEqualityComparer, valueEqualityComparer, equalityComparer,
hash.Clone(KeyAndValues.Copy));
}
///
/// Implements ICloneable.Clone. Makes a shallow clone of this dictionary; i.e., if keys or values are reference types, then they are not cloned.
///
/// The cloned dictionary.
object ICloneable.Clone()
{
return Clone();
}
///
/// Throw an InvalidOperationException indicating that this type is not cloneable.
///
/// Type to test.
private void NonCloneableType(Type t)
{
throw new InvalidOperationException(string.Format(Strings.TypeNotCloneable, t.FullName));
}
///
/// Makes a deep clone of this dictionary. A new dictionary is created with a clone of
/// each entry of this dictionary, by calling ICloneable.Clone on each element. If TKey or TValue is
/// a value type, then each element is copied as if by simple assignment.
///
/// If TKey or TValue is a reference type, it must implement
/// ICloneable. Otherwise, an InvalidOperationException is thrown.
/// Cloning the dictionary takes time O(N log N), where N is the number of key-value pairs in the dictionary.
/// The cloned dictionary.
/// TKey or TValue is a reference type that does not implement ICloneable.
public MultiDictionary CloneContents()
{
bool keyIsValueType, valueIsValueType;
// Make sure that TKey and TValue can be cloned.
if (!Util.IsCloneableType(typeof(TKey), out keyIsValueType))
NonCloneableType(typeof(TKey));
if (!Util.IsCloneableType(typeof(TValue), out valueIsValueType))
NonCloneableType(typeof(TValue));
// It's tempting to do a more efficient cloning, utilizing the hash.Clone() method. However, we can't know that
// the cloned version of the key has the same hash value.
MultiDictionary newDict = new MultiDictionary(allowDuplicateValues, keyEqualityComparer, valueEqualityComparer);
foreach (KeyAndValues item in hash) {
// Clone the key and values parts. Value types can be cloned
// by just copying them, otherwise, ICloneable is used.
TKey keyClone;
TValue[] valuesClone;
if (keyIsValueType)
keyClone = item.Key;
else {
if (item.Key == null)
keyClone = default(TKey); // Really null, because we know TKey isn't a value type.
else
keyClone = (TKey)(((ICloneable)item.Key).Clone());
}
valuesClone = new TValue[item.Count];
if (valueIsValueType)
Array.Copy(item.Values, valuesClone, item.Count);
else {
for (int i = 0; i < item.Count; ++i) {
if (item.Values[i] == null)
valuesClone[i] = default(TValue); // Really null, because we know TKey isn't a value type.
else
valuesClone[i] = (TValue)(((ICloneable)item.Values[i]).Clone());
}
}
newDict.AddMany(keyClone, valuesClone);
}
return newDict;
}
#endregion Cloning
}
}