English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Fundamentos de programación en MQL5 - Arrays

Fundamentos de programación en MQL5 - Arrays

MetaTrader 5Ejemplos | 8 abril 2014, 16:49
5 246 0
Dmitry Fedoseev
Dmitry Fedoseev

Introducción

Junto con las variables y las funciones, los arrays forman prácticamente una parte integrante de cualquier lenguaje de programación. Como se ha observado, algunas personas que empiezan a estudiar la programación tienen horror a los arrays. ¡Parece extraño, pero es verdad! Puedo asegurarles que no hay que temerlos. En realidad, los arrays son como las variables comunes. Si no entramos en los detalles de las particularidades de la notación, no hay gran diferencia entre escribir una expresión utilizando variables simples:

Variable0=1;
Variable1=2;

Variable2=Variable0+Variable1;

o utilizando arrays:

double Variable[3];

Variable[0]=1;
Variable[1]=2;

Variable[2]=Variable[0]+Variable[1];

Como vemos la diferencia no es tan grande. Lo único que cuando usamos los arrays, en los nombres de las variables se utilizan los corchetes. Hay otra diferencia más importante: al declarar las variables, hay que especificar el nombre de cada variable; cuando se declara el array, bastará con escribir el nombre sólo una vez, indicando entre los corchetes el número de las variables (cantidad de elementos del array). Las ventajas del uso de los arrays en comparación con las variables serán aún más evidentes cuando vamos a solucionar muchas tareas reales de programación.

¿Tal vez las dificultades del uso de los arrays estés relacionadas de alguna manera con el uso de los símbolos "[" y "]"? Estos símbolos se utilizan muy rars veces, sólo en la programación cuando se trabaja con los arrays, por eso el usuario puede olvidar su ubicación en el teclado lo que le causa ciertas inconvenientes. En realidad, es muy fácil recordar su ubicación en el teclado: son dos teclas que se sitúan al lado de la tecla "Enter" en secuencia lógica (primero va el corchete de apertura, luego el corchete de cierre).


Definición y propiedades principales del array

Pues bien, el array es un conjunto numerado de variables con el mismo nombre. Las principales propiedades del array son las siguientes: nombre del array, tipo de sus variables (int, double, etc.) y el tamaño del array. Los elementos del array se empiezan a contar a partir de cero. En cuanto a la numeración de los elementos del array es mejor usar la palabra "índice" en vez de "número", lo que supone que empezamos a contar a partir de cero (mientras que la numeración normalmente se realiza a partir de uno). Con esta indexación el último elemento tiene el valor del índice a uno menos que el número total de los elementos en el array.

Si el array está declarado así:

double Variable[3];

tiene los siguientes elementos: Variable[0], Variable[1], Variable[2].

A primera vista la falta de correspondencia entre el número de los elementos y el índice del último elemento puede parecer incómoda. En realidad, eso ofrece unas importantes ventajas en comparación con los lenguajes de programación donde los elementos del array son indexados desde 1, o el tamaño del array se determina por el índice de su último elemento en vez de la cantidad real de todos sus elementos.

Para determinar el tamaño del array, en el lenguaje MQL5 se utiliza la función ArraySize():

double Variable[3];

int Size=ArraySize(Variable);

Después de ejecutar este código, la variable Size va a tener el valor 3.


Arrays estáticos y dinámicos

Los arrays pueden ser estáticos y dinámicos. Si en la declaración del array se indica su tamaño, entonces se trata de un array estático:

double Variable[3];

El tamaño de un array estático no se podrá cambiar en el programa. Al declarar un array, puedemos especificar su tamaño directamente con un número (como se muestra en el ejemplo de arriba), o bien a través de una constante predefinida:

#define SIZE 3

double Variable[SIZE];

Si durante la declaración del array no se especifica su tamaño, este array será dinámico:

double Variable[];

Antes de usar el array de este tipo, hay que definir su tamaño. El tamaño se define a través de la función ArrayResize():

ArrayResize(Variable,3);

El tamaño de un array dinámico se puede cambiar cuantas veces sea necesario durante el proceso de ejecución del programa, en esto consiste su principal diferencia de un array estático.

Si hace falta liberar totalmente un array, se utiliza la función ArrayFree():

ArrayFree(Variable);

Cuando se ejecuta esta función, el tamaño del array se define como 0. El efecto producido por esta función es similar a lo siguiente:

ArrayResize(Variable,0);

La liberación del array puede ser útil cuando este array ya no es necesario para el siguiente trabajo del programa (eso permite reducir el volumen de la memoria operativa que utiliza el programa), o al principio de ejecución de alguna función (si el array se utiliza para recoger algunos datos).

Además, podemos utilizar la función ArrayIsDynamic() para averiguar si un array es estático o dinámico:

bool dynamicArray=ArrayIsDynamic(Variable);

Si el array es dinámico, la variable dynamicArray va a tener el valor true, si es estático, el valor va a ser false.


Inicialización del array

A veces surge la necesidad de rellenar el array con valores directamente después de su declaración. Supongamos que hay que crear varios botones del mismo tipo y colocarlos en una fila, además cada botón debe llevar su propio texto. Precisamente es el caso cuando los arrays descubren sus grandes ventajas. No es necesario copiar el código para crear cada botón (pueden ser incluso varias decenas), e incluso no hace falta llamar la misma función varias veces. Se puede crear la cantidad necesaria de los botones repitiendo el array en el ciclo, después de escribir el código de llamada a la función sólo una vez.

Declaramos el array e inmediatamente asignamos los valores a sus elementos:

string Variable[] = {"Botón 1", "Botón 2", "Botón 3"};

Con esta declaración, a pesar de que el tamaño del array no esté especificado, este array es estático porque el número de sus elementos se determina por la lista de valores (entre las llaves).

Tampoco será un error si ponemos el número de los elementos del array:

string Variable[3] = {"Botón 1", "Botón 2", "Botón 3"};

Pero mejor no hacerlo, porque durante las mejoras adicionales del programa puede surgir la necesidad de modificar la lista de los valores del array o reducir o aumentar la cantidad de elementos. Para determinar el tamaño del array, en las partes del código donde se utiliza este array, se recomienda utilizar la función ArraySize() en vez de un valor numérico concreto. Este enfoque permite cambiar sólo la lista de valores sin alterar el código principal. Será más apropiado declarar una variable para el tamaño del array y asignarle el valor obtenido por la función ArraySize() durante la inicialización del programa.

Si el array estático no se inicializa por la lista de valores, será mejor utilizar una constante para indicar su tamaño. En general, seguimos el principio de reducción del número de las las partes a modificar, por si vamos a necesitar realizar algunas mejoras del programa. Si hay que rellenar todos los elementos del array con los mismos valores, utilizamos la función ArrayInitialize():

ArrayInitialize(Variable,1);

Tras la ejecución de este código, todos los elementos del array Var van a tener el valor 1. Para asignar el mismo valor sólo a algunos elementos del array, se utiliza la función ArrayFill():

double Variable[4];

ArrayFill(Variable,0,2,1);
ArrayFill(Variable,2,2,2);

Tras la ejecución de este código, los elementos 0 y 1 van a tener el valor 1, y los elementos 2 y 3 van a tener el valor 2.


Repaso del array en el ciclo

El trabajo con los arrays suele realizarse utilizando el ciclo for. Cuando se utiliza un array estático cuyo tamaño se conoce de antemano, en función de la tarea que se realiza repasamos el array en adelante o hacia atrás:

//--- en sentido directo
for(int i=0; i<SIZE; i++){ 
  // aquí algunas acciones con el elemento Variable[i]
}

//--- en sentido contrario
for(int i=SIZE-1; i>=0; i--){
  // aquí algunas acciones con el elemento Variable[i]
}

Si se trata de un array dinámico, justo antes de ejecutar el ciclo hay que declarar una variable para el tamaño del array, obtener el tamaño del array y luego ejecutar el ciclo:

int Size=ArraySize(Var);

for(int i=0; i<Size; i++){
  // aquí algunas acciones con el elemento Variable[i]
}

Si en vez de usar la variable para el tamaño, Usted llama a la función ArraySize() durante la comprobación de la condición en el ciclo for, eso puede aumentar considerablemente el tiempo de ejecución de este ciclo porque la función ArraySize() va a invocarse en cada iteración del ciclo. La llamada a la función requiere más tiempo que la llamada a la variable:

for(int i=0; i<ArraySize(Variable); i++){
   // aquí algunas acciones con el elemento Variable[i]
}
No se recomienda utilizar este código.

Si el algoritmo del programa permite realizar la iteración del ciclo en dirección contraria, se puede no utilizar la variable para el tamaño.

for(int i=ArraySize(Variable)-1; i>=0; i--){
  // aquí algunas acciones con el elemento Variable[i]
}

En este caso la función ArraySize() será invocada sólo una vez al inicio del ciclo, y el ciclo va trabajar rápido.


Arrays multidimensionales

Hasta ahora hemos considerado sólo los arrays unidimensionales (vectores) que se puede representar en forma de la regla:

Vector unidimensional

Los arrays pueden ser multidimensionales (matrices). Si en un array unidimensional a un índice le corresponde un valor, en un array multidimensional son varios valores. Los arrays multidimensionales se declaran de la siguiente manera:

double Variable[10][3];

Eso quiere decir que en la primera dimensión del array se encuentran diez elementos y en la segunda hay tres. Gráficamente el array de este tipo se puede representar de la siguiente manera:

Matriz multidimensional

Para que se entienda mejor, un array bidimensional puede ser representado como un plano. El tamaño de la primera dimensión determina el largo, el tamaño de la segunda, el ancho, el valor del elemento define la característica de este punto del plano, por ejemplo, la altura sobre el nivel del mar.

Además, un array puede ser de tres dimensiones (tridemensional):

double Variable[10][10][10];

Se puede representar este array en forma de un cubo o paralelogramo: una dimensión determina el largo, la segunda determina el ancho, la tercera determina el alto, y el valor define la característica del punto del espacio.

El número máximo permitido de las dimensiones de los arrays en MQL5 asciende a 4.

Un array multidimensional puede ser estático o dinámico sólo en la primera dimensión, todas las demás dimensiones son estáticas. De esta manera, la función ArrayResize() permite cambiar el tamaño sólo en la primera dimensión. Los tamaños de las demás dimensiones tienen que especificarse durante la declaración del array:

double Variable[][3][3];

