Fundamentos de programación en MQL5 - Listas

Denis Kirichenko | 30 mayo, 2014

Introducción

La nueva versión del lenguaje MQL5 ha proporcionado al programador que trabaja en los proyectos de sistemas automáticos de trading las eficaces herramientas para la implementación de complejas tareas. No se puede negar el hecho de que las capacidades de programación del lenguaje han sido ampliadas considerablemente. ¡La presencia de los recursos de la programación orientada a objetos (POO) en MQL5 ya vale bastante! Es más, no podemos olvidar mencionar la Librería estándar. A juzgar por la presencia del error con el código 359, muy pronto aparecerán las plantillas de las clases.

En este artículo me gustaría abordar el tema que puede ser de alguna manera la ampliación o continuación de los temas que describen los tipos de datos y sus conjuntos. Aquí, voy a hacer referencia a un artículo publicado en la web de MQL5.community. La descripción muy detallada y sistémica de los principios y la lógica del trabajo con los arrays ha sido propuesta por Dmitry Fedoseev (Integer) en su artículo «Fundamentos de programación en MQL5 - Arrays».

Bien, hoy propongo ocuparnos de las listas, o para ser más exacto de las listas enlazadas lineales. Empezaremos con la estructura, propósito y lógica de la construcción de la lista. Luego veremos las herramientas que ofrece la Librería estándar referentes a nuestro tema. A continuación, les mostraré los ejemplos de aplicación de las listas en su trabajo con el lenguaje MQL5.

  1. Concepto de la "Lista" y el "Nodo": teoría
  2. Concepto de la "Lista" y el "Nodo": programación
  3. Listas en la Librería estándar MQL5
  4. Ejemplos del uso de las listas en MQL5


1. Concepto de la "Lista" y el "Nodo": teoría

Pues bien, ¿qué es una lista para un programador y cómo manejarla? Voy a recurrir a la fuente pública Wikipedia para la definición general de este término:

En Ciencias de la Computación, una lista (en inglés list) es un tipo de datos abstracto que representa un conjunto ordenado de valores en el que un valor puede encontrarse más de una vez. La instancia de una lista es la representación computacional del concepto matemático de una secuencia finita: la tupla. Las instancias de los valores de la lista se llaman los elementos de la lista (en inglés item, entry o element). Si un valor se encuentra varias veces, cada entrada se considera un elemento separado.

El término «la lista» también determina ciertas estructuras de datos concretas que se utilizan para la implementación de las listas abstractas, sobre todo, listas enlazadas.

Espero que estén de acuerdo conmigo en que la definición es algo abstrusa.

Para los propósitos del presente artículo, estamos más interesados en la última frase de la definición. Por eso vamos a hacer hincapié en esta frase:

En Ciencias de la Computación, una lista enlazada es una estructura de datos dinámica fundamental que consiste en una secuencia de nodos cada uno de los cuales guarda tanto los datos, como una o dos referencias («enlaces») al nodo anterior o posterior de la lista. [1] La principal ventaja de las listas enlazadas respecto a los arrays es la flexibilidad estructural: el orden de los elementos de la lista enlazada puede ser diferente del orden de almacenamiento de los elementos en la memoria, y el orden de recorrido de la lista se establece explicitamente por sus enlaces internos.

Vamos a estudiarlos paso a paso.

En programación, la lista por sí misma ya es algún tipo de datos. Bien, eso ya lo hemos aclarado. Es más bien un tipo sintético porque incluye otros tipos de datos. Las listas parecen en cierta medida a los arrays. Si un array de datos del mismo tipo fuera clasificado como un tipo de datos nuevo, obtendríamos una lista. Pero no es exactamente así.

La principal ventaja de la lista consiste en su posibilidad de crecer o reducirse cuando es necesario. Y en eso la lista parece al array dinámico. Lo único, para la lista no hace falta aplicar constantemente la función ArrayResize().

Hablando del orden de almacenamiento de los elementos en la memoria, los nodos de la lista no se almacenan o no necesitan almacenarse del mismo modo como se almacenan los elementos del array en las zonas adyacentes de la memoria.

Pues, eso es todo más o menos. Seguimos adelante según la lista.


1.1 Nodo para una lista simplemente enlazada

La lista permite almacenar nodos en vez de elementos. El nodo es un tipo de datos compuesto de dos partes.

La primera parte es el campo de datos, la segunda se utiliza para los enlaces con otro(s) nodo(s) (Fig. 1). El primer nodo de la lista se llama head (la cabeza), y el último lleva el nombre tail (la cola). El puntero de la cola es NULL. Básicamente, se utiliza para apuntar al nodo fuera de la lista. En otros recursos especializados la cola la llaman al resto de la lista a excepción de la cabeza.


Fig. 1 Nodes in a singly linked list

Fig. 1 "Nodos para la lista enlazada simple"

Además del nodo para la lista simplemente enlazada hay otros tipos de nodos. Quizá el nodo para la lista doblemente enlazada sea el más popular.


1.2 Nodo para una lista doblemente enlazada

Además, vamos a necesitar un nodo que va a servir para las necesidades de la lista doblemente enlazada. A diferencia del tipo anterior, éste contiene un puntero más que hace referencia al nodo anterior. Y naturalmente, este nodo de la cabeza de la lista va a ser NULL. En el esquema se muestra la estructura de la lista que contiene este tipo de nodos (Fig. 2), y los punteros que se apuntan a los nodos anteriores se muestran con flechas rojas.


Fig. 2 Nodes in a doubly linked list

Fig.2 Nodos para la lista doblemente enlazada


En conclusión, las posibilidades del nodo para la lista doblemente enlazada van a ser las mismas que tiene el nodo para las listas simplemente enlazadas, a menos que surja la necesidad de procesar el puntero al nodo anterior.


1.3 Nodo para una lista circular doblemente enlazada

Hay casos cuando los nodos arriba mencionados pueden ser utilizados para las listas no lineales. Aunque este artículo va a tratar principalmente de las listas lineales, pondré un ejemplo de una lista circular.

Fig. 3 Nodes in a circular doubly linked list

Fig.3 Nodos para la lista circular doblemente enlazada


En el diagrama de la lista circular doblemente enlazada (Fig. 3) se puede observar que los nodos con dos punteros simplemente están ciclados. Esto ha sido hecho usando la flecha naranja y verde. Así, el nodo de cabeza va a usar uno de los punteros para referirse a la cola (como a un elemento anterior). Y el campo del enlace en el nodo de la cola tampoco va a estar vacío, ya que tendrá el puntero a la cabeza.


1.4 Operaciones básicas en la lista

Según la literatura especializada, todas las operaciones en la lista se puede dividir en 3 grupos básicos:

  1. Inserción (en la lista aparece un nodo nuevo);
  2. Eliminación (se elimina algún nodo de la lista);
  3. Comprobación (se solicitan los datos de algún nodo).

Los métodos de inserción incluyen los siguientes:

Respecto a las operaciones de eliminación, pues es prácticamente el espejo virtual de las operaciones de inserción:

Aquí podría destacar el destructor que no sólo tiene que finalizar el trabajo de la lista de forma correcta, sino también eliminar correctamente todos sus elementos.

Y el tercer grupo relacionado con diferentes tipos de comprobaciones, en realidad, proporciona el acceso a los nodos o a los valores de los nodos de la lista:

En adición a los grupos principales, me gustaría mencionar el cuarto grupo (grupo de servicio). Es un grupo auxiliar para los arriba mencionados:

Pues, eso es todo. Desde luego, si el programador lo desea, puede ampliar las posibilidades y la funcionalidad de la clase de listas en cualquier momento.


2. Concepto de la "Lista" y el "Nodo": programación

En esta parte del artículo, propongo pasar directamente a la programación de nodos y listas. Voy a ilustrar el código con diagramas en la medida en que fuera necesario.

2.1 Nodo para una lista simplemente enlazada

Vamos a crear una base para la clase del nodo (Fig. 4) que va a servir para las necesidades de la lista simplemente enlazada. Puede familiarizarse con la notación (maqueta) de la clase en el artículo "Cómo desarrollar un Asesor Experto usando las herramientas de UML" (ver Fig. 5. Modelo UML de la clase CTradeExpert).

Modelo de la clase CiSingleNode

Fig.4 Modelo de la clase CiSingleNode

Ahora vamos a intentar trabajar con el código. Está basado en un ejemplo del libro de Art Friedman y otros autores "C/C+. Archivo de programas".

//+------------------------------------------------------------------+
//|                     Clase CiSingleNode                           |
//+------------------------------------------------------------------+
class CiSingleNode
  {
protected:
   int               m_val;   // datos
   CiSingleNode     *m_next;  // puntero al siguiente nodo
public:
   void              CiSingleNode(void);                             // constructor por defecto
   void              CiSingleNode(int _node_val);                    // constructor paramétrico
   void             ~CiSingleNode(void);                             // destructor
   void              SetVal(int _node_val);                          // método set para los datos
   void              SetNextNode(CiSingleNode *_ptr_next);           // método set para el siguiente nodo
   virtual void      SetPrevNode(CiSingleNode *_ptr_prev){};         // método set para el nodo anterior
   virtual CiSingleNode *GetPrevNode(void) const {return NULL;};     // método get para el nodo anterior
   CiSingleNode     *GetNextNode(void) const;                        // método get para el siguiente nodo 
   int               GetVal(void){TRACE_CALL(_t_flag) return m_val;} // método get para los datos 
  };

No voy a explicar cada método de la clase CiSingleNode. Puede conseguir la información más detallada en el archivo CiSingleNode.mqh. Pues, me gustaría matizar una cosa interesante. La clase tiene métodos virtuales que trabajan con nodos anteriores. En realidad, son ficticios y su presencia se explica con los propósitos de polimorfismo para los futuros descendentes.

En el código se utiliza la directiva del preprocesador TRACE_CALL(f). Es necesaria para el emplazamiento de la llamada de cada método utilizado.

#define TRACE_CALL(f) if(f) Print("Calling: "+__FUNCSIG__);

Teniendo sólo la clase CiSingleNode ya podemos crear una lista enlazada simple. El ejemplo del código.

//=========== Example 1 (processing the CiSingleNode type )
 
   CiSingleNode *p_sNodes[3];                             // #1
   p_sNodes[0]=NULL;
   srand(GetTickCount()); // initialize a random values generator
//--- create nodes
   for(int i=0;i<ArraySize(p_sNodes);i++)
      p_sNodes[i]=new CiSingleNode(rand());               // #2
//--- links
   for(int j=0;j<(ArraySize(p_sNodes)-1);j++)
      p_sNodes[j].SetNextNode(p_sNodes[j+1]);             // #3
//--- check values
   for(int i=0;i<ArraySize(p_sNodes);i++)
     {
      int val=p_sNodes[i].GetVal();                       // #4
      Print("Node #"+IntegerToString(i+1)+                // #5
            " value = "+IntegerToString(val));
     }
//--- check next-nodes
   for(int j=0;j<(ArraySize(p_sNodes)-1);j++)
     {
      CiSingleNode *p_sNode_next=p_sNodes[j].GetNextNode(); // #9
      int snode_next_val=p_sNode_next.GetVal();             // #10
      Print("Next-Node #"+IntegerToString(j+1)+             // #11
            " value = "+IntegerToString(snode_next_val));
     }
