📄 multidictionary.cs
字号:
//******************************
// 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
{
/// <summary>
/// <para>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.</para>
/// <para>When constructed, you can chose to allow the same value to be associated with a key multiple
/// times, or only one time. </para>
/// </summary>
/// <typeparam name="TKey">The type of the keys.</typeparam>
/// <typeparam name="TValue">The of values associated with the keys.</typeparam>
///<seealso cref="Dictionary<TKey,TValue>"/>
///<seealso cref="OrderedMultiDictionary<TKey,TValue>"/>
[Serializable]
public class MultiDictionary<TKey, TValue> : MultiDictionaryBase<TKey, TValue>, ICloneable
{
// The comparer for comparing keys
private IEqualityComparer<TKey> keyEqualityComparer;
// The comparer for comparing values;
private IEqualityComparer<TValue> valueEqualityComparer;
// The comparer for compaing keys and values.
private IEqualityComparer<KeyAndValues> equalityComparer;
// The hash that holds the keys and values.
private Hash<KeyAndValues> hash;
// Whether duplicate values for the same key are allowed.
private bool allowDuplicateValues;
/// <summary>
/// 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.
/// </summary>
[Serializable]
private struct KeyAndValues
{
/// <summary>
/// The key.
/// </summary>
public TKey Key;
/// <summary>
/// The number of values. Always at least 1 except in a dummy version for lookups.
/// </summary>
public int Count;
/// <summary>
/// An array of values.
/// </summary>
public TValue[] Values;
/// <summary>
/// Create a dummy KeyAndValues with just the key, for lookups.
/// </summary>
/// <param name="key">The key to use.</param>
public KeyAndValues(TKey key)
{
this.Key = key;
this.Count = 0;
this.Values = null;
}
/// <summary>
/// Make a copy of a KeyAndValues, copying the array.
/// </summary>
/// <param name="x">KeyAndValues to copy.</param>
/// <returns>A copied version.</returns>
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;
}
}
/// <summary>
/// This class implements IEqualityComparer for KeysAndValues, allowing them to be
/// compared by their keys. An IEqualityComparer on keys is required.
/// </summary>
[Serializable]
private class KeyAndValuesEqualityComparer : IEqualityComparer<KeyAndValues>
{
private IEqualityComparer<TKey> keyEqualityComparer;
public KeyAndValuesEqualityComparer(IEqualityComparer<TKey> 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
/// <summary>
/// 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.
/// </summary>
/// <remarks>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.</remarks>
/// <param name="allowDuplicateValues">Can the same value be associated with a key multiple times?</param>
/// <exception cref="InvalidOperationException">TKey or TValue does not implement either IComparable<T> or IComparable.</exception>
public MultiDictionary(bool allowDuplicateValues)
: this(allowDuplicateValues, EqualityComparer<TKey>.Default, EqualityComparer<TValue>.Default)
{
}
/// <summary>
/// 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.
/// </summary>
/// <param name="allowDuplicateValues">Can the same value be associated with a key multiple times?</param>
/// <param name="keyEqualityComparer">An IEqualityComparer<TKey> instance that will be used to compare keys.</param>
/// <exception cref="InvalidOperationException">TValue does not implement either IComparable<TValue> or IComparable.</exception>
public MultiDictionary(bool allowDuplicateValues, IEqualityComparer<TKey> keyEqualityComparer)
: this(allowDuplicateValues, keyEqualityComparer, EqualityComparer<TValue>.Default)
{
}
/// <summary>
/// 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.
/// </summary>
/// <param name="allowDuplicateValues">Can the same value be associated with a key multiple times?</param>
/// <param name="keyEqualityComparer">An IEqualityComparer<TKey> instance that will be used to compare keys.</param>
/// <param name="valueEqualityComparer">An IEqualityComparer<TValue> instance that will be used to compare values.</param>
public MultiDictionary(bool allowDuplicateValues, IEqualityComparer<TKey> keyEqualityComparer, IEqualityComparer<TValue> 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<KeyAndValues>(equalityComparer);
}
/// <summary>
/// Create a new MultiDictionary. Private constructor, for use by Clone().
/// </summary>
private MultiDictionary(bool allowDuplicateValues, IEqualityComparer<TKey> keyEqualityComparer, IEqualityComparer<TValue> valueEqualityComparer, IEqualityComparer<KeyAndValues> equalityComparer, Hash<KeyAndValues> 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
/// <summary>
/// <para>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.</para>
/// <para>If duplicate values are not permitted, and <paramref name="key"/> already has a value
/// equal to <paramref name="value"/> associated with it, then that value is replaced with <paramref name="value"/>,
/// and the number of values associate with <paramref name="key"/> is unchanged.</para>
/// </summary>
/// <param name="key">The key to associate with.</param>
/// <param name="value">The value to associated with <paramref name="key"/>.</param>
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;
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="key">A key to remove a value from.</param>
/// <param name="value">The value to remove.</param>
/// <returns>True if <paramref name="value"/> was associated with <paramref name="key"/> (and was
/// therefore removed). False if <paramref name="value"/> was not associated with <paramref name="key"/>.</returns>
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 {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -