Click here to Skip to main content
15,886,362 members
Articles / Programming Languages / C#
Article

Another C# sets this time generic ones

Rate me:
Please Sign up or sign in to vote.
2.77/5 (10 votes)
19 Feb 2006CPOL2 min read 35K   138   15   8
How to easily implement generic sets within C# for .NET Framework 2.0

Introduction

Hi everyone who's interested in what I hope is a fully functional "Delphi" sets implementation.

Background

I really needed some decent sets in C# and I came across the two very fine articles here. But those didn't satisfied me so I've written my own generic sets.. This time I hope it's a definite version.

Let's start somewhere

With my special kind of arrogance let's implement the class here: (Of course you can namespace it depending on your taste but I found it very practical this way :)

C#
using System;
using System.Collections;
using System.Collections.Generic;

namespace System.Collections.Generic
{
    public class Set<T> : IEnumerable<T>
    {

Now we'll need something to store "set" values in:

C#
private Dictionary<T, T> fItems = 
               new Dictionary<T, T>();

That's nice but there's more to make a functional sets. For example override C# operators to get all the comfort we deserve (and which Microsoft denied us).

So let's take one after another:

Somewhat this way may our "include element into set" operator be implemented:

C#
public static Set<T> operator +(Set<T> pSource, T pElement)
{
    try
    {
        Set<T> result = new Set<T>();
        result = (Set<T>)pSource.MemberwiseClone();
        result.fItems[pElement] = default(T);
        return result;
    }
    catch (Exception E)
    {
        throw new Exception("Set<T> - INCLUDE" + 
              " operation : Error while" + 
              " including element into set", E);
    }
}

Do you think that this is not a C# language ? Do you think that this not a C# code? And it will never compile? Well I thought that myself for the first time.

Let's return to our code. Now we possible want to implement an "exclude" operator (which is similar):

C#
public static Set<T> operator -(Set<T> pSource, T pElement)
{
    try
    {
        Set<T> result = new Set<T>();
        result = (Set<T>)pSource.MemberwiseClone();
        pSource.fItems.Remove(pElement);
        return result;
    }
    catch (Exception E)
    {
        throw new Exception("Set<T> - EXCLUDE" + 
              " operation : Error while" + 
              " excluding element from set", E);
    }
}

Ofcourse it wouldn't be sets without "contains" operator and it's counterpart "not contains" one:

C#
public static Boolean operator ==(Set<T> pSource, T pElement)
{
    return pSource.fItems.ContainsKey(pElement);
}
   
public static Boolean operator !=(Set<T> pSource, T pElement)
{
    return !(pSource == pElement);
}

You might say that those are easy then I have to tell you.. yeah they really are. Ok. Now for some real code here. Because we still need set operators. And here they go. As a first one an "union" operator:

C#
public static Set<T> operator +(Set<T> pSource, 
                                   Set<T> pDestiny)
{
     Set<T> result = new Set<T>();
     IEnumerator<T> listEnum = null;

     try
     {
         listEnum = pSource.fItems.Keys.GetEnumerator();
         listEnum.Reset();


         while (listEnum.MoveNext())
         {
             result.fItems[listEnum.Current] = default(T);
         }
     }
     catch (Exception E)
     {
         throw new Exception("Set<T> -" + 
               " UNION operation : Error while" + 
               " joining first set", E);
     }
     
     try
     {
         listEnum = pDestiny.fItems.Keys.GetEnumerator();
         listEnum.Reset();

         while (listEnum.MoveNext())
         {
             if (!pSource.fItems.ContainsKey(listEnum.Current))
             {
                 result.fItems[listEnum.Current] = default(T);
             }
         }
     }
     catch (Exception E)
     {
         throw new Exception("Set<T> - UNION" + 
               " operation : Error while" + 
               " joining second set", E);
     }
     
     return result;
}

And here goes.. yeah it's a "difference" operator:

C#
public static Set<T> operator -(Set<T> pSource, 
                                   Set<T> pDestiny)
 {
     Set<T> result = new Set<T>();
     IEnumerator<T> listEnum = null;
     
     try
     {
         listEnum = pSource.fItems.Keys.GetEnumerator();
         listEnum.Reset();
         
         while (listEnum.MoveNext())
         {
             if (!pDestiny.fItems.ContainsKey(listEnum.Current))
             {
                 result.fItems[listEnum.Current] = default(T);
             }
         }
     }
     catch (Exception E)
     {
         throw new Exception("Set<T> - DIFFERENCE" + 
               " operation : Error while forward" + 
               " comparing two sets for difference", E);
     }
     
     try
     {
         listEnum = pDestiny.fItems.Keys.GetEnumerator();
         listEnum.Reset();
         
         while (listEnum.MoveNext())
         {
             if (!pSource.fItems.ContainsKey(listEnum.Current))
             {
                 result.fItems[listEnum.Current] = default(T);
             }
         }
     }
     catch (Exception E)
     {
         throw new Exception("Set<T> - DIFFERENCE" + 
               " operation : Error while backward " + 
               "comparing two sets for difference", E);
     }
     
     return result;
}

and that last but not in a way the least an "intersection" operator:

C#
public static Set<T> operator *(Set<T> pSource, 
                                   Set<T> pDestiny)
{
     Set<T> result = new Set<T>();
     IEnumerator<T> listEnum = null;
     
     try
     {
         listEnum = pSource.fItems.Keys.GetEnumerator();
         listEnum.Reset();
       
         while (listEnum.MoveNext())
         {
             if (pDestiny.fItems.ContainsKey(listEnum.Current))
             {
                 result.fItems[listEnum.Current] = default(T);
             }
         }
     }
     catch (Exception E)
     {
         throw new Exception("Set<T> - INTERSECTION" + 
               " operation : Error while comparing " + 
               "two sets for intersection", E);
     }
  
     return result;
}

Now for some cream

Well we have the basics done and now.. that you're so good audience I'll give you some more :P One thing I've found really nice is indexing the set because sometimes you just want to know what elements are in the set and it's boring to go thru all the elements and test them for being inside the set. You all surely know where I'm heading. So now we're ready to implement an indexer:

C#
public T this[int ItemIndex]
{
     get
     {
         if (ItemIndex < fItems.Keys.Count)
         {
             IEnumerator<T> listEnum = fItems.Keys.GetEnumerator();
             listEnum.Reset();
             
             for (int a = 0; a < fItems.Keys.Count; a++)
             {
                 if (a == ItemIndex)
                 {
                     return listEnum.Current;
                 }
                 listEnum.MoveNext();
             }
         }
      
         return default(T);
     } 
}

Another good thing is to have some intelligent constructors around when working with the classes. These'd do it:

C#
public static Set<T> Empty()
{
     return new Set<T>();
}

public static Set<T> Define(params T[] pElements)
{
     Set<T> result = new Set<T>();
    
     foreach (T element in pElements)
     {
         result += element;
     }
 
     return result;
}

OK, mom, I'll do it!

So we have successfully added some operators and some specials and what's next.. Well not to lie you it's all. But we still have to implement some minor "may be/have to be" things. Such as the implementation of an interface we've declared in the beginning:

C#
public IEnumerator<T> GetEnumerator()
{
     return fItems.Keys.GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
     return fItems.Keys.GetEnumerator();
}

And ofcourse it's a good habit to override some of the basal methods. Because we don't want the warnings to fly everywhere around:

C#
public override bool Equals(object obj)
{
     return (this == (Set<T>)obj);
}

public override int GetHashCode()
{
     return base.GetHashCode();
}

public override string ToString()
{
     String result = string.Empty;
     IEnumerator<T> listEnum = null;
     
     try
     {
         listEnum = fItems.Keys.GetEnumerator();
         listEnum.Reset();
    
         while (listEnum.MoveNext())
         {
             if (result == string.Empty)
             {
                 result = listEnum.Current.ToString();
             }
             else
             {
                 result += "," + listEnum.Current.ToString();
             }
         }
        
         return ("{" + result + "}");
     }
     catch (Exception E)
     {
         throw new Exception("Set<T> - TOSTRING" + 
               " operation : Error while exporting" + 
               " set to string", E);
     }
}

Something's still missing

Let's take a look at some examples of how these little beast can be used:

C#
// our testing sets make your Set<YourType>

Set<MyEnum> first = 
      Set<MyEnum>.Define(new MyEnum[] 
      { MyEnum.One, MyEnum.Two });
Set<MyEnum> second = 
      Set<MyEnum>.Define(new MyEnum[] 
      { MyEnum.Two, MyEnum.Three });
Set<MyEnum> result = null;

// flag of containment

Boolean contains = false;
result = first + second; // UNION

// now the result contains {One, Two, Three}
result = first - second; // DIFFERENCE

// now the result contains {One, Three}
result = first * second; // INTERSECTION

// now the result contains {Two} 
contains = first == MyEnum.Three;

// contains is now FALSE because
// it doesn't contain {Three}
 
first += MyEnum.Three; // INCLUDE
contains = first == MyEnum.Three;

// contains is now TRUE because
// we added {Three} before

first -= MyEnum.Three;
contains = first == MyEnum.Three;

// contains is now FALSE again because
// we deleted {Three} element

That's all for today I hope you'll find these sets practical for you or maybe they'll inspire you to make some better ones. I'll be checking the forum :))

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Czech Republic Czech Republic
Contacts: EMAIL - smartk8@gmail.com

Comments and Discussions

 
GeneralGreat Article Pin
Samir NIGAM8-May-08 19:25
Samir NIGAM8-May-08 19:25 
GeneralRe: Great Article Pin
Smart K88-May-08 20:14
professionalSmart K88-May-08 20:14 
Thanks a lot. Wink | ;)

regards,
Kate

The wisdom is to see things truthfully.

QuestionAny Benchmark Pin
ozkan.pakdil14-Oct-07 0:18
ozkan.pakdil14-Oct-07 0:18 
AnswerRe: Any Benchmark Pin
Smart K814-Oct-07 0:42
professionalSmart K814-Oct-07 0:42 
GeneralProblem with Difference Operator Pin
Starkman26-Apr-06 10:32
Starkman26-Apr-06 10:32 
GeneralRe: Problem with Difference Operator Pin
Smart K828-Apr-06 20:02
professionalSmart K828-Apr-06 20:02 
GeneralImprovements Pin
Shaun Wilde19-Feb-06 20:46
Shaun Wilde19-Feb-06 20:46 
GeneralRe: Improvements Pin
Smart K819-Feb-06 21:15
professionalSmart K819-Feb-06 21:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.