//--- delete nodes
   for(int i=0;i<ArraySize(p_sNodes);i++)
      delete p_sNodes[i];                                   // #12 

En la cadena #1 declaramos el array de punteros a los objetos del tipo CiSingleNode.  En la cadena #2 llenamos el array con punteros creados. Para los datos de cada nodo cogemos un número pseudoaleatorio entero de 0 a 32767 usando la función rand(). En la cadena #3 enlazamos los nodos a través del puntero next. En las cadenas #4-5 comprobamos los valores de los nodos. En las cadenas #9-11 comprobamos el resultado de ejecución de los enlaces. Eliminamos los punteros en la cadena #12.

Es lo que tendremos en el log.

DH      0       23:23:10        test_nodes (EURUSD,H4)  Node #1 value = 3335
KP      0       23:23:10        test_nodes (EURUSD,H4)  Node #2 value = 21584
GI      0       23:23:10        test_nodes (EURUSD,H4)  Node #3 value = 917
HQ      0       23:23:10        test_nodes (EURUSD,H4)  Next-Node #1 value = 21584
HI      0       23:23:10        test_nodes (EURUSD,H4)  Next-Node #2 value = 917

Gráficamente, la estructura de los nodos obtenidos se puede representar de la siguiente manera (Fig. 5).

"Enlaces entre los nodos en el array CiSingleNode *p_sNodes[3]"

Fig. 5 "Enlaces entre los nodos en el array CiSingleNode *p_sNodes[3]"

Ahora nos centramos en el nodo para la lista doblemente enlazada.


2.2 Nodo para una lista doblemente enlazada

Primero, vamos a recordar que el nodo en una lista doblemente enlazada se diferencia con que tiene 2 punteros: uno al nodo next (siguiente), y el otro al nodo previous (anterior). Es decir, a un nodo de la lista simplemente enlazada hay que añadirle un puntero al nodo anterior.

Propongo utilizar en este caso la herencia como relación de las clases. Entonces, el modelo para la clase del nodo de la lista doblemente enlazada será el siguiente (Fig. 6).

Modelo de la clase CDoubleNode

Fig.6 Modelo de la clase CDoubleNode

Ahora nos toca estudiar el código.

//+------------------------------------------------------------------+
//|                    Clase CDoubleNode                             |
//+------------------------------------------------------------------+
class CDoubleNode : public CiSingleNode
  {
protected:
   CiSingleNode     *m_prev;  // puntero al nodo anterior 

public:
   void              CDoubleNode(void);                     // constructor por defecto
   void              CDoubleNode(int node_val);             // constructor paramétrico
   void             ~CDoubleNode(void){TRACE_CALL(_t_flag)};// destructor
   virtual void      SetPrevNode(CiSingleNode *_ptr_prev);  // método set para el nodo anterior
   virtual CiSingleNode *GetPrevNode(void) const;           // método get para el nodo anterior CDoubleNode
  };

Aquí hay muy pocos métodos adicionales- son virtuales y están relacionados con el trabajo con el nodo anterior. La descripción completa de la clase se encuentra en el archivo CDoubleNode.mqh.

Vamos a intentar crear una lista doblemente enlazada a base de la clase CDoubleNode. Aquí tenemos el ejemplo del código.

//=========== Example 2 (processing the CDoubleNode type)

   CiSingleNode *p_dNodes[3];                             // #1
   p_dNodes[0]=NULL;
   srand(GetTickCount()); // initialize a random values generator
//--- create nodes
   for(int i=0;i<ArraySize(p_dNodes);i++)
      p_dNodes[i]=new CDoubleNode(rand());                // #2
//--- links
   for(int j=0;j<(ArraySize(p_dNodes)-1);j++)
     {
      p_dNodes[j].SetNextNode(p_dNodes[j+1]);             // #3
      p_dNodes[j+1].SetPrevNode(p_dNodes[j]);             // #4
     }
//--- check values
   for(int i=0;i<ArraySize(p_dNodes);i++)
     {
      int val=p_dNodes[i].GetVal();                       // #4
      Print("Node #"+IntegerToString(i+1)+                // #5
            " value = "+IntegerToString(val));
     }
//--- check next-nodes
   for(int j=0;j<(ArraySize(p_dNodes)-1);j++)
     {
      CiSingleNode *p_sNode_next=p_dNodes[j].GetNextNode(); // #9
      int snode_next_val=p_sNode_next.GetVal();             // #10
      Print("Next-Node #"+IntegerToString(j+1)+             // #11
            " value = "+IntegerToString(snode_next_val));
     }
//--- check prev-nodes
   for(int j=0;j<(ArraySize(p_dNodes)-1);j++)
     {
      CiSingleNode *p_sNode_prev=p_dNodes[j+1].GetPrevNode(); // #12
      int snode_prev_val=p_sNode_prev.GetVal();               // #13
      Print("Prev-Node #"+IntegerToString(j+2)+               // #14
            " value = "+IntegerToString(snode_prev_val));
     }
//--- delete nodes
   for(int i=0;i<ArraySize(p_dNodes);i++)
      delete p_dNodes[i];                                     // #15 

Prácticamente, es lo mismo que sucede durante la creación de la lista simplemente enlazada a diferencia de unas peculiaridades. Fíjense, en la cadena #1 se declara el array de punteros p_dNodes[]. El tipo de punteros puede ser el mismo que tiene la clase base. El principio de polimorfismo en la cadena #2 ayudará reconocerlos en el futuro. En las cadenas #12-14 se comprueban los nodos prev (anteriores).

En el log va a figurar la siguiente información.

GJ      0       16:28:12        test_nodes (EURUSD,H4)  Node #1 value = 17543
IQ      0       16:28:12        test_nodes (EURUSD,H4)  Node #2 value = 1185
KK      0       16:28:12        test_nodes (EURUSD,H4)  Node #3 value = 23216
DS      0       16:28:12        test_nodes (EURUSD,H4)  Next-Node #1 value = 1185
NH      0       16:28:12        test_nodes (EURUSD,H4)  Next-Node #2 value = 23216
FR      0       16:28:12        test_nodes (EURUSD,H4)  Prev-Node #2 value = 17543
LI      0       16:28:12        test_nodes (EURUSD,H4)  Prev-Node #3 value = 1185

Gráficamente, la estructura de los nodos obtenidos se puede representar de la siguiente manera (Fig. 7).

"Enlaces entre los nodos en el array CDoubleNode *p_sNodes[3]"

Fig. 7 Enlaces entre los nodos en el array CDoubleNode *p_sNodes[3]

Ahora propongo pasar al nodo que será útil para "construir" una lista doblemente enlazada usando nodos de arrays.


2.3 Nodo para una lista doblemente enlazada usando nodos de arrays

Imagínese que hay un nodo que contiene un miembro de datos que no se refiere a un valor, sino a un array entero. Es decir, contiene y describe un array entero. Entonces se puede utilizar el nodo de este tipo para formar una lista enlazada con nodos de arrays. No lo he mostrado gráficamente porque es exactamente idéntico a un nodo estándar para una lista doblemente enlazada. La única diferencia es que el atributo "datos" incluye un array entero.

Volvemos a utilizar la herencia. La clase base para el nodo de la lista doblemente enlazada usando nodos de arrays (unrolled doubly linked list) será la clase CDoubleNode. El modelo para la clase del nodo de este tipo de la lista enlazada será el siguiente (Fig. 8).

Modelo de la clase CiUnrollDoubleNode

Fig.8 Modelo de la clase CiUnrollDoubleNode

La clase CiUnrollDoubleNode puede ser definida usando el siguiente código:

//+------------------------------------------------------------------+
//|                    Clase CiUnrollDoubleNode                      |
//+------------------------------------------------------------------+
class CiUnrollDoubleNode : public CDoubleNode
  {
private:
   int               m_arr_val[]; // array de datos

public:
   void              CiUnrollDoubleNode(void);               // constructor por defecto 
   void              CiUnrollDoubleNode(int &_node_arr[]);   // constructor paramétrico
   void             ~CiUnrollDoubleNode(void);               // destructor
   bool              GetArrVal(int &_dest_arr_val[])const;   // método get para el array de datos
   bool              SetArrVal(const int &_node_arr_val[]);  // método set para el array de datos
  };

El archivo CiUnrollDoubleNode.mqh contiene la descripción detallada de cada método.

Vamos a ver un constructor paramétrico como ejemplo.

//+------------------------------------------------------------------+
//|                   Constructor paramétrico                        |
//+------------------------------------------------------------------+
void CiUnrollDoubleNode::CiUnrollDoubleNode(int &_node_arr[])
   : CDoubleNode(ArraySize(_node_arr))
  {
   ArrayCopy(this.m_arr_val,_node_arr);
   TRACE_CALL(_t_flag)
  }

Aquí, utilizando las lista de inicialización, introducimos el tamaño del array unidimensional en el miembro de datos this.m_val.

Después, crearemos "manualmente" una lista doblemente enlazada usando nodos de arrays y comprobaremos sus enlaces.

//=========== Example 3 (processing the CiUnrollDoubleNode type)

//--- data arrays
   int arr1[],arr2[],arr3[];                                  // #1
   int arr_size=15;
   ArrayResize(arr1,arr_size);
   ArrayResize(arr2,arr_size);
   ArrayResize(arr3,arr_size);
   srand(GetTickCount()); // initialize a random values generator
   
//--- fill the arrays with pseudorandom integers   
   for(int i=0;i<arr_size;i++)
     {
      arr1[i]=rand();                                         // #2
      arr2[i]=rand();
      arr3[i]=rand();
     }
//--- create nodes
   CiUnrollDoubleNode *p_udNodes[3];                          // #3
   p_udNodes[0]=new CiUnrollDoubleNode(arr1);
   p_udNodes[1]=new CiUnrollDoubleNode(arr2);
   p_udNodes[2]=new CiUnrollDoubleNode(arr3);
//--- links
   for(int j=0;j<(ArraySize(p_udNodes)-1);j++)
     {
      p_udNodes[j].SetNextNode(p_udNodes[j+1]);               // #4
      p_udNodes[j+1].SetPrevNode(p_udNodes[j]);               // #5
     }
//--- check values
   for(int i=0;i<ArraySize(p_udNodes);i++)
     {
      int val=p_udNodes[i].GetVal();                          // #6
      Print("Node #"+IntegerToString(i+1)+                    // #7
            " value = "+IntegerToString(val));
     }
//--- check arrays values
   for(int i=0;i<ArraySize(p_udNodes);i++)
     {
      int t_arr[]; // destination array 
      bool isCopied=p_udNodes[i].GetArrVal(t_arr);            // #8
      if(isCopied)
        {
         string arr_str=NULL;
         for(int n=0;n<ArraySize(t_arr);n++)
            arr_str+=IntegerToString(t_arr[n])+", ";
         int end_of_string=StringLen(arr_str);
         arr_str=StringSubstr(arr_str,0,end_of_string-2);
         Print("Node #"+IntegerToString(i+1)+                 // #9
               " array values = "+arr_str);
        }
     }
//--- check next-nodes
   for(int j=0;j<(ArraySize(p_udNodes)-1);j++)
     {
      int t_arr[]; // destination array 
      CiUnrollDoubleNode *p_udNode_next=p_udNodes[j].GetNextNode(); // #10
      bool isCopied=p_udNode_next.GetArrVal(t_arr);
      if(isCopied)
        {
         string arr_str=NULL;
         for(int n=0;n<ArraySize(t_arr);n++)
            arr_str+=IntegerToString(t_arr[n])+", ";
         int end_of_string=StringLen(arr_str);
         arr_str=StringSubstr(arr_str,0,end_of_string-2);
         Print("Next-Node #"+IntegerToString(j+1)+
               " array values = "+arr_str);
        }
     }
