Implementing Custom Binary Search for TObjectList<myClass> (Delphi XE) - delphi

I need to implement a binary search on TObjectList that uses a custom comparer, I believe using TCustomComparer.
Goal: binary search returns instances in the list that conform to a particular property parameter.
For example:
TMyClass=class
public
Index:integer
end;
TMyObjectList=TObjectList<TMyClass>;
begin
...
aMyClass.Index:=1;
aMyObjectList.binarysearch(aMyClass, aMyClassRef)
...
end;
Or simply:
begin
...
aMyObjectList.binarysearch(1, aMyClassRef)
...
end;
I want to loop and get back instances of TMyClass in the list that also have Index==1.
In C++, overloading the '==' operator achieves this goal.
The new Delphi 'help' is rather sparse and scattered around making things hard to find, and I'm not that familiar with all the nuances of the new Delphi generics.
So - how do I do it in Delphi XE with the Generics.TObjectList?
(Using Delphi XE).
TIA

The help file is indeed a little limited here. I generally just read the source code to Generics.Defaults and Generics.Collections. Anyway, you need to provide an IComparer<TMyClass>. There's lots of ways to do that. For example, using an anonymous function:
var
List: TObjectList<TMyClass>;
Target: TMyClass;
Index: Integer;
Comparer: IComparer<TMyClass>;
Comparison: TComparison<TMyClass>;
....
Comparison :=
function(const Left, Right: TMyClass): Integer
begin
//Result := ??;//your comparison rule goes here
end;
Comparer := TComparer<TMyClass>.Construct(Comparison);
List.BinarySearch(Target, Index, Comparer);
If you don't want to use an anonymous function you can implement Comparison some other way. For example a method of some object, or a class function, or even just a plain old fashioned non-OOP function. It just has to have the same signature as above.
As always with comparison functions, return <0 if Left<Right, >0 if Left>Right and 0 if Left=Right.

Related

How do I assign a pre-existing function to a TComparison<T>?

program Project55;
{$APPTYPE CONSOLE}
uses
System.Generics.Defaults;
type
TestRec<T> = record
Compare: TComparison<T>;
CompareI: IComparer<T>;
end;
var
TRI: TestRec<Integer>;
begin
TRI.CompareI:= TComparer<Integer>.Default;
TRI.Compare:= TRI.CompareI.Compare; //E2035 Not enough actual parameters
TRI.Compare:= #TRI.CompareI.Compare; //E2035 Not enough actual parameters
end.
I know I can assign the function body as an anonymous function, but why can't I assign an existing function?
Of course the following works, but that's just silly:
TRI.Compare:= function(const L,R: integer): Integer
begin
Result:= TRI.CompareI.Compare(L,R);
end;
PS. I'm using Delphi XE7, but I doubt the version matters.
Knowing that IComparer<T> is an interface with just one method that has the same signature as TComparison<T> and that anonymous methods are just interfaces with one method you can do the following.
IComparer<Integer>(TRI.Compare) := TRI.CompareI;
I am using that trick in Spring4D to avoid creating a wrapper object around a TComparison<T> to be passed as IComparer<T> because they are binary compatible.
Your attempts to perform this assignment fail because an interface method cannot be with assigned to a method reference variable. The language simply does not permit that. The types are not assignment compatible. Valid assignment sources are anonymous methods, methods of classes (instance or class) and unit scope procedures.
The tricks that can be seen in other answers all depend on in depth knowledge of the implementation details. Which means that they are subject to change. But in terms of the language, what you are attempting is not permitted.
Anonymous methods are not exactly method pointers. They are implemented as an interface with a single method "Invoke".
It is possible to extract a method pointer from an anonymous method, but as far as I know it relies on the current implementation details of anonymous method and could be subject to changes in future version of delphi. In other words, I would advise against it. This was taken verbatim from Barry Kelly's post here. (Which covers the topic more thoroughly than I do here)
procedure MethRefToMethPtr(const MethRef; var MethPtr);
type
TVtable = array[0..3] of Pointer;
PVtable = ^TVtable;
PPVtable = ^PVtable;
begin
// 3 is offset of Invoke, after QI, AddRef, Release
TMethod(MethPtr).Code := PPVtable(MethRef)^^[3];
TMethod(MethPtr).Data := Pointer(MethRef);
end;
Based on your example, I'd propose this as an alternative
type
TestRec<T> = record
CompareI: IComparer<T>;
function Compare(const L, R : T) : Integer;
end;
[...]
function TestRec<T>.Compare(const L, R : T) : Integer;
begin
Result := CompareI.Compare(L,R);
end;
But then, it may/may not apply to your current situation.