A la hora de determinar el tamaño de un array multidimensional utilizando la función ArraySize(), hay que tener en cuenta una cosa: al cambiar el tamaño del array utilizando la función ArrayResize(), el segundo parámetro de la función es el tamaño de la primera dimensión del array. Sin embargo, la función ArraySize() devuelve el número total de elementos en vez del tamaño de la primera dimensión:

double Variable[][3][3]; 

ArrayResize(Variable,3); 
int Size = ArraySize(Variable);

Después de ejecutar este código, la variable Size va a tener el valor 27. Recuerde esta particularidad cuando va a repasar los arrays multidimensionales en el ciclo si quiere calcular el tamaño de la primera dimensión:

double Variable[][3][3];
 
ArrayResize(Variable,3); 

int Size=ArraySize(Variable)/9; // Determina el tamaño de la primera dimensión

for(int i=0; i<Size; i++) {
   for(int j=0; j<3; j++) {
      for(int k=0; k<3; k++) {
            //  aquí algunas acciones con el elemento Var[i][j][k];
      }   
   }   
}

Como hemos mencionado antes, durante la codificación es aconsejable seguir el principio de reducción de las partes a editar para la realización de las siguientes mejoras. En el ejemplo del código que acabamos de mostrar se utiliza el 9. No obstante, se puede calcularlo. Para eso podemos utilizar la función ArrayRange() que devuelve el número de elementos que contiene la dimensión especificada. Si sabemos el número de la dimensiones del array, podemos hacer un simple cálculo:

int Elements=ArrayRange(Variable,1)*ArrayRange(Variable,2);
int Size=ArraySize(Variable)/Elements;

Podemos hacerlo de manera más universal:

int Elements=1; // Un elemento para un array unidimensional
int n=1; // Empezamos con la segunda dimensión (las dimensiones llevan la numeración desde cero)

while(ArrayRange(Variable,n) > 0){ // Hasta que haya elementos en la dimensión
   Elements*=ArrayRange(Variable,n); // Multiplicación de la cantidad de elementos
   n++; // Aumentamos el número de la dimensión
}

En este punto le pueden entrar ganas de crear una función para este cálculo. Es una lástima, pero es imposible porque no se puede pasar a la función un array aleatorio. Durante la declaración del argumento de la función hay que indicar claramente el número de elementos en todas las dimensiones del array salvo la primera dimensión. Entonces, el sentido de la función se pierde, sería mejor hacer estos cálculos durante la inicialización del programa. Durante la declaración del array es deseable utilizar las constantes que determinan los tamaños de las dimensiones:

#define SIZE1 3
#define SIZE2 3
#define TOTAL SIZE1*SIZE2 

La inicialización de los arrays multidimensionales mediante la lista de valores se realiza de la misma forma que la inicialización de los arrays unidimensionales. Puesto que un array se compone prácticamente de varios otros arrays, cada uno de ellos debe ir separado con las llaves.

Por ejemplo, tenemos el array:

double Variable[3][3];

Este array está compuesto de tres arrays con tres elementos cada uno:

double Variable[][3]={{1, 2, 3},{ 4, 5, 6},{7, 8, 9}};

Lo mismo pasa con el array de tres dimensiones. Su código puede dividirse por líneas para que sea más cómodo orientarse en la estructura del array:

double Variable[][3][3]={
   {
      {1, 2, 3},
      {4, 5, 6},
      {7, 8, 9}
   },
   {
      {10, 20, 30},
      {40, 50, 60},
      {70, 80, 90}
   },
   {
      {100, 200, 300},
      {400, 500, 600},
      {700, 800, 900}
   }
};

La inicialización de un array multidimensional por la función ArrayInitialize() se hace de la misma manera que la inicialización de un array unidimensional:

ArrayInitialize(Variable,1);

Después de ejecutar este código, todos los elementos del array van a tener el valor 1. Lo mismo se refiere a la función ArrayFill():

double var[3][3][3];

ArrayFill(Variable,0,9,1);
ArrayFill(Variable,9,9,10);
ArrayFill(Variable,18,9,100);

Después de ejecutar este código, todos los elementos referentes al primer elemento de la primera dimensión van a tener el valor 1, referentes al segundo elemento, el valor 10, referentes al tercer elemento, el valor 100.


Pasar el array a la función

A diferencia de las variables, los arrays se pasan a la función sólo por referencia. Eso significa que la función no crea su ejemplar del array, sino trabaja directamente con el array que se le ha sido pasado. Es decir, todos los cambios que realiza la función se reflejan en el array original.

Si la variable se pasa a la función de manera usual (por valor), la función no puede cambiar el valor de esta variable.

int x=1;
Func(x);

void  Func(int arg){
   arg=2;
}

Después de ejecutar la función Func(), el valor x se queda igual a 1.

Si la variable se pasa por referencia (lleva el signo &), la función puede cambiar el valor de la variable pasada:

int x=1;
Func(x);

void  Func(int &arg){
   arg=2;
}

Después de ejecutar la función Func(), el valor x es igual a 2.

Cuando el array se pasa a la función, hay que indicar que el argumento se pasa por referencia, y que se pasa el array (entre los corchetes):

void Func(double &arg[]){
   // ...
}

Cuando se pasan los arrays multidimensionales, hay que indicar los tamaños de las dimensiones, a excepción de la primera:

double var[][3][3];

void Func(double &arg[][3][3]){
   // ...
}

En este caso es mejor utilizar las constantes:

#define SIZE1 3
#define SIZE2 3

double Var[][SIZE1][SIZE2];

void Func(double &arg[][SIZE1][SIZE2]){
   // ...
}


Guardar y cargar los arrays desde los archivos

Al guardar y cargar un array desde el archivo, hay que tener en cuenta la diferencia de los valores del tamaño de la primera dimensión del array y el número total de los elementos del array. A la hora de guardar el array, primero vamos a escribir en el archivo el tamaño del array (el número total de los elementos que se determina por la función ArraySize()), luego el array entero:

bool SaveArrayToFile(string FileName,string &Array[])
  {
//--- Abrir archivo
   int h=FileOpen(FileName,FILE_TXT|FILE_WRITE);
   if(h==-1) return(false); // Error de apertura del archivo
//--- Escribir en el archivo
   FileWriteInteger(h,ArraySize(Array),INT_VALUE); // Escribir el tamaño del array
   FileWriteArray(h,Array); // Escribir el array
//--- Cerrar archivo
   FileClose(h);
   return(true); // Guardado con éxito
  }

Como resultado, tenemos una función bastante universal para guardar los arrays unidimensionales.

Durante la carga desde el archivo, primero vamos a leer el tamaño del array, luego cambiamos el tamaño del array y leemos el array:

bool LoadArrayFromFile(string FileName,double &Array[])
  {
//--- Abrir archivo
   int h=FileOpen(FileName,FILE_BIN|FILE_READ);
   if(h==-1) return(false); // Error de apertura del archivo
//--- Leer el archivo
   int Size=FileReadInteger(h,INT_VALUE); // Leer el número de elementos del array
   ArrayResize(Array,Size); // Cambiar el tamaño del array. 
                            // En un array unidimensional el tamaño de la primera dimensión es igual al número de los elementos del array.
   FileReadArray(h,Array); // Leer el array desde el archivo
//--- Cerrar archivo
   FileClose(h);
   return(true); // Lectura con éxito
  }

Cuando cargamos un array multidimensional desde el archivo, tenemos que calcular el tamaño de la primera dimensión. Por ejemplo, leemos desde al archivo un array de tres dimensiones:

bool LoadArrayFromFile3(string FileName,double &Array[][SIZE1][SIZE2])
  {
//--- Abrir archivo
   int h=FileOpen(FileName,FILE_BIN|FILE_READ);
   if(h==-1)return(false); // Error de apertura del archivo
//--- Leer el archivo   
   int SizeTotal=FileReadInteger(h,INT_VALUE); // Leer el número de elementos del array
   int Elements=SIZE1*SIZE2; // Calculamos el número de elementos 
   int Size=SizeTotal/Elements; // Calculamos el tamaño de la primera dimensión
   ArrayResize(Array,Size); // Cambiar el tamaño del array
   FileReadArray(h,Array); // Leer el array
//--- Cerrar archivo
   FileClose(h);
   return(true); // Lectura con éxito
  }

Puede pasar que el archivo contiene un array, por ejemplo, con los tamaños 2 por 3, y nosotros intentamos leerlo como 3 por 3. Usted puede verificar la correspondencia de los tamaños multiplicando el tamaño calculado de la primera dimensión por el número de los elementos. Si el valor resultante es igual al número total de los elementos del array, entonces hay correspondencia.

Sin embargo, el array Var[2][3] va a corresponder al array Var[3][2]. Si en este caso también necesita hacer la verificación, entonces a la hora de guardar un array multidimensional hay que guardar más información sobre él. Por ejemplo, primero guardar el número de los elementos del array, luego el número de las dimensiones, los tamaños de cada dimensión, y por fin el propio array.

La última función no va a ser universal y va a utilizarse sólo para leer los arrays tridimensionales cuyo tamaño de la segunda dimensión es igual a SIZE1, y el tamaño de la tercera dimensión es igual a SIZE2. Ya que no existe ninguna posibilidad del cambio dinámico de los tamaños de todas las dimensiones del array, a excepción de la primera, eso no supone un problema: creamos las funciones para aquellos arrays con los que vamos a trabajar en el programa.

En este caso la universalidad tampoco es necesaria: los tamaños de las dimensiones del array (salvo la primera dimensión) no van a controlarse por los parámetros externos del programa. Y si necesita poder controlar los tamaños de las demás dimensiones, puede solucionar este problema utilizando un array multidimensional de tamaño deliberadamente mayor y las variables adicionales, o bien, a través de la Programación Orientada a Objetos (POO). Hablaremos del segundo enfoque un poco más tarde en este artículo.


Uso de arrays dinámicos

Los arrays dinámicos se utilizan cuando no se sabe de antemano de qué tamaño tiene que ser el array. Si el tamaño del array depende de los parámetros establecidos en la ventana de propiedades del programa, el uso de los arrays dinámicos no supone ningún problema: el tamaño del array se cambia sólo una vez durante la inicialización.

Se puede utilizar el array para recopilar algunos datos de forma dinámica, por ejemplo la información sobre las órdenes pendientes. Su cantidad puede variar. Es decir, no conocemos de antemano el tamaño de este array. En este caso, más fácil sería cambiar el tamaño a cero antes de pasar por las órdenes, y aumentar el array a un elemento a medida que pasemos cada orden. Este método va a funcionar pero muy lento.

Se puede cambiar una vez el tamaño del array en función de la cantidad de las órdenes antes de pasar por ellas. En este caso nos hará falta una variable más para el índice del último elemento ocupado en el array (o para la cantidad real de los elementos ocupados, en vez del índice). Este método nos vale si sabemos de antemano el tamaño máximo del array. Si ignoramos el tamaño máximo del array, podemos acelerar el trabajo con él mediante el cambio del tamaño del array utilizando los bloques, tal como se muestra en la siguiente clase:

class CDynamicArray
  {
private:
   int               m_ChunkSize;    // Tamaño del bloque
   int               m_ReservedSize; // Tamaño real del array
   int               m_Size;         // Número de elementos activos en el array
public:
   double            Element[];      // El propio array. Se encuentra en la sección public 
                                     // para poder trabajar con él directamente en caso de necesidad
   //+------------------------------------------------------------------+
   //|   Constructor                                                    |
   //+------------------------------------------------------------------+
   void CDynamicArray(int ChunkSize=1024)
     {
      m_Size=0;                            // Número de elementos activos
      m_ChunkSize=ChunkSize;               // Tamaño del bloque
      m_ReservedSize=ChunkSize;            // Tamaño real del array
      ArrayResize(Element,m_ReservedSize); // Preparando array
     }
   //+------------------------------------------------------------------+
   //|   Función de adición al final del array                          |
   //+------------------------------------------------------------------+
   void AddValue(double Value)
     {
      m_Size++; // Aumentar el número de elementos activos
      if(m_Size>m_ReservedSize)
        { // El número requerido supera el tamaño real del array
         m_ReservedSize+=m_ChunkSize; // Calculamos nuevo tamaño del array
         ArrayResize(Element,m_ReservedSize); // Aumentamos el tamaño real del array
        }
      Element[m_Size-1]=Value; // Agregamos el valor
     }
   //+------------------------------------------------------------------+
   //|   Función para obtener el número de elementos activos en el array|
   //+------------------------------------------------------------------+
   int Size()
     {
      return(m_Size);
     }
  };

Esta clase se encuentra en el archivo "CDynamicArray.mqh" de los archivos adjuntos. El archivo tiene que ubicarse en la carpeta "MQL5\Include" de la carpeta de datos del terminal.

Vamos a comparar velocidad de funcionamiento del código en ambas situaciones: cuando aumentamos el array en 1 de una manera consecutiva y cuando aumentamos el tamaño del array utilizando los bloques:

int n=50000;

   double ar[];
   CDynamicArray da;

//--- Opción 1 (aumento del tamaño en un elemento)
   long st=GetTickCount(); // Recordamos el tiempo de inicio 
   ArrayResize(ar,0); // Ponemos el tamaño a cero 
   for(int i=0;i<n;i++)
     {
      ArrayResize(ar,i+1); // Cambio sucesivo del tamaño
      ar[i]=i;
     }
   Alert("Opción 1: "+IntegerToString(GetTickCount()-st)+" mseg"); // Mensaje sobre el tiempo requerido para realizar la primera opción

//--- Opción 2 (aumento del tamaño utilizando bloques)
   st=GetTickCount(); // Recordamos el tiempo de inicio 
   for(int i=0;i<n;i++)
     {
      da.AddValue(i); // Agregamos elemento
     }
   Alert("Opción 2: "+IntegerToString(GetTickCount()-st)+" mseg"); // Mensaje sobre el tiempo requerido para realizar la segunda opción

  }

Esta prueba ha sido realizada en forma del script que se encuentra en el archivo "sTest_Speed.mq5" de los archivos adjuntos. El archivo tiene que ubicarse en la carpeta «MQL5\Scripts" de la carpeta de datos del terminal.

La ejecución de la primera opción ha durado unos cuantos segundos, la segunda opción ha sido prácticamente instantánea.


Dirección de la indexación del array

Normalmente, al cambiar el tamaño del array, los nuevos elementos se añaden al final del array:

double ar[]; // Массив
ArrayResize(ar,2); // Preparación del array
ar[0]=1; // Establecer los valores
ar[1]=2; 
ArrayResize(ar,3); // Aumento del tamaño del array
ar[2]=3; // Establecer el valor para nuevo elemento
Alert(ar[0]," ",ar[1]," ",ar[2]); // Mostrar los valores del array

Después de ejecutar este código, los valores contenidos en el array van a ser 1, 2, 3.

Los arrays pueden tener la indexación inversa de los elementos. El tipo de la indexación lo establece la función ArraySetAsSeries():

ArraySetAsSeries(ar,true); // establecer la indexación inversa
ArraySetAsSeries(ar,false); // establecer la indexación normal

Cuando se cambia el tamaño del array con indexación inversa, el nuevo elemento se añade al inicio del array:

double ar[]; // Array
ArrayResize(ar,2); // Preparación del array
ar[0]=1; // Establecer los valores
ar[1]=2; 
ArraySetAsSeries(ar,true); // Cambio de la dirección de la indexación
ArrayResize(ar,3); // Aumento del tamaño del array
ar[0]=3; // Establecer el valor para nuevo elemento del array
Alert(ar[0]," ",ar[1]," ",ar[2]); // Mostrar los valores del array

Después de ejecutar este código, los valores contenidos en el array van a ser 3, 2, 1.

Pues, resulta que en ambos casos el nuevo elemento se inserta en el mismo lado del array, la única diferencia consiste en el orden de la indexación de elementos. No se puede utilizar esta función para añadir los elementos al inicio de un array con la indexación normal. Si el array tiene la indexación normal, entonces para añadir un elemento al final del array sólo hay que aumentar su tamaño y asignar un valor al último elemento.

Para añadir un elemento al inicio del array, tenemos que aumentar el tamaño del array, mover todos los valores y asignar un valor nuevo al elemento cero. Es muy fácil añadir un elemento nuevo al inicio de un array indexado de manera inversa. Pero si hace falta añadir un elemento nuevo al final de este array, entonces después de aumentar su tamaño, hay que mover todos los valores al inicio y asignar un valor nuevo al último elemento. Las manipulaciones con el orden de indexación no resuelven este problema.

Para averiguar la dirección de la indexación del array, se utiliza la función ArrayIsSeries():

bool series=ArrayIsSeries(ar);

Si el array tiene la indexación inversa, la función devuelve true.

El principal campo de aplicación de los arrays con indexación inversa son los Asesores Expertos. A la hora de programarlos resulta más cómodo contar las barras de derecha a izquierda, y en consecuencia, copiar los datos de precios y búferes indicadores a los arrays con la indexación inversa.


Copiar los arrays

Lo más fácil es repasar el array en el ciclo y copiar paso a paso cada elemento de un array al otro. Sin embargo, en MQL5 hay una función especial para copiar los arrays, ArrayCopy():

double ar1[]={1,2,3};
double ar2[];

ArrayCopy(ar2,ar1);

Después de ejecutar este código, el array ar2 va componerse de tres elementos que van a tener los mismos valores que en el array ar1: 1, 2, 3.

Si el número de los elementos a copiar no cabe en array de destino, entonces su tamaño se aumenta automáticamente (el array tiene que ser dinámico). Si los elementos caben, el tamaño del array de destino se queda intacto.

La función ArrayCopy() permite realizar el copiado parcial de los arrays. Utilizando los parámetros opcionales de la función, se puede indicar la siguiente información: el inicio de los elementos a copiar en el array de origen, el índice a partir del cual van a ubicarse los elementos copiados en el array de destino, y el número de los elementos a copiar.

Además, se puede utilizar la función ArrayCopy() no sólo para copiar los elementos de un array al otro, sino también para el copiado de los elementos del mismo array:

double ar1[]={1,2,3,4,5};
ArrayCopy(ar1,ar1,1,2);

Cogemos los datos partiendo del elemento con el índice 2, y los colocamos a partir del índice 1. Después de ejecutar este código, el array va a contener los siguientes valores: 1, 3, 4, 5, 5.

La función ArrayCopy() también permite mover los datos a la derecha:

double ar1[]={1,2,3,4,5};
ArrayCopy(ar1,ar1,2,1);

Cogemos los datos partiendo del elemento con el índice 1, y los colocamos a partir del índice 2. Después de ejecutar este código, el array va a contener los siguientes valores: 1, 2, 2, 3, 4.

También se puede aplicar la función ArrayCopy() a los arrays multidimensionales, como si el array fuera unidimensional y todos sus elementos estibieran organizados en serie.

double ar[3][2]={{1, 2},{3, 4},{5, 6}};
ArrayCopy(ar,ar,2,4);

Después de ejecutar este código, el array va a ser el siguiente: {1, 2}, {5, 6}, {5, 6}.


Ordenar el array

Para ordenar un array, se utiliza la función ArraySort():

double ar[]={1,3,2,5,4};
ArraySort(ar);

Después de ejecutar este código, los valores del array van a estar ordenados en el siguiente orden: 1, 2, 3, 4, 5.

La función ArraySort() no vale para los arrays multidimensionales. Usted puede encontrar la información sobre el ordenamiento de los arrays multidimensionales y las estructuras de datos en el artículo Las tablas electrónicas en MQL5.


Para realizar la búsqueda binaria se utiliza la función ArrayBsearch(). Esta función va a trabajar correctamente sólo con los arrays ordenados. La búsqueda binaria se llama así porque durante su ejecución se realiza la división consecutiva del array en dos partes. Primero el algoritmo compara el valor a buscar con el elemento central del array. De esta manera se determina la mitad en la que se encuentra el elemento: la de inicio o la de final. A continuación, se realiza la comparación con el elemento central de la mitad, y así sucesivamente.

La función ArrayBsearch() devuelve el índice del elemento con el valor de búsqueda:

double ar[]={1,2,3,4,5};

int index=ArrayBsearch(ar,3);

Tras la ejecución de este código, la variable index va a tener el valor 2.

Si en el array no hay valor que se busca, la función devolverá el índice del elemento con el valor menor más cercano. Gracias a esa propiedad, se puede utilizar dicha función para buscar las barras por el tiempo. Si no hay ninguna barra con el tiempo establecido, en los cálculos hay que utilizar la barra con el tiempo menor.

Si el valor buscado no se encuentra en el array y sale fuera del rango de los valores del array, la función devolverá 0 (el valor buscado es menor que el valor mínimo) o el último índice (el valor buscado es mayor que el valor máximo).

Para la búsqueda en un array no ordenado existe sólo una opción: repaso del array.

int FindInArray(int &Array[],int Value){
   int size=ArraySize(Array);
      for(int i=0; i<size; i++){
         if(Array[i]==Value){
            return(i);
         }
      }
   return(-1);
}

En este ejemplo la función devuelve el índice del elemento con el valor que se buscaba. Si el elemento con el valor de búsqueda no está, la función devuelve -1.


Búsqueda del máximo y del mínimo

Para la búsqueda del valor máximo y el valor mínimo, se utiliza la función ArrayMaximum() y ArrayMinimum(). La función devuelve el índice del elemento con el valor máximo y el mínimo, respectivamente:

double ar[]={3,2,1,2,3,4,5,4,3};

int MaxIndex=ArrayMaximum(ar);
int MinIndex=ArrayMinimum(ar);
double MaxValue=ar[MaxIndex];
double MinValue=ar[MinIndex];

Después de la ejecución de este código, en la variable MaxIndex va a haber el valor 6, en MinIndex va a haber el valor 2, en MaxVaue va a haber el valor 5, y en MinValue va a haber el valor 1.

Las funciones ArrayMaximum() y ArrayMinimum() permiten limitar el rango de búsqueda dentro del array. Para eso hay que indicar el índice del elemento para empezar la búsqueda y el número de elementos que hay que revisar:

int MaxIndex=ArrayMaximum(ar,5,3);
int MinIndex=ArrayMinimum(ar,5,3);

En este caso, MaxIndex va a tener el valor 6, MinIndex va a tener el valor 5. Por favor, fíjense que en el rango especificado el valor mínimo 4 lo podemos encontrar dos veces (en la posición 5 y en la posición 7), la función ha devuelto el índice del elemento más cercano al inicio del array. Estas funciones trabajan de la misma manera con los arrays que tienen la indexación inversa, devuelven el menor índice.

Pues, hemos revisado todas las funciones estándar del lenguaje MQL5 que sirven para trabajar con los arrays.


Matriz multidimensional utilizando POO

El conjunto de las clases para crear un array multidimensional incluye tres clases: una clase base y dos clases derivadas (herederos). Dependiendo del heredero seleccionado en la etapa de creación del objeto, el objeto puede representar un array de variables double o un array de objetos. Cada elemento del array de objetos puede representar otro array de objetos o variables.

La clase base y las clases derivadas prácticamente no contienen ningunas funciones, a excepción del destructor en la primera y constructores en las segundas. El destructor en la clase base sirve para eliminar todos los objetos después de que el programa o la función finalice su trabajo. Los constructores en las clases derivadas se utilizan sólo para redimensionar los arrays dependiendo del tamaño especificado en los parámetros del constructor: en una clase se redimensiona el array de objetos, en la otra, el array de variables. En el ejemplo de abajo se muestra el código de estas clases:

//+------------------------------------------------------------------+
//|   Clase base                                                     |
//+------------------------------------------------------------------+
class CArrayBase
  {
public:
   CArrayBase       *D[];
   double            V[];

   void ~CArrayBase()
     {
      for(int i=ArraySize(D)-1; i>=0; i--)
        {
         if(CheckPointer(D[i])==POINTER_DYNAMIC)
           {
            delete D[i];
           }
        }
     }
  };
//+------------------------------------------------------------------+
//|   Heredero 1                                                     |
//+------------------------------------------------------------------+
class CDim : public CArrayBase
  {
public:
   void CDim(int Size)
     {
      ArrayResize(D,Size);
     }
  };
//+------------------------------------------------------------------+
//|   Heredero 1                                                     |
//+------------------------------------------------------------------+
class CArr : public CArrayBase
  {
public:
   void CArr(int Size)
     {
      ArrayResize(V,Size);
     }
  };

Estas clases se encuentran en el archivo "CMultiDimArray.mqh" de los archivos adjuntos. El archivo tiene que ubicarse en la carpeta "MQL5\Include" de la carpeta de datos del terminal.

Ahora vamos a usar esta clase para crear una semejanza del array unidimensional:

CArrayBase * A; // Declaración del puntero
   A=new CArr(10); // Cargamos el ejemplo de la clase derivada que redimensiona el array de variables. 
                   // El array va a componerse de 10 elementos.

//--- Ahora podemos usar el array:
   for(int i=0; i<10; i++)
     {
      //--- Asignamos a cada elemento del array los valores sucesivos de 1 a 10
      A.V[i]=i+1;
     }
   for(int i=0;i<10;i++)
     {
      //--- Comprobamos los valores
      Alert(A.V[i]);
     }
   delete A; // Eliminación del objeto
  }

Este ejemplo ha sido realizado en forma del script que se encuentra en el archivo "sTest_1_Arr.mq5" de los archivos adjuntos. El archivo tiene que ubicarse en la carpeta "MQL5\Scripts" de la carpeta de datos del terminal.

Ahora intentaremos crear un archivo bidimensional. Cada elemento de la primera dimensión va a contener un número diferente de los elementos de la segunda dimensión: en el primero, uno; en el segundo, dos; etc.:

CArrayBase*A;  // Declaración del puntero
   A=new CDim(3); // La primera dimensión representa el array de objetos

//--- Cada objeto de la primera dimensión representa un array de variables de diferente tamaño 
   A.D[0]=new CArr(1);
   A.D[1]=new CArr(2);
   A.D[2]=new CArr(3);
//--- Asignamos el valor
   A.D[0].V[0]=1;

   A.D[1].V[0]=10;
   A.D[1].V[1]=20;

   A.D[2].V[0]=100;
   A.D[2].V[1]=200;
   A.D[2].V[2]=300;
//--- Comprobamos los valores
   Alert(A.D[0].V[0]);

   Alert(A.D[1].V[0]);
   Alert(A.D[1].V[1]);

   Alert(A.D[2].V[0]);
   Alert(A.D[2].V[1]);
   Alert(A.D[2].V[2]);
//---
   delete A; // Eliminación del objeto

Este ejemplo ha sido realizado en forma del script que se encuentra en el archivo "sTest_2_Dim.mq5" de los archivos adjuntos. El archivo tiene que ubicarse en la carpeta "MQL5\Scripts" de la carpeta de datos del terminal.

Los arrays obtenidos podrían considerarse como estáticos ya que en las clases no existen métodos para cambiar los tamaños de los arrays. Pero los arrays D[] y V[] se encuentran en la sección pública de la clase, por esa razón están disponibles para cualquier tipo de manipulación. Se puede redimensionar el array V[] sin ningún problema. Al redimencionar el array D[] y al reducir su tamaño, primero hay que eliminar los objetos a los que apuntan los elementos a eliminar. Y cuando se aumenta el tamaño, hay que cargar los objetos en ellos.

Si desea, puede inventar también otros modos de implementar los arrays multidimensionales utilizando POO o estructuras de datos.


Conclusión

En este artículo hemos examinado todas las funciones estándar del lenguaje MQL5 que sirven para trabajar con los arrays. Hemos abordado las particularidades y algunas técnicas más importantes del trabajo con los arrays. El lenguaje ofrece un total de 15 funciones. Algunas de ellas son sumamente importantes, otras pueden quedar prácticamente sin uso, a excepción de los casos cuando hay que solucionar problemas fuera de serie. En función de su importancia y frecuencia de uso, podemos organizarlas de la siguiente manera:

  1. ArraySize(), ArrayResize() son las funciones esenciales.

  2. ArrayMaximum(), ArrayMinimum(), ArrayCopy(), ArrayInitialize(), ArrayFill(), ArrayFree() son las funciones que facilitan mucho el trabajo con los arrays.

  3. ArraySort() es una función importante y útil, pero se utiliza poco debido a su baja funcionalidad.

  4. ArrayBsearch() es una función que se utiliza raramente, aunque es muy importante en raras situaciones excepcionales.

  5. ArraySetAsSeries(), ArrayRange(), ArrayGetAsSeries(), ArrayIsDynamic(), ArrayIsSeries() son las funciones que se utilizan muy raras veces, o casi nunca.

De todas las técnicas de programación descritas en este artículo se debe prestar atención especial al uso de los arrays dinámicos, ya que eso influye en gran medida en, y se puede decir determina, la velocidad de operación del programa.

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/567

Archivos adjuntos |
cdynamicarray.mqh (2.59 KB)
stest_speed.mq5 (1.6 KB)
cmultidimarray.mqh (1.67 KB)
stest_1_arr.mq5 (1.29 KB)
stest_2_dim.mq5 (1.43 KB)
Indicador para la representación del gráfico "Renko" Indicador para la representación del gráfico "Renko"
En el artículo vamos a hablar del gráfico "Renko" y mostraremos una de las variantes de su realización en el lenguaje MQL5, en forma de indicador. El indicador tiene multitud de modificaciones que lo distinguen del gráfico clásico. La representación se realiza no sólo en la ventana del indicador, sino también el gráfico principal. Además, se ve realizada la representación del indicador en forma de línea en "ZigZag". Les mostraremos, igualmente, varios ejemplos de estrategias de trabajo con el gráfico.
El reproductor de trading basado en el historial de las transacciones El reproductor de trading basado en el historial de las transacciones
El reproductor de trading. Solo son cuatro palabras, y no requieren ninguna explicación. Le viene a la mente un pequeño dispositivo con botones. Empieza a reproducir al presionar un botón y cambia la velocidad de reproducción al mover la palanca. En realidad, es muy parecido. En este artículo, quiero mostrar el reproductor que he desarrollado y que reproduce el historial de las operaciones como si fuera en tiempo real. El artículo trata algunos matices de la programación orientada a objetos, el trabajo con indicadores y la gestión de los gráficos.
Cómo trabajar con el módem GSM de un experto de MQL5 Cómo trabajar con el módem GSM de un experto de MQL5
En la actualidad existen medios suficientes para monitorizar a distancia una cuenta comercial, con toda comodidad: con la ayuda de los terminales móviles, las notificaciones push y el trabajo con ICQ. Pero para todo ello se debe tener conexión a internet. Este artículo describe la creación un experto que les permitirá mantenerse en contacto con su terminal comercial, incluso en el caso de que el internet móvil no está disponible, más concretamente con ayuda de llamadas y mensajes SMS.
Moving Mini-Max (Minimax móvil): un nuevo indicador de análisis técnico y su implementación en MQL5 Moving Mini-Max (Minimax móvil): un nuevo indicador de análisis técnico y su implementación en MQL5
En este artículo voy a describir el proceso de implementación del indicador Moving Mini-Max basado en el artículo publicado por Z.G. Silagadze "Movig Mini-Max: un nuevo indicador para el análisis técnico". El concepto de este indicador se basa en la simulación del fenómeno del túnel cuántico, propuesto por G. Gamov en la teoría de la desintegración alfa.