【问题标题】:Delphi String SortDelphi 字符串排序
【发布时间】:2021-06-19 05:45:59
【问题描述】:

我有一个非常大的文件,如下所示

45313904626416486480179546360796323469287116537171
465573254230695450538671922463236910370073247307526
5906233480284069039032926795367974774430427486375

这种num怎么排序?

结果应该是这样的(真实文件是 100000 行):

5906233480284069039032926795367974774430427486375
45313904626416486480179546360796323469287116537171
465573254230695450538671922463236910370073247307526

我尝试一下

  MyFlexibleArray := TList<UInt64>.Create;
  AssignFile(F, OpenTextFileDialog1.FileName);
  Reset(F);
  repeat
    Readln(F, str);
    MyFlexibleArray.Add(UInt64(str));
  until EOF(F);
  CloseFile(F);
  MyFlexibleArray.Sort;

使用 TStringList,结果不是以自然方式排序的!

任何帮助将不胜感激。

full file

【问题讨论】:

  • 最大 UInt64 为 18,446,744,073,709,551,615,比您的数字小很多。您可以在数字前面添加零,以便它们全部对齐,对它们进行字符串排序并在删除前导零的情况下输出。
  • 谁会上传一个 未压缩 大小的文本文件到主机上,然后它会在几天内消失?它包含像5079598143104014379457667649331467875308351922_8 这样的行,_ 是一个 NUL 字节。以后有需要这个文件的可以回复我。
  • 如果你需要做更多(数学)然后只是对这些大数字进行排序,有一个库:github.com/rvelthuis/DelphiBigNumbers

标签: delphi delphi-10.4-sydney


【解决方案1】:

首先,您的文本文件已损坏。包含NUL字节,无法正常解析。

但是,如果我们忽略这个问题,像这样对文件进行排序几乎是微不足道的。

假设没有前导零,以下算法将给出正确的结果:

var Data := TFile.ReadAllLines('K:\numbers.txt', TEncoding.ASCII);

TArray.Sort<string>(
  Data,
  TComparer<string>.Construct(
    function(const L, R: string): Integer
    begin
      Result := CompareValue(L.Length, R.Length);
      if Result <> 0 then
        Exit;
      for var i := 1 to L.Length do
      begin
        Result := CompareValue(Ord(L[i]), Ord(R[i]));
        if Result <> 0 then
          Exit;
      end;
    end
  )
);

TFile.WriteAllLines('K:\sorted.txt', Data, TEncoding.ASCII);

我们根据这些规则构建自己的字符串比较器:

  • 如果L 的位数比R 多(少),那么显然它更大(更小)。
  • 如果 LR 的位数相同,请逐位比较 MSD 和 LSD 的位数。

只需将IOUtilsGenerics.DefaultsGenerics.CollectionsMath 添加到您的uses 子句中即可。

【讨论】:

  • @NomiLaura 需要多长时间?
  • @Olivier:在我的系统上,1M 行文件的排序需要 3 秒。我想我的系统上的大多数例程都会接近这个。 CompareStrCompareText 经过高度优化,运行时间稍好(约 2.3 秒);具体来说,他们似乎一次比较一个 32 位 int,即使一个 char 只有 16 位。
  • 1s多100000行。 ? TMemo 提供的额外时间。对不起
【解决方案2】:

如果将数据排序为字符串,则不考虑字符串的长度。

如果您使用Generic(因此您需要在uses 子句中使用System.Generics.Collections),您可以指定如何将参数中的对象与构造函数进行比较。这意味着您的字符串列表将被声明为:

  FMyStrings: TList<String>;

您的比较器将比较两个字符串,如果您假设字符串只能包含十进制数字,那么您的比较器将类似于:

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

  function TMyStringSorter.Compare(const Left, Right: String): Integer;
  begin
    if(Length(Left)<Length(Right) then Result:=-1
    else if(Length(Right)<Length(Left) then Result:=1
    else Result:=CompareStr(Left, Right);
  end;

然后将接口传给比较器给TList构造器,你可以根据自己的排序算法对其进行排序。

【讨论】:

  • 如果您不需要区分大小写比较数字 (!),您可以使用 CompareStr 而不是 CompareText 来加快速度。
  • @AndreasRejbrand - 是的,你当然是对的 (+1)。 CompareText 不区分大小写,但 CompareStr 可能会快一点。
  • 这与我的答案基本相同,除了这个使用泛型。使用标准 TStringList (如我的回答),使用 CustomSort 和您自己的比较器函数,可以在没有泛型的情况下实现相同的目标。
【解决方案3】:

您可以将大数字保留在 TStringList 中的字符串中,并使用自定义比较器对其进行排序,该比较器首先按字符串长度排序,然后(如果长度相等)按字符串值排序。像这样:

function NumberStringComparer(List: TStringList; Index1, Index2: Integer): Integer;
var
  Value1, Value2: string;
  Len1, Len2: Integer;
begin
  Value1 := List[Index1];
  Value2 := List[Index2];
  Len1 := Length(Value1);
  Len2 := Length(Value2);

  Result := Len1 - Len2;
  if Result = 0 then
  begin
    if Value1 = Value2 then
      Result := 0
    else if Value1 > Value2 then
      Result := 1
    else
      Result := -1;
  end;
end;

使用示例:

MyStringList.CustomSort(NumberStringComparer);

【讨论】:

  • 数字如 ``` 10 2 7 1 3 4 5 8 7 6 ``` 排序为 ``` 1 10 2 3 4 5 6 7 7 8 ```
  • 使用这个比较器是不可能的。所有长度为 1 的字符串都排在所有长度为 2 的字符串之前。排序为 1 2 3 4 5 6 7 7 8 10。您提到的输出是 TStringList 的默认排序所给出的。
猜你喜欢
  • 1970-01-01
  • 2023-04-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-29
  • 1970-01-01
  • 2014-07-14
相关资源
最近更新 更多