Click here to Skip to main content
15,881,803 members
Articles / Programming Languages / C#

Add Support for "Set" Collections to .NET

Rate me:
Please Sign up or sign in to vote.
4.76/5 (83 votes)
28 Mar 20047 min read 469.8K   6.6K   178  
An implementation of "Sets" for .NET
/*Copyright 2002 by Insight Enterprise Systems, Inc., and Jason Smith*/
using System;
using System.Collections;
using System.Collections.Specialized;

namespace Iesi.Collections
{
	/// <summary>
	/// Contains a collection of static methods for creating sets and for
	/// arithmetic operations on sets.
	/// </summary>
	public class Sets
	{
		private Sets(){}

		/// <summary>
		/// Create a new <c>GenericSet</c> based on a <c>Hashtable</c>.
		/// </summary>
		/// <returns>An empty <c>GenericSet</c>.</returns>
		public static GenericSet NewHashSet()
		{
			return new GenericSet(new Hashtable());
		}

		/// <summary>
		/// Create a new <c>GenericSet</c> based on a <c>SortedList</c>.
		/// </summary>
		/// <returns>An empty <c>GenericSet</c>.</returns>
		public static GenericSet NewSortedSet()
		{
			return new GenericSet(new SortedList());
		}

		/// <summary>
		/// Create a new <c>GenericSet</c> based on a <c>HybridDictionary</c>.
		/// </summary>
		/// <returns>An empty <c>GenericSet</c>.</returns>
		public static GenericSet NewHybridSet()
		{
			return new GenericSet(new HybridDictionary());
		}

		/// <summary>
		/// Create a new <c>GenericSet</c> based on a <c>ListDictionary</c>.
		/// </summary>
		/// <returns>An empty <c>GenericSet</c>.</returns>
		public static GenericSet NewListSet()
		{
			return new GenericSet(new ListDictionary());
		}

		/// <summary>
		/// Create a new <c>GenericSet</c> based on a <c>Hashtable</c>.
		/// </summary>
		/// <returns>An initialized <c>GenericSet</c>.</returns>
		public static GenericSet NewHashSet(ICollection initialValues)
		{
			return new GenericSet(new Hashtable(), initialValues);
		}

		/// <summary>
		/// Create a new <c>GenericSet</c> based on a <c>SortedList</c>.
		/// </summary>
		/// <returns>An initialized <c>GenericSet</c>.</returns>
		public static GenericSet NewSortedSet(ICollection initialValues)
		{
			return new GenericSet(new SortedList(), initialValues);
		}

		/// <summary>
		/// Create a new <c>GenericSet</c> based on a <c>HybridDictionary</c>.
		/// </summary>
		/// <returns>An initialized <c>GenericSet</c>.</returns>
		public static GenericSet NewHybridSet(ICollection initialValues)
		{
			return new GenericSet(new HybridDictionary(), initialValues);
		}

		/// <summary>
		/// Create a new <c>GenericSet</c> based on a <c>ListDictionary</c>.  Note that 
		/// this type of set should only be used if you know you will never have more than
		/// 10 elements, or thereabouts.  Some of the set operations can exceed O(n^2)
		/// running time.  
		/// </summary>
		/// <returns>An initialized <c>GenericSet</c>.</returns>
		public static GenericSet NewListSet(ICollection initialValues)
		{
			return new GenericSet(new ListDictionary(), initialValues);
		}

		/// <summary>
		/// Returns a set containing all the elements from both sets.
		/// </summary>
		/// <param name="a">A set of elements.</param>
		/// <param name="b">A set of elements.</param>
		/// <returns>A set containing the union of the input sets.</returns>
		public static ISet Union(ISet a, ISet b)
		{
			ISet result = NewHybridSet(a);
			result.AddAll(b);
			return result;
		}

		/// <summary>
		/// Returns a set containing all the elements common to both sets.
		/// </summary>
		/// <param name="a">A set of elements.</param>
		/// <param name="b">A set of elements.</param>
		/// <returns>The intersection of the two input sets.</returns>
		public static ISet Intersect(ISet a, ISet b)
		{
			ISet result = NewHybridSet(a);
			result.RetainAll(b);
			return result;
		}

		/// <summary>
		/// Returns a set containing all the elements from <c>A</c> with all the elements from
		/// <c>B</c> removed.
		/// </summary>
		/// <param name="a">A set of elements.</param>
		/// <param name="b">A set of elements.</param>
		/// <returns>A set containing <c>A - B</c> elements.</returns>
		public static ISet Minus(ISet a, ISet b)
		{
			ISet result = NewHybridSet(a);
			result.RemoveAll(b);
			return result;
		}
	}
	
