I needed a more robust dictionary-like structure to obtain:
- values by providing a key (default dictionary behavior);
- keys by providing a value (not so much trivial).
Also, I wanted to expand this dictionary-like structure's functionality so that a key could be associated with multiple values.
Through this discussion, this answer and this other one provided the tools to implement that. I decided to remove the "access by index" from the BiDictionary (Jon Skeet's answer) since the ambiguity would happen more often than I would like (for example, when mapping strings into strings). The dictionary-like "structure" that I came up with is:
using System.Collections.Generic;
public interface IBiLookup<TLeft, TRight>
{
IDictionary<TLeft, ICollection<TRight>> LeftToRight { get; }
IDictionary<TRight, ICollection<TLeft>> RightToLeft { get; }
bool TryGetByLeft(TLeft left, out ICollection<TRight> rights);
bool TryGetByRight(TRight right, out ICollection<TLeft> lefts);
void Add(TLeft left, TRight right);
}
public class BiLookup<TLeft, TRight> : IBiLookup<TLeft, TRight>
{
public IDictionary<TLeft, ICollection<TRight>> LeftToRight
{
get { return this.leftToRight; }
}
public IDictionary<TRight, ICollection<TLeft>> RightToLeft
{
get { return this.rightToLeft; }
}
public bool TryGetByLeft(TLeft left, out ICollection<TRight> rights)
{
return LeftToRight.TryGetValue(left, out rights);
}
public bool TryGetByRight(TRight right, out ICollection<TLeft> lefts)
{
return RightToLeft.TryGetValue(right, out lefts);
}
public void Add(TLeft left, TRight right)
{
AddLeftToRight(left, right);
AddRightToLeft(right, left);
}
private void AddLeftToRight(TLeft left, TRight right)
{
ICollection<TRight> rights;
// 1) Is there an entry associated with the "left" value?
// 2) If so, is the "right" value already associated?
if (!TryGetByLeft(left, out rights))
{
// Then we have to add an entry in the leftToRight dictionary.
rights = new List<TRight> { right };
}
else
{
// So there are entries associated with the "left" value.
// We must verify if the "right" value itself is not there.
if (((List<TRight>)rights).FindIndex(element => element.Equals(right)) < 0)
{
// We don't have that association yet.
rights.Add(right);
}
else
{
// The value is already in the list: do nothing.
return;
}
}
LeftToRight[left] = rights;
}
private void AddRightToLeft(TRight right, TLeft left)
{
ICollection<TLeft> lefts;
// 1) Is there an entry associated with the "right" value?
// 2) If so, is the "left" value already associated?
if (!TryGetByRight(right, out lefts))
{
// Then we have to add an entry in the leftToRight dictionary.
lefts = new List<TLeft> { left };
}
else
{
// So there are entries associated with the "right" value.
// We must verify if the "right" value itself is not there.
if (((List<TLeft>)lefts).FindIndex(element => element.Equals(left)) < 0)
{
// We don't have that association yet.
lefts.Add(left);
}
else
{
// The value is already in the list: do nothing.
return;
}
}
RightToLeft[right] = lefts;
}
#region Fields
private IDictionary<TLeft, ICollection<TRight>> leftToRight = new Dictionary<TLeft, ICollection<TRight>>();
private IDictionary<TRight, ICollection<TLeft>> rightToLeft = new Dictionary<TRight, ICollection<TLeft>>();
#endregion
}
I'm worried if splitting the Add(...) method into two, more specific methods, is a good implementation, since the BiLookup class would be extensively used and performance is something to keep in mind. Also, the purpose of this thread is to discuss if the interface and class implementations are as good as they could be, or what could be improved on them.
If I save both the Interface and the "deafult" implementation to a Class Library project, is the design good enough to be reused?