I was recently working on a .NET 3.0 project and found that this version of the framework didn't have any support for a mathematical
Set collection. As of .NET 3.5 there is the
System.Collections.Generic.HashSet implementation, however, I was working on a WinForms project and updating the framework on the client machines was out of scope for this piece of work.
I had some spare time available and thought it would be a good exercise to write a custom generic Set collection. The first step was to define an interface:
using System.Collections.Generic;
...
public interface ISet<T> : ICollection<T>
{
void UnionWith(ISet<T> set);
void IntersectWith(ISet<T> set);
}
By inheriting from
ICollection, we get all of the generally useful collection-based methods included in the ISet interface (e.g. Add, Clear, Contains etc.). I just had two methods to add to those, these were the
set union and
set intersection operations.
The next step was the implementation of the set. To keep things simple, I opted for a list-based implementation. I used the
adapter pattern to map the ISet interface to a list interface. Therefore, the internal representation of the Set was actually an instance of
System.Collections.Generic.List. By using this approach, I could delegate a lot of the method logic to use the list method implementations (i.e. Clear, Contains, CopyTo and Remove). The only custom logic I needed to write was the Add method (to avoid adding items already within the set), UnionWith method, IntersectWith method, ToString method and the readonly indexer. The implementation is below:
public sealed class ListBasedSet<T> : ISet<T>
{
private IList<T> _thisSet = new List<T>();
public int Count { get { return _thisSet.Count; } }
public bool IsReadOnly { get { return false; } }
public T this[int i] { get { return _thisSet[i]; } }
public void Add(T item)
{
if (!Contains(item))
_thisSet.Add(item);
}
public void Clear()
{
_thisSet.Clear();
}
public bool Contains(T item)
{
return _thisSet.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
_thisSet.CopyTo(array, arrayIndex);
}
public bool Remove(T item)
{
return _thisSet.Remove(item);
}
public IEnumerator<T> GetEnumerator()
{
return _thisSet.GetEnumerator();
}
System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator()
{
return _thisSet.GetEnumerator();
}
public void UnionWith(ISet<T> otherSet)
{
foreach (var item in otherSet)
Add(item);
}
public void IntersectWith(ISet<T> otherSet)
{
var itemsToRemove = new List<T>();
for (int i = 0; i < Count; i++)
{
T item = this[i];
if (!otherSet.Contains(item))
itemsToRemove.Add(item);
}
foreach (var itemToRemove in itemsToRemove)
Remove(itemToRemove);
}
public override string ToString()
{
var setStringBuilder = new StringBuilder();
setStringBuilder.Append("{");
foreach (var item in _thisSet)
setStringBuilder.Append(
string.Format(" {0},", item));
return string.Format("{0} }}",
setStringBuilder.ToString().TrimEnd(','));
}
}
Example usage:
ISet<string> set = new ListBasedSet<string>
{ "Mars", "Earth", "Pluto" };
ISet<string> otherSet = new ListBasedSet<string>
{ "Jupiter", "Earth", "Pluto", "Venus" };
ISet<string> earthSet = new ListBasedSet<string>
{ "Earth" };
ISet<string> emptySet = new ListBasedSet<string>();
set.UnionWith(otherSet);
Console.WriteLine("set UNION otherset = {0}", set);
Console.WriteLine("|set| (cardinality of set) = {0}", set.Count);
set.IntersectWith(earthSet);
Console.WriteLine("set INTERSECTION earthSet = {0}", set);
set.UnionWith(emptySet);
Console.WriteLine("set UNION emptySet = {0}", set);
set.IntersectWith(emptySet);
Console.WriteLine("set INTERSECTION emptySet = {0}", set);
You can download the source files for the ISet interface and the ListBasedSet class from
here.