	/// <summary>
	/// <p><c>GenericSet</c> is an implementation of <c>ISet</c> which uses an <c>IDictionary</c> object
	/// to implement set functionality.</p> 
	///  
	/// <p>You can use any object that implements the <c>IDictionary</c> interface to hold set data.
	/// You can define your own, or you can use one of the objects provided in the Framework.  There are
	/// a number of static methods on the <c>Sets</c> <see cref="Iesi.Collections.Sets"/> object that will create a new <c>GenericSet</c> based on 
	/// the <c>IDictionary</c> of your choice.  There are a number of things you need to take into consideration 
	/// when choosing an implementation:</p>
	/// <list type="bullet">
	///     <item>
	///         <description><c>Hashtable</c>: Good (very fast) for large sets where enumeration order is not important.</description>
	///     </item>
	///     <item>
	///         <description><c>SortedList</c>: Good for large sets.  Enumerates in sort order.  A little slower
	///         than <c>Hashtable</c> for very large datasets, but this effect is usually not noticable.</description>
	///     </item>
	///     <item>
	///         <description><c>ListDictionary</c>: Good if you have lots of very small sets, since this
	///         type of <c>IDictionary</c> does not incur as much overhead.  Watch out though!  If the sets
	///         get beyond about 10 items each, running time on some set functions will increase by
	///         O(n^2).</description>
	///     </item>
	///     <item>
	///         <description><c>HybridDictionary</c>: If you have lots of small sets, but are unsure about the
	///         maximum size you need, use this instead of <c>ListDictionary</c>.  If the set is small, it will
	///         use a <c>ListDictionary</c>, but if the set grows too large it will use a <c>Hashtable</c>
	///         instead.</description>
	///     </item>
	/// </list>
	/// <p>This object overrides both the <c>Equals()</c> and <c>GetHashCode()</c> object methods, so it
	/// is safe to use as a key value in a dictionary that does not rely on keys, as long as all the elements
	/// contained in the set implement <c>Equals()</c> and <c>GetHashCode()</c> as well.</p>
	/// </summary>
	public class GenericSet : ISet
	{
		//I'm using XOR to implement the hashcode.  XOR when you add, XOR when you remove.
		//As long as the hashcodes of all included objects are value based, it works.
		private int         _hashcode = unchecked((int)0xDEADBEEF);
		private IDictionary _set;
		
		private static object _object = new object();
		
		/// <summary>
		/// Creates a new <c>GenericSet</c> based on a <c>HybridDictionary</c>.
		/// </summary>
		public GenericSet() : this(new HybridDictionary())
		{}

		
		/// <summary>
		/// Creates a new <c>GenericSet</c> based on the <c>IDictionary</c> of your choice.
		/// </summary>
		/// <param name="baseDictionary">An object that implements <c>IDictionary</c> which will contain the set data.</param>
		public GenericSet(IDictionary baseDictionary)
		{
			_set = baseDictionary;
		}

		/// <summary>
		/// Creates a new <c>GenericSet</c> based on the <c>IDictionary</c> of your choice, and
		/// initializes it based on a collection of elements.
		/// </summary>
		/// <param name="baseDictionary">An object that implements <c>IDictionary</c> which will contain the set data.</param>
		/// <param name="initialValues">A collection of elements that defines the initial set contents.</param>
		public GenericSet(IDictionary baseDictionary, ICollection initialValues) : this(baseDictionary)
		{
			this.AddAll(initialValues);
		}


		/// <summary>
		/// Adds the specified element to this set if it is not already present.
		/// </summary>
		/// <param name="o">The object to add to the set.</param>
		/// <returns><c>true</c> is the object was added, <c>false</c> if it was already present.</returns>
		public bool Add(object o)
		{
			if(_set[o] != null)
				return false;
			else
			{
				//The object we are adding is just a placeholder.  The thing we are
				//really concerned with is 'o', the key.
				_set.Add(o, _object);
				_hashcode ^= o.GetHashCode();
				return true;
			}
		}

		/// <summary>
		/// Adds all the elements in the specified collection to the set if they are not already present.
		/// </summary>
		/// <param name="c">A collection of objects to add to the set.</param>
		/// <returns><c>true</c> is the set changed as a result of this operation, <c>false</c> if not.</returns>
		public bool AddAll(ICollection c)
		{
			bool changed = false;
			foreach(object o in c)
				changed |= this.Add(o);
			return changed;
		}

		/// <summary>
		/// Removes all objects from the set.
		/// </summary>
		public void Clear()
		{
			_set.Clear();
			_hashcode = unchecked((int)0xDEADBEEF);
		}

		/// <summary>
		/// Returns <c>true</c> if this set contains the specified element.
		/// </summary>
		/// <param name="o">The element to look for.</param>
		/// <returns><c>true</c> if this set contains the specified element, <c>false</c> otherwise.</returns>
		public bool Contains(object o)
		{
			return _set[o] != null;
		}

		/// <summary>
		/// Returns <c>true</c> if the set contains all the elements in the specified collection.
		/// </summary>
		/// <param name="c">A collection of objects.</param>
		/// <returns><c>true</c> if the set contains all the elements in the specified collection, <c>false</c> otherwise.</returns>
		public bool ContainsAll(ICollection c)
		{
			foreach(object o in c)
			{
				if(!this.Contains(o))
					return false;
			}
			return true;
		}

