【问题标题】:Best way to sort an array对数组进行排序的最佳方法
【发布时间】:2010-09-07 16:34:06
【问题描述】:

假设我有一个记录数组,我想根据记录中的一个字段对其进行排序。实现这一目标的最佳方法是什么?

TExample = record
  SortOrder : integer;
  SomethingElse : string;
end;

var SomeVar : array of TExample;

【问题讨论】:

  • 刚刚完成了这个练习,发现最好的方法是编写我自己的代码。我认为任何答案都不应该被推荐为最佳
  • 点了。也许您也可以在问题的解决方案中添加答案?
  • Julian Bucknall 的 Tomes of Delphi Algorithms and Data Structures 中有一些很好的信息。 (秒
  • 在 Tomes of Delphi Algorithms and Data Structures 中有一些很好的信息。 (这里,以及其他地方:blog.boyet.com/blog/blog/…

标签: delphi arrays sorting


【解决方案1】:

您可以将指向数组元素的指针添加到TList,然后使用比较函数调用TList.Sort,最后创建一个新数组并按所需顺序将值从TList 中复制出来。

但是,如果您使用的是下一个版本 D2009,则有一个新的集合库可以对数组进行排序。对于自定义排序顺序,它需要一个可选的IComparer<TExample> 实现。这是针对您的特定情况的操作:

TArray.Sort<TExample>(SomeVar , TDelegatedComparer<TExample>.Construct(
  function(const Left, Right: TExample): Integer
  begin
    Result := TComparer<Integer>.Default.Compare(Left.SortOrder, Right.SortOrder);
  end));

【讨论】:

  • 请注意,在 D2009 中,泛型编译器存在多个问题,因此使用集合库绝不是一个好的选择(因为虚假的编译器内部错误和代码生成错误)。在 Delphi XE 中,这些问题大部分都得到了解决,尽管使用泛型版本会影响性能(并且您可能会失去一些代码清晰度/可调试性)。
  • 解决问题是一回事。编写一个优雅的解决方案是另一回事(意思是“容易上手”)。我不认为这段代码看起来不错或可读性不够。我不希望 Delphi 慢慢失去它的可读性,否则就没有理由不将 C# 作为我的首选语言了。
  • 这个解决方案紧凑而简洁,因为它重用了许多常用构建的代码,并允许内联编写匿名函数,而不是强迫你创建一个虚拟函数声明,这样你就可以将它传递给它一项功能。您可以自己重写所有这些代码并占用一两页或三页。或者你可以用五行来完成。我认为较长的版本是否比较短的版本更“可读”,这有点值得商榷。
  • @jep 部分问题在于匿名函数语法过于冗长。将整个函数放在另一个函数中间的可读性要差得多。其次,如果我们有 lambda,特别是如果我们的排序使用键而不是自定义比较函数,那么整个企业将是一条线。例如,在 Python 中,上面的整个排序操作都会被排序(someVar, key=lambda x: x.SortOrder)! Python 不允许使用大于单行 lambda 的函数内联,因为它会导致代码不可读。我们真的可以使用 lambdas 和键排序。
【解决方案2】:

(我知道这是一年后,但仍然有用的东西。)

Skamradt 的填充整数值的建议假设您将使用字符串比较进行排序。这会很慢。为每个插入调用 format(),速度仍然较慢。相反,您想要进行整数比较。

你从一个记录类型开始:

TExample = record
  SortOrder : integer;
  SomethingElse : string;
end;

您没有说明记录的存储方式,或者排序后您希望如何访问它们。所以让我们假设你把它们放在一个动态数组中:

var MyDA  Array of TExample; 
...
  SetLength(MyDA,NewSize);           //allocate memory for the dynamic array
  for i:=0 to NewSize-1 do begin        //fill the array with records
    MyDA[i].SortOrder := SomeInteger;
    MyDA[i].SomethingElse := SomeString;
  end;

现在您要按整数值 SortOrder 对该数组进行排序。如果您想要的是 TStringList (因此您可以使用 ts.Find 方法),那么您应该将每个字符串添加到列表中并将 SortOrder 添加为指针。然后按指针排序:

var  tsExamples: TStringList;         //declare it somewhere (global or local)
...
  tsExamples := tStringList.create;   //allocate it somewhere (and free it later!)
...
  tsExamples.Clear;                   //now let's use it
  tsExamples.sorted := False;         //don't want to sort after every add
  tsExamples.Capacity := High(MyDA)+1 //don't want to increase size with every add
                                      //an empty dynamic array has High() = -1
  for i:=0 to High(MyDA) do begin
    tsExamples.AddObject(MyDA[i].SomethingElse,TObject(MyDA[i].SortOrder));
  end;

请注意将 Integer SortOrder 转换为 TObject 指针的技巧,该指针存储在 TStringList.Object 属性中。 (这取决于整数和指针大小相同的事实。)我们必须在某个地方定义一个函数来比较 TObject 指针:

function CompareObjects(ts:tStringList; Item1,Item2: integer): Integer;
var i,j: integer;
begin
  Result := integer(ts.Objects[i]) - integer(ts.Objects[j];
end;

现在,我们可以通过调用 .CustomSort 而不是 .Sort 对 .Object 上的 tsList 进行排序(这将根据字符串值进行排序。)

tsExample.CustomSort(@CompareObjects);     //Sort the list

TStringList 现在已排序,因此您可以从 0 迭代到 .Count-1 并按排序顺序读取字符串。

但是假设您不想要一个 TStringList,而只是一个按排序顺序排列的数组。或者,记录包含的数据多于本示例中的一个字符串,并且您的排序顺序更复杂。您可以跳过添加每个字符串的步骤,只需将数组索引添加为 TList 中的 Items。以相同的方式执行上述所有操作,除了使用 TList 而不是 TStringList:

var Mlist: TList;                 //a list of Pointers
...
  for i:=0 to High(MyDA) do
    Mlist.add(Pointer(i));        //cast the array index as a Pointer
  Mlist.Sort(@CompareRecords);    //using the compare function below

function CompareRecords(Item1, Item2: Integer): Integer;
var i,j: integer;
begin
  i := integer(item1);            //recover the index into MyDA
  j := integer(item2);            // and use it to access any field
  Result := SomeFunctionOf(MyDA[i].SomeField) - SomeFunctionOf(MyDA[j].SomeField);
end;

现在 Mlist 已排序,将其用作查找表以按排序顺序访问数组:

  for i:=0 to Mlist.Count-1 do begin
    Something := MyDA[integer(Mlist[i])].SomeField;
  end;

当 i 遍历 TList 时,我们按排序顺序取回数组索引。我们只需要将它们转换回整数,因为 TList 认为它们是指针。

我喜欢这样做,但您也可以通过添加数组元素的地址而不是它的索引来将真正的指针指向 TList 中的数组元素。然后要使用它们,您可以将它们转换为指向 TExample 记录的指针。这就是 Barry Kelly 和 CoolMagic 在他们的回答中所说的。

【讨论】:

  • 看起来只是一天后 :)
【解决方案3】:

如果您需要按字符串排序,请使用 sorted TStringList 和 通过TString.AddObject(string, Pointer(int_val))添加记录。

但如果需要按整数字段和字符串排序 - 使用 TObjectList 并在添加所有记录后调用 TObjectList.Sort 并将必要的排序函数作为参数。

【讨论】:

    【解决方案4】:

    这一切都取决于您要排序的记录数。如果您只排序少于几百个,那么其他排序方法可以正常工作,如果您要排序更多,那么好好看看旧的可信赖 Turbo Power SysTools 项目。源代码中包含一个非常好的排序算法。一种以高效方式对数百万条记录进行排序的出色表现。

    如果您要使用 tStringList 方法对记录列表进行排序,请确保在将整数插入列表之前将其填充到右侧。例如,您可以使用 format('%.10d',[rec.sortorder]) 来右对齐到 10 位。

    【讨论】:

      【解决方案5】:

      快速排序算法常用于需要快速排序的情况。例如,Delphi 正在(或曾经)将它用于 List.Sort。 Delphi List 可用于对任何内容进行排序,但它是一个重量级容器,应该看起来像结构上的指针数组。即使我们在这个线程中使用像 Guy Gordon 这样的技巧,它也是重量级的(用索引或任何东西代替指针,或者如果它们小于 32 位,则直接放置值):我们需要构造一个列表等等......

      因此,轻松快速地对结构数组进行排序的替代方法可能是使用来自 msvcrt.dll 的qsort C runtime function

      这是一个可能不错的声明(警告:代码仅在 Windows 上可移植)。

      type TComparatorFunction = function(lpItem1: Pointer; lpItem2: Pointer): Integer; cdecl;
      procedure qsort(base: Pointer; num: Cardinal; size: Cardinal; lpComparatorFunction: TComparatorFunction) cdecl; external 'msvcrt.dll';
      

      完整示例here

      请注意,如果记录很大,直接对记录数组进行排序可能会很慢。在这种情况下,对指向记录的指针数组进行排序会更快(有点像 List 方法)。

      【讨论】:

        【解决方案6】:

        对于数组,我会使用quicksortheapsort,只需将比较更改为使用TExample.SortOrder,交换部分仍将只作用于数组并交换指针。如果数组非常大,如果有很多插入和删除,你可能需要一个链表结构。

        基于C的例程,这里有几个 http://www.yendor.com/programming/sort/

        另一个网站,但有帕斯卡源 http://www.dcc.uchile.cl/~rbaeza/handbook/sort_a.html

        【讨论】:

          【解决方案7】:

          使用Wikipedia 提出的排序算法之一。 Swap 函数应该使用与数组元素相同类型的临时变量来交换数组元素。如果您希望具有相同 SortOrder 整数值的条目保持最初的顺序,请使用稳定排序。

          【讨论】:

            【解决方案8】:

            TStringList 有高效的排序方法。
            如果您想要排序,请使用 TStringList 对象并将 Sorted 属性设置为 True。

            注意:为了提高速度,请在未排序的 TStringList 中添加对象,最后将属性更改为 True。
            注意:对于按整数字段排序,请转换为字符串。
            注意:如果存在重复值,则此方法无效。

            问候。

            【讨论】:

              【解决方案9】:

              如果你有 Delphi XE2 或更新版本,你可以试试:

              var 
                someVar: array of TExample;
                list: TList<TExample>;
                sortedVar: array of TExample;
              begin
                list := TList<TExample>.Create(someVar);
                try
                  list.Sort;
                  sortedVar := list.ToArray;
                finally
                  list.Free;
                end;
              end;
              

              【讨论】:

                【解决方案10】:

                我创建了一个非常简单的示例,如果排序字段是字符串,则该示例可以正常工作。

                Type
                  THuman = Class
                  Public
                    Name: String;
                    Age: Byte;
                    Constructor Create(Name: String; Age: Integer);
                  End;
                
                Constructor THuman.Create(Name: String; Age: Integer);
                Begin
                  Self.Name:= Name;
                  Self.Age:= Age;
                End;
                
                Procedure Test();
                Var
                 Human: THuman;
                 Humans: Array Of THuman;
                 List: TStringList;
                Begin
                
                 SetLength(Humans, 3);
                 Humans[0]:= THuman.Create('David', 41);
                 Humans[1]:= THuman.Create('Brian', 50);
                 Humans[2]:= THuman.Create('Alex', 20);
                
                 List:= TStringList.Create;
                 List.AddObject(Humans[0].Name, TObject(Humans[0]));
                 List.AddObject(Humans[1].Name, TObject(Humans[1]));
                 List.AddObject(Humans[2].Name, TObject(Humans[2]));
                 List.Sort;
                
                 Human:= THuman(List.Objects[0]);
                 Showmessage('The first person on the list is the human ' + Human.name + '!');
                
                 List.Free;
                End;
                

                【讨论】:

                  最近更新 更多