【问题标题】:How to use TComparer to sort a list of records?如何使用 TComparer 对记录列表进行排序?
【发布时间】:2020-06-08 20:41:42
【问题描述】:

我有一个包含文件数据的记录:

TYPE
 RFile= record
  public
    FileName     : string;
    Resolution   : Integer;
    FileSize     : Cardinal;
    Rating       : Byte;
   end;
 PFile= ^RFile;

我将这些文件/记录的列表保存在一个 TList

 TFileList= class(TList<PFile>)
     procedure SortByFilename;
     procedure SortByRating;
     procedure SortByResolution;
     procedure SortBySize;
  end;

我有诸如 SortByFilename、SortBySize 等方法来对列表进行排序。
我做“经典”排序。

现在我想升级到全新酷炫的 System.Generics.Defaults.TComparer。
据我了解,我需要为我的 TFileList 分配一个比较器,例如

  TIntStringComparer = class(TComparer<String>)
  public
    function Compare(const Left, Right: String): Integer; override;
  end;

我该怎么做?
如何为每个数据字段(文件名、文件大小、分辨率)处理一个比较器?


更新:
这段代码可以编译,但我有一个 EIntegerOverflow,因为 FileSize 是一个基数,而我返回一个整数(两个基数之间的差异)。

Sort(TComparer<PFile>.Construct(
  function(CONST A,B: PFile): integer
  begin
   Result:= A.FileSize - B.FileSize;
  end
 ));

【问题讨论】:

  • 当你写一个数字类型的比较器时,你不应该使用减法,即使你的数据类型是有符号的(尝试将a = 100b = -2147483640比较为Integers;显然@987654328 @,但减法会产生错误的值)。相反,您应该始终比较:if a = b then Result := 0 else if a &lt; b then Result := -1 else Result := 1。但是 Delphi 的 RTL 已经包含了这方面的功能:CompareValue from Math。因此,尽管如果您使用Result := Integer(A.FileSize) - Integer(B.FileSize),您的 sn-p 将“大部分时间”工作,但它仍然不理想。
  • 我看到CompareValue 对于Cardinal(32 位无符号)没有过载。但例如,您可以使用Result := CompareValue(Int64(A.FileSize), Int64(B.FileSize))。或者自己写 if 的东西。
  • 为什么要存储指向记录的指针?
  • 请注意,如果要处理大文件 (>4GB),文件大小应为 Int64
  • @AndreasRejbrand - 谢谢。如果您发表评论作为答案,我会接受。

标签: delphi generics


【解决方案1】:

当你为数值类型编写比较器时,你永远不应该使用减法,即使你的数据类型是有符号的。

确实,尝试将a = 100b = -2147483640Integers 进行比较;很明显a &gt; b,但是减法会产生错误的结果。

相反,你应该总是做类似的事情

if a = b then
  Result := 0
else if a < b then
  Result := -1
else
  Result := 1;

但 Delphi 的 RTL 已经包含用于此目的的函数:Math 单元中有几个 CompareValue 重载(用于不同类型的整数和浮点数 - 但不幸的是,不适用于 Cardinals)。

因此,尽管如果您这样做,您的 sn-p 将“大部分时间”工作

Result := Integer(A.FileSize) - Integer(B.FileSize)

这还不够好:一方面,不是每个Cardinal 都适合Integer。此外,如上所述,减法不是要走的路。

在您的情况下,您可以直接使用上面的if 东西,或者您可以为Cardinals 创建一个新的CompareValue 重载。或者,你可以这样做

Result := CompareValue(Int64(A.FileSize), Int64(B.FileSize)).

(另外,正如其他人在 cmets 中所说,您应该重新考虑首先使用 Cardinal 来存储文件大小是否明智。如果您将其升级到 Int64UInt64 您会写简单

Result := CompareValue(A.FileSize, B.FileSize).)

【讨论】:

  • 别管红衣主教,大于 4GB 的文件怎么办?
  • @DavidHeffernan:我完全同意,但我的答案是假设 OP 的 RFile 类型是固定的,并且其中的文件大小是 Cardinal。我专注于排序部分,而不是上下文。
  • @AndreasRejbrand - 像往常一样很好且完整的答案。谢谢。顺便说一句,知道为什么在文档中他们(emba)使用减法而不是 CompareStr 吗?这样做有什么好处?
  • @Migrate2Lazarusseemyprofile:实际上,Embarcadero's example 似乎正在按整数值对恰好是整数的文本表示形式的字符串列表进行排序。比较函数通过使用StrToInt 将字符串转换为整数然后比较整数来实现这一点。它使用减法来比较整数,我们知道这是不好的,因为它会在许多(尽管很大)输入中失败,比如我的 A 中的那个。它确实应该使用 CompareValue 代替。但是在这个...
  • ... 如果字符串是从 0 到 4 的随机数,所以减法可以正常工作(但我仍然不喜欢它,因为也许未来的代码维护者会增加范围随机数)。 CompareStr 不完全进入这个讨论,因为 CompareStr 用于比较字符串(逐个字符),而不是数字。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-25
  • 1970-01-01
  • 2018-12-22
  • 1970-01-01
  • 2020-09-11
  • 1970-01-01
  • 2018-06-21
相关资源
最近更新 更多