自定义将Delphi TStringList名称/值对排序为整数

自定义将Delphi TStringList名称/值对排序为整数,delphi,sorting,Delphi,Sorting,我有一组名/值对。名称都是整数值(当然存储为字符串),值都是字符串(逗号分隔) e、 g 。 . 我想调用add例程并在TStringlist中添加新行,但需要一种方法来对列表进行排序 e、 g 以下是我尝试过的: Tags:= TStringList.Create; Tags.CustomSort(StringListSortComparefn); Tags.Sorted:= True; 我的自定义排序例程: function StringListSortComparefn(List:

我有一组名/值对。名称都是整数值(当然存储为字符串),值都是字符串(逗号分隔)

e、 g

。 .

我想调用add例程并在TStringlist中添加新行,但需要一种方法来对列表进行排序

e、 g

以下是我尝试过的:

Tags:= TStringList.Create;
 Tags.CustomSort(StringListSortComparefn);
 Tags.Sorted:= True;
我的自定义排序例程:

function StringListSortComparefn(List: TStringList; Index1, Index2: Integer): Integer;
var
 i1, i2 : Integer;
begin
 i1 := StrToIntDef(List.Names[Index1], 0);
 i2 := StrToIntDef(List.Names[Index2], 0);
 Result:= CompareValue(i1, i2);
end;
然而,它似乎仍然像字符串一样对它们进行排序,而不是整数

30686=Ozarktree1 Goes to town,ozarktreel,0
5016=Catch the Fish!,honeyman,0
我甚至尝试创建自己的类:

type
 TXStringList = class(TStringList)
 procedure Sort;override;
end;

implementation

function StringListSortComparefn(List: TStringList; Index1, Index2: Integer): Integer;
var
i1, i2 : Integer;
begin
i1 := StrToIntDef(List.Names[Index1], 0);
i2 := StrToIntDef(List.Names[Index2], 0);
Result:= CompareValue(i1, i2);
end;

procedure TXStringList.Sort;
begin
 CustomSort(StringListSortComparefn);
end;
我甚至试过一些例子(例如)

有人能告诉我我做错了什么吗?每次,列表都被排序为字符串而不是整数

30686=Ozarktree1 Goes to town,ozarktreel,0
5016=Catch the Fish!,honeyman,0

您可以执行简单的整数减法:

function StringListSortComparefn(List: TStringList; Index1, Index2: Integer): Integer;
var
 i1, i2 : Integer;
begin
 i1 := StrToIntDef(List.Names[Index1], 0);
 i2 := StrToIntDef(List.Names[Index2], 0);
 Result := i1 - i2
end;
Result := i2 - i1;
要反转排序顺序,只需反转减法中的操作数:

function StringListSortComparefn(List: TStringList; Index1, Index2: Integer): Integer;
var
 i1, i2 : Integer;
begin
 i1 := StrToIntDef(List.Names[Index1], 0);
 i2 := StrToIntDef(List.Names[Index2], 0);
 Result := i1 - i2
end;
Result := i2 - i1;
下面是一个快速、可编译的控制台示例:

program Project2;

{$APPTYPE CONSOLE}

uses
  SysUtils, Classes;

function StringListSortProc(List: TStringList; Index1, Index2: Integer): Integer;
var
  i1, i2: Integer;
begin
  i1 := StrToIntDef(List.Names[Index1], -1);
  i2 := StrToIntDef(List.Names[Index2], -1);
  Result := i1 - i2;
end;

var
  SL: TStringList;
  s: string;
begin
  SL := TStringList.Create;
  SL.Add('3456=Line 1');
  SL.Add('345=Line 2');
  SL.Add('123=Line 3');
  SL.Add('59231=Line 4');
  SL.Add('545=Line 5');
  WriteLn('Before sort');
  for s in SL do
    WriteLn(#32#32 + s);
  SL.CustomSort(StringListSortProc);
  WriteLn('');
  WriteLn('After sort');
  for s in SL do
    WriteLn(#32#32 + s);
  ReadLn;
  SL.Free;
end.
以及由此产生的输出:

Before sort
  3456=Line 1
  345=Line 2
  123=Line 3
  59231=Line 4
  545=Line 5

After sort
  123=Line 3
  345=Line 2
  545=Line 5
  3456=Line 1
  59231=Line 4

问题是,您是否要求列表保持排序?或者,在添加完所有项目后,在末尾对其进行排序就足够了

如果您只需要能够根据需要对列表进行排序,那么第一个示例几乎是正确的。添加项目后,您只需在最后调用CustomSort即可

  Tags := tStringList . Create;
  Tags . Add ( '5016=Catch the Fish!,honeyman,0' );
  Tags . Add ( '30686=Ozarktree1 Goes to town,ozarktreel,0' );
  Tags.CustomSort(StringListSortComparefn);
如果需要列表保持排序,则需要覆盖CompareStrings

type
 TXStringList = class(TStringList)
    function CompareStrings(const S1, S2: string): Integer; override;
end;

function NumberOfNameValue ( const S : string ) : integer;
begin
  Result := StrToIntDef(copy(S,1,pos('=',S)-1), 0);
end;

function txStringList . CompareStrings ( const S1, S2 : string ) : integer;
var
 i1, i2 : Integer;
begin
 i1 := NumberOfNameValue ( S1 );
 i2 := NumberOfNameValue ( S2 );
 Result:= CompareValue(i1, i2);
end;


begin
  Tags := txstringlist . Create;
  Tags . Sorted := true;
  Tags . Add ( '5016=Catch the Fish!,honeyman,0' );
  Tags . Add ( '30686=Ozarktree1 Goes to town,ozarktreel,0' );
 // List will be correctly sorted at this point. 
end;

CustomSort
命令是一次性操作。您似乎在使用它,就好像您正在设置一个属性,以便进一步的排序将使用自定义比较函数,但实际上它不是这样工作的。它对(新创建的、空的)列表进行一次排序。然后,当设置
Sorted
属性时,使用默认比较对列表重新排序,并指定应使用该默认排序顺序插入列表中的任何其他添加项

当您重写
Sort
方法时,您离解决方案更近了一点,但是插入到排序列表(其中
sorted=True
)实际上并不调用
Sort
!相反,它们对正确的插入位置执行二进制搜索,然后在那里插入。您可以尝试覆盖
比较项,而不是覆盖
排序

type
  TXStringList = class(TStringList)
  protected
    function CompareStrings(const S1, S2: string): Integer; override;
  end;

function TXStringList.CompareStrings(const S1, S2: string): Integer;
var
  i1, i2, e1, e2: Integer;
begin
  Val(S1, i1, e1);
  Assert((e1 = 0) or (S1[e1] = NameValueSeparator));
  Val(S2, i2, e2);
  Assert((e2 = 0) or (S2[e2] = NameValueSeparator));
  Result := CompareValue(i1, i2);
end;

但要注意,这会破坏
IndexOf
方法。它也可能会中断
查找
,但您可能需要这样做,这取决于您希望如何使用相同的数字键处理元素。(
Find
用于定位已排序列表的正确插入点,使用上述代码,它会将具有相同键的所有元素视为相等。)它们都使用
CompareStrings
,就像
Sort
一样。

可以使用TDictionary吗?在这种情况下,您可以定义自己的ComRever,从而确定排序顺序。@Jake为什么您接受的答案不能解决您的问题?这不是一个真正有效的测试。即使没有自定义排序功能,也会得到相同的结果,因为
'1'<'3'<'5'
。此外,您的答案似乎暗示原始代码中的问题是
CompareValue
。你愿意明确地说吗?你能证明这才是真正的问题吗?@Rob:在stringlist中添加了两个额外的项,以证明排序确实可以正常工作。感谢您指出需要更好的数据。虽然我不能肯定地说,
CompareValue
是问题所在,但我可以肯定地说a)这里不需要函数调用来进行比较,b)我已经成功地使用了上述方法在TStringList中对数字的文本表示进行排序,多年来没有出现任何问题。@KenWhite,我尝试了你的答案。结果如下:我在列表中的第一项是“10000=鲨鱼人,滑稽,0”,而事实上,第一项应该是“5016=抓鱼!”,霍尼曼,0”,最后一项应该是“30771=僵尸声纳,tfrog316,0”,但我的最后一项是:“9999=快乐露营者,zicky,0”。因此,它似乎仍在按字符串排序:下面是我的代码:Tags:=TStringList.Create;Tags.CustomSort(StringListSortProc);Tags.Sorted:=True;Tags.LoadFromFile(AppPath+'PTags.dat');标签。排序;ShowMessage(标记[0]);-这些代码有错吗?不,不可能,除非你的值实际上不是数字。请编辑您的问题,提供一个具体的项目列表,我可以从那里复制和粘贴。你知道如果你想“即时”排序,你必须在每个项目都添加到列表后调用
CustomSort
,对吗?典型的做法是添加所有项目,然后在最后调用CustomSort一次;它不会简单地通过将TStringList.Sorted属性设置为True来替换默认排序。列表中的项目是无关的。比较的方法也是如此。是周围的代码导致了这个问题。接得好。在调用问题代码中的
CustomSort
之后,我立即错过了
Sorted
属性的设置。我只是想澄清一下,因为@David似乎错过了它:我对Rob的答案投了赞成票,因为它正确地识别了问题(正如我在之前的评论中所述)。罗伯应该是这里接受的人,而不是我。@Ken我没有错过。用户的回答也很好。@David:显然,你是根据你在我的回答中添加的评论来回答的。“我想向你(和其他人)表明,我同意罗布的答案比我的答案更适合这个问题。@KenWhite不。我对你答案的评论纯粹是关于这个答案的内容。它没有提及其他答案。