//--- check prev-nodes
   for(int j=0;j<(ArraySize(p_udNodes)-1);j++)
     {
      int t_arr[]; // destination array 
      CiUnrollDoubleNode *p_udNode_prev=p_udNodes[j+1].GetPrevNode(); // #11
      bool isCopied=p_udNode_prev.GetArrVal(t_arr);
      if(isCopied)
        {
         string arr_str=NULL;
         for(int n=0;n<ArraySize(t_arr);n++)
            arr_str+=IntegerToString(t_arr[n])+", ";
         int end_of_string=StringLen(arr_str);
         arr_str=StringSubstr(arr_str,0,end_of_string-2);
         Print("Prev-Node #"+IntegerToString(j+2)+
               " array values = "+arr_str);
        }
     }
//--- delete nodes
   for(int i=0;i<ArraySize(p_udNodes);i++)
      delete p_udNodes[i];                                            // #12
  }

El número de las cadenas del código ha crecido un poco. Eso se debe a la necesidad de crear y llenar el array para cada uno de los nodos.

El trabajo con los arrays de datos empieza en la cadena #1. Básicamente, tenemos lo mismo que pasaba con los nodos anteriores. Lo único que ahora tendremos que imprimir los valores de datos de cada nodo para el array entero (por ejemplo, la cadena #9).

Es lo que me ha salido:

IN      0       00:09:13        test_nodes (EURUSD.m,H4)        Node #1 value = 15
NF      0       00:09:13        test_nodes (EURUSD.m,H4)        Node #2 value = 15
CI      0       00:09:13        test_nodes (EURUSD.m,H4)        Node #3 value = 15
FQ      0       00:09:13        test_nodes (EURUSD.m,H4)        Node #1 array values = 31784, 4837, 25797, 29079, 4223, 27234, 2155, 32351, 12010, 10353, 10391, 22245, 27895, 3918, 12069
EG      0       00:09:13        test_nodes (EURUSD.m,H4)        Node #2 array values = 1809, 18553, 23224, 20208, 10191, 4833, 25959, 2761, 7291, 23254, 29865, 23938, 7585, 20880, 25756
MK      0       00:09:13        test_nodes (EURUSD.m,H4)        Node #3 array values = 18100, 26358, 31020, 23881, 11256, 24798, 31481, 14567, 13032, 4701, 21665, 1434, 1622, 16377, 25778
RP      0       00:09:13        test_nodes (EURUSD.m,H4)        Next-Node #1 array values = 1809, 18553, 23224, 20208, 10191, 4833, 25959, 2761, 7291, 23254, 29865, 23938, 7585, 20880, 25756
JD      0       00:09:13        test_nodes (EURUSD.m,H4)        Next-Node #2 array values = 18100, 26358, 31020, 23881, 11256, 24798, 31481, 14567, 13032, 4701, 21665, 1434, 1622, 16377, 25778
EH      0       00:09:13        test_nodes (EURUSD.m,H4)        Prev-Node #2 array values = 31784, 4837, 25797, 29079, 4223, 27234, 2155, 32351, 12010, 10353, 10391, 22245, 27895, 3918, 12069
NN      0       00:09:13        test_nodes (EURUSD.m,H4)        Prev-Node #3 array values = 1809, 18553, 23224, 20208, 10191, 4833, 25959, 2761, 7291, 23254, 29865, 23938, 7585, 20880, 25756

Pues, propongo terminar con los nodos y pasar directamente a la definición de las clases de diferentes listas. Los ejemplos 1-3 se encuentran en el script test_nodes.mq5.


2.4 Lista simplemente enlazada

Ha llegado el momento para que hagamos el modelo de la clase de la lista simplemente enlazada usando los grupos de operaciones principales (Fig. 9).

Modelo de la clase CiSingleList

Fig.9 Modelo de la clase CiSingleList

Es fácil de notar que la clase CiSingleList utiliza el nodo del tipo CiSingleNode. Hablando de los tipos de relaciones entre las clases, podemos decir que:

  1. la clase CiSingleList incluye la clase CiSingleNode (composición);
  2. la clase CiSingleList utiliza los métodos de la clase CiSingleNode (dependencia).

El esquema de relaciones mencionadas se muestra en la Fig. 10.

Fig. 10 Types of relationships between CiSingleList class and CiSingleNode class

Fig.10 Tipos de relaciones entre la clase CiSingleList y CiSingleNode

Vamos a crear la nueva clase CiSingleList. Adelantándose, quiero decir que esta clase va a servir de base para todas las clases de la lista utilizadas en este artículo. Por eso es tan «cargado».

//+------------------------------------------------------------------+
//|                     Clase CiSingleList                           |
//+------------------------------------------------------------------+
class CiSingleList
  {
protected:
   CiSingleNode     *m_head;    // cabeza
   CiSingleNode     *m_tail;    // cola
   uint              m_size;    // número de nodos en la lista
public:
   //--- constructor y destructor
   void              CiSingleList();                              // constructor por defecto 
   void              CiSingleList(int _node_val);                 // constructor paramétrico 
   void             ~CiSingleList();                              // destructor                

   //--- añadir nodos   
   void              AddFront(int _node_val);                         // nuevo nodo al principio de la lista
   void              AddRear(int _node_val);                          // nuevo nodo al final de la lista   
   virtual void      AddFront(int &_node_arr[]){TRACE_CALL(_t_flag)}; // nuevo nodo al principio de la lista
   virtual void      AddRear(int &_node_arr[]){TRACE_CALL(_t_flag)};  // nuevo nodo al final de la lista
   //--- eliminar  nodos     
   int               RemoveFront(void);                           // eliminar el nodo de cabeza       
   int               RemoveRear(void);                            // eliminar el nodo de cola 
   void              DeleteNodeByIndex(const uint _idx);          // eliminar el nodo i en la lista

   //--- prueba   
   virtual bool      Find(const int _node_val) const;             // buscar valor especificado    
   bool              IsEmpty(void) const;                         // comprobar si la lista está vacía
   virtual int       GetValByIndex(const uint _idx) const;        // valor del nodo i de la lista 
   virtual CiSingleNode *GetNodeByIndex(const uint _idx) const;   // obtener el nodo i en la lista  
   virtual bool      SetNodeByIndex(CiSingleNode *_new_node,const uint _idx); // insertar nuevo nodo i en la lista        
   CiSingleNode     *GetHeadNode(void) const;                     // obtener el nodo de cabeza
   CiSingleNode     *GetTailNode(void) const;                     // obtener el nodo de cola   
   virtual uint      Size(void) const;                            // tamaño de la lista            
   //--- servicio   
   virtual void      PrintList(string _caption=NULL);             // imprimir la lista         
   virtual bool      CopyByValue(const CiSingleList &_sList);     // copiado de la lista por valores  
   virtual void      BubbleSort(void);                            // ordenación por método de la burbuja
   //---plantillas
   template<typename dPointer>
   bool              CheckDynamicPointer(dPointer &_p);           // plantilla de comprobación del puntero dinámico
   template<typename dPointer>
   bool              DeleteDynamicPointer(dPointer &_p);          // plantilla de eliminación del puntero dinámico

protected:
   void              operator=(const CiSingleList &_sList) const; // operador de asignación
   void              CiSingleList(const CiSingleList &_sList);    // constructor de copiado 
   virtual bool      AddToEmpty(int _node_val);                   // insertar nuevo nodo en la lista vacía
   virtual void      addFront(int _node_val);                     // nuevo nodo "nativo" al principio de la lista
   virtual void      addRear(int _node_val);                      // nuevo nodo "nativo" al finalde la lista
   virtual int       removeFront(void);                           // eliminar nodo "nativo" de cabeza       
   virtual int       removeRear(void);                            // eliminar nodo "nativo" del final de la lista
   virtual void      deleteNodeByIndex(const uint _idx);          // eliminar nodo "nativo" número i de la lista
   virtual CiSingleNode *newNode(int _val);                       // nuevo nodo "nativo"
   virtual void      CalcSize(void) const;                        // calcular el tamaño de la lista   
  };

La descripción completa de los métodos de la clase se encuentra en el archivo CiSingleList.mqh.

Cuando estaba desarrollando esta clase, al principio había sólo 3 miembros de datos y apenas algunos métodos. En vista de que esta clase sirve de base para las demás, he tenidos que añadir unas funciones miembro virtuales. No voy poner la descripción detallada de los métodos. En el script test_sList.mq5 se puede ver el ejemplo del uso de esta clase de la lista simplemente enlazada.

Si lo inicia sin la bandera de emplazamiento (trace flag), en el log aparecerán las siguientes entradas:

KG      0       12:58:32        test_sList (EURUSD,H1)  =======List #1=======
PF      0       12:58:32        test_sList (EURUSD,H1)  Node #1, val=14 
RL      0       12:58:32        test_sList (EURUSD,H1)  Node #2, val=666 
MD      0       12:58:32        test_sList (EURUSD,H1)  Node #3, val=13 
DM      0       12:58:32        test_sList (EURUSD,H1)  Node #4, val=11 
QE      0       12:58:32        test_sList (EURUSD,H1)  
KN      0       12:58:32        test_sList (EURUSD,H1)  
LR      0       12:58:32        test_sList (EURUSD,H1)  =======List #2=======
RE      0       12:58:32        test_sList (EURUSD,H1)  Node #1, val=14 
DQ      0       12:58:32        test_sList (EURUSD,H1)  Node #2, val=666 
GK      0       12:58:32        test_sList (EURUSD,H1)  Node #3, val=13 
FP      0       12:58:32        test_sList (EURUSD,H1)  Node #4, val=11 
KF      0       12:58:32        test_sList (EURUSD,H1)  
MK      0       12:58:32        test_sList (EURUSD,H1)  
PR      0       12:58:32        test_sList (EURUSD,H1)  =======renewed List #2=======
GK      0       12:58:32        test_sList (EURUSD,H1)  Node #1, val=11 
JP      0       12:58:32        test_sList (EURUSD,H1)  Node #2, val=13 
JI      0       12:58:32        test_sList (EURUSD,H1)  Node #3, val=14 
CF      0       12:58:32        test_sList (EURUSD,H1)  Node #4, val=34 
QL      0       12:58:32        test_sList (EURUSD,H1)  Node #5, val=35 
OE      0       12:58:32        test_sList (EURUSD,H1)  Node #6, val=36 
MR      0       12:58:32        test_sList (EURUSD,H1)  Node #7, val=37 
KK      0       12:58:32        test_sList (EURUSD,H1)  Node #8, val=38 
MS      0       12:58:32        test_sList (EURUSD,H1)  Node #9, val=666 
OF      0       12:58:32        test_sList (EURUSD,H1)  
QK      0       12:58:32        test_sList (EURUSD,H1)  

El script ha llenado dos listas simplemente enlazadas, luego ha ampliado y ha ordenado la segunda lista.


2.5 Lista doblemente enlazada

Intentaremos crear ahora una lista doblemente enlazada a base de la lista del tipo anterior. En la Fig. 11 se muestra el modelo de la clase de la lista doblemente enlazada:

Modelo de la clase CDoubleList

Fig. 11 Modelo de la clase CDoubleList

La clase derivada (heredero) contiene menos métodos, y los miembros de datos no están presentes en absoluto. Abajo viene la definición de la clase CDoubleList.

//+------------------------------------------------------------------+
//|                      Clase CDoubleList                           |
//+------------------------------------------------------------------+
class CDoubleList : public CiSingleList
  {
public:
   void              CDoubleList(void);                  // constructor por defecto    
   void              CDoubleList(int _node_val);         // constructor paramétrico   
   void             ~CDoubleList(void){};                // destructor                  
   virtual bool      SetNodeByIndex(CiSingleNode *_new_node,const uint _idx); // insertar nuevo nodo i en la lista  

protected:
   virtual bool      AddToEmpty(int _node_val);          // insertar nuevo nodo en la lista vacía
   virtual void      addFront(int _node_val);            // nuevo nodo "nativo" al principio de la lista
   virtual void      addRear(int _node_val);             // nuevo nodo "nativo" al final de la lista 
   virtual int       removeFront(void);                  // eliminar nodo "nativo" de la cabeza
   virtual int       removeRear(void);                   // eliminar nodo "nativo" de la cola
   virtual void      deleteNodeByIndex(const uint _idx); // eliminar nodo "nativo" número i en la lista
   virtual CiSingleNode *newNode(int _node_val);         // nuevo nodo "nativo"
  };

La descripción completa de los métodos de la clase CDoubleList se encuentra en el archivo CDoubleList.mqh.

Hablando en general, las funciones virtuales se utilizan aquí sólo para servir a las necesidades del puntero al nodo anterior que no está presente en la lista simplemente enlazada.

El ejemplo del uso de la lista del tipo CDoubleList se puede encontrar en el script test_dList.mq5. Ahí se demuestran todas las operaciones comunes con las listas de este tipo. El código del script contiene una cadena muy interesante:

CiSingleNode *_new_node=new CDoubleNode(666);     // crear nuevo nodo del tipo CDoubleNode

No habrá ningún error porque esta construcción es totalmente aceptable en el caso cuando el puntero de la clase base describe un objeto de la clase derivada. En eso consiste una de las ventajas de la herencia.

En MQL5, igual que en С++, el puntero a la clase base puede apuntar a un objeto de la subclase derivada de esta clase base. Pero la construcción inversa será incorrecta.

Si escribimos la cadena de la siguiente manera:

CDoubleNode*_new_node=new CiSingleNode(666);

el compilador no va a poner pegas o informar sobre el error, y el programa va a funcionar hasta que se encuentre precisamente con esta cadena. En este caso saltará un mensaje sobre la conversión errónea de tipos a los que hacen referencia los punteros. Puesto que el mecanismo de vinculación tardía se pone en acción sólo en la fase de ejecución del programa, hay que prestar mucha atención a la jerarquía de las relaciones entre las clases.

Después de iniciar el script, veremos las siguientes entras en el log:

DN      0       13:10:57        test_dList (EURUSD,H1)  =======List #1=======
GO      0       13:10:57        test_dList (EURUSD,H1)  Node #1, val=14 
IE      0       13:10:57        test_dList (EURUSD,H1)  Node #2, val=666 
FM      0       13:10:57        test_dList (EURUSD,H1)  Node #3, val=13 
KD      0       13:10:57        test_dList (EURUSD,H1)  Node #4, val=11 
JL      0       13:10:57        test_dList (EURUSD,H1)  
DG      0       13:10:57        test_dList (EURUSD,H1)  
CK      0       13:10:57        test_dList (EURUSD,H1)  =======List #2=======
IL      0       13:10:57        test_dList (EURUSD,H1)  Node #1, val=14 
KH      0       13:10:57        test_dList (EURUSD,H1)  Node #2, val=666 
PR      0       13:10:57        test_dList (EURUSD,H1)  Node #3, val=13 
MI      0       13:10:57        test_dList (EURUSD,H1)  Node #4, val=11 
DO      0       13:10:57        test_dList (EURUSD,H1)  
FR      0       13:10:57        test_dList (EURUSD,H1)  
GK      0       13:10:57        test_dList (EURUSD,H1)  =======renewed List #2=======
PR      0       13:10:57        test_dList (EURUSD,H1)  Node #1, val=11 
QI      0       13:10:57        test_dList (EURUSD,H1)  Node #2, val=13 
QP      0       13:10:57        test_dList (EURUSD,H1)  Node #3, val=14 
LO      0       13:10:57        test_dList (EURUSD,H1)  Node #4, val=34 
JE      0       13:10:57        test_dList (EURUSD,H1)  Node #5, val=35 
HL      0       13:10:57        test_dList (EURUSD,H1)  Node #6, val=36 
FK      0       13:10:57        test_dList (EURUSD,H1)  Node #7, val=37 
DR      0       13:10:57        test_dList (EURUSD,H1)  Node #8, val=38 
FJ      0       13:10:57        test_dList (EURUSD,H1)  Node #9, val=666 
HO      0       13:10:57        test_dList (EURUSD,H1)  
JR      0       13:10:57        test_dList (EURUSD,H1)  

Igual que en el caso con la lista enlazada simple, el script ha llenado la primera lista (doblemente enlazada), ha hecho su copia y la ha pasado a la segunda lista. Luego ha aumentado el número de nodos en la segunda lista, ha ordenado y ha imprimido la lista.


2.6 Lista doblemente enlazada usando nodos de arrays

Esta lista es bastante cómoda porque permite guardar en su nodo no sólo un valor cualquiera, sino un array entero.

Creamos la base para la lista del tipo  CiUnrollDoubleList (Fig. 12).

Modelo de la clase CiUnrollDoubleList

Fig. 12 Modelo de la clase CiUnrollDoubleList

Aquí, puesto que vamos a tratar un array de datos, tendremos que redefinir los métodos que han sido definidos antes en la clase base indirecta CiSingleList.

Tenemos la siguiente definición de la clase CiUnrollDoubleList.

//+------------------------------------------------------------------+
//|                     Clase CiUnrollDoubleList                     |
//+------------------------------------------------------------------+
class CiUnrollDoubleList : public CDoubleList
  {
public:
   void              CiUnrollDoubleList(void);                      // constructor por defecto    
   void              CiUnrollDoubleList(int &_node_arr[]);          // constructor paramétrico   
   void             ~CiUnrollDoubleList(void){TRACE_CALL(_t_flag)}; // destructor                  
   //---
   virtual void      AddFront(int &_node_arr[]);                    // nuevo nodo al principio de la lista
   virtual void      AddRear(int &_node_arr[]);                     // nuevo nodo al final de la lista
   virtual bool      CopyByValue(const CiSingleList &_udList);      // copiado por valores
   virtual void      PrintList(string _caption=NULL);               // imprimir la lista
   virtual void      BubbleSort(void);                              // ordenación por método de la burbuja

protected:
   virtual bool      AddToEmpty(int &_node_arr[]);                  // insertar nodo en la lista vacía
   virtual void      addFront(int &_node_arr[]);                    // nuevo nodo "nativo" al principio de la lista
   virtual void      addRear(int &_node_arr[]);                     // nuevo nodo "nativo" al final de la lista 
   virtual int       removeFront(void);                             // eliminar nodo "nativo" del principio de la lista
   virtual int       removeRear(void);                              // eliminar nodo "nativo" del final de la lista 
   virtual void      deleteNodeByIndex(const uint _idx);            // eliminar nodo "nativo" con el número i en la lista
   virtual CiSingleNode *newNode(int &_node_arr[]);                 // nuevo nodo "nativo"
  };

La descripción completa de los métodos de la clase se encuentra en el archivo CiUnrollDoubleList.mqh.

Iniciamos el script test_UdList.mq5 para ver cómo funcionan los métodos de la clase. Aquí las operaciones con los nodos son las mismas que han sido utilizadas en los scripts anteriores. Tal vez habría que decir unas palabras aparte sobre los métodos de ordenación e impresión. El método de ordenación ordena los nodos según el número de elementos. El nodo que contiene el array de valores de menor tamaño se pone a la cabeza de la lista.

El método de impresión se encarga de visualizar (imprimir) en una cadena los valores del array que forma parte de un cierto nodo.

Después de iniciar el script, veremos las siguientes entras en el log:

II      0       13:22:23        test_UdList (EURUSD,H1) =======List #1=======
FN      0       13:22:23        test_UdList (EURUSD,H1) List node #1, array: 55, 12, 1, 2, 11, 114, 33, 113, 14, 15, 16, 17, 18, 19, 20
OO      0       13:22:23        test_UdList (EURUSD,H1) List node #2, array: 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
GG      0       13:22:23        test_UdList (EURUSD,H1) 
GP      0       13:22:23        test_UdList (EURUSD,H1) 
GR      0       13:22:23        test_UdList (EURUSD,H1) =======List #2 before sorting=======
JO      0       13:22:23        test_UdList (EURUSD,H1) List node #1, array: 55, 12, 1, 2, 11, 114, 33, 113, 14, 15, 16, 17, 18, 19, 20
CH      0       13:22:23        test_UdList (EURUSD,H1) List node #2, array: 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
CF      0       13:22:23        test_UdList (EURUSD,H1) List node #3, array: -89, -131, -141, -139, -129, -25, -105, -24, -122, -120, -118, -116, -114, -112, -110
GD      0       13:22:23        test_UdList (EURUSD,H1) 
GQ      0       13:22:23        test_UdList (EURUSD,H1) 
LJ      0       13:22:23        test_UdList (EURUSD,H1) =======List #2 after sorting=======
FN      0       13:22:23        test_UdList (EURUSD,H1) List node #1, array: 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
CJ      0       13:22:23        test_UdList (EURUSD,H1) List node #2, array: 55, 12, 1, 2, 11, 114, 33, 113, 14, 15, 16, 17, 18, 19, 20
II      0       13:22:23        test_UdList (EURUSD,H1) List node #3, array: -89, -131, -141, -139, -129, -25, -105, -24, -122, -120, -118, -116, -114, -112, -110
MD      0       13:22:23        test_UdList (EURUSD,H1) 
MQ      0       13:22:23        test_UdList (EURUSD,H1) 

Como podemos ver, después de la ordenación la lista udList2 imprime del nodo con el menor tamaño del array hasta el nodo con el mayor tamaño.


2.7 Lista circular doblemente enlazada

Las listas no lineales no se consideran en este artículo, pero a pesar de eso vamos a trabajar con ellas también. Cómo se hace el ciclo de los nodos ha sido mostrado en el diagrama de arriba (Fig. 3).

Creamos el modelo de la clase CiCircleDoubleList (Fig. 13). Esta clase va a ser heredero de la clase CDoubleList.

Modelo de la clase CiCircleDoubleList

Fig. 13 Modelo de la clase CiCircleDoubleList

Puesto que los nodos en esta lista tienen un carácter específico (la cabeza y la cola están enlazadas), tendremos que hacer virtuales casi todos los métodos de la clase base CiSingleList.

//+------------------------------------------------------------------+
//|                      Clase CiCircleDoubleList                    |
//+------------------------------------------------------------------+
class CiCircleDoubleList : public CDoubleList
  {
public:
   void              CiCircleDoubleList(void);                       // constructor por defecto    
   void              CiCircleDoubleList(int _node_val);              // constructor paramétrico   
   void             ~CiCircleDoubleList(void){TRACE_CALL(_t_flag)};  // destructor   
   //---
   virtual uint      Size(void) const;                                        // tamaño de la lista 
   virtual bool      SetNodeByIndex(CiSingleNode *_new_node,const uint _idx); // insertamos el nuevo nodo i en la lista
   virtual int       GetValByIndex(const uint _idx) const;           // el valor del nodo i en la lista
   virtual CiSingleNode *GetNodeByIndex(const uint _idx) const;      // obtener el nodo i en la lista 
   virtual bool      Find(const int _node_val) const;                // buscar el valor establecido 
   virtual bool      CopyByValue(const CiSingleList &_sList);        // copiado de la lista por valores  

protected:
   virtual void      addFront(int _node_val);                        // nuevo nodo "nativo" al principio de la lista
   virtual void      addRear(int _node_val);                         // nuevo nodo "nativo" al final de la lista 
   virtual int       removeFront(void);                              // eliminar el nodo "nativo" de la cabeza
   virtual int       removeRear(void);                               // eliminar el nodo "nativo" de la cola
   virtual void      deleteNodeByIndex(const uint _idx);             // eliminar el nodo "nativo" i en la lista

protected:
   void              CalcSize(void) const;                           // calcular el tamaño de la lista 
   void              LinkHeadTail(void);                             // enlazar la cabeza con la cola
  };

La descripción completa de la clase se encuentra en el archivo CiCircleDoubleList.mqh.

Vamos a ver algunos métodos de la clase. El método CiCircleDoubleList::LinkHeadTail() se encarga de enlazar el nodo de la cabeza con el de la cola. Se puede llamarlo cuando aparece la nueva cabeza o la cola, y el enlace anterior se pierde.

//+------------------------------------------------------------------+
//|                  Enlazar la cabeza con la cola                   |
//+------------------------------------------------------------------+
void CiCircleDoubleList::LinkHeadTail(void)
  {
   TRACE_CALL(_t_flag)
   this.m_head.SetPrevNode(this.m_tail);      // enlace de la cabeza con la cola
   this.m_tail.SetNextNode(this.m_head);      // enlace de la cola con la cabeza 
  }

Piense en cómo sería este método si trataramos con una lista circular simplemente enlazada.

Veremos, por ejemplo, el método CiCircleDoubleList::addFront().

//+------------------------------------------------------------------+
//|          Nuevo nodo "nativo" al principio de la lista            |
//+------------------------------------------------------------------+
void CiCircleDoubleList::addFront(int _node_val)
  {
   TRACE_CALL(_t_flag)
   CDoubleList::addFront(_node_val); // llamada al método similar de la clase base
   this.LinkHeadTail();              // enlazar la cabeza y la cola
  }

Podemos ver que en el cuerpo del método se invoca el mismo método de la clase base CDoubleList. Con eso podríamos terminar el trabajo del método (prácticamente, aquí el método es innecesario) si no hubiera un "pero". Entre la cabeza y la cola se pierde el enlazamiento, y sin ella la lista no será circular. Por eso tenemos que llamar al método de enlazamiento de la cabeza y la coal.

El trabajo con la lista circular doblemente enlazada se verifica en el script test_UdList.mq5.

En términos de las tareas y objetivos, se utilizan los mismos métodos que han sido utilizados en los ejemplos anteriores.

Como resultado, veremos en el log las siguientes entradas:

PR      0       13:34:29        test_CdList (EURUSD,H1) =======List #1=======
QS      0       13:34:29        test_CdList (EURUSD,H1) Node #1, val=14 
QI      0       13:34:29        test_CdList (EURUSD,H1) Node #2, val=666 
LQ      0       13:34:29        test_CdList (EURUSD,H1) Node #3, val=13 
OH      0       13:34:29        test_CdList (EURUSD,H1) Node #4, val=11 
DP      0       13:34:29        test_CdList (EURUSD,H1) 
DK      0       13:34:29        test_CdList (EURUSD,H1) 
DI      0       13:34:29        test_CdList (EURUSD,H1) =======List #2 before sorting=======
MS      0       13:34:29        test_CdList (EURUSD,H1) Node #1, val=38 
IJ      0       13:34:29        test_CdList (EURUSD,H1) Node #2, val=37 
IQ      0       13:34:29        test_CdList (EURUSD,H1) Node #3, val=36 
EH      0       13:34:29        test_CdList (EURUSD,H1) Node #4, val=35 
EO      0       13:34:29        test_CdList (EURUSD,H1) Node #5, val=34 
FF      0       13:34:29        test_CdList (EURUSD,H1) Node #6, val=14 
DN      0       13:34:29        test_CdList (EURUSD,H1) Node #7, val=666 
GD      0       13:34:29        test_CdList (EURUSD,H1) Node #8, val=13 
JK      0       13:34:29        test_CdList (EURUSD,H1) Node #9, val=11 
JM      0       13:34:29        test_CdList (EURUSD,H1) 
JH      0       13:34:29        test_CdList (EURUSD,H1) 
MS      0       13:34:29        test_CdList (EURUSD,H1) =======List #2 after sorting=======
LE      0       13:34:29        test_CdList (EURUSD,H1) Node #1, val=11 
KL      0       13:34:29        test_CdList (EURUSD,H1) Node #2, val=13 
QS      0       13:34:29        test_CdList (EURUSD,H1) Node #3, val=14 
NJ      0       13:34:29        test_CdList (EURUSD,H1) Node #4, val=34 
NQ      0       13:34:29        test_CdList (EURUSD,H1) Node #5, val=35 
NH      0       13:34:29        test_CdList (EURUSD,H1) Node #6, val=36 
NO      0       13:34:29        test_CdList (EURUSD,H1) Node #7, val=37 
NF      0       13:34:29        test_CdList (EURUSD,H1) Node #8, val=38 
JN      0       13:34:29        test_CdList (EURUSD,H1) Node #9, val=666 
RJ      0       13:34:29        test_CdList (EURUSD,H1) 
RE      0       13:34:29        test_CdList (EURUSD,H1)

En fin, el diagrama final de la herencia entre las clases de las listas presentadas se ve de la siguiente manera (Fig. 14).

No estoy seguro de que merece la pena relacionar todas las clases entre sí con las relaciones de la herencia, pero he decidido dejarlo tal como está.

Herencia entre las clases de las listas

Fig. 14 Herencia entre las clases de las listas

Terminando este apartado del artículo en el que han sido mostradas las listas personalizadas, me gustaría señalar que no hemos tocado prácticamente el grupo de las listas no lineales, las multilistas, etc. Según vaya acumulando el material y la experiencia de trabajo con este tipo de estructuras dinámicas, intentaré escribir otro artículo.


3. Listas en la Librería estándar MQL5

Echemos un vistazo a la clase de listas que está disponible en la Librería estándar (Fig. 15).

Pertenece a la sección Clases para organización de datos (Data Structures).

Modelo de la clase CList

Fig. 15 Modelo de la clase CList

Es curioso que la clase CList se deriva de la clase CObject. Es decir, la lista hereda los datos y los métodos de la clase que figura como nodo.

La clase de listas contiene un importante conjunto de métodos. La verdad es que no esperaba encontrar una clase tan grande en la Librería estándar.

La clase CList tiene 8 miembros de datos. Me gustaría remarcar unos momentos. Los atributos de la clase contienen el índice del nodo actual (int m_curr_idx) y el puntero al nodo actual (CObject* m_curr_node). Podemos decir que la lista es «inteligente»: puede indicar el lugar en el que está localizado el control. Además, consta de las siguientes herramientas: mecanismo de trabajo con la memoria (podemos eliminar el nodo físicamente o simplemente excluirlo de la lista), bandera de la lista ordenada, modo de ordenación de la lista.

En cuanto a los métodos, todos los métodos de la clase CList se dividen en siguientes grupos:

Hay un constructor y un destructor estándar.

El primero vacía (NULL) todos los punteros. La bandera de control de la memoria se pone en la posición «eliminar físicamente». La nueva lista no estará ordenada.

El destructor en su cuerpo sólo llama al método Clear() y vacía la lista de los nodos. No siempre el fin de la existencia de la lista tiene que provocar la «muerte» de sus elementos (nodos). De esta manera, el establecimiento de la bandera de control de la memoria, a la hora de eliminar los elementos de la lista, produce que le relación «composición» entre las clases se convierte en la relación «agregación».

Pues, podemos manejar esta bandera usando los métodos set y get de FreeMode().

En la clase hay 2 métodos que permiten aumentar la lista: Add() y Insert(). El primero parece al método AddRear() que he utilizado en la primera sección del artículo, y el segundo parece al método SetNodeByIndex().

Empezamos nuestro trabajo con un pequeño ejemplo. Pero primero tenemos que crear la clase del nodo CNodeInt que se deriva de la clase de interfaz CObject. Vamos a almacenar en él los valores del tipo integer.

//+------------------------------------------------------------------+
//|                        Clase CNodeInt                            |
//+------------------------------------------------------------------+
class  CNodeInt : public CObject
  {
private:
   int               m_val;  // datos del nodo

public:
   void              CNodeInt(void){this.m_val=WRONG_VALUE;}; // constructor por defecto
   void              CNodeInt(int _val);                      // constructor paramétrico
   void             ~CNodeInt(void){};                        // destructor
   int               GetVal(void){return this.m_val;};        // método get para los datos del nodo
   void              SetVal(int _val){this.m_val=_val;};      // método set para los datos del nodo
  };
//+------------------------------------------------------------------+
//|                    Constructor paramétrico                       |
//+------------------------------------------------------------------+
void CNodeInt::CNodeInt(int _val):m_val(_val)
  {

  };

De esta forma, vamos a trabajar con la lista CList en el script test_MQL5_List.mq5.

En el Ejemplo 1 la lista y los nodos se crean de forma dinámica. Luego los nodos llenan la lista, se comprueban los valores del primer nodo antes y después de la eliminación de la lista.

//--- Example 1 (testing memory management)
   CList *myList=new CList;
// myList.FreeMode(false);  // reset flag
   bool _free_mode=myList.FreeMode();
   PrintFormat("\nList \"myList\" - memory management flag: %d",_free_mode);
   CNodeInt *p_new_nodes_int[10];
   p_new_nodes_int[0]=NULL;
   for(int i=0;i<ArraySize(p_new_nodes_int);i++)
     {
      p_new_nodes_int[i]=new CNodeInt(rand());
      myList.Add(p_new_nodes_int[i]);
     }
   PrintFormat("List \"myList\" has as much nodes as: %d",myList.Total());
   Print("=======Before deleting \"myList\"=======");
   PrintFormat("The 1st node value is: %d",p_new_nodes_int[0].GetVal());
   delete myList;
   int val_to_check=WRONG_VALUE;
   if(CheckPointer(p_new_nodes_int[0]))
      val_to_check=p_new_nodes_int[0].GetVal();
   Print("=======After deleting \"myList\"=======");
   PrintFormat("The 1st node value is: %d",val_to_check);

Si la cadena de reinicio de la bandera se queda sin comentar (inactiva), en el log aparecen las siguientes entradas:

GS      0       14:00:16        test_MQL5_List (EURUSD,H1)      
EO      0       14:00:16        test_MQL5_List (EURUSD,H1)      List "myList" - memory management flag: 1
FR      0       14:00:16        test_MQL5_List (EURUSD,H1)      List "myList" has as much nodes as: 10
JH      0       14:00:16        test_MQL5_List (EURUSD,H1)      =======Before deleting "myList"=======
DO      0       14:00:16        test_MQL5_List (EURUSD,H1)      The 1st node value is: 7189
KJ      0       14:00:16        test_MQL5_List (EURUSD,H1)      =======After deleting "myList"=======
QK      0       14:00:16        test_MQL5_List (EURUSD,H1)      The 1st node value is: -1

Quiero indicar que la eliminación dinámica de la lista myList provoca la eliminación física de todos los nodos de la memoria.

Pero si descomentamos la cadena:

// myList.FreeMode(false); // reset flag

el resultado en el log será el siguiente:

NS      0       14:02:11        test_MQL5_List (EURUSD,H1)      
CN      0       14:02:11        test_MQL5_List (EURUSD,H1)      List "myList" - memory management flag: 0
CS      0       14:02:11        test_MQL5_List (EURUSD,H1)      List "myList" has as much nodes as: 10
KH      0       14:02:11        test_MQL5_List (EURUSD,H1)      =======Before deleting "myList"=======
NL      0       14:02:11        test_MQL5_List (EURUSD,H1)      The 1st node value is: 20411
HJ      0       14:02:11        test_MQL5_List (EURUSD,H1)      =======After deleting "myList"=======
LI      0       14:02:11        test_MQL5_List (EURUSD,H1)      The 1st node value is: 20411
QQ      1       14:02:11        test_MQL5_List (EURUSD,H1)      10 undeleted objects left
DD      1       14:02:11        test_MQL5_List (EURUSD,H1)      10 objects of type CNodeInt left
DL      1       14:02:11        test_MQL5_List (EURUSD,H1)      400 bytes of leaked memory

No resulta difícil de notar que antes de la eliminación de la lista y después, el nodo que se encuentra y se encontraba en la cabeza no pierde su valor. En este caso, quedarán objetos sin eliminar si el script no va a contener el código que procese su correcta eliminación.

Ahora vamos a probar el método de ordenación.

//--- Example 2 (sorting)

   CList *myList=new CList;
   CNodeInt *p_new_nodes_int[10];
   p_new_nodes_int[0]=NULL;
   for(int i=0;i<ArraySize(p_new_nodes_int);i++)
     {
      p_new_nodes_int[i]=new CNodeInt(rand());
      myList.Add(p_new_nodes_int[i]);
     }
   PrintFormat("\nList \"myList\" has as much nodes as: %d",myList.Total());
   Print("=======List \"myList\" before sorting=======");
   for(int i=0;i<myList.Total();i++)
     {
      CNodeInt *p_node_int=myList.GetNodeAtIndex(i);
      int node_val=p_node_int.GetVal();
      PrintFormat("Node #%d is equal to: %d",i+1,node_val);
     }
   myList.Sort(0);
   Print("\n=======List \"myList\" after sorting=======");
   for(int i=0;i<myList.Total();i++)
     {
      CNodeInt *p_node_int=myList.GetNodeAtIndex(i);
      int node_val=p_node_int.GetVal();
      PrintFormat("Node #%d is equal to: %d",i+1,node_val);
     }
   delete myList;

El log contiene las siguientes entradas:

OR      0       22:47:01        test_MQL5_List (EURUSD,H1)      
FN      0       22:47:01        test_MQL5_List (EURUSD,H1)      List "myList" has as much nodes as: 10
FH      0       22:47:01        test_MQL5_List (EURUSD,H1)      =======List "myList" before sorting=======
LG      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #1 is equal to: 30511
CO      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #2 is equal to: 17404
GF      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #3 is equal to: 12215
KQ      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #4 is equal to: 31574
NJ      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #5 is equal to: 7285
HP      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #6 is equal to: 23509
IH      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #7 is equal to: 26991
NS      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #8 is equal to: 414
MK      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #9 is equal to: 18824
DR      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #10 is equal to: 1560
OR      0       22:47:01        test_MQL5_List (EURUSD,H1)      
OM      0       22:47:01        test_MQL5_List (EURUSD,H1)      =======List "myList" after sorting=======
QM      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #1 is equal to: 26991
RE      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #2 is equal to: 23509
ML      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #3 is equal to: 18824
DD      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #4 is equal to: 414
LL      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #5 is equal to: 1560
IG      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #6 is equal to: 17404
PN      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #7 is equal to: 30511
II      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #8 is equal to: 31574
OQ      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #9 is equal to: 12215
JH      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #10 is equal to: 7285

Si al fin y al cabo se ha realizado alguna ordenación, su metodología se ha quedado bastante misteriosa para mí. Y el asunto es el siguiente. No voy a entrar en los detalles del orden de la llamada pero el método CList::Sort() llama al método virtual CObject::Compare() que no está implementado de ninguna manera en la clase base. Entonces, el mismo programador tiene que encargarse de la implementación del método de ordenación.

Algunas palabras sobre el método Total(). Él devuelve el número de elementos (nodos) del que se responsabiliza el miembro de datos m_data_total. Es un método muy corto y conciso. En esta versión el recuento de elementos va a funcionar mucho más rápido en comparación con la versión que he propuesto yo. Efectivamente, para qué tenemos que recorrer cada vez la lista y contar los nodos si el número exacto de los nodos podemos establecer al añadir o eliminar los nodos.

El Ejemplo 3 nos muestra la comparación de la velocidad de relleno de la lista del tipo CList y CiSingleList. Además, se calcula el tiempo en que se tarda obtener el tamaño de cada lista.

//--- Example 3 (nodes number)

   int iterations=1e7;   // 10 million iterations
//--- the new CList 
   CList *p_mql_List=new CList;
   uint start=GetTickCount(); // starting value
   for(int i=0;i<iterations;i++)
     {
      CNodeInt *p_node_int=new CNodeInt(rand());
      p_mql_List.Add(p_node_int);
     }
   uint time=GetTickCount()-start; // spent time, msec
   Print("\n=======the CList type list=======");
   PrintFormat("Filling the list of %.3e nodes has taken %d msec",iterations,time);
//--- get the size
   start=GetTickCount();
   int list_size=p_mql_List.Total(); 
   time=GetTickCount()-start;
   PrintFormat("Getting the size of the list has taken %d msec",time);

   delete p_mql_List;

//---  the new CiSingleList 
   CiSingleList *p_sList=new CiSingleList;

   start=GetTickCount(); // starting value
   for(int i=0;i<iterations;i++)
      p_sList.AddRear(rand());
   time=GetTickCount()-start; // spent time, msec
   Print("\n=======the CiSingleList type list=======");
   PrintFormat("Filling the list of %.3e nodes has taken %d msec",iterations,time);
//--- get the size
   start=GetTickCount();
   list_size=(int)p_sList.Size();  
   time=GetTickCount()-start;
   PrintFormat("Getting the size of the list has taken %d msec",time);
   delete p_sList;   

Es lo que me ha salido en el log:

KO      0       22:48:24        test_MQL5_List (EURUSD,H1)      
CK      0       22:48:24        test_MQL5_List (EURUSD,H1)      =======the CList type list=======
JL      0       22:48:24        test_MQL5_List (EURUSD,H1)      Filling the list of 1.000e+007 nodes has taken 2606 msec
RO      0       22:48:24        test_MQL5_List (EURUSD,H1)      Getting the size of the list has taken 0 msec
LF      0       22:48:29        test_MQL5_List (EURUSD,H1)      
EL      0       22:48:29        test_MQL5_List (EURUSD,H1)      =======the CiSingleList type list=======
KK      0       22:48:29        test_MQL5_List (EURUSD,H1)      Filling the list of 1.000e+007 nodes has taken 2356 msec
NF      0       22:48:29        test_MQL5_List (EURUSD,H1)      Getting the size of the list has taken 359 msec

El método del obtención del tamaño en la lista CList funciona momentáneamente. Por cierto, la inserción de los nodos en la lista también se hace bastante rápido.

En el siguiente bloque (Ejemplo 4) quiero que preste atención a una de las mayores desventajas del contenedor de datos como la lista: se trata de la velocidad de acceso a los elementos. Es que el acceso a los elementos de la lista se realiza de forma lineal, y en caso de la clase de listas CList este acceso se hace con un coeficiente reducido (N/2), lo que a su vez reduce la intensidad en el uso del algoritmo.

Durante la búsqueda lineal, la intensidad en el uso es O(N).

Éste es el ejemplo del código para el acceso a los elementos del conjunto de datos:

//--- Example 4 (speed of accessing the node)

   const uint Iter_arr[]={1e3,3e3,6e3,9e3,1e4,3e4,6e4,9e4,1e5,3e5,6e5};
   for(uint i=0;i<ArraySize(Iter_arr);i++)
     {
      const uint cur_iterations=Iter_arr[i]; // iterations number     
      uint randArr[];                        // randoms array
      uint idxArr[];                         // indexes array
      //--- set the arrays size    
      ArrayResize(randArr,cur_iterations);
      ArrayResize(idxArr,cur_iterations);
      CRandom myRand;                        // randoms generator
      //--- fill the randoms array
      for(uint t=0;t<cur_iterations;t++)
         randArr[t]=myRand.int32();
      //--- fill the indexes array with the randoms (from 0 to 10 million)
      int iter_log10=(int)log10(cur_iterations);
      for(uint r=0;r<cur_iterations;r++)
        {
         uint rand_val=myRand.int32(); // random value (from 0 to 4 294 967 295)
         if(rand_val>=cur_iterations)
           {
            int val_log10=(int)log10(rand_val);
            double log10_remainder=val_log10-iter_log10;
            rand_val/=(uint)pow(10,log10_remainder+1);
           }
         //--- check the limit
         if(rand_val>=cur_iterations)
           {
            Alert("Random value error!");
            return;
           }
         idxArr[r]=rand_val;
        }
      //--- time spent for the array
      uint start=GetTickCount();
      //--- accessing the array elements 
      for(uint p=0;p<cur_iterations;p++)
         uint random_val=randArr[idxArr[p]];

      uint time=GetTickCount()-start; // spent time, msec
      Print("\n=======the uint type array=======");
      PrintFormat("Random accessing the array of elements %.1e has taken %d msec",cur_iterations,time);

      //--- the CList type list
      CList *p_mql_List=new CList;
      //--- fill the list
      for(uint q=0;q<cur_iterations;q++)
        {
         CNodeInt *p_node_int=new CNodeInt(randArr[q]);
         p_mql_List.Add(p_node_int);
        }
      start=GetTickCount();
      //--- accessing the list nodes
      for(uint w=0;w<cur_iterations;w++)
         CNodeInt *p_node_int=p_mql_List.GetNodeAtIndex(idxArr[w]);
      time=GetTickCount()-start; // spent time, msec
      Print("\n=======the CList type list=======");
      PrintFormat("Random accessing the list of nodes %.1e has taken %d msec",cur_iterations,time);

      //--- free the memory
      ArrayFree(randArr);
      ArrayFree(idxArr);
      delete p_mql_List;
     }

Basándose en los resultados de trabajo de este bloque, en el log se imprimen las siguientes entradas:

