在 Delphi 中对 Double 数组的动态数组进行排序

Sorting dynamic Array of Array of Double in Delphi

我在 Delphi 中创建了一个动态矩阵:

AMatrix : Array of Array of Double;

假设我以这种方式初始化它。

SetLength(Amatrix,1000,10);

并使用一些值填充此矩阵。现在,我想根据存储在第二个维度上特定位置(从 0 到 9)中的特定值对第一个维度上的 1000 个项目进行排序。

有没有办法创建一个可以直接应用于 Amatrix 而无需创建其他数据结构(TList 或 TArray)的 TComparer?

使用@R.Hoeck 的想法,我写了一个简单的演示程序,它创建了一个双精度的二维数组,用随机数据填充它并使用给定的列作为键对其进行排序。

排序是通过为该列创建 index/value 的列表然后对列表进行排序来完成的。

排序后,未排序数组中的数据被复制到另一个将排序的数组中。

unit MatrixDemoMain;

interface

uses
  Winapi.Windows, Winapi.Messages,
  System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
  System.Generics.Defaults,
  System.Generics.Collections;

type
    TMyRecord = record
        Index : Integer;
        Value : Double;
    end;
    TDynArray2OfDouble = array of array of Double;

    TForm1 = class(TForm)
        Button1: TButton;
        Memo1: TMemo;
        procedure Button1Click(Sender: TObject);
    private
        procedure DisplayArray(const Title : String;
                               const Arr   : TDynArray2OfDouble);
    end;

var
    Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
    AMatrix      : TDynArray2OfDouble;
    SortedMatrix : TDynArray2OfDouble;
    I, J         : Integer;
    SortCol      : Integer;
    Rec          : TMyRecord;
    List         : TList<TMyRecord>;
begin
    // Give dimension to unsorted array
    SetLength(AMatrix, 10, 3);
    // Give dimension to the sorted array
    SetLength(SortedMatrix, High(AMatrix) + 1, High(AMatrix[0]) + 1);
    // Select column to use as sort key
    SortCol := 2;

    // Fill matrix with random data
    for I := 0 to High(AMatrix) do begin
        for J := 0 to High(AMatrix[0]) do
            AMatrix[I, J] := Random(1000);
    end;
    DisplayArray('Unsorted:', AMatrix);

    // Create a list to sort data
    List := TList<TMyRecord>.Create;
    try
        for I := 0 to High(AMatrix) do begin
            Rec.Index := I;
            Rec.Value := AMatrix[I, SortCol];
            List.Add(Rec);
        end;
        // Sort the list
        List.Sort(TComparer<TMyRecord>.Construct(
                    function(const Left, Right: TMyRecord): Integer
                    begin
                        if Left.Value = Right.Value then
                            Result := 0
                        else if Left.Value > Right.Value then
                            Result := 1
                        else
                            Result := -1;
                    end)
                  );

        // Copy data from unsorted matrix using sorted list
        for I := 0 to High(AMatrix) do
            SortedMatrix[I] := AMatrix[List[I].Index];

        DisplayArray('Sorted on column ' + SortCol.ToString, SortedMatrix);
    finally
        FreeAndNil(List);
    end;
end;

// This procedure will display an array into the memo
procedure TForm1.DisplayArray(
    const Title : String;
    const Arr   : TDynArray2OfDouble);
var
    I, J    : Integer;
    Buf     : String;
begin
    Memo1.Lines.Add(Title);
    for I := 0 to High(Arr) do begin
        Buf := I.ToString + ') ';
        for J := 0 to High(Arr[0]) do
            Buf := Buf + Arr[I, J].ToString + '  ';
        Memo1.Lines.Add(Buf);
    end;
end;

end.

运行 演示将在备忘录中显示此结果:

Unsorted:
0) 293  547  16  
1) 238  503  543  
2) 428  950  663  
3) 150  444  739  
4) 160  388  373  
5) 945  382  417  
6) 863  818  392  
7) 344  131  617  
8) 91  458  330  
9) 370  717  191  
Sorted on column 2
0) 293  547  16  
1) 370  717  191  
2) 91  458  330  
3) 160  388  373  
4) 863  818  392  
5) 945  382  417  
6) 238  503  543  
7) 344  131  617  
8) 428  950  663  
9) 150  444  739  

Is there a way to create a TComparer<T> that can be applied directly on [a variable of type array of array of Double] without the need to create other data structures (TList or TArray)?

让我们试一试,但为简单起见,我们将使用整数:

program FailedAttempt;

{$APPTYPE CONSOLE}

{$R *.res}

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

var
  A: array of array of Integer;

begin

  A :=
    [
      [5, 2, 1, 3, 6],
      [1, 2, 6, 3, 2],
      [1, 6, 7, 8, 3],
      [5, 7, 4, 2, 1],
      [0, 4, 9, 0, 5],
      [4, 1, 8, 9, 6]
    ];

  TArray.Sort<array of Integer>(A,
    TComparer<array of Integer>.Construct(
      function(const Left, Right: array of Integer): Integer
      begin
        if Left[2] < Right[2] then
          Result := -1
        else if Left[2] > Right[2] then
          Result := +1
        else
          Result := 0;
      end
    )
  );

  for var i := 0 to High(A) do
  begin
    Writeln;
    for var j := 0 to High(A[i]) do
      Write(A[i, j]);
  end;

  Readln;

end.

不幸的是,这不会编译,因为 array of Integer 不是您可以用作 T 的有效类型。请注意,这就像您不能将 array of Integer 用作函数的 return 类型一样。解决方案也是一样的:创建一个定义为 array of Integer.

的类型
program Solution1;

{$APPTYPE CONSOLE}

{$R *.res}

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

type
  TIntArray = array of Integer;

var
  A: array of TIntArray;

begin

  A :=
    [
      [5, 2, 1, 3, 6],
      [1, 2, 6, 3, 2],
      [1, 6, 7, 8, 3],
      [5, 7, 4, 2, 1],
      [0, 4, 9, 0, 5],
      [4, 1, 8, 9, 6]
    ];

  TArray.Sort<TIntArray>(A,
    TComparer<TIntArray>.Construct(
      function(const Left, Right: TIntArray): Integer
      begin
        if Left[2] < Right[2] then
          Result := -1
        else if Left[2] > Right[2] then
          Result := +1
        else
          Result := 0;
      end
    )
  );

  for var i := 0 to High(A) do
  begin
    Writeln;
    for var j := 0 to High(A[i]) do
      Write(A[i, j]);
  end;

  Readln;

end.

但是在 Delphi 的现代版本中,您不需要创建自己的类型(事实上,这是一个坏主意,因为不同的此类类型不兼容)。相反,只需使用确实定义为 array of IntegerTArray<Integer> ——这是一个动态整数数组,就像您的 array of Integer:

program Solution2;

{$APPTYPE CONSOLE}

{$R *.res}

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

var
  A: array of TArray<Integer>;

begin

  A :=
    [
      [5, 2, 1, 3, 6],
      [1, 2, 6, 3, 2],
      [1, 6, 7, 8, 3],
      [5, 7, 4, 2, 1],
      [0, 4, 9, 0, 5],
      [4, 1, 8, 9, 6]
    ];

  TArray.Sort<TArray<Integer>>(A,
    TComparer<TArray<Integer>>.Construct(
      function(const Left, Right: TArray<Integer>): Integer
      begin
        if Left[2] < Right[2] then
          Result := -1
        else if Left[2] > Right[2] then
          Result := +1
        else
          Result := 0;
      end
    )
  );

  for var i := 0 to High(A) do
  begin
    Writeln;
    for var j := 0 to High(A[i]) do
      Write(A[i, j]);
  end;

  Readln;

end.

如果实在无法改变A的定义,可以使用强制转换:

program Solution3;

{$APPTYPE CONSOLE}

{$R *.res}

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

var
  A: array of array of Integer;

begin

  A :=
    [
      [5, 2, 1, 3, 6],
      [1, 2, 6, 3, 2],
      [1, 6, 7, 8, 3],
      [5, 7, 4, 2, 1],
      [0, 4, 9, 0, 5],
      [4, 1, 8, 9, 6]
    ];

  TArray.Sort<TArray<Integer>>(TArray<TArray<Integer>>(A),
    TComparer<TArray<Integer>>.Construct(
      function(const Left, Right: TArray<Integer>): Integer
      begin
        if Left[2] < Right[2] then
          Result := -1
        else if Left[2] > Right[2] then
          Result := +1
        else
          Result := 0;
      end
    )
  );

  for var i := 0 to High(A) do
  begin
    Writeln;
    for var j := 0 to High(A[i]) do
      Write(A[i, j]);
  end;

  Readln;

end.

最后,我还应该指出一个明显的问题:可以在不使用 TComparer<T> 的情况下对数据进行排序。 (实际上,在 Delphi 2009 年引入泛型之前,您被迫这样做。)