|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionIn this article I'm going to talk about set collections and an implementation of them I have written in C#. BackgroundI've been programming for over 30 years, and for the last 20 I've used either C, C++, JavaScript or Delphi. So when C# was first proposed my initial reaction was “Oh no! Not another C-based language”. However, as I studied the implementation of C# I was impressed by the work Microsoft had put into making an elegant and unified programming system. That was until I saw this: [Flags]
public enum FontStyle
{
Regular = 0,
Bold = 1,
Italic = 2,
Underline = 4,
Strikeout = 8,
}
Now to me this looks like a hangover from the days when C was used as a kind of ‘super assembler’. To fully make sense of it you have got to understand binary notation, i.e. that 1 is in the lowest bit position, two in the next and so on. And you have to realise that the values must all be powers of two for the flags to work correctly. Now here’s the equivalent definition from Delphi: TFontStyle = (fsBold, fsItalic, fsUnderline, fsStrikeOut);
TFontStyles = set of TFontStyle;
I think this is much more descriptive, and does not force the compiler into a particular mapping from the concept to the hardware. You will see ‘set of’ used extensively in a Delphi program. Using sets in Delphi is also much easier. Suppose you wanted to test that a particular font style included bold. Here is how you would do it in C#: if ((fontStyles & FontStyle.Bold) == FontStyle.Bold)
Whereas the corresponding construct in Delphi is much clearer: if fsBold in fontStyles then
And here is how you would test that a particular character is alpha-numeric in Delphi: if char in ['A'..'Z','a'..'z','0'..'9'] then
Now I want to do all my future developments in C# as it seems to have a lot of momentum behind it, but I missed those Delphi sets, so I decided to implement my own set classes for C#. Existing Implementations of Set ClassesBut before writing a line of code, it’s always a good idea to see if someone has already done the job for you, so I did some Googling for possible existing implementations. I found several.
Each of these has its merits, but none of them seemed to support all the features I wanted. Briefly these were as follows:
The implementation by Jason Smith came closest to what I wanted, so I decided to use that as my starting point. In particular he defines a generic interface – An ApologyThis is some of the first code I have written in C#, so there may well be aspects that I could have done better. In particular I was not very concerned with efficiency of the code, just with getting something that worked. If you have any suggestions for improvements please let me know. Also, it has taken me two years to get this far – unfortunately I have a lot of other things on my plate at the moment. So for instance this was written using the 2005 version of C# Express, though I have tested it using the 2008 version. Using the CodeThe download contains the source for a single solution –
In order to build and run this solution you will also need the latest NUnit. It can be downloaded from here. Iesi.CollectionsThe primary concept I have borrowed from the
The KMB.Library.CollectionsThe download contains my library
For more detailed information on each of these classes please see the source. I will just give an overview here. Meeting the RequirementsUse Templates to Make Declaration as Simple as PossibleThe primary class – SetOf<int> x = new SetOf<int> ( new int[] {2,5,7,32,66} );
SetOf<string> y = new SetOf<string> ( new string[] {"apples","pears","bananas"} );
SetOf<FontStyle> fontStyle =
new SetOf<FontStyle>( new FontStyle[] {FontStyle.Bold, FontStyle.Italic} );
Have a Number of Classes Supporting the Same Interface, Tuned for Various ScenariosAll the classes implement the SetOfInts setOfInts = new SetOfInts();
setOfInts.SetRange(10, 20, true);
SetOf<string> setOfStrings =
new SetOf<string>(new string[] { "apples", "pears", "bananas" });
SetOfAll<FontStyle> allFontStyles = new SetOfAll<FontStyle>();
ISet unionSet = setOfInts | setOfStrings | allFontStyles;
Include an Empty Set, Sets for Values in a Range and Sets for all Values of a Given TypeThere is an empty set, which is useful in cases were you are combining sets and need to return ‘nothing’: public static ISet Intersect(ISet a, ISet b)
{
if (a.IsEmpty || b.IsEmpty)
return EmptySet.e;
else
return new IntersectSet(a, b);
}
There is a range set, SetBetween<string> set1 = new SetBetween<string>("excretion", "geometry");
Assert.IsFalse(set1.Contains("examination"), "set1 should not contain 'examination'");
Assert.IsTrue(set1.Contains("excretion"), "set1 should contain 'excretion'");
For integers, this set can return an enumerator for use in SetBetween<int> setBetween = new SetBetween<int>(10, 20);
int total = 0;
foreach(int i in setBetween)
{
total += i;
}
Assert.AreEqual(165, total);
There is also a class which includes all objects of a given type. This is particularly useful for enumerations: enum Colour { Red, Orange, Yellow, Green, Blue, Indigo, Violet };
SetOfAll<Colour> setOfAllColours = new SetOfAll<Colour>();
foreach(Colour clr in setOfAllColours )
{
:
Implement a Contains Method to Make Testing for Membership as Simple as PossibleSetOf<int> x;
x.Add(33);
bool b = false;
if( x.Contains(33) )
b = true;
Support all the Boolean OperatorsOperators are defined to perform the standard Boolean operations of union, intersection, minus and exclusive-or: SetOf<int> a = new SetOf<int>(new int[]{ 2, 5, 7, 32, 66 });
SetOf<int> b = new SetOf<int>(new int[]{ 3, 5, 8, 11 });
SetOf<int> c = a | b;
SetOf<int> d = a & b;
SetOf<int> e = a - b;
SetOf<int> f = a ^ b;
Allow Straightforward Iteration Through the MembersAll the sets support iteration through the members, via the SetOfInts set1 = new SetOfInts();
set1.SetRange(7, 11, true);
long n = 0;
foreach (int i in set1)
n += i;
Have an Efficient Implementation for the Common Cases of Sets of Integers or EnumerationsThe set of integers is defined as a wrapper around SetOfInts set1 = new SetOfInts();
set1.SetRange(-100000, +100000, true); // will take up about 50K
For small sets of integers you can use SetOfInts16 set1 = new SetOfInts16();
set1.SetRange(10, 13, true);
string s = set1.ToString();
Assert.AreEqual("{10,11,12,13}", s);
Enumerations Should be Handled Correctly, and Not just Treated as IntegersThe SetOf<Colour> setOfColours = new SetOf<Colour>();
setOfColours.Add(Colour.Red); // Red has the numeric value zero
Assert.IsFalse(setOfColours.Contains(FontStyle.Regular));
// Regular also has the value zero
// but is not in the set of colours
KMB.Library.TestsA set of unit tests have been provided for the library. Use these to get more information on using the classes. Points of InterestHaving implemented these set classes, I then speculated on how they could be more tightly integrated into the C# language. I came up with the following proposals (some of which I have passed on to the C# implementation team). Provide an "in" OperatorYou can currently use the if( 32 in x )…
Ideally this would work with any collection class, as the compiler could simply translate the above into: if ( x.Contains(32) )
Define a Double Dot OperatorIt is often necessary to work with ranges of integers, so it would be useful if C# also supported a double dot operator like Delphi. This can be used as a short hand way of defining a set of integers in a particular range. So for example {1..100} would be handled by the compiler as meaning SetOf<int> x = {1 .. 100};
Use Set Notation to Put Constraints on VariablesAgain this would overload the use of int age in {1..120};
Attempting to assign an invalid value to this variable would cause an exception to be thrown. age = 42; // OK
age = -5; // throws exception
Function to Obtain the Domain of a VariableA ISet s = domainof(age); // would return the set of
// ints between 1 and 120
foreach (int i in s )
{
:
}
History
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||