2012-08-08 23 views
6

Tengo que traducir algunos códigos Fortran 90 y encontré una función de idioma interesante.Dada una matriz de registros, ¿cómo puedo obtener una matriz que represente un campo de cada uno?

Como un ejemplo, se definen del siguiente tipo y dinámico-matriz de variables:

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

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

Más adelante en el código, se llama a una función:

CALL HeatFlow(Wall%Area, Wall%Azimuth) 

Como programador Delphi, esta arrojó un poco porque Wall es una variedad de discos!

A partir del uso en la rutina, está claro que Fortran puede proyectar campos de la matriz de registros como una matriz propia.

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

¿Alguien sabe si hay una manera de hacer esto con Delphi (estoy usando la versión de 2010)?

Podría escribir una función para extraer un valor de registro como una matriz, pero esto es un poco tedioso porque tendré que escribir una rutina dedicada para cada campo (y hay bastantes).

Espero que exista alguna característica del lenguaje en Delphi 2010 que me haya perdido.

+0

¿Intentó con una matriz o grabó en su matriz de caso de WallInfo? Delphi admite matrices dinámicas. Antes de ingresar un nuevo valor, primero configure el tamaño de la matriz con SetLength. –

+0

Por muy buena que sea la respuesta RTTI de Remy, me sentiría tentado a traducir el tipo de registro anterior en matrices lineales separadas: 'Nombre: matriz de cadena; Azimuth: matriz de doble; ... 'y luego no tendría que recopilar los datos usando este encantador truco RTTI porque ya estaría reunido. –

Respuesta

8

El uso extendido de RTTI, es posible crear una función genérica que tiene la matriz y un nombre de campo como entrada y utiliza el RTTI de la matriz para extraer solo los valores de ese campo y crear una nueva matriz con ellos, con el tipo de datos correcto.

El siguiente código funciona para mí en 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

Gracias por su ayuda, voy a probar este método. Estoy empezando a darme cuenta de que la traducción directa no es tan simple. Fortran parece tener algunas buenas características de matriz, pero intentar simularlas en Delphi no es muy eficiente. En lugar de pasar la matriz, simplemente pasé la matriz de pared completa en un solo parámetro. – bruce

+0

Remmy, tuve que cambiar el ciclo de I: = 0 a Length (Arr) -1 para hacerlo funcionar en Delphi 2010. Estoy impresionado, resuelve el problema y es bastante limpio. – bruce

+0

El código fue escrito y probado en XE2. ¿Estás diciendo que 'High()' no funciona en arreglos dinámicos en D2010? Actualicé mi respuesta con ese cambio. –

2

Para responder a su pregunta, no, no existe una construcción de lenguaje o método práctico para dividir una sola columna de una matriz de registros en una matriz simple propia.

recomendaría algo como lo siguiente:

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; 

Eso es, si desea utilizar matrices dinámicas. Personalmente, si está transfiriendo esto, que haría uso de la lista y la lista, como en:

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

Al utilizar RTTI extendido, es posible crear una función genérica que tome como entrada una matriz y un nombre de campo y utiliza el RTTI de la matriz para extraer los valores de ese campo y crear una nueva matriz con ellos. –

+0

Publiqué una respuesta con una demostración de RTTI ampliado en acción. –

7

Lo escribo como respuesta porque los comentarios están demasiado restringidos para pronunciar esto.

esta pregunta trata de explicar las diferencias en la distribución de la memoria de matrices y registros en FORTRAN y Delphi y modifica el answer por Todd Grigsby y la answer por Remy Lebeau (I upvoted ambos).

FORTRAN y algunos otros idiomas céntricos de cálculo almacenan matrices anidadas en column major order. Delphi y muchos otros idiomas usan un row major order.

Desde una perspectiva de memoria, un disco no es más que una serie de campos que:

  • tener un nombre y no un índice
  • puede tener diferentes tipos

Para el cálculo intensivo operaciones, puede tener sentido almacenar orden jerarquizada de columnas anidadas cuando sus algoritmos favorecen las columnas. Lo mismo para la orden principal de la fila. Entonces en loops, necesitas match the order of your indexes with the order of your storage.

Dada esta definición de registro y la matriz en FORTRAN:

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

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

y la definición equivalente funcional en Delphi:

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

var 
    Wall: array of WallInfo; 

y un conjunto de 3 elementos WallInfo, así es como el diseño de memoria se vería (todas serían áreas de memoria continuas, las dividí en líneas para mantenerlas legibles):

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

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

Así que esta llamada FORTRAN:

LLAMADA flujo de calor (Wall% Área, Wall% acimut)

habría sólo tiene que pasar los punteros de la zona [ 0] y Azimuth [0] ubicaciones de memoria y longitud de esas áreas de memoria para la función.

En Delphi, eso no es posible, así que hay que

  1. construir nuevo Espacio y Azimut Arays
  2. copiarlos de la información en la matriz de la instancia de registro WallInfo llama Wall
  3. enviarlos a la función
  4. si estos son parámetros var: copiar los cambios de las dos matrices de nuevo a la pared

Todd Grigsby y Remy Lebeau mostraron los primeros tres pasos en su respuesta usando código Delphi directo, o registro RTF de Delphi.
El paso 4 funciona de manera similar.

Ambas soluciones usan genéricos que se introdujeron en Delphi 2009.
Until Delphi 2010, RTTI on records was very minimal), por lo que obtuvo la versión correcta de Delphi para ambas respuestas.

Nota (otra vez): al traducir los algoritmos de FORTRAN a Delphi, asegúrese de mirar los bucles y otras indexaciones en las matrices debido al cambio importante de columna/fila.

+0

Jeroen, apreciamos sus comentarios sobre la indexación de bucle. Ya tropecé contra este problema. No estaba al tanto de la diferencia en las órdenes de arreglo entre Delphi y Fortran. Tiene mucho más sentido ahora. – bruce

+0

Me alegra que haya ayudado a obtener más información. ¡Sigue con las buenas preguntas! –

+0

No puedo ver nada en la pregunta que se relaciona con los problemas principales y principales de la fila. ¿Dónde surgen en la pregunta? La pregunta me parece sobre las instalaciones de Fortran para apoyar la proyección. Eso probablemente se implementará con una técnica de zancada a la numpy array. –

Cuestiones relacionadas