You could also declare a helper class like this, to avoid the requirement of IComparer
that both left and right side of the comparison must be of the specialized type:
type
TLeftComparison<T> = reference to function(const Left: T; var Value): Integer;
TListHelper<T> = class
public
class function BinarySearch(Instance: TList<T>; var Value; out FoundIndex: Integer;
Comparison: TLeftComparison<T>; Index, Count: Integer): Boolean; overload;
class function BinarySearch(Instance: TList<T>; var Value; out FoundIndex: Integer;
Comparison: TLeftComparison<T>): Boolean; overload;
class function Contains(Instance: TList<T>; var Value; Comparison: TLeftComparison<T>): Boolean;
class function IndexOf(Instance: TList<T>; var Value; Comparison: TLeftComparison<T>): Integer;
class function LastIndexOf(Instance: TList<T>; var Value; Comparison: TLeftComparison<T>): Integer;
end;
class function TListHelper<T>.BinarySearch(Instance: TList<T>; var Value; out FoundIndex: Integer;
Comparison: TLeftComparison<T>; Index, Count: Integer): Boolean;
var
L, H: Integer;
mid, cmp: Integer;
begin
Result := False;
L := Index;
H := Index + Count - 1;
while L <= H do
begin
mid := L + (H - L) shr 1;
cmp := Comparison(Instance[mid], Value);
if cmp < 0 then
L := mid + 1
else
begin
H := mid - 1;
if cmp = 0 then
Result := True;
end;
end;
FoundIndex := L;
end;
class function TListHelper<T>.BinarySearch(Instance: TList<T>; var Value; out FoundIndex: Integer;
Comparison: TLeftComparison<T>): Boolean;
begin
Result := BinarySearch(Instance, Value, FoundIndex, Comparison, 0, Instance.Count);
end;
class function TListHelper<T>.Contains(Instance: TList<T>; var Value; Comparison: TLeftComparison<T>): Boolean;
begin
Result := IndexOf(Instance, Value, Comparison) >= 0;
end;
class function TListHelper<T>.IndexOf(Instance: TList<T>; var Value; Comparison: TLeftComparison<T>): Integer;
var
I: Integer;
begin
for I := 0 to Instance.Count - 1 do
if Comparison(Instance[I], Value) = 0 then
Exit(I);
Result := -1;
end;
class function TListHelper<T>.LastIndexOf(Instance: TList<T>; var Value; Comparison: TLeftComparison<T>): Integer;
var
I: Integer;
begin
for I := Instance.Count - 1 downto 0 do
if Comparison(Instance[I], Value) = 0 then
Exit(I);
Result := -1;
end;
Then you could use it like this:
// TComparison (requires instances on both sides)
function CompareEstr(const Left, Right: TEstr): Integer;
begin
if Left.Date < Right.Date then
Exit(-1);
if Left.Date > Right.Date then
Exit(1);
Result := 0;
end;
// TLeftComparison: requires instance only on the left side
function CompareEstr2(const Left: TEstr; var Value): Integer;
begin
if Left.Date < TDateTime(Value) then
Exit(-1);
if Left.Date > TDateTime(Value) then
Exit(1);
Result := 0;
end;
procedure Main;
var
Date: TDate;
Comparer: IComparer<TEstr>;
List: TEstrList;
Item: TEstr;
Index: Integer;
I: Integer;
begin
Comparer := nil;
List := nil;
try
// create a list with a comparer
Comparer := TComparer<TEstr>.Construct(CompareEstr);
List := TEstrList.Create(Comparer);
// fill with some data
Date := EncodeDate(2011, 1, 1);
for I := 0 to 35 do
begin
Item.Date := IncMonth(Date, I);
List.Add(Item);
end;
// sort (using our comparer)
List.Sort;
Date := EncodeDate(2011, 11, 1);
Item.Date := Date;
// classic approach, needs Item on both sides
Index := List.IndexOf(Item);
Writeln(Format('TList.IndexOf(%s): %d', [DateToStr(Date), Index]));
List.BinarySearch(Item, Index);
Writeln(Format('TList.BinarySearch(%s): %d', [DateToStr(Date), Index]));
Writeln;
// here we can pass Date directly
Index := TListHelper<TEstr>.IndexOf(List, Date, CompareEstr2);
Writeln(Format('TListHelper.IndexOf(%s): %d', [DateToStr(Date), Index]));
TListHelper<TEstr>.BinarySearch(List, Date, Index, CompareEstr2);
Writeln(Format('TListHelper.BinarySearch(%s): %d', [DateToStr(Date), Index]));
Readln;
finally
List.Free;
end;
end;
This is of course less type-safe (due to the untyped right-side comparison parameter) but needed to allow to generically compare values of different types. With a bit of care this should not be a problem. Otherwise you could also write overloaded versions for most used types you need to compare.
BinarySearch
to search for aDate
. Not cool. – ParkerparkhurstTDictionary<Date, Integer>
to make search even faster. – ParkerparkhurstTEstr
TExtr
TExtrList
andTEstrList
(they differ in code and explanation). It would be nice if you could edit these. – Region