		/// <summary>
		/// Returns <c>true</c> if this set contains no elements.
		/// </summary>
		public bool IsEmpty
		{
			get{return _set.Count == 0;}
		}

		/// <summary>
		/// Removes the specified element from the set.
		/// </summary>
		/// <param name="o">The element to be removed.</param>
		/// <returns><c>true</c> if the set contained the specified element, <c>false</c> otherwise.</returns>
		public bool Remove(object o)
		{
			bool contained = this.Contains(o);
			if(contained)
			{
				_hashcode ^= o.GetHashCode();
				_set.Remove(o);
			}
			return contained;
		}

		/// <summary>
		/// Remove all the specified elements from this set, if they exist in this set.
		/// </summary>
		/// <param name="c">A collection of elements to remove.</param>
		/// <returns><c>true</c> if the set was modified as a result of this operation.</returns>
		public bool RemoveAll(ICollection c)
		{
			bool changed = false;
			foreach(object o in c)
				changed |= this.Remove(o);
			return changed;
		}

		/// <summary>
		/// Retains only the elements in this set that are contained in the specified collection.
		/// </summary>
		/// <param name="c">Collection that defines the set of elements to be retained.</param>
		/// <returns><c>true</c> if this set changed as a result of this operation.</returns>
		public bool RetainAll(ICollection c)
		{
			//Put data from C into a set so we can use the Contains() method.
			ISet cSet = Sets.NewHashSet(c);

			//We are going to build a set of elements to remove.
			ISet removeSet = Sets.NewHashSet();
			
			foreach(object o in this)
			{
				//If C does not contain O, then we need to remove O from our
				//set.  We can't do this while iterating through our set, so
				//we put it into RemoveSet for later.
				if(!cSet.Contains(o))
					removeSet.Add(o);
			}

			return this.RemoveAll(removeSet);
		}



		public void CopyTo(Array array, int index)
		{
			_set.Keys.CopyTo(array, index);
		}

		public int Count
		{
			get{return _set.Count;}		
		}

		public bool IsSynchronized
		{
			get{return false;}
		}

		public object SyncRoot
		{
			get{return null;}
		}

		public IEnumerator GetEnumerator()
		{
			return _set.Keys.GetEnumerator();
		}

		public override bool Equals(object o)
		{
			if(o == null || !(o is ISet) || ((ISet)o).Count != this.Count)
				return false;
			else 
			{
				foreach(object elt in ((ISet)o))
				{
					if(!this.Contains(elt))
						return false;
				}
				return true;
			}
		}

		public override int GetHashCode()
		{
			return _hashcode;
		}
	}

	/// <summary>
	/// A collection that contains no duplicate elements.  This interface models the mathematical
	/// <c>Set</c> abstraction.
	/// </summary>
	public interface ISet : ICollection
	{
		/// <summary>
		/// Adds the specified element to this set if it is not already present.
		/// </summary>
		/// <param name="o">The object to add to the set.</param>
		/// <returns><c>true</c> is the object was added, <c>false</c> if it was already present.</returns>
		bool Add(object o);

		/// <summary>
		/// Adds all the elements in the specified collection to the set if they are not already present.
		/// </summary>
		/// <param name="c">A collection of objects to add to the set.</param>
		/// <returns><c>true</c> is the set changed as a result of this operation, <c>false</c> if not.</returns>
		bool AddAll(ICollection c);

		/// <summary>
		/// Removes all objects from the set.
		/// </summary>
		void Clear();

		/// <summary>
		/// Returns <c>true</c> if this set contains the specified element.
		/// </summary>
		/// <param name="o">The element to look for.</param>
		/// <returns><c>true</c> if this set contains the specified element, <c>false</c> otherwise.</returns>
		bool Contains(object o);

		/// <summary>
		/// Returns <c>true</c> if the set contains all the elements in the specified collection.
		/// </summary>
		/// <param name="c">A collection of objects.</param>
		/// <returns><c>true</c> if the set contains all the elements in the specified collection, <c>false</c> otherwise.</returns>
		bool ContainsAll(ICollection c);

		/// <summary>
		/// Returns <c>true</c> if this set contains no elements.
		/// </summary>
		bool IsEmpty {get;}

		/// <summary>
		/// Removes the specified element from the set.
		/// </summary>
		/// <param name="o">The element to be removed.</param>
		/// <returns><c>true</c> if the set contained the specified element, <c>false</c> otherwise.</returns>
		bool Remove(object o);

		/// <summary>
		/// Remove all the specified elements from this set, if they exist in this set.
		/// </summary>
		/// <param name="c">A collection of elements to remove.</param>
		/// <returns><c>true</c> if the set was modified as a result of this operation.</returns>
		bool RemoveAll(ICollection c);

		/// <summary>
		/// Retains only the elements in this set that are contained in the specified collection.
		/// </summary>
		/// <param name="c">Collection that defines the set of elements to be retained.</param>
		/// <returns><c>true</c> if this set changed as a result of this operation.</returns>
		bool RetainAll(ICollection c);
	}

}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions