繁体   English   中英

按多个字段对数组排序

[英]Sort arrays by multiple fields

我有多个数组,它们都以整数字段(从1到5个字段)开头,就像需要从min到max排序的索引一样:

    TArrayA = record
          Field1:integer;
          Field2:integer;
          Field3:integer;
          Field4:integer;
          Field5:integer;
          ... //other fields, strings, integers... up to 50 fields
        end;

    ArrayA:=array of TArrrayA;

目前,我使用这种方法进行排序:

    // sort by Field1
    top:=Length(ArrayA);
      for counter := 0 to top do
        begin
          min := counter;
          for look := counter + 1 to top do
            if ArrayA[look].Field1 < ArrayA[min].Field1 then
              min := look;
          vTmpRecord := ArrayA[min];
          ArrayA[min] := ArrayA[counter];
          ArrayA[counter] := vTmpRecord;
        end;

   // now sort by Field2
    top:=Length(ArrayA);
      for counter := 0 to top do
        begin
          min := counter;
          for look := counter + 1 to top do
            if (ArrayA[look].Field1 = ArrayA[min].Field1) And 
               (ArrayA[look].Field2 < ArrayA[min].Field2) then
              min := look;
          vTmpRecord := ArrayA[min];
          ArrayA[min] := ArrayA[counter];
          ArrayA[counter] := vTmpRecord;
        end;

这样就可以了。 虽然在我需要对所有5个字段进行排序时有点慢,这是我逐字段进行的方式,所以我对数组进行了5次排序。 有没有更好,更快的方法?

这是示例:

procedure TForm1.Button8Click(Sender: TObject);
type
  TArrayA = record
    Field1: integer;
    Field2: integer;
    Field3: integer;
    Field4: integer;
    Field5: integer;
  end;
var
  ArrayA: array of TArrayA;
  vTmpRecord: TArrayA;
  top, counter, min, max, look: integer;
  i,t1,t2:integer;
begin

  SetLength(ArrayA,100000);
  for i := 0 to 99999 do
  begin
    ArrayA[i].Field1:=1+Random(100);
    ArrayA[i].Field2:=1+Random(100);
    ArrayA[i].Field3:=1+Random(100);
    ArrayA[i].Field4:=1+Random(100);
    ArrayA[i].Field5:=1+Random(100);
  end;


  t1:=GetTickCount;
  // sort by Field1
  top := Length(ArrayA);
  for counter := 0 to top do
  begin
    min := counter;
    for look := counter + 1 to top do
      if ArrayA[look].Field1 < ArrayA[min].Field1 then
        min := look;
    vTmpRecord := ArrayA[min];
    ArrayA[min] := ArrayA[counter];
    ArrayA[counter] := vTmpRecord;
  end;

  // sort by Field2
  top := Length(ArrayA);
  for counter := 0 to top do
  begin
    min := counter;
    for look := counter + 1 to top do
      if (ArrayA[look].Field1 = ArrayA[min].Field1) and
        (ArrayA[look].Field2 < ArrayA[min].Field2) then
        min := look;
    vTmpRecord := ArrayA[min];
    ArrayA[min] := ArrayA[counter];
    ArrayA[counter] := vTmpRecord;
  end;

  // sort by Field3
  top := Length(ArrayA);
  for counter := 0 to top do
  begin
    min := counter;
    for look := counter + 1 to top do
      if (ArrayA[look].Field1 = ArrayA[min].Field1) and (ArrayA[look].Field2 = ArrayA[min].Field2) and
        (ArrayA[look].Field3 < ArrayA[min].Field3) then
        min := look;
    vTmpRecord := ArrayA[min];
    ArrayA[min] := ArrayA[counter];
    ArrayA[counter] := vTmpRecord;
  end;

  // sort by Field4
  top := Length(ArrayA);
  for counter := 0 to top do
  begin
    min := counter;
    for look := counter + 1 to top do
      if (ArrayA[look].Field1 = ArrayA[min].Field1) and (ArrayA[look].Field2 = ArrayA[min].Field2) and (ArrayA[look].Field3 = ArrayA[min].Field3) and
        (ArrayA[look].Field4 < ArrayA[min].Field4) then
        min := look;
    vTmpRecord := ArrayA[min];
    ArrayA[min] := ArrayA[counter];
    ArrayA[counter] := vTmpRecord;
  end;

  // sort by Field5
  top := Length(ArrayA);
  for counter := 0 to top do
  begin
    min := counter;
    for look := counter + 1 to top do
      if (ArrayA[look].Field1 = ArrayA[min].Field1) and (ArrayA[look].Field2 = ArrayA[min].Field2) and (ArrayA[look].Field3 = ArrayA[min].Field3) and (ArrayA[look].Field4 = ArrayA[min].Field4) and
        (ArrayA[look].Field5 < ArrayA[min].Field5) then
        min := look;
    vTmpRecord := ArrayA[min];
    ArrayA[min] := ArrayA[counter];
    ArrayA[counter] := vTmpRecord;
  end;

  t2:=GetTickCount;
  Button8.Caption:=IntToStr(t2-t1);
end;

您可以使用内置的快速排序方法通过自定义比较器对数组进行排序:

uses
  System.Math,
  System.Generics.Defaults,
  System.Generics.Collections;

  TArray.Sort<TArrayA>(ArrayA, TComparer<TArrayA>.Construct( function(const Left, Right: TArrayA): Integer
  begin
    if Left.Field1 = Right.Field1 then
      begin
        if Left.Field2 = Right.Field2 then
          begin
            Result := CompareValue(Left.Field3, Right.Field3);
          end
        else Result := CompareValue(Left.Field2, Right.Field2);
      end
    else Result := CompareValue(Left.Field1, Right.Field1);
  end
  ));

我仅为前三个字段添加了代码,但是您将获得如何为更多字段构建自己的比较器的图片。

您要做的最重要的事情是将排序算法与数据分开。 这样,您就可以一次次地对不同的数据编写或使用单个排序算法

做到这一点的经典方法是使用比较排序。 它们是排序算法,需要比较功能,该功能比较两个项目并返回小于等于的负整数,大于等于的正整数以及相等时返回零。

因此,让我们从演示数据的比较功能开始。 由于已经存储了多个字段,因此很难编写通用比较器。 最好将字段放在数组中。 一旦这样做,就可以使用像这样的迭代从字典上进行比较:

function CompareIntegerArray(const lhs, rhs: array of Integer): Integer;
var
  i: Integer;
begin
  Assert(Length(lhs) = Length(rhs));
  for i := low(lhs) to high(lhs) do
    if lhs[i] < rhs[i] then
      exit(-1)
    else if lhs[i] > rhs[i] then
      exit(1);

  exit(0);
end;

按照字典顺序,我们首先比较主字段。 如果它们不同,我们将给出答案,否则,我们将转到次要领域。 等等。 如上所述,这种算法非常适合迭代。

通过仅对数组排序一次,这克服了您的方法中的一个重大缺陷。

获得此比较功能后,需要将其包装在外部比较功能中,该功能从记录字段中提取数据并填充数组。 也许遵循以下思路:

type
  TMyArray = array [1..5] of Integer;

function GetMyArray(const Value: TArrayA): TMyArray;
begin
  Result[1] := Value.Field1;
  Result[2] := Value.Field2;
  ....
end;

function MyCompare(const lhs, rhs: TArrayA): Integer;
begin
  Result := CompareIntegerArray(
    GetMyArray(lhs),
    GetMyArray(rhs)
  );
end;

现在,如您TArray.Sort<T> ,您可以将此比较函数与Generics.Collections TArray.Sort<T>等通用类型一起使用。 这是Quicksort的实现, Quicksort是一种比较排序,平均复杂度为O(n log n)。 这通常会比O(n 2气泡排序产生巨大的好处。

如果您可以将记录替换为实际数组,则生活会更简单。 另一个有用的选择是在记录中添加一个方法,该方法返回一个整数数组,准备在字典比较功能中使用。

回顾一下:

  1. 分开进行数据,比较和排序,以方便重用和清晰化。
  2. 使用数组使字典比较可以通过循环实现。
  3. 使用高效的排序算法,例如Quicksort。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM