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

4

我有一个包含一些数字的字符串列表。我使用自己编写的冒泡排序对它们进行了排序。输出结果如下:

18
20
3
44
53

我不明白为什么会输出上面的内容,而不是我期望的内容:
3
18
20
44
53

我有什么遗漏吗?

尝试比较20和3... - Free Consulting
5个回答

10
首先,你不需要编写自己的排序代码来对字符串列表中的字符串进行排序。字符串列表类带有一个排序函数。您可以使用它而不是编写自己的排序代码。
现在进入问题的核心,从程序输出来看,这些值按照文本方式正确排序。它们按字典或词典顺序正确排序。例如:
'20' < '3'

因为文本是按字符比较的,而 '2' < '3'

但如果您希望将值按 数字 的顺序排序,则应该先将它们转换为数字,然后对数字进行排序,而不是按文本排序。请按以下方式操作:

  • 创建一个大小为字符串列表中每个值的数组 TArray<Integer>
  • 使用 StrToInt 将列表中的每个字符串转换为整数来填充数组。
  • 使用Generics.Collections 单位中的 TArray.Sort<T> 排序数组。

或者也许更好的方法是从一开始就不要将值存储为文本。将数据保存为整数的数组或列表,然后对其进行排序。毕竟,如果您的数据具有整数值,将它们存储为整数是有意义的。

如果您将数据保存在数组中,则可以像上面描述的那样使用 TArray.Sort<T> 进行排序。如果您使用 TList<T>,则可以利用其 Sort 方法。


7

正如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);

3
在TObject字段中保留整数值的建议是一个非常糟糕的想法,我认为这可能会引导人们采取危险的方式。有TList<integer>/TArray<integer>等数据结构。自定义排序功能也比指针和整数类型之间的转换更加优雅。 - Andrei Galatyn
MySortProc的建议:您不需要检查Value1是否比Value2小或大。 SortProc只需要返回零、负整数或正整数来指示哪个值更大。请使用Result := Value1 - Value2;替换if..else结构。 - Wosi
3
@Wosi我们之前已经谈论过这个问题了。Remy的答案原本就是那样的。但是请思考一下这个程序的输出结果是什么:`{$APPTYPE CONSOLE}var i, j: Integer;begin i := high(Integer); j := low(Integer); Writeln(i-j); end. ` 它输出正数还是负数?你觉得呢?这是一个非常常见的反模式。 - David Heffernan
谢谢 @DavidHeffernan,我没有考虑到那个边缘情况。 - Wosi
1
我希望MySortProc返回StrCmpLogicalW的结果。 - NGLN
显示剩余2条评论

1
这种方法与Remy的方法相似,但它使用一个派生自TStringList的新类来覆盖CompareStrings,而非使用CustomSort。实现中使用了System.Math中的CompareValue和字符串辅助函数ToInteger。优点是,新类可以将Sorted属性设置为true(触发排序),使得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的方案。

请注意,您需要在uses子句中使用System.SysUtils才能使用String.ToInteger()辅助程序。 - Remy Lebeau

0

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

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

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

00000018

00000020

00000003

00000044

00000053

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

00000003

00000018

00000020

00000044

00000053

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

1
@David Heffernen感谢您编辑我的留言,因为我的英语有时不正确,但我不明白最后一次编辑为什么删除了问候语。 根据规则,应该这样编辑帖子: (1)修正语法或拼写错误。 (2)澄清含义而不改变它。 (3)纠正小错误。 (4)添加相关资源或链接。 (5)始终尊重原作者。 为什么要删除问候信息?这不是与第(5)点相矛盾吗? - Germán Estévez -Neftalí-
1
这个问题在 Meta 上已经被讨论过很多次了,例如 http://meta.stackoverflow.com/questions/260776/should-i-remove-fluff-when-editing-questions 和 http://meta.stackexchange.com/questions/28416/what-is-the-policy-on-signatures-and-links-in-answers。我现在看到你的所有帖子似乎都以相同的方式签名。我认为你应该停止这样做。 - David Heffernan
@David Heffernan。好的David。谢谢你提供的链接。 - Germán Estévez -Neftalí-

0

最快的方法是由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;

再说一遍,这种方法会做很多冗余的额外工作,因此对于任何更多或更少的重要数据量,使用整数的正确方式是更可取的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接