关于通用TList
的一切。我有这个结构:
Type
TExtract = record
Wheel: string;
Extract: array [1..5] of Byte;
end;
TExtractList = TList<TExtract>
TEstr = record
Date: TDate;
Extract: TExtractList;
end;
TEstrList = TList<TEstr>;
主要列表是TExtrList
,在此列表中,我有所有日期和日期所有轮次与该日期。我想搜索是否存在日期。如果不存在,我会从TExtractList
信息中添加Extract的子列表TEstr
。当我从TExtrList
搜索Delphi时询问TEstr
类型。我只需要搜索Date
。那么如何在通用TList
?
PS:我删除了上一篇文章,因为在这里我试图更好地解释。
答案 0 :(得分:8)
我们再来一次。
您应该使用内置的TList<T>.BinarySearch()
函数,即使它正确地要求将TEstr
记录作为参数。您首先需要使用TList<T>.Sort()
使用与搜索相同的条件对列表进行排序,然后致电BinarySearch()
查找您的记录。
这是一个同时进行(排序和搜索)的函数:
uses Generics.Defaults; // this provides TDelegatedComparer
uses Math; // this provides Sign()
function SearchList(Date:TDate; Sort:Boolean; List:TList<TEstr>): Integer;
var Comparer: IComparer<TEstr>;
Dummy: TEstr;
begin
// Prepare a custom comparer that'll be used to sort the list
// based on Date alone, and later to BinarySearch the list using
// date alone.
Comparer := TDelegatedComparer<TEstr>.Construct(
function (const L, R: TEstr): Integer
begin
Result := Sign(L.Date - R.Date);
end
);
// If the list is not sorted, sort it. We don't know if it's sorted or not,
// so we rely on the "Sort" parameter
if Sort then List.Sort(Comparer);
// Prepare a Dummy TEstr record we'll use for searching
Dummy.Date := Date;
// Call BinarySearch() to look up the record based on Date alone
if not List.BinarySearch(Dummy, Result, Comparer) then
Result := -1;
end;
BinarySearch
假设列表已排序(这是二进制搜索的本质!)。在第一次通话时,您需要设置Sort=True
,以便对列表进行正确排序。在后续调用中,Sort
应为False
。当然,在实际使用中,您可能有单独的搜索和排序例程,并且您可能将它们作为从TList<TEstr>
降序的类的方法(以使事情更容易)。我将两者放在同一个例程中用于dempnstration目的。
答案 1 :(得分:2)
您还可以声明一个这样的辅助类,以避免IComparer
要求比较的左侧和右侧都必须是专用类型:
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;
然后你可以像这样使用它:
// 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;
这当然不是类型安全的(由于无类型的右侧比较参数),但需要允许一般地比较不同类型的值。有点小心,这应该不是问题。否则,您还可以为需要比较的大多数使用类型编写重载版本。
答案 2 :(得分:2)
我发现只有一种方法可以搜索具有特定值的列表。
我将重复使用Cosmin Prund示例:
uses Generics.Defaults; // this provides TDelegatedComparer
uses Math; // this provides Sign()
function SearchList(Date:TDate; Sort:Boolean; List:TList<TEstr>): Integer;
var Dummy : TEstr;
begin
// If the list is not sorted, sort it. We don't know if it's sorted or not,
// so we rely on the "Sort" parameter
if Sort then List.Sort(TDelegatedComparer<TEstr>.Construct(
function (const L, R: TEstr): Integer
begin
Result := Sign(L.Date - R.Date);
end
);
// Call BinarySearch() to look up the record based on Date alone
if not List.BinarySearch(Dummy, Result, TDelegatedComparer<TEstr>.Construct(
function (const L, R: TEstr): Integer
begin
//By implementation, the binarySearch Dummy parameter is passed in the "R" parameter of the Comparer function. (In delphi 2010 at least)
Result := Sign(L.Date - Date); //Use the Date parameter instead of R.Date
end) then
Result := -1;
end;
然而,这种方法仅在“实施”而非“按设计”(据我所知)有效。换句话说,它很容易在Delphi版本之间中断。因此,建议将此方法用于可能“性能昂贵”的项目。如果您这样做,我强烈建议您在代码中添加类似的内容。
{$IF RTLVersion > *YourCurrentVersion*}
{$MESSAGE WARNING 'Verify if BinarySearch implementation changed'}
{$IFEND}
点 这样,下次在较新版本的Delphi中构建此代码时,您将自动收到警告,告知您确保代码仍能按预期工作。但是,如果您的代码需要同时支持多个版本的Delphi,这仍然会导致问题。
答案 3 :(得分:1)
我认为您在TDynArrayHashed 中有此功能,这是Post
中的一个示例答案 4 :(得分:0)
真的是否需要成为TList? Imo,二进制搜索对此来说太复杂了。也许你可以简单地使用TDictionary
:
type
TEstrCollection = TDictionary<TDate, TEstr>;
var
EstrCollection: TEstrCollection;
begin
EstrCollection := TEstrCollection.Create;
// Add an item
EstrCollection.Add(Date, TExtractList.Create)
// Search
ExtractList := EstrCollection[Date];
end;
现在,这需要日期字段是唯一的,因为它是字典的键。此外,这些项目没有特定的顺序。
如果订单很重要,我会结合数据结构。例如,你可以让TList按顺序保存项目加上一个TDictionary来执行搜索,就像这样。
唯一的问题是records
不是指针。要在两个不同的数据结构中添加相同的record
,您需要为它们创建指针
PEstr = ^TEstr
或者只是使用对象,因为它们已经是指针。您可以使用TObjectList
和TObjectDictionary
来保存集合自动管理的项目的生命周期(只记得只有一个集合管理对象的生命周期,如果它在多个集合中) )。