如何对字符串列表中的数字进行排序?

How can I sort numbers in stringlist?

我有一个包含一些数字的字符串列表。我已经使用我编写的冒泡排序对它们进行了排序。输出为:

18
20
3
44
53

我不明白为什么上面的输出不是我预期的:

3
18
20
44
53

我错过了什么?

首先,您不需要编写自己的排序代码来对字符串列表中的那些字符串进行排序。字符串列表 class 带有排序功能。您可以使用它而不是编写自己的排序代码。

现在进入问题的关键,在程序的输出中,当被视为 text 时,这些值的顺序是正确的。它们按词典顺序或字典顺序正确排序。例如

'20' < '3'

因为文本是逐个字符比较的,所以'2' < '3'.

但您希望这些值按 数字 排序。在这种情况下,您应该首先将它们转换为数字,然后对数字而不是文本进行排序。通过以下方式做到这一点:

  • 创建一个数组 TArray<Integer>,大小可容纳字符串列表的每个值。
  • 使用 StrToInt 填充数组以将列表中的每个字符串转换为整数。
  • 使用 Generics.Collections 单元中的 TArray.Sort<T> 对数组进行排序。

或许更好的方法是首先不将值存储为文本。将数据保存为数组或整数列表,然后对其进行排序。毕竟,如果您的数据是整数值,那么按原样存储它们是有意义的。

如果您将数据保存在一个数组中,您可以使用 TArray.Sort<T> 如上所述进行排序。或者,如果您使用 TList<T> 然后利用它的 Sort 方法。

正如 David 在 中解释的那样,您将字符串排序为文本字符,而不是整数。

可以使用TStringList.CustomSort()方法,比较时将字符串转为整数:

function MySortProc(List: TStringList; Index1, Index2: Integer): Integer;
var
  Value1, Value2: Integer;
begin
  Value1 := StrToInt(List[Index1]);
  Value2 := StrToInt(List[Index2]);
  if Value1 < Value2 then
    Result := -1
  else if Value2 < Value1 then
    Result := 1
  else
    Result := 0;
end;

SL.Add('18');
SL.Add('20');
SL.Add('3');
SL.Add('44');
SL.Add('53');
SL.CustomSort(MySortProc);

或者,将实际整数值存储在 Objects 属性 中,这样您就不必在排序时连续转换字符串:

function MySortProc(List: TStringList; Index1, Index2: Integer): Integer;
var
  Value1, Value2: Integer;
begin
  Value1 := Integer(List.Objects[Index1]);
  Value2 := Integer(List.Objects[Index2]);
  if Value1 < Value2 then
    Result := -1
  else if Value2 < Value1 then
    Result := 1
  else
    Result := 0;
end;

SL.Add('18', TObject(18));
SL.Add('20', TObject(20));
SL.Add('3', TObject(3));
SL.Add('44', TObject(44));
SL.Add('53', TObject(53));
SL.CustomSort(MySortProc);

或者,您可以使用 StrCmpLogicalW() 让 Windows 为您比较字符串:

function StrCmpLogicalW(const psz1, psz2: PWideChar): Integer; stdcall; external 'Shlwapi.dll';

function MySortProc(List: TStringList; Index1, Index2: Integer): Integer;
begin
  Result := StrCmpLogicalW(PChar(List[Index1]), PChar(List[Index2]));
end;

SL.Add('18');
SL.Add('20');
SL.Add('3');
SL.Add('44');
SL.Add('53');
SL.CustomSort(MySortProc);

您使用的是 TStringList(字符串列表),因此排序适用于字符串,而不适用于数字。 一个简单的解决方案是使用 sort 方法 TStringList。 您必须在代码中引入一个小变体。

如果您正在使用 TStringList,则您正在将数字转换为字符串,反之亦然。通过同样的工作,您可以在 TStringList 中插入数字,其格式可以使排序方法正常工作。

  1. 创建 TStringListSorted 属性 为 False -default-)
  2. 当您在 TStringList 上插入数字(并将其转换为字符串)时,在左侧部分使用“0”。

00000018

00000020

00000003

00000044

00000053

  1. Sorted 属性 更改为 True 并让 TStringList 完成工作。
  2. 现在数字已经排序了。

00000003

00000018

00000020

00000044

00000053

  1. 当您获得数字时,像以前一样使用标准 StrToInt 转换字符串。

David 描述了最快的方法 - 将字符串转换为整数数组,对该数组进行排序,然后在必要时重新创建字符串列表(或者最好保留整数列表)。

虽然我会添加最懒惰的方法。对于小型列表(大约 100 行),它可能足够好,但它会比前面提到的正确方法慢,而且你拥有的项目越多,它就会变得越来越慢。

偷懒的方法是安装Jedi Code Library,使用增强的StringList。

你可以这样做:

var sl: iJclStringList;

begin
  sl := JclStringList();

  sl.Split( '18, 20, 3, 44, 53', ',' ).Trim().SortAsInteger();

  ShowMessage( sl.Join(', ') );
  ShowMessage( sl.Join(^M^J) );

  sl := nil;
end;

同样,这种方法会做很多多余的额外工作,因此对于或多或少的重要数据量,更喜欢使用整数的正确方法。

这是一种与 Remy 非常相似的方法,但它没有使用 CustomSort,而是使用了一个新的 class,该 class 派生自 TStringList,覆盖了 CompareStrings。该实现使用 System.Math 中的 CompareValue 和字符串辅助函数 ToInteger.

优点是新的 class 可以将 Sorted 属性 设置为 true(这会触发排序 btw)。这使得 Find 函数起作用,而 IndexOf 又使用它。新添加的(整数)字符串也按排序顺序插入。

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

function TIntegerStringList.CompareStrings(const S1, S2: string): Integer;
begin
  result := CompareValue(S1.ToInteger, S2.ToInteger);
end;

如果这不是必需的,出于性能原因,我也会选择 David 的解决方案。