MR      0       22:51:22        test_MQL5_List (EURUSD,H1)      
QL      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======the uint type array=======
IG      0       22:51:22        test_MQL5_List (EURUSD,H1)      Random accessing the array of elements 1.0e+003 has taken 0 msec
QF      0       22:51:22        test_MQL5_List (EURUSD,H1)      
IQ      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======the CList type list=======
JK      0       22:51:22        test_MQL5_List (EURUSD,H1)      Random accessing the list of nodes 1.0e+003 has taken 0 msec
MJ      0       22:51:22        test_MQL5_List (EURUSD,H1)      
QD      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======the uint type array=======
GO      0       22:51:22        test_MQL5_List (EURUSD,H1)      Random accessing the array of elements 3.0e+003 has taken 0 msec
QN      0       22:51:22        test_MQL5_List (EURUSD,H1)      
II      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======the CList type list=======
EP      0       22:51:22        test_MQL5_List (EURUSD,H1)      Random accessing the list of nodes 3.0e+003 has taken 16 msec
OR      0       22:51:22        test_MQL5_List (EURUSD,H1)      
OL      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======the uint type array=======
FG      0       22:51:22        test_MQL5_List (EURUSD,H1)      Random accessing the array of elements 6.0e+003 has taken 0 msec
CF      0       22:51:22        test_MQL5_List (EURUSD,H1)      
GQ      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======the CList type list=======
CH      0       22:51:22        test_MQL5_List (EURUSD,H1)      Random accessing the list of nodes 6.0e+003 has taken 31 msec
QJ      0       22:51:22        test_MQL5_List (EURUSD,H1)      
MD      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======the uint type array=======
MO      0       22:51:22        test_MQL5_List (EURUSD,H1)      Random accessing the array of elements 9.0e+003 has taken 0 msec
EN      0       22:51:22        test_MQL5_List (EURUSD,H1)      
MJ      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======the CList type list=======
CP      0       22:51:22        test_MQL5_List (EURUSD,H1)      Random accessing the list of nodes 9.0e+003 has taken 47 msec
CR      0       22:51:22        test_MQL5_List (EURUSD,H1)      
KL      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======the uint type array=======
JG      0       22:51:22        test_MQL5_List (EURUSD,H1)      Random accessing the array of elements 1.0e+004 has taken 0 msec
GF      0       22:51:22        test_MQL5_List (EURUSD,H1)      
KR      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======the CList type list=======
MK      0       22:51:22        test_MQL5_List (EURUSD,H1)      Random accessing the list of nodes 1.0e+004 has taken 343 msec
GJ      0       22:51:22        test_MQL5_List (EURUSD,H1)      
GG      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======the uint type array=======
LO      0       22:51:22        test_MQL5_List (EURUSD,H1)      Random accessing the array of elements 3.0e+004 has taken 0 msec
QO      0       22:51:24        test_MQL5_List (EURUSD,H1)      
MJ      0       22:51:24        test_MQL5_List (EURUSD,H1)      =======the CList type list=======
NP      0       22:51:24        test_MQL5_List (EURUSD,H1)      Random accessing the list of nodes 3.0e+004 has taken 1217 msec
OS      0       22:51:24        test_MQL5_List (EURUSD,H1)      
KO      0       22:51:24        test_MQL5_List (EURUSD,H1)      =======the uint type array=======
CP      0       22:51:24        test_MQL5_List (EURUSD,H1)      Random accessing the array of elements 6.0e+004 has taken 0 msec
MG      0       22:51:26        test_MQL5_List (EURUSD,H1)      
ER      0       22:51:26        test_MQL5_List (EURUSD,H1)      =======the CList type list=======
PG      0       22:51:26        test_MQL5_List (EURUSD,H1)      Random accessing the list of nodes 6.0e+004 has taken 2387 msec
GK      0       22:51:26        test_MQL5_List (EURUSD,H1)      
OG      0       22:51:26        test_MQL5_List (EURUSD,H1)      =======the uint type array=======
NH      0       22:51:26        test_MQL5_List (EURUSD,H1)      Random accessing the array of elements 9.0e+004 has taken 0 msec
JO      0       22:51:30        test_MQL5_List (EURUSD,H1)      
NK      0       22:51:30        test_MQL5_List (EURUSD,H1)      =======the CList type list=======
KO      0       22:51:30        test_MQL5_List (EURUSD,H1)      Random accessing the list of nodes 9.0e+004 has taken 3619 msec
HS      0       22:51:30        test_MQL5_List (EURUSD,H1)      
DN      0       22:51:30        test_MQL5_List (EURUSD,H1)      =======the uint type array=======
RP      0       22:51:30        test_MQL5_List (EURUSD,H1)      Random accessing the array of elements 1.0e+005 has taken 0 msec
OD      0       22:52:05        test_MQL5_List (EURUSD,H1)      
GS      0       22:52:05        test_MQL5_List (EURUSD,H1)      =======the CList type list=======
DE      0       22:52:05        test_MQL5_List (EURUSD,H1)      Random accessing the list of nodes 1.0e+005 has taken 35631 msec
NH      0       22:52:06        test_MQL5_List (EURUSD,H1)      
RF      0       22:52:06        test_MQL5_List (EURUSD,H1)      =======the uint type array=======
FI      0       22:52:06        test_MQL5_List (EURUSD,H1)      Random accessing the array of elements 3.0e+005 has taken 0 msec
HL      0       22:54:20        test_MQL5_List (EURUSD,H1)      
PD      0       22:54:20        test_MQL5_List (EURUSD,H1)      =======the CList type list=======
FN      0       22:54:20        test_MQL5_List (EURUSD,H1)      Random accessing the list of nodes 3.0e+005 has taken 134379 msec
RQ      0       22:54:20        test_MQL5_List (EURUSD,H1)      
JI      0       22:54:20        test_MQL5_List (EURUSD,H1)      =======the uint type array=======
MR      0       22:54:20        test_MQL5_List (EURUSD,H1)      Random accessing the array of elements 6.0e+005 has taken 15 msec
NE      0       22:58:48        test_MQL5_List (EURUSD,H1)      
FL      0       22:58:48        test_MQL5_List (EURUSD,H1)      =======the CList type list=======
GE      0       22:58:48        test_MQL5_List (EURUSD,H1)      Random accessing the list of nodes 6.0e+005 has taken 267589 msec

Podemos observar que según vaya aumentando el tamaño de la lista, aumenta el tiempo de acceso aleatorio a los elementos de la lista (Fig. 16).

El tiempo tardado para el acceso aleatorio a los elementos del array y de la lista

Fig. 16 El tiempo tardado para el acceso aleatorio a los elementos del array y de la lista

Ahora hablaremos sobre los métodos de guardar y cargar los datos.

En la clase base de listas CList estos métodos existen pero son virtuales. Por eso tendremos que realizar un pequeño trabajo preparatorio para poder probar su trabajo en un ejemplo.

Vamos a heredar las capacidades de la clase CList usando la clase derivada CIntList. La última va a tener sólo un método para crear el elemento nuevo CIntList::CreateElement().

//+------------------------------------------------------------------+
//|                      Clase CIntList                              |
//+------------------------------------------------------------------+
class CIntList : public CList
  {

public:
   virtual CObject *CreateElement(void);
  };
//+------------------------------------------------------------------+
//|                  Nuevo elemento de la lista                      |
//+------------------------------------------------------------------+
CObject *CIntList::CreateElement(void)
  {
   CObject *new_node=new CNodeInt();
   return new_node;
  }

Tenemos que añadir también los métodos virtuales CNodeInt::Save() y CNodeInt::Load() en el tipo derivado del nodo CNodeInt. Van a invocarse de las funciones miembro CList::Save() y CList::Load(), respectivamente.

Como resultado, tenemos el siguiente ejemplo (Ejemplo 5):

//--- Example 5 (saving list data)
//--- the CIntList type list
   CList *p_int_List=new CIntList;
   int randArr[1000];                        // randoms array
   ArrayInitialize(randArr,0);
//--- fill the randoms array 
   for(int t=0;t<1000;t++)
      randArr[t]=(int)myRand.int32();
//--- fill the list
   for(uint q=0;q<1000;q++)
     {
      CNodeInt *p_node_int=new CNodeInt(randArr[q]);
      p_int_List.Add(p_node_int);
     }
//--- save the list into the file 
   int  file_ha=FileOpen("List_data.bin",FILE_WRITE|FILE_BIN);
   p_int_List.Save(file_ha);
   FileClose(file_ha);             
   p_int_List.FreeMode(true);    
   p_int_List.Clear();           
//--- load the list from the file  
   file_ha=FileOpen("List_data.bin",FILE_READ|FILE_BIN);
   p_int_List.Load(file_ha);
   int Loaded_List_size=p_int_List.Total();
   PrintFormat("As much as many nodes loaded from the file: %d",Loaded_List_size);
//--- free the memory     
   delete p_int_List;

Una vez iniciado el script en el gráfico, en el log aparecerá la siguiente entrada:

ND      0       11:59:35        test_MQL5_List (EURUSD,H1)      As many as 1000 nodes loaded from the file.

Así, hemos implementado los métodos de la entrada y salida para el miembro de datos del nodo tipo CNodeInt.

En la siguiente sección, verá los ejemplos de aplicación de las listas en su trabajo con el lenguaje MQL5 para resolver los problemas planteados.


4. Ejemplos del uso de las listas en MQL5

En la sección anterior, al considerar los métodos de la clase de listas CList de la Librería estándar, puse algunos ejemplos.

Ahora propongo a la atención del lector los casos cuando la lista se usa para resolver unos problemas específicos. Y aquí, no puedo sin mencionar otra vez más las ventajas que ofrece una lista como tipo de datos de contenedor. Podemos trabajar con el código de forma eficaz utilizando la flexibilidad de la lista.


4.1 Procesamiento de objetos gráficos

Imagínese que tenemos que crear los objetos gráficos de manera programada en el gráfico. Pueden ser objetos diferentes, y pueden aparecer en el gráfico por varias razones.

Me viene a la memoria un caso cuando fue precisamente la lista que me ayudó resolver una situación con objetos gráficos. Y ahora me gustaría compartir esta experiencia con Ustedes.

Tenía una tarea para crear unas línea verticales según la condición especificada. Según esta condición, las verticales limitaban un intervalo de tiempo determinado. Además, el intervalo de tiempo variaba de vez en cuando su duración. Aparte de eso, no siempre estaba completamente formado.

Yo estaba estudiando el comportamiento de la media móvil (EMA21), y para eso tenía que recopilar las estadísticas.

Particularmente, estaba interesado en la duración de inclinación de la media móvil (MA). Por ejemplo, para el movimiento descendiente el punto de inicio se identificaba así: se registraba el cambio negativo de la MA (es decir, reducción del valor) y se dibujaba la vertical. En la Fig. 17 para EURUSD, H1 este punto ha sido encontrado el 5 de septiembre de 2013 en el momento de apertura de la barra a las 16:00.

Fig. 17 First point of the downward interval

Рис.17 El primer punto del intervalo descendiente 


El segundo punto que indicaba el final del movimiento descendiente se identificaba basándose en el principio inverso: se registraba el movimiento positivo de la MA, es decir, el aumento del valor (Fig. 18). 


Fig. 18 Second point of the downward interval

Рис.18 El segundo punto del intervalo descendiente


De esta manera, este intervalo empezaba el 5 de septiembre de 2013 a las 16:00 y se terminaba el 6 de septiembre de 2013 a las 17:00.

