24 June 2010

In my blog "Initialising Dynamic Arrays" I mentioned I have been writing some unit tests that needed numerous repetative array initialisation operations. Well, one of the other common operations I found myself writing was the deletion of items from dynamic arrays. So I decided to find a generic way of doing this.

Assuming we know the index of the item to be deleted we need the equivalent of Delphi's built-in Delete procedure for strings. If you read the previous post you won't be surprised to learn that I decided to further extend the Generics.Collections.TArray class by adding to the TArrayEx class I introduced there.

My first attempt emulates Delphi's Delete procedure by deleting the array element in-place: i.e. it updates the array passed as a parameter. Unsurprisingly the method is called DeleteInPlace. Here's how it goes:

This uses a common algorithm. It just copies all array elements above the index of the deleted item down one place then shrinks the array by one element.

Next I decided it would also be useful to be able to construct a new array that contained a copy of a given array with the specified element removed. Continuing my startlingly inspired naming plan I called this method Delete. Here it is:

Noticing how similar these functions were - only the first line differs - I decided to try a generic approach. Since I didn't want to parameterise both the type of the array (e.g. the TIntegerDynArray return value) and the array element (e.g. Integer) I first decided to change the return values of the functions to use the TArray<T> type defined in the System unit. So the function prototypes got changed to

functionCloneArray(constA:arrayofInteger):TArray<Integer>;overload;

and

functionCloneArray(constA:arrayofstring):TArray<string>;overload;

The implementation of the routines did not need changing. It seemed clear how to parameterise the routines: just replace the Integer or string types with T, like this:

functionCloneArray<T>(constA:arrayofT):TArray<T>;

Wrong! At this point I learned that you can't parameterise global routines, as my blog entry "Being dim #2: No generic global procedures" explains. I needed to make the CloneArray routine a class method of some suitable class. Rather than create a new class I cast around and decided to extend the TArray class defined in the Generics.Collections unit.

Don't confuse Generics.Collections.TArray with System.TArray<T> - the first is a class, the second a generic dynamic array type.

CloneArray assumes that straighforward assignment of array elements is what we want. For example if the array elements are objects, just the object pointer is copied. If you need the object's fields to be physically copied this simplistic approach won't work.

I'll close this post with an example of use. Here's a code fragment that initialises string and integer arrays:

22 June 2010

The other day I was writing some unit tests and needed some helper functions to look up some array elements of in arrays of different base types. I had several overloaded functions to do the job, like this:

One of my favourite additions to Delphi over the past years has been the for..in construct and the associated enumerators. I just love the way we can do

varCh:Char;S:string;beginS:='Some text';forChinSdo// Do something with Chend;

So how come I missed that enumerators work on sets and arrays? I'm mentioning this here just in case it's slipped past anyone else, and in case you can tell me about any other enumerators I might have missed!

Since forever I've being having to do stuff like this for arrays and sets:

constcMyArray:array[1..4]ofByte=(51,60,80,81);cMySet:setofByte=[50,51,80,81,40,30,32];varI:Integer;B:Byte;beginforB:=Low(Byte)toHigh(Byte)doifBincMySetthen// Do stuff with BforI:=Low(cMyArray)toHigh(cMyArray)do// Do stuff my cMyArray[I]end;

When all along I could have been doing:

constcMyArray:array[1..4]ofByte=(51,60,80,81);cMySet:setofByte=[50,51,80,81,40,30,32];varItem:Byte;beginforItemincMySetdo// Do stuff with ItemforItemincMyArraydo// Do stuff with Itemend;