PDA

Ver la Versión Completa : Asignar valores a Arrays dinámicos - (migrar datos desde CLIPPER)


jplj
09-10-2007, 17:23:15
Hola:

Pretendo migrar unos datos de un programa hecho en clipper. Estos datos están en forma de matrices, de forma similar a:



FUNCTION L_72_F_32( nS, cC )

LOCAL aLineas:={}

DO CASE


CASE nS = 1 .AND. cC = "1V"
aLineas:={ ;
{ 100, 11.20, 0.00, 0.00, 9, 1, 0.10, -1, 1 },;
{ 500, 56.30, 2.30, 0.86, 9, 3, 1.10, -5, 5 },;
{ 700, 79.40, 3.30, 0.61, 9, 4, 1.60, -7, 7 },;
{ 1000, 114.70, 4.70, 0.42, 8, 6, 2.50, -10, 10 },;
{ 1200, 138.90, 5.70, 0.35, 8, 7, 3.10, -12, 12 },;
{ 1500, 176.20, 7.30, 0.28, 8, 9, 4.00, -15, 15 },;
{ 2000, 242.10, 9.90, 0.21, 7, 13, 5.60, -19, 20 },;
{ 2500, 314.70, 12.80, 0.16, 6, 18, 7.60, -24, 24 },;
{ 3000, 398.00, 16.00, 0.13, 5, 25, 10.00, -28, 28 },;
{ 3200, 436.10, 17.40, 0.12, 5, 30, 11.10, -29, 30 },;
{ 3500, 501.70, 19.80, 0.10, 4, 39, 13.30, -31, 32 },;
{ 3600, 527.00, 20.70, 0.10, 4, 44, 14.20, -32, 33 },;
{ 3700, 554.90, 21.70, 0.10, 3, 50, 15.20, -33, 34 },;
{ 3800, 586.50, 22.70, 0.09, 3, 60, 16.40, -33, 34 },;
{ 3900, 624.10, 24.00, 0.09, 2, 77, 17.90, -34, 35 },;
{ 4000, 673.60, 25.60, 0.08, 2, 77, 20.10, -35, 35 } ;
}

CASE nS = 1 .AND. cC = "2V"
aLineas:={ ;
{ 500, 44.70, 2.00, 0.96, 11, 2, 0.80, -5, 5 },;
{ 1000, 90.80, 4.20, 0.48, 11, 2, 1.90, -10, 10 },;
{ 1300, 119.30, 5.50, 0.37, 10, 3, 2.60, -13, 13 },;
{ 1500, 138.80, 6.40, 0.31, 10, 3, 3.00, -15, 15 },;
{ 2000, 189.30, 8.70, 0.23, 10, 4, 4.30, -19, 20 },;
{ 2500, 243.20, 11.20, 0.18, 9, 6, 5.60, -24, 24 },;
{ 3000, 301.80, 13.80, 0.15, 8, 8, 7.20, -28, 28 },;
{ 3500, 367.10, 16.60, 0.12, 7, 11, 9.00, -31, 32 },;
{ 4000, 443.10, 19.80, 0.10, 6, 15, 11.30, -35, 36 },;
{ 4300, 497.40, 21.90, 0.09, 5, 20, 13.10, -37, 38 },;
{ 4500, 539.80, 23.60, 0.09, 4, 25, 14.60, -38, 39 },;
{ 4600, 564.00, 24.50, 0.08, 4, 29, 15.50, -39, 40 },;
{ 4700, 591.00, 25.60, 0.08, 3, 34, 16.60, -39, 40 },;
{ 4800, 622.50, 26.70, 0.08, 3, 43, 17.80, -40, 41 },;
{ 4900, 661.70, 28.10, 0.07, 2, 62, 19.60, -40, 41 },;
{ 5000, 722.40, 30.20, 0.07, 0, 63, 22.50, -41, 41 } ;
}

CASE nS = 1 .AND. cC = "3V"

.....


ENDCASE

RETURN aLineas



Existen muchas funciones similares a FUNCTION L_72_F_32( nS, cC ) que devuelven aLineas al mismo procedimiento que se encarga de leer los datos necesarios en cada caso.
Aunque en la función expuesta sólo varía el número de filas, en otras funciones aLineas tiene distinto número de columnas, por lo que en principio creo que debo usar un array dinámico, el problema es cómo realizo la asignación de valores.
Por ahora he pensado en "traducir" los array de clipper en array de cadenas y posteriormente en una función cargar los datos en un vector numérico.


function L_72_F_32( nS, cC ): TArray_Dinamico_Real;
var
aLineas: TStringList;
begin


DO CASE


CASE nS = 1 .AND. cC = "1V"

aLinea.Add( '100, 11.20, 0.00, 0.00, 9, 1, 0.10, -1, 1' );
aLinea.Add( '500, 56.30, 2.30, 0.86, 9, 3, 1.10, -5, 5' );
....
aLinea.Add( '4000, 673.60, 25.60, 0.08, 2, 77, 20.10, -35, 35' );


...



result:= Conver_aLineas(aLineas);

end;



function Conver_aLineas(aLineas): TArray_Dinamico_Real;
var
A: TArray_Dinamico_Real;
begin

Dimensionar A;
Leer Valores de aLineas y cargarlos en A;

resutl:= A;

end;


Mi pregunta es si existe un método más eficiente y sencillo.

Un Saludo
Gracias de antemano.
Juan P.

Robert01
09-10-2007, 18:34:14
Hola

Yo tomaria los datos de un archivo de texto o de una tabla de una base de datos pero no se que vas a hacer con esos datos.

Saludos

Mick
09-10-2007, 18:38:22
Hay varias posibilidades, pero todo depende de como se vaya a acceder a los datos despues y como se vayan a usar, por ejempo podrias guardar la informacion en arrays estaticos:


const

L_72_F_32_1_1V: array [0..3,0..4] of double =
(
( 1,2,3,4,1 ),
( 2,3,4,5,3 ),
( 2,3,3,4,8 ),
( 2,2,3,4,9 )
);

L_72_F_32_1_2V: array [0..2,0..4] of double =
(
( 1,2,3,4,1 ),
( 2,3,4,5,3 ),
( 2,3,3,4,8 )
);

L_72_F_32_1_3V: array [0..3,0..4] of double =
(
( 1,2,3,4,1 ),
( 2,3,4,5,3 ),
( 2,3,3,4,8 ),
( 2,2,3,4,9 )
);


Y despues hacer una funcion o un objeto que recupere la informacion adecuada segun los parametros que se le pasen.

Tambien podrias guarada la informacion en un archivo externo, y cargarlo en un array dinamico,
en delphi un array dinamico de dos dimesiones puedes definirlo de esta forma:


var
Data: array of array of double;



Saludos

Lepe
09-10-2007, 20:07:41
... No acabo yo de pillar el tema....

Desde mi punto de vista, haría un programa que lea el archivo de clipper (se supone que puede abrirse con notepad ¿no?), lo parsea, y ahora crea un archivo de salida .pas con el contenido leido y traducido a codigo delphi.

No creas que es muy complicado hacerlo a mano, aunque TRegExpr puede servir de ayuda.

La idea:


procedure LeerArchivo(filename:string); // el archio clipper
var Clipper, Delphi:TStringlist;
begin
Clipper := TStringlist.Create;
Clipper.LoadFromFile(Filename);
Delphi := TStringList.Create;
CreaCabeceraDelphi;
for i:= 0 to Archivo.Count
if pos('FUNCTION ', Archivo[i]) <> 0 then
Procesa_matriz( Archivo, i);
end;
Clipper.Free;
Delphi.SaveToFile('c:\mio.pas');
end;

procedure CreaCabeceraDelphi;
begin
Delphi.Add('Unit unidad1;');
Delphi.Add('');
Delphi.Add('interface');
Delphi.Add('');
Delphi.Add('uses sysutils;');
Delphi.Add('');
Delphi.Add('var ');
end;

procedure Procesa_matriz(Lineas:TStrings; idx:integer);
var f:textfile;
begin

// en lineas tenemos todo el texto, a partir del índice idx tenemos la función que queremos parsear:
delete(lineas[idx], 'FUNCTION ',9);// quitamos el texto "function " de esa linea
assign(f, 'c:\mio.pas');
Append; // preparamos el archivo para seguir escribiendo en él.

writeln(Copy(Archivo[idx], 1, 9) + ' : array of array of double = (' );

Buscar_el_case_mas_cercano;

CloseFile(f);
end;


El resumen de todo esto, es que el contenido de "c:\mio.pas" queda como:

