2012-08-08 71 views
6

我必须翻译一些Fortran 90代码,并找到一个有趣的语言功能。给定一个记录数组,我怎样才能得到一个数组表示每个字段的数组?

作为一个例子,它们定义了以下类型和动态数组变量:

TYPE WallInfo 
    CHARACTER(len=40) :: Name 
    REAL    :: Azimuth 
    REAL    :: Tilt 
    REAL    :: Area 
    REAL    :: Height 
END TYPE WallInfo 

TYPE(WallInfo), ALLOCATABLE, DIMENSION(:) :: Wall 

在代码之后,他们调用一个函数:

CALL HeatFlow(Wall%Area, Wall%Azimuth) 

作为Delphi程序员,这扔我有点因为沃尔是一系列记录!

从例程中的用法可以清楚看出,Fortran可以将来自记录数组的字段作为自己的数组来投影。

SUBROUTINE HeatFlow(Area, Azimuth) 
    REAL, INTENT(IN), DIMENSION(:) :: Area 
    REAL, INTENT(IN), DIMENSION(:) :: Azimuth 

有没有人知道是否有一种方法可以做到这一点与德尔福(我使用2010版)?

我可以写一个函数来提取一个数组的记录值,但这有点乏味,因为我将不得不为每个字段编写专用的例程(并且有很多)。

我希望Delphi 2010中有一些我错过的语言功能。

+0

您是否尝试使用WallInfo的case数组中的数组或记录? Delphi支持动态数组。在你输入新的值之前,首先设置数组的大小和SetLength。 – 2012-08-08 14:54:30

+0

就像Remy的RTTI答案一样酷,我会试图将上面的记录类型转换成单独的线性数组:'Name:String of Array;方位角:Double的数组; ...',然后我不必使用这个可爱的RTTI黑客来收集数据,因为它已经被收集。 – 2012-08-14 16:59:55

回答

8

使用扩展RTTI,有可能创建一个通用的函数,它的阵列和一个字段名作为输入和使用数组的RTTI来提取该字段的值并使用正确的数据类型与它们一起创建一个新数组。

下面的代码对我的作品在XE2:

uses 
    System.SysUtils, System.Rtti; 

type 
    FieldArray<TArrElemType, TFieldType> = class 
    public 
    class function Extract(const Arr: TArray<TArrElemType>; const FieldName: String): TArray<TFieldType>; 
    end; 

class function FieldArray<TArrElemType, TFieldType>.Extract(const Arr: TArray<TArrElemType>; const FieldName: String): TArray<TFieldType>; 
var 
    Ctx: TRttiContext; 
    LArrElemType: TRttiType; 
    LField: TRttiField; 
    LFieldType: TRttiType; 
    I: Integer; 
begin 
    Ctx := TRttiContext.Create; 
    try 
    LArrElemType := Ctx.GetType(TypeInfo(TArrElemType)); 
    LField := LArrElemType.GetField(FieldName); 
    LFieldType := Ctx.GetType(TypeInfo(TFieldType)); 
    if LField.FieldType <> LFieldType then 
     raise Exception.Create('Type mismatch'); 
    SetLength(Result, Length(Arr)); 
    for I := 0 to Length(Arr)-1 do 
    begin 
     Result[I] := LField.GetValue(@Arr[I]).AsType<TFieldType>; 
    end; 
    finally 
    Ctx.Free; 
    end; 
end; 

type 
    WallInfo = record 
    Name: array[0..39] of Char; 
    Azimuth: Real; 
    Tilt: Real; 
    Area: Real; 
    Height: Real; 
    end; 

procedure HeatFlow(const Area: TArray<Real>; const Azimuth: TArray<Real>); 
begin 
    // Area contains (4, 9) an Azimuth contains (2, 7) as expected ... 
end; 

var 
    Wall: TArray<WallInfo>; 
begin 
    SetLength(Wall, 2); 

    Wall[0].Name := '1'; 
    Wall[0].Azimuth := 2; 
    Wall[0].Tilt := 3; 
    Wall[0].Area := 4; 
    Wall[0].Height := 5; 

    Wall[1].Name := '6'; 
    Wall[1].Azimuth := 7; 
    Wall[1].Tilt := 8; 
    Wall[1].Area := 9; 
    Wall[1].Height := 10; 

    HeatFlow(
    FieldArray<WallInfo, Real>.Extract(Wall, 'Area'), 
    FieldArray<WallInfo, Real>.Extract(Wall, 'Azimuth') 
    ); 
end; 
+0

感谢您的帮助,我将尝试此方法。我开始意识到直接翻译并不那么简单。 Fortran似乎有一些很好的矩阵特性,但在Delphi中试图模拟这些特性效率不高。我只是简单地将整个墙数组传递给一个参数,而不是传递数组。 – bruce 2012-08-10 18:40:08

+0

Remmy,我不得不改变循环为我:= 0到长度(Arr)-1做它使它在德尔福2010年的工作。我印象深刻,它确实解决了这个问题,是非常整洁。 – bruce 2012-08-10 18:59:26

+0

代码是在XE2中编写和测试的。你是说在D2010中'High()'不适用于动态数组吗?我更新了我的答案。 – 2012-08-10 20:18:45

2

要回答你的问题,不,没有语言构造或便利的方法来将记录数组中的单个列拆分为它自己的简单数组。

我建议像下面这样:

function SplitColumn(RecordArray : Array of {recordtype}) : Array of {columntype}; 
var 
    column : array of {type}; 
    x : Integer; 
begin 
    setlength(result, high(RecordArray) + 1); 
    for x := 0 to high(RecordArray) do 
    result[ x ] := RecordArray[ x ].{columnname}; 
end; 

,如果你想使用动态数组那。就个人而言,如果你移植这个,我会使用列表和目录,如:

type 
    TWallList = class(TList<TWallInfo>); 
    TDoubleList = class(TList<Double>); 

function SplitColumn(WallList : TWallList; AreaList, AzimuthList : TDoubleList); 
var 
    x : Integer; 
begin 
    for x := 0 to RecList.Count-1 do 
    begin 
    AreaList.add(RecordArray[ x ].Area); 
    Azimuth.add(RecordArray[ x ].Azimuth); 
    end; 
end; 
+0

使用扩展RTTI,可以创建一个通用函数,该函数将数组和字段名称作为输入,并使用数组的RTTI来提取该字段的值并使用它们创建一个新数组。 – 2012-08-08 23:31:41

+0

我发布了一个扩展RTTI实例演示的答案。 – 2012-08-09 00:04:58

7

我发布这个作为答案,因为评论是有点太局限了这句话。

此答案试图解释在FORTRAN和Delphi数组和记录的内存布局的差异,并修改了answer通过Todd Grigsbyanswer通过Remy Lebeau(I upvoted两者)。

FORTRAN和其他一些以计算为中心的语言在column major order中存储嵌套数组。德尔福和许多其他语言使用row major order

从存储的角度看,一个创纪录的不是别的,正是那场数组:

  • 有一个名称,而不是索引
  • 可能有不同类型的

对于计算密集操作时,当您的算法偏好列时,存储嵌套数组列的主要顺序是有意义的。行主要订单相同。因此,在循环中,您需要match the order of your indexes with the order of your storage

鉴于这种记录和数组定义在FORTRAN:

TYPE WallInfo 
    CHARACTER(len=40) :: Name 
    REAL    :: Azimuth 
    REAL    :: Tilt 
    REAL    :: Area 
    REAL    :: Height 
END TYPE WallInfo 

TYPE(WallInfo), ALLOCATABLE, DIMENSION(:) :: Wall 

和在Delphi功能等价物的定义:

type 
    WallInfo = record 
    Name: array[0..39] of Char; 
    Azimuth: Real; 
    Tilt: Real; 
    Area: Real; 
    Height: Real; 
    end; 

var 
    Wall: array of WallInfo; 

和3个WallInfo元件的阵列, 这是怎样的存储器布局看起来(它们都将是连续的内存区域,我将它们分成几行以保持可读性):

in FORTRAN:

Name[0,0]...Name[0,39], Name[1,0]...Name[1,39], Name[2,0]...Name[2,39], 
Azimuth[0], Azimuth[1], Azimuth[2], 
Tilt[0], Tilt[1], Tilt[2], 
Area[0], Area[1], Area[2], 
Height[0], Height[1], Height[2], 
德尔福

Name[0,0]...Name[0,39], Azimuth[0], Tilt[0], Area[0], Height[0], 
Name[1,0]...Name[1,39], Azimuth[1], Tilt[1], Area[1], Height[1], 
Name[2,0]...Name[2,39], Azimuth[2], Tilt[2], Area[2], Height[2], 

所以这FORTRAN电话:

CALL热流(长城%面积,长城%方位角)

只想通过指针指向区域[ 0]和方位角[0]存储器位置和这些存储器区域的长度。

在Delphi中,这是不可能的,所以你必须

  1. 构建新的区域和方位一阳指
  2. 从信息复制它们WallInfo记录实例的阵列称为华尔街
  3. 送进来在功能
  4. 如果这些VAR参数:复制从两个排列的变化背靠墙

Todd GrigsbyRemy Lebeau显示了使用直接Delphi代码或Delphi记录RTTI的答案中的前三个步骤。
步骤4以类似的方式工作。

这两个解决方案都使用Delphi 2009中引入的泛型。
Until Delphi 2010, RTTI on records was very minimal),所以你为这两个答案都得到了正确的Delphi版本。

注意(再次):当您的算法从FORTRAN转换为Delphi时,请确保您注意由于列/行主要更改而导致的数组中的循环和其他索引。

+0

Jeroen,您对循环索引的评论非常感谢。我已经遇到了这个问题。我没有意识到Delphi和Fortran之间的数组顺序差异。现在更有意义了。 – bruce 2012-08-10 19:13:29

+0

我很高兴它有助于获得更多的见解。保持好的问题! – 2012-08-11 13:51:27

+0

在涉及行主要和主要问题的问题中,我看不到任何内容。他们在问题中出现在哪里?在我看来,问题在于Fortran支持投影的工具。这可能会用一种非常规的阵列来实现。 – 2012-08-14 18:35:32

相关问题