How to add type-specific code for generics in Delphi

Is there any efficient way to add type specific code for delphi generics ?
For example something like this:
function TGT<T>.GetSize(a: T): integer;
begin
{$IF TypeInfo(T)=TypeInfo(String)}
result := Length(A);
{$ELSE}
result := SizeOf(A);
{$IFEND}
end;
function TGT<T>.Compare(a,b: T): integer;
begin
{$IF TypeInfo(T)=TypeInfo(String)}
result := AnsiCompareText(a,b);
{$ELSE}
result := a-b;
{$IFEND}
end;
So i need to implement some parts of the code in different ways depending on type.
For example if i implement Sort routine i would like to use direct comparison of values of integer/double/etc types (it is more efficient than calling of interface methods like delphi's standard generic containers do) and function AnsiCompareText for String type.
There some examples how to do it, but all of them based on check like this:
if TypeInfo(T)=TypeInfo(String) then xxx else if TypeInfo(T)=TypeInfo(Integer) then xxx
Problem here is that Delphi will check it in run-time only, that is (again) not so efficient.
I would like to make compiler to do all checks in compile-time and use only code specific for selected type.
Well, you certainly cannot hope to do anything like that with conditional compilation. Remember that conditional compilation for generics are handled in the generic compilation phase rather than the instantiation phase. And so you cannot expect different instantiations to be compiled with different branches of your conditional statement.
And you certainly can never get the compiler to accept Length(a) where the type of a is parametrised, because there is no way to specify a constraint that would allow the use of Length.
The only option is a run-time check.

Can I use generics to do the same operation on similar types of controls?

I am using Delphi 2010 and I have a unit where over the years I have added my own procedures and functions that can be used with any project I make, such as:
function ListBoxIsSelected(ListBox: TListBox): Boolean;
begin
Result:= ListBox.ItemIndex <> -1;
end;
The above uses TListBox as a parameter, so whenever the above function is used I must supply a listbox that is of TListBox class.
Now suppose I have some other component libraries that could work with the same function, For example the Jedi component classes.
How could I use the above function, when the Jedi listbox is TJvListBox class and my function is looking for TListBox class? Although both components are practically the same, the class names are different. If I provided the same function specifically for the TJvListBox it would likely work because they are both "listboxes":
function ListBoxIsSelected(ListBox: TJvListBox): Boolean;
begin
Result:= ListBox.ItemIndex <> -1;
end;
Now, I have whole load of procedures and functions written in the same kind of way where I need to pass a component as a parameter. Having to rewrite them again just to work with a different component class is not feasible!
How can I write this with generics?
You can't write that with generics, unless your target classes all descend from the same base class of course. (But then you wouldn't need generics for it.)
If you really want something that can check if the ItemIndex property on any object <> -1, though, you can do that with a different Delphi 2010 feature: extended RTTI.
uses
SysUtils, RTTI;
function IsSelected(item: TObject): boolean;
var
context: TRttiContext;
cls: TRttiType;
prop: TRttiProperty;
ItemIndex: integer;
begin
if item = nil then
raise Exception.Create('Item = nil');
context := TRttiContext.Create;
cls := context.GetType(item.ClassType);
prop := cls.GetProperty('ItemIndex');
if prop = nil then
raise Exception.Create('Item does not contain an ItemIndex property.');
ItemIndex := prop.GetValue(item).AsInteger;
result := ItemIndex <> -1;
end;
Careful, though. There's no compile-time type checking here, and this process is significantly slower than your original routine. You probably won't notice it, but if you call something like this in a tight loop, it will slow it down.
I don't understand how I can write this with Generics?
You can’t – not unless your component implements a common interface or inherits from a common base class with the standard ListBox, and that interface / base class offers the ItemIndex property.
In fact, this use-case isn’t such a great example of generics because using an interface or base class in the declaration would work just as well.
In this case, you can write two overloaded functions, one expecting TJvListBox and the other expecting TListBox.
In more complex cases this approach may not apply so well, but I think your case is simple enough for this solution.
I cannot look it up right now (on holiday, no Delphi), but don't TJvListBox and TListBox descend from a common ancestor (my guess would be: TCustomListBox)? In that case something like this should work:
interface
function TListBox_IsItemSelected(_ListBox: TCustomListBox): boolean;
implementation
function TListBox_IsItemSelected(_ListBox: TCustomListBox): boolean;
begin
Result := _ListBox.ItemIndex <> -1;
end;
Just in case ItemIndex (as I said: I cannot check right now) is protected in TCustomListBox, you can just use a typecast hack:
type
TListBoxHack = class(TCustomListBox)
end;
function TListBox_IsItemSelected(_ListBox: TCustomListBox): boolean;
begin
Result := TListBoxHack(_ListBox).ItemIndex <> -1;
end;
(I just thought I should mention this since the original question has already been answered: Using Generics does not help here.)