Unit Unidad1;

interface

uses sysutils;

var
L_72_F_32 : array of array of double = (

y hasta aquí he llegado yo :D

Tu nuevo archivo .pas tiene los arrays creados como si los hubieses escrito tu mismo. Después ese archivo .pas lo agregas al proyecto donde lo necesites.

Edito: Por supuesto lo he escrito todo de memoria... habrá cosas que ni compile, pero bueno... ahí queda. Ya puestos, puedes crear las funciones de clipper con parámetros incluidos para que la unidad final te quede como:

Unit Unidad1;

interface

uses sysutils;

var
L_72_F_32 : array of array of double = (
( 100, 11.20, 0.00, 0.00, 9, 1, 0.10, -1, 1 ),
( 500, 56.30, 2.30, 0.86, 9, 3, 1.10, -5, 5 ),
( 700, 79.40, 3.30, 0.61, 9, 4, 1.60, -7, 7 )
)

FUNCTION proc_L_72_F_32(ns:integer; CC:String):double;


implementation

FUNCTION proc_L_72_F_32(ns:integer; CC:String):double;
begin
if (nS = 1) AND (cC = '1V') then
Result := L_72_F_32[ los indices que sean ]
else if ...
end;

Saludos

Lepe
09-10-2007, 20:25:02
Al ver el código de Mick he recordado que al usar arrays inicializados, se necesita conocer los índices, pero bueno, no es problema tampoco, se puede deducir contando las "comas" que existen en cada línea que se va parseando, y las filas según el conteo de llaves abiertas y cerradas (por ejemplo).

Saludos

jplj
09-10-2007, 23:21:24
Muchas gracias por las respuestas.

Desde un primer momento he pensado en arrays dinámicos, y aunque el resultado final de la función lo sea, la solución es crear los arrays de datos estáticos tal como decís.

Presentáis dos opciones para definirlos, como variables o constantes: ¿cuál es la forma más adecuada de hacerlo?

La finalidad de todo esto es emplear el vector devuelto en un procedimiento que por interpolación -habitualmente polinómica- nos devuelva valores intermedios de la tabla.


Function DatosInter(ValorEntrada, col, p1, p2)

// ValorEntrada: se corresponde con la primera columna.
// col: es la columna para la que queremos obtener el dato.

aLineas:= L_72_F_32(p1, p2);
dato:= Interpola(aLineas, ValorEntrada, col)

Retrun dato


Por ejemplo, tomando los valores puesto en mi primer post:
DatosInter(2115, 2, 1, '1V')
Proporcionaría un valor similar a 265.9

Lepe
10-10-2007, 01:25:08
Al parecer hay que tener más datos en cuenta.

Debes hacerte la pregunta: ¿Deseo tener todas esas matrices en memoria o no?

Si resulta que solo accederas a una sola matriz varias veces, lo óptimo sería tener todos los datos guardados en disco, y recuperarlos en tiempo de ejecución.

Si vas a necesitar todas las matrices, o las vas a necesitar frecuentemente, quizás debas tenerlas en memoria.

Ya sea en forma de constantes o variables, al cargar tu aplicación se cargarán todas las matrices en memoria, si son muchas matrices... estas sobrecargando el sistema.

Otra idea más, Tener cada matriz en un archivo:
L_72_F_32_1_1V.dat
L_72_F_32_1_2V.dat

En tiempo de ejecución cargas el que necesites, lo adaptas a un array dinámico y listo. Usando un Stream puede resultar mucho más cómodo (busca por el foro).

De todas formas, la migración de Clipper a Delphi no te la quita nadie. Acabo de modificar la rutina "LeerArchivo" de mi mensaje anterior para hacerlo de una forma más clara.

Saludos

jplj
10-10-2007, 09:49:50
Hola:

Estoy probando lo que me habeis dicho:

Unit datos;

interface

uses sysutils;


type
TVector = Array of array of double;

var

L_72_F_32_1_1V: array [0..3,0..4] of double =
(
( 0,2,3,4,1 ),
( 1,3,4,5,3 ),
( 2,3,3,4,8 ),
( 3,2,3,4,9 )
);

L_72_F_32_1_2V: array [0..2,0..4] of double =
(
( 1,2,3,4,1 ),
( 2,3,4,5,3 ),
( 2,3,3,4,8 )
);

L_72_F_32_1_3V: array [0..3,0..4] of double =
(
( 1,2,3,4,1 ),
( 2,3,4,5,3 ),
( 2,3,3,4,8 ),
( 2,2,3,4,9 )
);

FUNCTION proc_L_72_F_32(ns:integer; CC:String):TVector;


implementation


FUNCTION proc_L_72_F_32(ns:integer; CC:String): TVector;
begin

if (nS = 1) AND (cC = '1V') then
Result := L_72_F_32_1_1V
else if (nS = 1) AND (cC = '2V') then
Result := L_72_F_32_1_2V
else if (nS = 1) AND (cC = '3V') then
Result := L_72_F_32_1_3V
end if
end;

end.


Pero al compilar me dice -lineas marcada en rojo de proc_L_72_F_32- que los tipos Array - TVector son incompatible .

He modificado la función de la siguiente forma:

FUNCTION proc_L_72_F_32(ns:integer; CC:String): TVector;
var
v: TVector;
i, h: integer;
begin


if (nS = 1) AND (cC = '1V') then
begin
Setlength(v, High(L_72_F_32_1_1V)+1, High(L_72_F_32_1_1V[0])+1);
for i:= High(v) downto Low(v) do
begin
for h:= High(v[i]) downto Low(v[i]) do
v[i,h]:= L_72_F_32_1_1V[i,h];
end;
end
else if (nS = 1) AND (cC = '2V') then
Setlength(v, High(L_72_F_32_1_2V)+1, High(L_72_F_32_1_2V[0])+1);
for i:= High(v) downto Low(v) do
begin
for h:= High(v[i]) downto Low(v[i]) do
v[i,h]:= L_72_F_32_1_2V[i,h];
end;
else if (nS = 1) AND (cC = '3V') then
....
end if

result:= v;
end;


Con lo que en cada caso hay que "recorrer" el array para asignarselo al array dinámico.

¿Alguna forma de mejorar esto, o de realizar una asiganción "directa"?

Mick
10-10-2007, 11:55:34
Aunque se pudiese realizar una asignacion directa, seria algo ineficiente porque el programa tendria igualmente que copiar los datos de una variable a otra .
La ventaja de usar directamente los datos de los arrays es que es muy rapido no se necesita nunca copiar esos arrrays de un lado a otro ya estan en memoria en su sitio una vez cargado el ejecutable.

Si necesitas la maxima eficiencia, tendrias que trabajar con punteros, aunque se puede ocultar la complejidad de usar punteros definiendo y utilizando una clase que facilite el acceso a los datos.

Todo depende de si esos arrays cambian el numero de columnas y/o filas. Si esos arrays tienen el mismo numero de columnas y filas, lo mas rapido y eficiente es usar un puntero a un array para devolver los datos, ejemplo:



type
// Definimos mas filas de las que realmente pudiera tener cualquier array que inicialicemos, como accederemos despues a traves de un puntero
// no importa que ALineas tenga muchas mas filas que los datos reales.
ALineas= array [0..1023,0..4] of double;
PLineas= ^ALineas;

function proc_L_72_F_32(ns:integer; CC:String): PLineas
begin
if (nS = 1) AND (cC = '1V') then
Result := @L_72_F_32_1_1V
else if (nS = 1) AND (cC = '2V') then
Result := @L_72_F_32_1_2V
else if (nS = 1) AND (cC = '3V') then
Result := @L_72_F_32_1_3V
end if
end;

function test;
va
Ptr: PLineas;
begin

Ptr:= proc_L_72_F_32( ... );

// Y Accederiamos a los datos de la siguiente manera
Ptr^[0,3];
Ptr^[1,2];
Ptr^[2,3];
end;



Si los arrays tienen numero de filas variables, el problema es que en un array estatico no se sabe a posteriori cuantas filas tiene asi que o se usa alguna otra constate que nos lo diga (usando un tipo record por ejemplo que almacena tanto el array como el numero de filas) o se crea una ultima fila "en blanco" con algun valor numerico que nunca pueda salir en los datos reales y que marque el final.



L_72_F_32_1_3V: array [0..3,0..4] of double =
(
( 1,2,3,4,1 ),
( 2,3,4,5,3 ),
( 2,3,3,4,8 ),
( 2,2,3,4,9 ),
( 0,0,0,0,0 ) // cuando recorremos el array al llegar a esta fila sabemos que no hay mas
);



Si el numero de filas y columnas varía luego es un pelin mas complejo, si realmente necesitas un sistema con todo variable (filas y columnas) , indicalo e intentare poner un ejemplo.

Saludos

jplj
10-10-2007, 12:51:41
El funcionamiento del programa es el siguiente.

En un archivo prg tenemos las tablas de datos, esta tablas está agrupadas en funciones en base al tipo de datos que proporcionan. De esta forma en este archivo nos encontramos con funciones similares a:


FUNCTION L_72_F_32( nS, cC ) -> devuelve tablas con valores del tipo F

FUNCTION L_72_G_32( nS, cC ) -> devuelve tablas con valores del tipo G

FUNCTION L_72_HJ_32( nS, cC ) -> devuelve tablas con valores del tipo HJ

FUNCTION L_72_TM_32( nS, cC ) -> devuelve tablas con valores del tipo TM



Dentro de cada función los array tienen el mismo número de columnas, pero el número de filas varía.

Entre los arrays devuelto por diferentes funciones varía también el número de columnas.

El array devuelto por esta funciones es recuperado en una función similar a:

FUNCTION LeeLinea(nS, cC, cClase)
LOCAL aDatos:= {}

IF cClase = "F"
aDatos:= L_72_F_32( nS, cC )
Else If cClase = "G"
aDatos:= L_72_G_32( nS, cC )
Else If cClase = "HJ"
aDatos:= L_72_HJ_32( nS, cC )
Else If cClase = "TM"
aDatos:= L_72_TM_32( nS, cC )
End if

RETURN aDatos



Por último una función de intrepolación trata los datos obtenidos:

FUNCTION IntrePolacion(cClase, nValorEntrada, nIndice, sS, c)
LOCAL nValor:= 0;
aDatos:= {}

aDatos:= FUNCTION (nS, cC, cClase)

nValor:= Interpolar(aDatos, nValorEntrada, nIndice, cClase)

RETURN nValor



Mick
La ventaja de usar directamente los datos de los arrays es que es muy rapido no se necesita nunca copiar esos arrrays de un lado a otro ya estan en memoria en su sitio una vez cargado el ejecutable

¿Cómo?:confused:

Lepe
10-10-2007, 19:11:33
Yo creo que nos estamos mareando demasiado. Hay tantas opciones que nos perdemos, para mí la solución más limpia sería:


type TVector = Array of array of double;

function LeeLinea(nS, cC, cClase:string):TVector;
var ruta:string;
begin
ruta := Extractfilepath(Application.Exename) + 'L_72_'+ cClase+ '_'+ns+'_'+cC+'.dat';

Result:= CargaDatos(ruta);
end;


La rutina establece dinamicamente las columnas y las filas del array y devuelve los datos; cuando dicha variable pierda su visibilidad, delphi liberará la memoria del array, punto y final.

Nos quitamos de encima los siguientes puntos:
- el nombre de las funciones que tanto nos marea.
- los punteros que se arrastrarían por todo el programa.
- Tener en memoria todos los arrays cargados.

No estamos hablando de arrays con 1.000.000 de datos, son unos 100 o 200 que se leerán de un solo golpe del disco duro, y el tiempo en hacerlo no se notará.

Los archivos se guardarían en binario, para guardarse directamente como números floats (nos ahorramos leer como texto y convertir cada uno a número). La migración requiere un paso más, pero el resultado creo lo merece.

Saludos

Lepe
10-10-2007, 20:59:14
Para muestra un boton:

Saludos

Mick
11-10-2007, 13:09:42
Una forma de hacerlo con un minimo uso de memoria y rapido acceso a los datos.


unit uDataMatrix;

//##############################################################################
interface
//##############################################################################

uses SysUtils;

type

TDataArray= array [0..MaxInt div 16-1] of double;
PDataArray= ^TDataArray;

TDataMatrix= class
private
FColCount,FRowCount:integer;
FData: PDataArray;
procedure SetData(AData:PDataArray; ARows,ACols:integer);
function GetCell(ARow,ACol:integer):double;
public
procedure Select(Model:string); overload;
procedure Select(Family:string; nS:integer; cC:string); overload;
property RowCount:integer read FRowCount;
property ColCount:integer read FColCount;
property Cells[ARow,ACol:integer]:double read GetCell; default;
end;

var
DataMatrix: TDataMatrix;


//##############################################################################
implementation
//##############################################################################

type
RModel= record
Name:string;
Rows:integer;
Cols:integer;
Data:Pointer;
end;

var

//#######################################################################
//#### Datos, cualquier nuevo modelo solo hace falta añadirlo aqui ######
//#######################################################################

L_72_F_32_1_1V: array [0..19] of double = ( 0,2,3,4,1, 1,3,4,5,3, 2,3,3,4,8, 3,2,3,4,9 );
L_72_F_32_1_2V: array [0..14] of double = ( 1,2,3,4,1, 2,3,4,5,3, 2,3,3,4,8 );
L_72_F_32_1_3V: array [0..13] of double = ( 1,2,3,4,1,3,4, 2,3,4,5,3,2,3 );
OTRFAMILY_1_3V: array [0..13] of double = ( 1,2,3,4,1,3,4, 2,3,4,5,3,2,3 );

Models: array [0..3] of RModel= (
( Name:'L_72_F_32_1_1V'; Rows:4; Cols:5; Data:@L_72_F_32_1_1V ),
( Name:'L_72_F_32_1_2V'; Rows:3; Cols:5; Data:@L_72_F_32_1_2V ),
( Name:'L_72_F_32_1_3V'; Rows:2; Cols:7; Data:@L_72_F_32_1_3V ),
( Name:'OTRFAMILY_1_3V'; Rows:2; Cols:7; Data:@OTRFAMILY_1_3V )
);

//####### Metodos ########################################################

function TDataMatrix.GetCell(ARow,ACol:integer):double;
begin
Result:= FData^[ARow*FColCount+ACol];
end;

procedure TDataMatrix.SetData(AData:PDataArray; ARows,ACols:integer);
begin
FData := AData;
FRowCount:= ARows;
FColCount:= ACols;
end;

procedure TDataMatrix.Select(Model:string);
var
i:integer;
begin
for i:=High(Models) downto 0 do
if Models[i].Name=Model then begin
SetData( Models[i].Data, Models[i].Rows, Models[i].Cols );
Exit;
end;
Raise Exception.Create('Unknow Model');
end;

procedure TDataMatrix.Select(Family:string; nS:integer; cC:string);
begin
Select( Format( '%s_%d_%s' ,[family,nS,cC]) );
end;

//###############################################################
// Creamos un objeto para acceder a los datos (en el arranque del programa)
// Lo que sigue a continuacion se puede eliminar si queremos crear nuestros
// propios objetos en el programa solo cuando sean necesarios
initialization
begin
DataMatrix:= TDataMatrix.Create;
end;
//###############################################################
finalization
begin
DataMatrix.free;
end;

end.


Como usar la unit:


uses uDataMatrix;

// Seleccionar modelo/datos
DataMatrix.Select('L_72_F_32', 1,'3V');
// Usar los datos
for Row:=0 to DataMatrix.RowCount-1 do
for Col:=0 to DataMatrix.ColCount-1 do
Sum:= sum+ DataMatrix[Row,Col];
// Otra forma de seleccionar los datos que nos interesen
DataMatrix.Select('L_72_F_32_1_3V');

// Si no queremos usar el objeto que crea la unit al arrancar
// Podemos crear otros objetos

var
Data:TDataMatrix;
begin
Data:= TDataMatrix.Create;
try
Data.Select('L_72_F_32',1,'3V');
Data.RowCount; // Numero de filas
Data.ColCount; // Numero de colmnas
Data[columna,fila]; // Acceder a un dato de la matriz
//
Data.Select('OTROMODELO', 1,'1V');
..etc.
finally
Data.Free;
end;

end;



Saludos

Lepe
11-10-2007, 14:50:16
¡¡ Que derroche de ingenio Mick!! :eek::eek: :rolleyes:

Saludos

jplj
15-10-2007, 11:49:48
En primer lugar agradeceros vuestras respuesta.

Debido principalmente a las peculiares características de los usuarios finales de la aplicación -son unos increíbles ingenieros de I+D- voy a emplear el método expuesto por Mick. Emplear ficheros de datos exigiría entre otras cosas verificar que no han sido modificados por los citados "ingenieros" antes de proceder a su lectura.

Un saludo.
Juan P.