Given an array of records, how can I get an array representing a field from each?

I need to translate Fortran 90 code and find an interesting language feature.

As an example, they define the following type variable and dynamic array:

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

Later in the code they call the function:

 CALL HeatFlow(Wall%Area, Wall%Azimuth) 

As a Delphi programmer, this made me a little bit because Wall is an array of records!

From the use of the subroutine, it is clear that Fortran can design fields from an array of records as its own array.

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

Does anyone know if there is a way to do this using Delphi (I am using version 2010)?

I could write a function to extract the record value as an array, but this is a bit tedious because I have to write a special routine for each field (and there are quite a lot of them).

I hope there is some language feature in Delphi 2010 that I missed.

+6
source share
3 answers

Using Extended RTTI, you can create a generic function that takes an array and a field name as input, and uses an RTTI array to extract only the values ​​of that field and create a new array with the correct data type.

The following code works for me in 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; 
+8
source

I am posting this as an answer because the comments are too limited to express it.

This answer attempts to explain the differences in layout layouts of arrays and records in FORTRAN and Delphi and modifies Todd Grigsby 's answer and Remy Lebo's answer (I saved both).

FORTRAN and several other oriented oriented languages ​​store nested arrays in the column ordinal . Delphi and many other languages ​​use the basic string order .

From the point of view of memory, a record is nothing more than an array of fields, which:

  • have a name, not an index
  • can have different types

For intensive computing operations, it might make sense to store the basic column order of nested arrays when your algorithms favor the columns. The same goes for a large row order. Therefore, in loops, you need to match the order of your indexes with the storage order .

Given this entry and array definition in FORTRAN:

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

and a functionally equivalent definition in Delphi:

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

and an array of 3 WallInfo elements, the memory layout will look like this (they will all be solid memory areas, I divide them into lines so that they can be read):

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], 

in Delphi:

 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], 

So this FORTRAN call:

CALL HeatFlow (Wall area%, wall% azimuth)

just pass pointers to memory areas [0] and Azimuth [0] and the length of these memory areas.

In Delphi this is not possible, so you need to

  • build a new Area and Azimuth arays
  • copy them from the information in the array of the WallInfo record instance called Wall
  • send them functions
  • If these are var parameters: copy the changes from the two arrays back to Wall

Todd Grigsby and Remy Lebo demonstrated the first three steps in their answer using the direct Delphi code, or Delphi RTTI record.
Stage 4 works in a similar way.

Both solutions use the generics that were introduced in Delphi 2009.
Prior to Delphi 2010, RTTI on the record was very minimal ), so you got the correct version of Delphi for both answers.

Note (again): when translating your algorithms from FORTRAN to Delphi, make sure that you are observing loops and other indexing in arrays due to a large column / row change.

+7
source

To answer your question, no, there is no language construct or convenience method to split a single column from an array of records into a simple array.

I would recommend something like the following:

 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; 

This is if you want to use dynamic arrays. Personally, if you port this, I would use List and List, as in:

 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; 
+2
source

Source: https://habr.com/ru/post/922336/


All Articles