using System;
using System.Collections.Generic;
using System.Linq;
namespace miew.Multimap
{
/// <summary>
/// Simple non-unique map wrapper
/// </summary>
/// <remarks>
/// ApplyResultSelector (from Lookup[TKey, TElement] is not implemented, since the caller could just
/// as easily (or more-so) use .Select() with a Func[IGrouping[TKey, TElement], TResult], since
/// IGrouping[TKey, TElement] already includes both the "TKey Key" and the IEnumerable[TElement].
/// </remarks>
public sealed class Multimap<TKey, TElement> : ILookup<TKey, TElement>
{
internal sealed class Grouping : List<TElement>, IGrouping<TKey, TElement>
{
#if DEBUG
readonly
#endif
private TKey key;
public TKey Key { get { return key; } }
public Grouping(TKey key)
{
this.key = key;
}
public Grouping(TKey key, IEnumerable<TElement> ie)
: base(ie)
{
this.key = key;
}
}
private readonly Dictionary<TKey, Grouping> groups;
/// <summary>
/// Creates a new EditableLookup using the default key-comparer
/// </summary>
public Multimap() : this(default(IEqualityComparer<TKey>)) { }
/// <summary>
/// Creates a new EditableLookup using the specified key-comparer
/// </summary>
/// <param name="keyComparer"></param>
public Multimap(IEqualityComparer<TKey> keyComparer)
{
groups = new Dictionary<TKey, Grouping>(keyComparer ?? EqualityComparer<TKey>.Default);
}
/// <summary>
///
/// </summary>
/// <param name="keyComparer"></param>
public Multimap(ILookup<TKey, TElement> il)
{
groups = new Dictionary<TKey, Grouping>();
foreach (IGrouping<TKey, TElement> q in il)
groups.Add(q.Key, new Grouping(q.Key, q));
}
public IEnumerable<KeyValuePair<TKey, TElement>> RecoverKeys(IEnumerable<TElement> values)
{
Dictionary<TElement, TKey> rev = groups
.SelectMany(e => e.Value.Select(f => new { obj = f, k = e.Key }))
.ToDictionary(e => e.obj, e => e.k);
foreach (TElement e in values)
{
TKey recover_key;
if (rev.TryGetValue(e, out recover_key))
yield return new KeyValuePair<TKey, TElement>(recover_key, e);
}
}
/// <summary>
///
/// </summary>
public Multimap(IEnumerable<TElement> ie, Func<TElement, TKey> keySelector)
{
groups = new Dictionary<TKey, Grouping>();
foreach (TElement e in ie)
this.Add(keySelector(e), e);
}
public IEnumerable<TKey> Keys { get { return groups.Keys; } }
/// <summary>
/// Does the lookup contain any value(s) for the given key?
/// </summary>
public bool Contains(TKey key)
{
Grouping group;
return groups.TryGetValue(key, out group) ? group.Count > 0 : false;
}
/// <summary>
/// Does the lookup the specific key/value pair?
/// </summary>
public bool Contains(TKey key, TElement value)
{
Grouping group;
return groups.TryGetValue(key, out group) ? group.Contains(value) : false;
}
/// <summary>
/// Adds a key/value pair to the lookup
/// </summary>
/// <remarks>If the value is already present it will be duplicated</remarks>
public void Add(TKey key, TElement value)
{
Grouping group;
if (!groups.TryGetValue(key, out group))
{
group = new Grouping(key);
groups.Add(key, group);
}
group.Add(value);
}
/// <summary>
/// Adds a range of values against a single key
/// </summary>
/// <remarks>Any values already present will be duplicated</remarks>
public void AddRange(TKey key, IEnumerable<TElement> values)
{
Grouping group;
if (!groups.TryGetValue(key, out group))
{
group = new Grouping(key);
groups.Add(key, group);
}
foreach (TElement value in values)
{
group.Add(value);
}
if (group.Count == 0)
{
groups.Remove(key); // nothing there after all!
}
}
/// <summary>
/// Add all key/value pairs from the supplied lookup to the current lookup
/// </summary>
/// <remarks>Any values already present will be duplicated</remarks>
public void AddRange(ILookup<TKey, TElement> lookup)
{
foreach (IGrouping<TKey, TElement> group in lookup)
{
AddRange(group.Key, group);
}
}
/// <summary>
/// Remove all values from the lookup for the given key
/// </summary>
/// <returns>True if any items were removed, else false</returns>
public bool Remove(TKey key)
{
return groups.Remove(key);
}
/// <summary>
/// Remove the specific key/value pair from the lookup
/// </summary>
/// <returns>True if the item was found, else false</returns>
public bool Remove(TKey key, TElement value)
{
Grouping group;
if (groups.TryGetValue(key, out group))
{
bool removed = group.Remove(value);
if (removed && group.Count == 0)
{
groups.Remove(key);
}
return removed;
}
return false;
}
/// <summary>
/// Trims the inner data-structure to remove any surplus space
/// </summary>
public void TrimExcess()
{
foreach (var group in groups.Values)
{
group.TrimExcess();
}
}
/// <summary>
/// Returns the number of dictinct keys in the lookup
/// </summary>
public int Count
{
get { return groups.Count; }
}
private static readonly IEnumerable<TElement> arr_telement_empty = new TElement[0];
/// <summary>
/// Returns the set of values for the given key
/// </summary>
public IEnumerable<TElement> this[TKey key]
{
get
{
Grouping group;
if (groups.TryGetValue(key, out group))
return group;
return arr_telement_empty;
}
}
/// <summary>
/// Returns the sequence of keys and their contained values
/// </summary>
public IEnumerator<IGrouping<TKey, TElement>> GetEnumerator()
{
foreach (var group in groups.Values)
{
yield return group;
}
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
};
}