El sistema de búsqueda de diferentes intervalos se puede hacer más complejo o más sencillo. Aquí no se trata de eso. Lo que más importa es que esta técnica de trabajo con los objetos gráficos y recopilación de estadísticas concierne a una de las principales ventajas de la lista: flexibilidad de composición.

En cuanto al ejemplo en cuestión, para empezar he creado el nodo del tipo CVertLineNode que se encarga de 2 objetos gráficos "línea vertical".

La definición de la clase es la siguiente:

//+------------------------------------------------------------------+
//|                      Clase CVertLineNode                         |
//+------------------------------------------------------------------+
class CVertLineNode : public CObject
  {
private:
   SVertLineProperties m_vert_lines[2];      // array de estructuras de propiedades de la línea vertical   
   uint              m_duration;             // duración de frames
   bool              m_IsFrameFormed;        // bandera de formación de frames

public:
   void              CVertLineNode(void);
   void             ~CVertLineNode(void){};
   //--- métodos set   
   void              SetLine(const SVertLineProperties &_vert_line,bool IsFirst=true);
   void              SetDuration(const uint _duration){this.m_duration=_duration;};
   void              SetFrameFlag(const bool _frame_flag){this.m_IsFrameFormed=_frame_flag;};
   //--- métodos get   
   void              GetLine(SVertLineProperties &_vert_line_out,bool IsFirst=true) const;
   uint              GetDuration(void) const;
   bool              GetFrameFlag(void) const;
   //--- trazado de líneas
   bool              DrawLine(bool IsFirst=true) const;
  };

Básicamente, esta clase de nodo describe un frame (aquí yo interpreto este concepto como una cierta cantidad de velas limitadas por dos verticales). Los límites del frame están representados por dos estructuras de propiedades de la línea vertical, duración y la bandera de formación.

Además del constructor y destructor estándar, la clase tiene unos métodos set y get. También hay método para trazar la línea en el gráfico.

Bueno, permítanme volver a recordarles que el nodo de verticales (frame) en mi ejemplo puede considerarse formado cuando tenemos la primera vertical que registra el inicio del movimiento descendiente de la MA y la segunda que registra el inicio del movimiento ascendiente de la MA.

Usando el script Stat_collector.mq5, visualicé en el gráfico todos los frames y conté qué número de nodos (frames) correspondía a uno u otro límite de duración en las últimas 2 mil barras.

Como ejemplo creé 4 listas que podrían contener cualquier frame. La primera lista contenía los frames cuyo número de velas no superaba 5, la segunda - 10, la tercera - 15, la cuarta - sin límite. 

NS      0       15:27:32        Stat_collector (EURUSD,H1)      =======List #1=======
RF      0       15:27:32        Stat_collector (EURUSD,H1)      Duration limit: 5
ML      0       15:27:32        Stat_collector (EURUSD,H1)      Nodes number: 65
HK      0       15:27:32        Stat_collector (EURUSD,H1)      
OO      0       15:27:32        Stat_collector (EURUSD,H1)      =======List #2=======
RI      0       15:27:32        Stat_collector (EURUSD,H1)      Duration limit: 10
NP      0       15:27:32        Stat_collector (EURUSD,H1)      Nodes number: 15
RG      0       15:27:32        Stat_collector (EURUSD,H1)      
FH      0       15:27:32        Stat_collector (EURUSD,H1)      =======List #3=======
GN      0       15:27:32        Stat_collector (EURUSD,H1)      Duration limit: 15
FG      0       15:27:32        Stat_collector (EURUSD,H1)      Nodes number: 6
FR      0       15:27:32        Stat_collector (EURUSD,H1)      
CD      0       15:27:32        Stat_collector (EURUSD,H1)      =======List #4=======
PS      0       15:27:32        Stat_collector (EURUSD,H1)      Nodes number: 20

Como resultado, en el gráfico me salió lo siguiente (Fig. 19). Para que la percepción fuera más cómoda, la segunda vertical del frame la visualicé con el color azul.


Fig.19 Visualización de frames

Es curioso que el último frame fue formado a la última hora del viernes del 13 de diciembre de 2013. Entró en la segunda lista porque su duración era igual a 6 horas.


4.2 Procesamiento del trading virtual

Imagínese que necesitamos crear un Asesor Experto que implemente varias estrategias independientes respecto a una herramienta en un flujo de ticks. Claro que en realidad se puede implementar simultáneamente sólo 1 estrategia respecto a una herramienta. Todas las demás estrategias van a tener el carácter virtual. Por eso diré desde el principio que eso se implementa exclusivamente para la simulación y optimización de la idea de trading.

Aquí, tengo que hacer referencia a un artículo fundamental en el que se describen al detalle los conceptos básicos referentes tanto al trading en general, como al terminal MetaTrader 5 en particular. Se trata del artículo "Órdenes, posiciones y transacciones en MetaTrader 5".

Pues, si para solucionar este problema, utilizamos el concepto de trading, sistema de administración de objetos de trading y la metodología de almacenamiento de la información sobre los objetos de trading que son habituales en el entorno de MetaTrader 5, probablemente necesitamos pensar en la creación de una base de datos virtual.

Permítanme recordarles que el desarrollador clasifica todos los objetos de trading en las órdenes, posiciones, transacciones y órdenes históricas. Algún crítico puede decir que el término "objeto de trading" ha sido introducido en el uso por el autor a su antojo. Y tendrá toda la razón...

Propongo utilizar para el trading virtual el mismo planteamiento de la cuestión y obtener los siguientes objetos de trading: órdenes virtuales, posiciones virtuales, transacciones virtuales y órdenes de trading virtuales.

Creo que este tema merece una discusión más detallada y profunda. Entretanto, volviendo al objeto de nuestro artículo, quiero decir que precisamente los tipo de datos de contenedor y también las listas pueden facilitar la vida al programador durante la implementación de estrategias virtuales.

Imagine que aparece una nueva posición virtual que naturalmente no puede estar en la parte del servidor comercial. Eso significa que hay que guardar toda la información sobre ella en la parte del terminal. De base de datos aquí puede servir una lista que a su vez se compone de varias listas una de las cuales va a contener los nodos de la posición virtual.

Usando el planteamiento del desarrollador, las clases del trading virtual podrían ser las siguientes:

Clase/grupo

Descripción

CVirtualOrder

Clase para trabajar con la orden pendiente virtual

CVirtualHistoryOrder

Clase para trabajar con la orden "histórica" virtual

CVirtualPosition

Clase para trabajar con la posición abierta virtual

CVirtualDeal

Clase para trabajar con la transacción "histórica" virtual

CVirtualTrade

Clase para ejecutar operaciones virtuales de trading

Tabla 1. Clases del trading virtual


No voy a entrar en detalles de la composición de cualquiera de las clases del trading virtual. Pero, probablemente pueda contener todos, o casi todos, los métodos que tiene una clase de trading estándar. Quiero hacer hincapié en una cosa, el desarrollador no utiliza la clase del propio objeto, sino de sus propiedades.

Pues bien, para utilizar las listas en sus algoritmos, hacen falta también los nodos. Por eso tendremos que envolver la clase del objeto virtual de trading en el nodo.

Qué el nodo de la posición abierta virtual pertenezca al tipo CVirtualPositionNode. Entonces, la definición de este tipo inicialmente podría ser la siguiente:

//+------------------------------------------------------------------+
//|                Clase CVirtualPositionNode                        |
//+------------------------------------------------------------------+
class CVirtualPositionNode : public CObject
  {
protected:
   CVirtualPositionNode *m_virt_position;         // puntero al trading virtual

public:
   void              CVirtualPositionNode(void);  // constructor por defecto
   void             ~CVirtualPositionNode(void);  // destructor
  };

Ahora, en cuanto se abra una posición virtual, se puede añadirla en la lista de posiciones virtuales.

Una cosa más, con este planteamiento de trabajo con los objetos virtuales de trading se pierde la necesidad de usar la caché ya que la misma base de datos va a almacenarse en la memoria operativa. Por supuesto, se puede implementar su almacenamiento en otros medios de almacenamiento.


Conclusión

En este artículo he intentado demostrar las ventajas de un tipo de datos de contenedor como la "lista". Tampoco he podido omitir sus desventajas. En cualquier caso, espero que este material sea útil para los que están estudiando la POO en general, y uno de sus principios fundamentales que es el polimorfismo, en particular.


Ubicación de archivos:

Desde mi punto de vista, lo más conveniente sería crear y guardar los archivos en una carpeta del proyecto. Por ejemplo, la ruta puede ser así: %MQL5\Projects\UserLists. Es donde guardaba todos mis archivos de códigos fuente. Si va a utilizar las carpetas predefinidas, en el código de algunos archivos tendrá que cambiar el modo de designación del archivo de inclusión (en vez de comillas poner corchetes angulares).

#ArchivoRutaDescripción
1 CiSingleNode.mqh  %MQL5\Projects\UserLists  Clase del nodo para la lista simplemente enlazada
2 CDoubleNode.mqh  %MQL5\Projects\UserLists  Clase del nodo para la lista doblemente enlazada
3 CiUnrollDoubleNode.mqh  %MQL5\Projects\UserLists  Clase del nodo para la lista doblemente enlazada  usando nodos de arrays
4 test_nodes.mq5  %MQL5\Projects\UserLists  Script con ejemplos de trabajo con nodos
5 CiSingleList.mqh  %MQL5\Projects\UserLists  Clase de la lista simplemente enlazada
6 CDoubleList.mqh  %MQL5\Projects\UserLists  Clase de la lista doblemente enlazada
7 CiUnrollDoubleList.mqh  %MQL5\Projects\UserLists  Clase de la lista doblemente enlazada usando nodos de arrays
 8 CiCircleDoublList.mqh %MQL5\Projects\UserLists Clase de la lista circular doblemente enlazada
 9 test_sList.mq5 %MQL5\Projects\UserLists Script con ejemplos de trabajo con la lista simplemente enlazada
 10 test_dList.mq5 %MQL5\Projects\UserLists Script con ejemplos de trabajo con la lista doblemente enlazada
 11 test_UdList.mq5 %MQL5\Projects\UserLists Script con ejemplos de trabajo con la lista doblemente enlazada usando nodos de arrays
 12 test_CdList.mq5 %MQL5\Projects\UserLists Script con ejemplos de trabajo con la lista circular doblemente enlazada
 13 test_MQL5_List.mq5 %MQL5\Projects\UserLists Script con ejemplos de trabajo con la clase CList
 14 CNodeInt.mqh %MQL5\Projects\UserLists Clase del nodo del tipo integer
 15 CIntList.mqh %MQL5\Projects\UserLists Clase de la lista para los nodos CNodeInt
 16 CRandom.mqh %MQL5\Projects\UserLists Clase del generador de números aleatorios
 17 CVertLineNode.mqh %MQL5\Projects\UserLists Clase del nodo para el procesamiento del frame de verticales
 18 Stat_collector.mq5 %MQL5\Projects\UserLists Script del ejemplo de recopilación de estadísticas


Referencias:

  1. A. Friedman, L. Klander, M. Michaelis, H. Schildt, C/C++ Annotated Archives. Mcgraw-Hill Osborne Media, 1999. 1008 pages.
  2. V.D. Daleka, A.S. Derevyanko, O.G. Kravets, L.E. Timanovskaya. Data Models and Structures. Study Guide. Kharkov, KhGPU, 2000. 241 pages (en ruso).