Delphi trouble: Sorting a Tobjectlist<>

I want to sort my generic tobjectlist using the built-in sort method.
here is what I do:
//create the list object
myList := TObjectList<MyType>.Create(false);
[...] //populate the list with unsorted entries
//sort the list
myList.sort(#Comparer);
[...]//store sorted results back to array
myList.Destroy;
my Comparer function looks like this:
function Comparer(Item1, Item2 : pointer):integer;
begin
result := myCompare(item1, item2);
end;
According to the specs, it should work like this.
I get an compiler error E2250 No overloaded version of 'Sort' exist with these parameters (exact wording differs, i use a non english version of RAD Studio)
I have no idea why this should not be valid Pascal - does anyone of you have insight to share on this?
You are almost there. Since I don't know what MyType is you may need to change the call to your myCompare function.
myList.Sort(TComparer<MyType>.Construct(
function (const L, R: MyType): integer
begin
result := myCompare(L, R);
end
));
TObjectList<T>.Sort is declared as:
procedure Sort(const AComparer: IComparer<T>);
IComparer<T> is defined as:
IComparer<T> = interface
function Compare(const Left, Right: T): Integer;
end;
You are instantiating TObjectList<MyType> and so you need to pass an IComparer<MyType> to Sort. In order to do this you will need an object to provide a concrete implementation of that interface.
One obvious way to do this would be to subclass TObjectList<MyType> and implement the interface there.
Another way to do this is to use TComparer<T> to create an IComparer<T> on demand using its Construct class function. You would need to supply a comparison function:
TComparison<T> = reference to function(const Left, Right: T): Integer;
Leonardo's answer demonstrates how to do this.
If the compiler says no overloaded version exists with that parameter type, ask yourself what overloads do exist. Check the source code or the documentation to find out.
There you'll see that TObjectList<T> inherits two Sort methods from TList<T>. One takes no arguments, and the other takes a reference to something implementing the IComparer<T> interface. Your standalone function doesn't fit that. Write a descendant of TComparer<MyType> and override its Compare method.

How to group constant strings together in Delphi

I application that uses strings for different status an item can be during its life.
ie
OPEN,
ACTIVE,
CLOSED,
DELETE,
and so on, at the moment they are all hard coded into code like so
MyVar := 'OPEN';
I am working on changing this as it can be a maintenance problem, so I want to change them all to a constants, I was going to do it like so
MyVar := STATUS_OPEN;
but I would like to group them together into one data structure like so
MyVar := TStatus.Open;
What is the best way to do this in delphi 2007?
I know I can make a record for this, but how do I populate it with the values, so that it is available to all objects in the system without then having to create a variable and populating the values each time?
Ideal I would like to have one central place for the data structure and values, and have them easily accessible (like TStatus.Open) without having to assign it to a variable or creating an object each time I use it.
I am sure there is a simple solution that i am just missing. any ideas?
As Jim mentioned you could use class constants or an enumerated type:
type
TItemStatus = (isOpen, isActive, isClosed);
const
ItemStatusStrings: array[TItemStatus] of string = ('Open', 'Active', 'Closed');
See http://edn.embarcadero.com/article/34324 ("New Delphi language features since Delphi 7".
A class constant would do nicely. From that link above:
type
TClassWithConstant = class
public
const SomeConst = 'This is a class constant';
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
ShowMessage(TClassWithConstant.SomeConst);
end;
I personally have used TOndrej's approach extensively in several large mission critical data processing platforms. The benefit of enumerations is that they can be easily passed around within an application, are very compact (an ordinal type), work perfectly in case statements and are completely type safe. The later point is important for maintenance since deleting or changing enumeration values will cause a compile error (a good think IMHO).
Some gotcha's to look out for with this approach:
Changing the declared order of enum values will foo bar the enum->string lookup array.
If you use the enum in case statements (a nice feature) be sure to take into account new values being added. I usually add an else to the case and throw an exception on unknown values. Much better than falling through the case.
If you are very concerned about the first gotcha, you can use a record in the lookup array and include the enum value in each record and validate the ordering from the unit initialization. This has saved my bacon many times in mission critical systems where maintenance is frequent. This approach can also be used to add additional meta data to each value (a very handy feature).
Best of luck!
unit Unit1;
interface
type
TItemStatusEnum = (isOpen, isActive, isClosed);
TItemStatusConst = class
class function EnumToString(EnumValue : TItemStatusEnum): string;
property OPEN: string index isOpen read EnumToString;
property CLOSED: string index isClosed read EnumToString;
property ACTIVE: string index isActive read EnumToString;
end;
var
ItemStatusConst : TItemStatusConst;
implementation
uses
SysUtils;
type
TItemStatusRec = record
Enum : TItemStatusEnum;
Value : string;
end;
const
ITEM_STATUS_LOOKUP : array[TItemStatusEnum] of TItemStatusRec =
((Enum: isOpen; Value: 'OPEN'),
(Enum: isActive; Value: 'ACTIVE'),
(Enum: isClosed; Value: 'CLOSED'));
procedure ValidateStatusLookupOrder;
var
Status : TItemStatusEnum;
begin
for Status := low(Status) to high(Status) do
if (ITEM_STATUS_LOOKUP[Status].Enum <> Status) then
raise Exception.Create('ITEM_STATUS_LOOKUP values out of order!');
end;
class function TItemStatusConst.EnumToString(EnumValue: TItemStatusEnum): string;
begin
Result := ITEM_STATUS_LOOKUP[EnumValue].Value;
end;
initialization
ValidateStatusLookupOrder;
end.
Update:
Thanks for the critique - you are absolutely correct in that I was solving what I perceived as the problem underlying the question. Below is a much simpler approach if you can live with the constraint that this must ONLY work in Delphi 2007 or later versions (might work in D2006?). Hard to shed those nagging thoughts of backward compatibility ;)
type
ItemStatusConst = record
const OPEN = 'OPEN';
const ACTIVE = 'ACTIVE';
const CLOSED = 'CLOSED';
end;
This approach is simple and has a similar feel to what one might do in a Java or .Net application. Usage semantics are as expected and will work with code completion. A big plus is that the constants are scoped at a record level so there is no risk of clashes with other definitions as happens with unit scoped types.
Sample usage:
ShowMessage(ItemStatusConst.ACTIVE);
ShowMessage(ItemStatusConst.CLOSED);
Just for fun, I have also revised my earlier approach to directly address the original question with a similar outcome. This time I made use of "simulated class properties" (see here) with a property indexer. This is clearly more complex than the record approach, but retains the ability to work with enum values, sets, strings AND can also be extended to implement additional meta data features where required. I believe this works for Delphi versions going as far back as Delphi 5 IIRC.
An example of where I used this technique to great effect was a parsing framework I created in Delphi to handle the full IBM AFPDS print data stream grammar. This flexibility is why I love working in Delphi - it is like a Swiss army knife ;)
Or you could combine #Jim and #TOndrej
TGlobalConsts = class
type
TItemStatus = (isOpen, isActive, isClosed);
const
ItemStatusStrings: array[TItemStatus] of string = ('Open', 'Active', 'Closed');
end;
Although you could make it a class or a record really. Although if it is a class you can add the class functions like #mghie suggested, and instead just get the values from the const array. Personally I prefer having all the strings in a const array in the interface instead of peppered in the function bodies in the implementation.
class function TGlobalConsts.Active: string;
begin
Result := ItemStatusStrings[itsActive];
end;
class function TGlobalConsts.Open: string;
begin
Result := ItemStatusStrings[itsOpen];
end;
There are a lot of ways to do it, that is for sure.
This is something that you can do in all versions of Delphi:
type
TStatus = class
class function Active: string;
class function Open: string;
...
end;
class function TStatus.Active: string;
begin
Result := 'ACTIVE';
end;
class function TStatus.Open: string;
begin
Result := 'OPEN';
end;
You can use this exactly as you want:
MyVar := TStatus.Open;
there is exactly one place to change the strings, and there is only code involved, no runtime instantiation. New features of recent Delphi versions aren't always necessary to do stuff like that...
In addition to what TOndrej said:
One can also declare a record constant:
type
TRecordName = record
Field1: Integer;
Field2: String
end;
const
itemstatus : TRecordName = (
Field1: 0;
Field2: 'valueoffield2'
);
An array of records is also possible, by combining the const record syntax above and the array syntax shown by TOndrej.
However, the way I usually prefer needs initialization:
populating a TStringDynArray with the names of an enum via RTTI
If it then turns out that the names should be configurable, the persistance (however it is arranged) can simply load/store the dynarray.
You could have a global variable of a record type. The record type must have a field for each status you have.
You can populate the record in the Initialize section of any unit, calling a procedure or function declared in that unit. You could have a single unit to specifically do this work, something like StatusTypes.pas.
In the interface section you could declare something like:
type
TStatus = record
OPEN: string;
end;
var
Status: TStatus;

Resources