English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
Fundamentos básicos da Programação MQL5: Lista

Fundamentos básicos da Programação MQL5: Lista

MetaTrader 5Exemplos | 25 julho 2014, 08:56
3 791 0
Denis Kirichenko
Denis Kirichenko

Introdução

A nova versão da linguagem MQL proporcionou aos desenvolvedores de sistemas automatizados de negociação ferramentas eficazes para a implementação de tarefas complexas. Não se pode negar o fato de que as funcionalidades de programação da linguagem foram ampliadas consideravelmente. As características de POO em MQL5 por si só já valem muito. Além disso, não se deve deixar de mencionar a Biblioteca Padrão. A julgar pelo código de erro de número 359, os templates de classe serão suportados em breve.

Neste artigo, eu gostaria de conduzir algo que pode de alguma forma ser uma expansão ou continuação dos temas que descrevem os tipos de dados e seus conjuntos. Aqui, eu gostaria de fazer referência a um artigo publicado no site da Comunidade MQL5. Uma descrição muito detalhada dos princípios e da lógica de como trabalhar com arrays foi fornecido por Dmitry Fedoseev (Integer) em seu artigo "Fundamentos básicos da programação MQL5: Arrays".

Assim, hoje me proponho em recorrer as listas, e, mais precisamente, nas listas lineares encadeadas ou ligadas. Vamos começar com a estrutura da lista, seu significado e sua lógica. Depois disso, vamos estudar as ferramentas relacionadas que já estão disponíveis na Biblioteca Padrão. Em suma, eu vou fornecer exemplos de como as listas podem ser utilizados quando se trabalha com MQL5.

  1. Conceitos de Lista e Nó: Teoria
  2. Conceitos de Lista e Nó: Programação
  3. Listas na biblioteca padrão MQL5
  4. Exemplos de utilização das listas em MQL5


1. Conceitos de Lista e Nó: Teoria

Então, o que é uma lista para um desenvolvedor e como procedemos sobre isso? Eu vou fazer referência à uma fonte pública de informação, a Wikipédia, por uma definição genérica deste termo:

Em ciência da computação, uma lista é um tipo de dados abstrato que implementa uma coleção finita e ordenada de valores, onde o mesmo valor pode ocorrer mais de uma vez. Uma instância de uma lista é uma representação computacional do conceito matemático de uma sequência finita - uma tupla. Cada valor de uma instância na lista é geralmente chamado de um item, de entrada, ou elemento da lista; se o mesmo valor ocorre várias vezes, cada ocorrência é considerada um produto distinto.

O termo 'lista' também é utilizado por várias estruturas de dados concretas que pode ser utilizado para implementar listas abstratas, especialmente as listas encadeadas.

Eu acredito que você irá concordar comigo que esta definição é de certa maneira acadêmica demais.

Para os fins deste artigo, estamos mais interessados ​​na última frase desta definição. Então, vamos debruçar sobre ele:

Em ciência da computação, uma lista encadeada é uma estrutura de dados básica e dinâmica consistido de nós, onde cada nó é composto de dados e uma ou duas referências ('ligadas ou encadeadas') para o nó seguinte e / ou anterior da lista.[1] O principal benefício de uma lista encadeada sobre um array convencional é sua flexibilidade estrutural: a seqüência dos itens da lista encadeada não precisa coincidir com a seqüência de elementos de dados na memória do computador, enquanto que as ligações internas dos itens da lista são sempre mantidos para a lista de travessia.

Vamos tentar olhar para ela passo a passo.

Em ciência da computação, uma lista em si, é algum tipo de dados. Nós estabelecemos isso. Ela é preferivelmente um tipo de dados sintético, uma vez que inclui outros tipos de dados. Uma lista é um pouco semelhante a um array. Se um array de um tipo de dados nunca foi classificado como um novo tipo de dados, então ele seria uma lista. Mas não por completo.

A principal vantagem de uma lista é que ela permite a inserção ou a remoção dos nós em qualquer ponto na lista, como requerido. Aqui, a lista é semelhante a um array dinâmico, exceto para aquela lista em que você não precisa usar a função ArrayResize() o tempo todo.

Falando em termos de ordem de elementos de memória, os nós da lista não está armazenado e não precisam ser armazenados da mesma forma que os elementos do array são armazenados em áreas de memória adjacentes.

É mais ou menos isso. Indo mais para baixo na lista.


1.1 Nó em uma lista encadeada simples

As listas permitem que você armazene os nós, em vez de itens. Nó é um tipo de dados que consiste de duas partes.

A primeira parte é um campo de dados, e a segunda parte é usada para ligar (encadear) com outro nó(s) (Fig. 1). O primeiro nó da lista é chamado de 'cabeça', e o último nó da lista é chamado de "cauda". O campo de ligação da cauda contém uma referência NULL. Ela é usada basicamente para indicar a ausência de outros nós na lista. Outras fontes especializados se referem ao resto da lista, após a cabeça, como "cauda".

Fig. 1 Nós em uma lista encadeada simples

Fig. 1 Nós em uma lista encadeada simples

Além dos nós da lista encadeada simples, há outros tipos de nós. Um nó em uma lista duplamente encadeada é, talvez, a mais comum.


1.2 Nó em uma lista duplamente encadeada

Precisaremos também de um nó que irá atender às necessidades de uma lista duplamente encadeada. Ela é diferente do tipo da lista anterior uma vez que ela contém uma outra ligação que aponta para o nó anterior. E, naturalmente, o nó da cabeça da lista conterá uma referência NULL. No diagrama que mostra a estrutura da lista que contém tais nós (Fig. 2), as ligações que apontam para os nós anteriores são apresentadas como flechas vermelhas.

Fig. 2 Nós em uma lista duplamente encadeada

Fig. 2 Nós em uma lista duplamente encadeada


Assim, as capacidades de um nó de uma lista duplamente encadeada serão semelhantes às de um nó de uma lista encadeada simples Você só vai precisar lidar com mais uma ligação, que será para o nó anterior.


1.3 Nó em uma lista circular duplamente encadeada

Há casos em que os nós acima também podem ser utilizados em listas não-lineares. E, apesar do artigo descrever principalmente as listas lineares, vou dar um exemplo de uma lista circular também.

Fig. 3 Nós em uma lista circular duplamente encadeada

Fig. 3 Nós em um lista circular duplamente encadeada


O diagrama de uma lista circular duplamente encadeada (Fig. 3) mostra que os nós com dois campos de ligação são simplesmente um encadeamento circular. Isso é feito usando as flechas laranja e verde. Assim, o nó principal irá ser ligada à cauda (como o elemento anterior). E o campo de ligação do nó da cauda não estará vazio, uma vez que ele irá apontar para a cabeça.


1.4 Principais Operações de uma lista

Conforme estabelecido na literatura especializada, todas as operações da lista podem ser divididas em três grupos base:

  1. Inserção (de um novo nó na lista);
  2. Remoção (de um nó da lista);
  3. Verificação (dos dados de um nó).

Os métodos de inserção incluem:

  • inserir um novo nó no início da lista;
  • inserir um novo nó no fim da lista;
  • inserir um nó em uma posição específica da lista;
  • inserir um nó em uma lista vazia;
  • parametrização do construtor.

Quanto às operações de remoção, elas se assemelham praticamente com as operações correspondentes do grupo de inserção:

  • remover o nó principal;
  • remover o nó da cauda;
  • remover um nó a partir de uma posição específica da lista;
  • destrutor.

Aqui, eu gostaria de tomar nota que destrutor serve não só para completar e encerrar a operação de lista corretamente, mas também para eliminar corretamente todos os seus elementos.

O terceiro grupo, que são as operações de verificação, fornece acesso ao nós ou aos valores do nó da lista:

  • busca de um determinado valor;
  • verifica se a lista esta vazia;
  • obtém o valor do n-ésimo nó da lista;
  • obtém o ponteiro para o n-ésimo nó da lista;
  • obtém o tamanho da lista;
  • imprimi os valores dos elementos da lista.

Além dos grupos de base, eu gostaria também de separar o quarto, o grupo de serviço. Ele presta serviços de manutenção aos grupos anteriores:

  • operador de atribuição;
  • construtor de cópia;
  • trabalhar com ponteiro dinâmico;
  • copiar a lista por valores;
  • ordenação.

Bem, isso é tudo. O desenvolvedor pode, claro, expandir a funcionalidade e as características de uma classe lista a qualquer momento, conforme for sua necessidade.


2. Conceitos de Lista e Nó: Programação

Nesta parte, sugiro que devemos prosseguir diretamente para a programação dos nós e listas. Será fornecido Ilustrações para o código, se necessário.

2.1 Nó em uma lista encadeada simples

Vamos criar uma base para a classe nó (Fig. 4) e atender às necessidades de uma lista encadeada simples. Você pode se familiarizar com a notação de diagramas de classe (modelo) no artigo intitulado "Como Desenvolver um Expert Advisor usando ferramentas de UML" (Ver fig. 5. Modelo UML da classe CTradeExpert).

Modelo da classe CiSingleNode

Fig. 4 Modelo da classe CiSingleNode

Vamos agora tentar trabalhar com o código. Ele baseia-se no exemplo apresentado no livro de Art Friedman e outros autores. "C/C++ Annotated Archives".

//+------------------------------------------------------------------+
//|                     Classe CiSingleNode                          |
//+------------------------------------------------------------------+
class CiSingleNode
  {
protected:
   int               m_val;   // dados
   CiSingleNode     *m_next;  // aponta para o próximo nó
public:
   void              CiSingleNode(void);                             // construtor padrão
   void              CiSingleNode(int _node_val);                    // construtor parametrizado
   void             ~CiSingleNode(void);                             // destrutor
   void              SetVal(int _node_val);                          // método que define os dados
   void              SetNextNode(CiSingleNode *_ptr_next);           // método que define o próximo nó
   virtual void      SetPrevNode(CiSingleNode *_ptr_prev){};         // método que define o nó anterior
   virtual CiSingleNode *GetPrevNode(void) const {return NULL;};     // método que obtém o nó anterior
   CiSingleNode     *GetNextNode(void) const;                        // método que obtém o próximo nó 
   int               GetVal(void){TRACE_CALL(_t_flag) return m_val;} // método que obtém os dados 
  };

Eu não vou explicar cada método da classe CiSingleNode. Você será capaz de dar uma olhada neles no arquivo anexado CiSingleNode.mqh. No entanto, eu gostaria de chamar sua atenção para uma nuance interessante. A classe contém métodos virtuais que trabalham com os nós anteriores. Eles são, na verdade, manequins e sua presença serve para fins de polimorfismo para futuros descendentes.

O código usa a diretiva de pré-processamento TRACE_CALL(f) necessária para rastrear as chamadas de cada método utilizado.

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

Com apenas a classe CiSingleNode disponível, você já consegue criar uma lista encadeada simples. Deixe-me dar um código de exemplo.

/ / =========== Exemplo 1 (processamento do tipo CiSingleNode)
 
   CiSingleNode *p_sNodes[3];                             // #1
   p_sNodes[0]=NULL;
   srand(GetTickCount()); // Inicializa um gerador de números aleatórios
//--- cria os nós
   for(int i=0;i<ArraySize(p_sNodes);i++)
      p_sNodes[i]=new CiSingleNode(rand());               // #2
//--- ligações
   for(int j=0;j<(ArraySize(p_sNodes)-1);j++)
      p_sNodes[j].SetNextNode(p_sNodes[j+1]);             // #3
//--- verifica os valores
   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));
     }
//--- verifica os próximos nós
   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));
     }
//--- remove os nós
   for(int i=0;i<ArraySize(p_sNodes);i++)
      delete p_sNodes[i];                                   // #12 

No comentário #1, declaramos um array de ponteiros para objetos do tipo CiSingleNode. No comentário #2, o array é preenchido com os ponteiros criados. Para os dados de cada nó, nós obtemos um inteiro pseudo randômico na faixa de 0 a 32767 usando a função rand(). Os nós são ligados ao próximo ponteiro no comentário #3. Nos cometários #4 e #5, verificamos os valores dos nós, e nos cometários de #9 a #11 vamos verificar o desempenho das ligações. Os ponteiros são removidos no cometário #12.

Isto é o que foi impresso no log.

DH      0       23:23:10        test_nodes (EURUSD,H4)  Nó #1 valor = 3335
KP      0       23:23:10        test_nodes (EURUSD,H4)  Nó #2 valor = 21584
GI      0       23:23:10        test_nodes (EURUSD,H4)  Nó #3 valor = 917
HQ      0       23:23:10        test_nodes (EURUSD,H4)  Próximo Nó #1 valor = 21584
HI      0       23:23:10        test_nodes (EURUSD,H4)  Próximo Nó #2 valor = 917

A estrutura resultante do nó pode ser esquematicamente mostrada como segue na (Fig. 5).

Fig. 5 Ligações entre os nós no array CiSingleNode *p_sNodes[3]

Fig. 5 Ligações entre os nós no array CiSingleNode *p_sNodes[3]

Vamos agora proceder para os nós em uma lista duplamente encadeada.


2.2 Nó em uma lista duplamente encadeada

Primeiro, precisamos retocar ao fato de que um nó em uma lista duplamente encadeada é diferente devido a existência de dois ponteiros: o ponteiro do próximo nó e o ponteiro do nó anterior. Ou seja, além da ligação ao nó seguinte, você precisa adicionar um ponteiro para o nó anterior ao nó da lista encadeada simples.

Ao fazer isso, proponho usar a herança de classes como uma relação de classe. Em seguida, o modelo de classe para um nó em uma lista duplamente encadeada deve parecer como se segue (Fig. 6).

Modelo da classe CDoubleNode

Fig. 6 Modelo da classe CDoubleNode

Agora, é hora de dar uma olhada no código.

//+------------------------------------------------------------------+
//|                    Classe CDoubleNode                            |
//+------------------------------------------------------------------+
class CDoubleNode : public CiSingleNode
  {
protected:
   CiSingleNode     *m_prev;  // aponta para o nó anterior 

public:
   void              CDoubleNode(void);                     // construtor padrão
   void              CDoubleNode(int node_val);             // construtor parametrizado
   void             ~CDoubleNode(void){TRACE_CALL(_t_flag)};// destrutor
   virtual void      SetPrevNode(CiSingleNode *_ptr_prev);  // método para definir o nó anterior
   virtual CiSingleNode *GetPrevNode(void) const;           // método para obter o nó anterior CDoubleNode
  };

Há poucos métodos adicionais - eles são virtuais e estão relacionados em trabalhar com o nó anterior. A descrição completa da classe é fornecida em CDoubleNode.mqh.

Vamos tentar criar uma lista duplamente encadeada com base na classe CDoubleNode. Deixe-me dar um código de exemplo.

//=========== Exemplo 2 (processamento do tipo CDoubleNode)

   CiSingleNode *p_dNodes[3];                             // #1
   p_dNodes[0]=NULL;
   srand(GetTickCount()); // Inicializa um gerador de números aleatórios
//--- Criar nós
   for(int i=0;i<ArraySize(p_dNodes);i++)
      p_dNodes[i]=new CDoubleNode(rand());                // #2
//--- Ligações
   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
     }
//--- verifica os valores
   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));
     }
//--- verifica os próximos nós
   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));
     }
//--- verifica os nós anteriores
   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));
     }
//--- remove os nós
   for(int i=0;i<ArraySize(p_dNodes);i++)
      delete p_dNodes[i];                                     // #15 

A princípio, isto é semelhante à criação de uma lista encadeada simples, mas ainda há algumas peculiaridades. Observe como o array de ponteiros p_dNodes[] é declarado no comentário #1. O tipo de ponteiros pode ser definido de forma idêntica a classe base. O princípio do polimorfismo no comentário #2 irá ajudar-nos a reconhecê-los no futuro. Os Nós anteriores são verificadas nos comentários de #12 a #14.

As informações a seguir foram impressas no log.

GJ      0       16:28:12        test_nodes (EURUSD,H4)  Nó #1 valor = 17543
IQ      0       16:28:12        test_nodes (EURUSD,H4)  Nó #2 valor = 1185
KK      0       16:28:12        test_nodes (EURUSD,H4)  Nó #3 valor = 23216
DS      0       16:28:12        test_nodes (EURUSD,H4)  Próximo Nó #1 valor = 1185
NH      0       16:28:12        test_nodes (EURUSD,H4)  Próximo Nó #2 valor = 23216
FR      0       16:28:12        test_nodes (EURUSD,H4)  Nó anterior #2 valor = 17543
LI      0       16:28:12        test_nodes (EURUSD,H4)  Nó anterior #3 valor = 1185

A estrutura resultante do nó pode ser esquematicamente mostrada como segue na (Figura 7):

Fig. 7 Ligações entre os nós no array CDoubleNode *p_sNodes[3]

Fig. 7 Ligações entre os nós no array CDoubleNode *p_sNodes[3]

Agora eu sugiro que nós consideremos um nó que será necessário na criação de uma lista vetorial duplamente encadeada.


2.3 Nó em uma lista vetorial duplamente encadeada

Pense em um nó que contém um membro de dados que, em vez de um valor único, é atribuível a todo array, isto é, ele contém e descreve todo o array. Tal nó pode ser utilizado para criar uma lista vetorial. Eu decidi não fornecer qualquer ilustração aqui já que este nó é exatamente o mesmo que um nó padrão em uma lista duplamente encadeada. A única diferença é que o atributo de dados encapsula todo o array.

Vou usar a herança de classe novamente. A classe CDoubleNode servirá como a classe base para um nó em uma lista vetorial duplamente encadeada. E o modelo de classe para um nó em uma lista vetorial duplamente encadeada ficará da seguinte forma (Fig. 8).

Modelo da classe CiUnrollDoubleNode

Fig. 8 Modelo da classe CiUnrollDoubleNode

A classe CiUnrollDoubleNode pode ser definida usando o seguinte código:

//+------------------------------------------------------------------+
//|                    Classe CiUnrollDoubleNode                     |
//+------------------------------------------------------------------+
class CiUnrollDoubleNode : public CDoubleNode
  {
private:
   int               m_arr_val[]; // data array

public:
   void              CiUnrollDoubleNode(void);               // construtor padrão 
   void              CiUnrollDoubleNode(int &_node_arr[]);   // construtor parametrizado
   void             ~CiUnrollDoubleNode(void);               // destrutor
   bool              GetArrVal(int &_dest_arr_val[])const;   // método para obter o array de dados
   bool              SetArrVal(const int &_node_arr_val[]);  // método para definir o array de dados
  };

É possível olhar para cada método com mais detalhe em CiUnrollDoubleNode.mqh.

Vamos considerar um construtor parametrizado como exemplo.

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

Aqui, usando a lista de inicialização, nós colocamos o tamanho do array unidimensional no membro de dados this.m_val.

Depois disso, criamos "manualmente" uma lista vetorial duplamente encadeada e verificamos as ligações dentro dele.

//=========== Exemplo 3 (processamento do tipo CiUnrollDoubleNode)

//--- arrays de dados
   int arr1[],arr2[],arr3[];                                  // #1
   int arr_size=15;
   ArrayResize(arr1,arr_size);
   ArrayResize(arr2,arr_size);
   ArrayResize(arr3,arr_size);
   srand(GetTickCount()); // Inicializa um gerador de números aleatórios
   
//--- Preenche os arrays com números inteiros pseudo aleatórios  
   for(int i=0;i<arr_size;i++)
     {
      arr1[i]=rand();                                         // #2
      arr2[i]=rand();
      arr3[i]=rand();
     }
//--- criar nós
   CiUnrollDoubleNode *p_udNodes[3];                          // #3
   p_udNodes[0]=new CiUnrollDoubleNode(arr1);
   p_udNodes[1]=new CiUnrollDoubleNode(arr2);
   p_udNodes[2]=new CiUnrollDoubleNode(arr3);
//--- ligações
   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
     }
//--- verifica os valores
   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));
     }
//--- verifica os valores dos arrays
   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);
        }
     }
//--- verifica o próximos nós
   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);
        }
     }
//--- verifica os nós anteriores
   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);
        }
     }
//--- remove os nós
   for(int i=0;i<ArraySize(p_udNodes);i++)
      delete p_udNodes[i];                                            // #12
  }

A quantidade de código tornou-se um pouco maior. Isso tem a ver com o fato de que precisamos criar e preencher um array para cada nó.

O trabalho com arrays de dados começa no comentário #1. Ele é semelhante ao que tivemos nos nós considerados anteriormente. É exatamente isso que precisamos para imprimir os valores dos dados de cada nó para toda o array (por exemplo, comentário #9).

Isto foi o que eu recebi:

IN      0       00:09:13        test_nodes (EURUSD.m,H4)        Nó #1 valor = 15
NF      0       00:09:13        test_nodes (EURUSD.m,H4)        Nó #2 valor = 15
CI      0       00:09:13        test_nodes (EURUSD.m,H4)        Nó #3 valor = 15
FQ      0       00:09:13        test_nodes (EURUSD.m,H4)        Nó #1 valores do array = 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)        Nó #2 valores do array = 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)        Nó #3 valores do array = 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)        Próximo Nó #1 valores do array = 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)        Próximo Nó #2 valores do array = 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)        Nó anterior #2 valores do array = 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)        Nó anterior #3 valores do array = 1809, 18553, 23224, 20208, 10191, 4833, 25959, 2761, 7291, 23254, 29865, 23938, 7585, 20880, 25756

Eu sugiro que devemos traçar uma linha sob trabalhar com nós e proceder diretamente para as definições de classe de listas diferentes. Os exemplos 1-3 podem ser encontrados no script test_nodes.mq5.


2.4 Lista encadeada simples

Já está na hora de criarmos um modelo de classe de uma lista encadeada simples fora dos principais grupos de operação da lista (Fig. 9).

Modelo da classe CiSingleList

Fig. 9 Modelo da classe CiSingleList

É fácil de ver que a classe CiSingleList usa o nó do tipo CiSingleNode. Falando nos tipos de relações entre as classes, podemos dizer que:

  1. a classe CiSingleList contém a classe CiSingleNode (composição);
  2. a classe CiSingleList usa os métodos da classe CiSingleNode (dependência).

A ilustração das relações acima é fornecido na figura. 10.

Fig. 10 Tipos de relações entre a classe CiSingleList e a classe CiSingleNode

Fig. 10 Tipos de relações entre a classe CiSingleList e a classe CiSingleNode

Vamos criar uma nova classe - CiSingleList. Olhando em frente, todas as outras classes de lista usadas neste artigo, serão baseadas nesta classe. É por isso que ela é tão "rica".

//+------------------------------------------------------------------+
//|                    Classe CiSingleList                           |
//+------------------------------------------------------------------+
class CiSingleList
  {
protected:
   CiSingleNode     *m_head;    // cabeça
   CiSingleNode     *m_tail;    // cauda
   uint              m_size;    // número de nós na lista
public:
   //--- construtor e destrutor
   void              CiSingleList();                              // construtor padrão 
   void              CiSingleList(int _node_val);                 // construtor parametrizado 
   void             ~CiSingleList();                              // destrutor                

   //--- inserir nós   
   void              AddFront(int _node_val);                         // insere um novo nó no inicio da lista
   void              AddRear(int _node_val);                          // insere um novo nó no fim da lista   
   virtual void      AddFront(int &_node_arr[]){TRACE_CALL(_t_flag)}; // insere um novo nó no inicio da lista
   virtual void      AddRear(int &_node_arr[]){TRACE_CALL(_t_flag)};  // insere um novo nó no fim da lista
   //--- remove nós     
   int               RemoveFront(void);                           // remove o nó cabeça       
   int               RemoveRear(void);                            // remove o nó do fim da lista
   void              DeleteNodeByIndex(const uint _idx);          // remove o n-ésimo nó da lista

   //--- verificação   
   virtual bool      Find(const int _node_val) const;             // busca o valor solicitado    
   bool              IsEmpty(void) const;                         // verifica se a lista esta vazia
   virtual int       GetValByIndex(const uint _idx) const;        // valor do n-ésimo nó da lista
   virtual CiSingleNode *GetNodeByIndex(const uint _idx) const;   // obtém o n-ésimo nó da lista
   virtual bool      SetNodeByIndex(CiSingleNode *_new_node,const uint _idx); // insere o n-ésimo novo nó na lista
   CiSingleNode     *GetHeadNode(void) const;                     // obtém o nó cabeça
   CiSingleNode     *GetTailNode(void) const;                     // obtém o nó cauda
   virtual uint      Size(void) const;                            // list size
   //--- serviço
   virtual void      PrintList(string _caption=NULL);             // imprime a lista
   virtual bool      CopyByValue(const CiSingleList &_sList);     // copia a lista pelos valores
   virtual void      BubbleSort(void);                            // Ordenação BubbleSort
   //---templates
   template<typename dPointer>
   bool              CheckDynamicPointer(dPointer &_p);           // template para verificar o ponteiro dinâmico
   template<typename dPointer>
   bool              DeleteDynamicPointer(dPointer &_p);          // template para remover o ponteiro dinâmico

protected:
   void              operator=(const CiSingleList &_sList) const; // operador de atribuição
   void              CiSingleList(const CiSingleList &_sList);    // copia o construtor
   virtual bool      AddToEmpty(int _node_val);                   // insere um novo nó a uma lista vazia
   virtual void      addFront(int _node_val);                     // insere um novo nó "nativo" no começo da lista
   virtual void      addRear(int _node_val);                      // insere um novo nó "nativo" no fim da lista
   virtual int       removeFront(void);                           // remove o nó cabeça "nativo"
   virtual int       removeRear(void);                            // remove o nó "nativo" do fim da lista
   virtual void      deleteNodeByIndex(const uint _idx);          // remove o n-ésimo nó "nativo" da lista
   virtual CiSingleNode *newNode(int _val);                       // novo nó "nativo"
   virtual void      CalcSize(void) const;                        // calcula o tamanho da lista
  };

A definição completa dos métodos da classe é fornecida em CiSingleList.mqh.

Quando eu comecei a desenvolver esta classe, havia apenas 3 membros de dados e apenas alguns métodos. Mas uma vez que esta classe serviu de base para outras classes, eu tive que adicionar várias funções virtuais. Eu não vou descrever estes métodos em detalhes. Um exemplo do uso dessa classe de lista encadeada simples pode ser encontrado no script test_sList.mq5.

Se ela é executada sem o flag de rastreamento, as seguintes entradas aparecerão no log:

KG      0       12:58:32        test_sList (EURUSD,H1)  =======Lista #1=======
PF      0       12:58:32        test_sList (EURUSD,H1)  Nó #1, val=14 
RL      0       12:58:32        test_sList (EURUSD,H1)  Nó #2, val=666 
MD      0       12:58:32        test_sList (EURUSD,H1)  Nó #3, val=13 
DM      0       12:58:32        test_sList (EURUSD,H1)  Nó #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)  =======Lista #2=======
RE      0       12:58:32        test_sList (EURUSD,H1)  Nó #1, val=14 
DQ      0       12:58:32        test_sList (EURUSD,H1)  Nó #2, val=666 
GK      0       12:58:32        test_sList (EURUSD,H1)  Nó #3, val=13 
FP      0       12:58:32        test_sList (EURUSD,H1)  Nó #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)  =======Lista renovada #2=======
GK      0       12:58:32        test_sList (EURUSD,H1)  Nó #1, val=11 
JP      0       12:58:32        test_sList (EURUSD,H1)  Nó #2, val=13 
JI      0       12:58:32        test_sList (EURUSD,H1)  Nó #3, val=14 
CF      0       12:58:32        test_sList (EURUSD,H1)  Nó #4, val=34 
QL      0       12:58:32        test_sList (EURUSD,H1)  Nó #5, val=35 
OE      0       12:58:32        test_sList (EURUSD,H1)  Nó #6, val=36 
MR      0       12:58:32        test_sList (EURUSD,H1)  Nó #7, val=37 
KK      0       12:58:32        test_sList (EURUSD,H1)  Nó #8, val=38 
MS      0       12:58:32        test_sList (EURUSD,H1)  Nó #9, val=666 
OF      0       12:58:32        test_sList (EURUSD,H1)  
QK      0       12:58:32        test_sList (EURUSD,H1)  

O script preencheu 2 listas encadeadas simples e depois aumentou e ordenou na segunda lista.


2,5 Lista duplamente encadeada

Vamos tentar criar agora uma lista duplamente encadeada com base na lista do tipo anterior. A ilustração do modelo da classe de uma lista duplamente encadeada é fornecido na figura. 11:

Modelo da classe CDoubleList

Fig. 11 Modelo da classe CDoubleList

A classe descendente contém menos métodos, enquanto que os membros de dados estão completamente ausentes. Abaixo está a definição da classe CDoubleList.

//+------------------------------------------------------------------+
//|                      Classe CDoubleList                          |
//+------------------------------------------------------------------+
class CDoubleList : public CiSingleList
  {
public:
   void              CDoubleList(void);                  // construtor padrão    
   void              CDoubleList(int _node_val);         // construtor parametrizado   
   void             ~CDoubleList(void){};                // destrutor                  
   virtual bool      SetNodeByIndex(CiSingleNode *_new_node,const uint _idx); // insere o n-ésimo novo nó na lista  

protected:
   virtual bool      AddToEmpty(int _node_val);          // insere um nó a uma lista vazia
   virtual void      addFront(int _node_val);            // insere um novo nó "nativo" no começo da lista
   virtual void      addRear(int _node_val);             // insere um novo nó "nativo" no fim da lista 
   virtual int       removeFront(void);                  // remove o nó cabeça "nativo"
   virtual int       removeRear(void);                   // remove o nó cauda "nativo"
   virtual void      deleteNodeByIndex(const uint _idx); // remove o n-ésimo nó "nativo" da lista
   virtual CiSingleNode *newNode(int _node_val);         // novo nó "nativo"
  };

A descrição completa dos métodos de classe CDoubleList é fornecido em CDoubleList.mqh.

De um modo geral, as funções virtuais são usados ​​aqui apenas para servir as necessidades do apontador para o nó anterior, que não existe nas listas encadeadas simples.

Um exemplo do uso da lista do tipo CDoubleList pode ser encontrado no script test_dList.mq5. Ele demonstra todas as operações da lista comuns que são pertinentes para este tipo de lista. O código do script contém uma seqüência peculiar:

CiSingleNode *_new_node=new CDoubleNode(666);     // Cria um novo nó do tipo CDoubleNode

Não há nenhum erro porque essa construção é bastante aceitável nos casos em que o ponteiro da classe base descreve um objeto da classe descendente. Esta é uma das vantagens da herança de classe.

Em MQL5, bem como em С++, o ponteiro para a classe base pode apontar para o objeto da subclasse derivada dessa classe base. Mas o inverso não é válido.

Se você escrever a linha a seguir:

CDoubleNode*_new_node=new CiSingleNode(666);

o compilador não irá relatar um erro ou um aviso, mas o programa será executado até que ele atinja esta linha. Neste caso, você verá uma mensagem sobre o lance errado dos tipos de referência pelos ponteiros. Uma vez que o mecanismo de ligação tardia só vem em ação quando o programa está sendo executado, é preciso considerar cuidadosamente a hierarquia das relações entre as classes.

Depois de executar o script, o registro conterá as seguintes entradas:

DN      0       13:10:57        test_dList (EURUSD,H1)  =======Lista #1=======
GO      0       13:10:57        test_dList (EURUSD,H1)  Nó #1, val=14 
IE      0       13:10:57        test_dList (EURUSD,H1)  Nó #2, val=666 
FM      0       13:10:57        test_dList (EURUSD,H1)  Nó #3, val=13 
KD      0       13:10:57        test_dList (EURUSD,H1)  Nó #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)  =======Lista #2=======
IL      0       13:10:57        test_dList (EURUSD,H1)  Nó #1, val=14 
KH      0       13:10:57        test_dList (EURUSD,H1)  Nó #2, val=666 
PR      0       13:10:57        test_dList (EURUSD,H1)  Nó #3, val=13 
MI      0       13:10:57        test_dList (EURUSD,H1)  Nó #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)  =======Lista renovada #2=======
PR      0       13:10:57        test_dList (EURUSD,H1)  Nó #1, val=11 
QI      0       13:10:57        test_dList (EURUSD,H1)  Nó #2, val=13 
QP      0       13:10:57        test_dList (EURUSD,H1)  Nó #3, val=14 
LO      0       13:10:57        test_dList (EURUSD,H1)  Nó #4, val=34 
JE      0       13:10:57        test_dList (EURUSD,H1)  Nó #5, val=35 
HL      0       13:10:57        test_dList (EURUSD,H1)  Nó #6, val=36 
FK      0       13:10:57        test_dList (EURUSD,H1)  Nó #7, val=37 
DR      0       13:10:57        test_dList (EURUSD,H1)  Nó #8, val=38 
FJ      0       13:10:57        test_dList (EURUSD,H1)  Nó #9, val=666 
HO      0       13:10:57        test_dList (EURUSD,H1)  
JR      0       13:10:57        test_dList (EURUSD,H1)  

Como no caso da lista encadeada simples, o script tem preenchido a primeira lista (duplamente ligada), o copiou e depois passou para a segunda lista. Em seguida, ele aumentou o número de nós na segunda lista, ordenou e imprimiu a lista.


2.6 Lista vetorial duplamente encadeada

Esse tipo de lista é conveniente na medida em que lhe permite armazenar não apenas um valor, mas um array inteiro.

Vamos criar uma base para a lista do tipo CiUnrollDoubleList (fig. 12).

Modelo da classe CiUnrollDoubleList

Fig. 12 Modelo da classe CiUnrollDoubleList

Já que aqui nós estamos indo lidar com um array de dados, nós teremos que redefinir os métodos definidos na classe base indireta CiSingleList.

Abaixo está a definição da classe CiUnrollDoubleList.

//+------------------------------------------------------------------+
//|                    Classe CiUnrollDoubleList                     |
//+------------------------------------------------------------------+
class CiUnrollDoubleList : public CDoubleList
  {
public:
   void              CiUnrollDoubleList(void);                      // construtor padrão
   void              CiUnrollDoubleList(int &_node_arr[]);          // construtor parametrizado
   void             ~CiUnrollDoubleList(void){TRACE_CALL(_t_flag)}; // destrutor
   //---
   virtual void      AddFront(int &_node_arr[]);                    // insere um novo nó no começo da lista
   virtual void      AddRear(int &_node_arr[]);                     // insere um novo nó no fim da lista
   virtual bool      CopyByValue(const CiSingleList &_udList);      // copia pelos valores
   virtual void      PrintList(string _caption=NULL);               // imprime a lista
   virtual void      BubbleSort(void);                              // Ordenação do tipo BubbleSort

protected:
   virtual bool      AddToEmpty(int &_node_arr[]);                  // insere um nó a uma lista vazia
   virtual void      addFront(int &_node_arr[]);                    // insere um novo nó "nativo" para o início da lista
   virtual void      addRear(int &_node_arr[]);                     // insere um novo nó "nativo" para o fim da lista
   virtual int       removeFront(void);                             // remove o nó "nativo" do início da lista
   virtual int       removeRear(void);                              // remove o nó "nativo" do fim da lista
   virtual void      deleteNodeByIndex(const uint _idx);            // remove o n-ésimo nó "nativo" da lista
   virtual CiSingleNode *newNode(int &_node_arr[]);                 // novo nó "nativo"
  };

A definição completa dos métodos da classe é fornecida em CiUnrollDoubleList.mqh.

Vamos executar o script test_UdList.mq5 para verificar o funcionamento dos métodos da classe. Aqui, as operações com o nó são semelhantes as utilizadas nos scripts anteriores. Talvez devêssemos dizer algumas palavras sobre a ordenação e os métodos de impressão. O método de ordenação classifica os nós pelo número de elementos que contém no array do nó, assim, o array de menor tamanho estará na cabeça da lista.

O método de impressão imprime uma série de valores do array contidos em um determinado nó.

Após a execução do script, o registro terá as seguintes entradas:

II      0       13:22:23        test_UdList (EURUSD,H1) =======Lista #1=======
FN      0       13:22:23        test_UdList (EURUSD,H1) Lista Nó #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) Lista Nó #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) =======Lista #2 antes da ordenação=======
JO      0       13:22:23        test_UdList (EURUSD,H1) List Nó #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) Lista Nó #2, array: 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
CF      0       13:22:23        test_UdList (EURUSD,H1) Lista Nó #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) =======Lista #2 após a ordenação=======
FN      0       13:22:23        test_UdList (EURUSD,H1) Lista Nó #1, array: 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
CJ      0       13:22:23        test_UdList (EURUSD,H1) Lista Nó #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) Lista Nó #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 você pode ver, depois de ser ordenada, a lista udList2 foi impressa a partir do nó com o menor array para o nó que contém o maior array.


2.7 Lista circular duplamente encadeada

Embora as listas não-lineares não são considerados neste artigo, sugiro que nós trabalhemos com elas também. Como você pode ligar os nós de forma circular já foi mostrado acima (Fig. 3).

Vamos criar um modelo da classe CiCircleDoubleList (Fig. 13). Esta classe será uma classe descendente da classe CDoubleList.

Modelo da classe CiCircleDoubleList

Fig. 13 Modelo da classe CiCircleDoubleList

Devido ao fato de que os nós nesta lista são de caráter específico (a cabeça e a cauda são ligadas), quase todos os métodos da base de origem da classe CiSingleList terá de ser feito virtualmente.

//+------------------------------------------------------------------+
//|                      Classe CiCircleDoubleList                   |
//+------------------------------------------------------------------+
class CiCircleDoubleList : public CDoubleList
  {
public:
   void              CiCircleDoubleList(void);                       // construtor padrão
   void              CiCircleDoubleList(int _node_val);              // construtor parametrizado
   void             ~CiCircleDoubleList(void){TRACE_CALL(_t_flag)};  // destrutor
   //---
   virtual uint      Size(void) const;                                        // tamanho da lista
   virtual bool      SetNodeByIndex(CiSingleNode *_new_node,const uint _idx); // inserir o n-ésimo novo nó na lista
   virtual int       GetValByIndex(const uint _idx) const;           // valor do n-ésimo nó da lista
   virtual CiSingleNode *GetNodeByIndex(const uint _idx) const;      // obter o n-ésimo nó da lista
   virtual bool      Find(const int _node_val) const;                // buscar o valor solicitado
   virtual bool      CopyByValue(const CiSingleList &_sList);        // copiar a lista pelos valores

protected:
   virtual void      addFront(int _node_val);                        // inserir um novo nó "nativo" para o início da lista
   virtual void      addRear(int _node_val);                         // inserir um novo nó "nativo" para o fim da lista
   virtual int       removeFront(void);                              // remover o nó cabeça "nativo"
   virtual int       removeRear(void);                               // remover o nó cauda "nativo"
   virtual void      deleteNodeByIndex(const uint _idx);             // remover o n-ésimo nó "nativo" da lista

protected:
   void              CalcSize(void) const;                           // calcula o tamanho da lista
   void              LinkHeadTail(void);                             // liga a cabeça a cauda
  };

A descrição completa da classe é fornecida em CiCircleDoubleList.mqh.

Vamos considerar alguns métodos da classe. O método CiCircleDoubleList::LinkHeadTail() liga o nó da cauda para o nó cabeça. Ele deve ser chamado quando não há nem uma nova cauda ou cabeça e a ligação anterior foi perdida.

//+------------------------------------------------------------------+
//|                  Ligando cabeça à cauda                          |
//+------------------------------------------------------------------+
void CiCircleDoubleList::LinkHeadTail(void)
  {
   TRACE_CALL(_t_flag)
   this.m_head.SetPrevNode(this.m_tail);      // liga cabeça à cauda
   this.m_tail.SetNextNode(this.m_head);      // liga cauda à cabeça
  }

Pense que este método seria como se estivéssemos lidando com uma lista circular encadeada simples.

Considere, por exemplo, o método CiCircleDoubleList::addFront().

//+------------------------------------------------------------------+
//|                Novo nó "nativo" para o início da lista           |
//+------------------------------------------------------------------+
void CiCircleDoubleList::addFront(int _node_val)
  {
   TRACE_CALL(_t_flag)
   CDoubleList::addFront(_node_val); // Chama um método semelhante da classe base
   this.LinkHeadTail();              // liga cabeça à cauda
  }

Você pode ver no corpo do método que um método semelhante da classe base CDoubleList é chamado. Neste ponto, podemos concluir a operação do método (o método como tal, basicamente, não é necessário aqui), se não fosse por uma coisa. A ligação entre a cabeça e a cauda está perdida e que a lista não pode ser circularmente ligada sem ele. É por isso que nós temos que chamar o método de ligar a cabeça e a cauda.

Podemos trabalhar com a lista circular duplamente encadeada no script test_UdList.mq5.

Em termos de funções e objetivos, os outros métodos utilizados são os mesmos que nos exemplos anteriores.

Como resultado, o log contém as seguintes entradas:

PR      0       13:34:29        test_CdList (EURUSD,H1) =======Lista #1=======
QS      0       13:34:29        test_CdList (EURUSD,H1) Nó #1, val=14 
QI      0       13:34:29        test_CdList (EURUSD,H1) Nó #2, val=666 
LQ      0       13:34:29        test_CdList (EURUSD,H1) Nó #3, val=13 
OH      0       13:34:29        test_CdList (EURUSD,H1) Nó #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) =======Lista #2 antes da ordenação=======
MS      0       13:34:29        test_CdList (EURUSD,H1) Nó #1, val=38 
IJ      0       13:34:29        test_CdList (EURUSD,H1) Nó #2, val=37 
IQ      0       13:34:29        test_CdList (EURUSD,H1) Nó #3, val=36 
EH      0       13:34:29        test_CdList (EURUSD,H1) Nó #4, val=35 
EO      0       13:34:29        test_CdList (EURUSD,H1) Nó #5, val=34 
FF      0       13:34:29        test_CdList (EURUSD,H1) Nó #6, val=14 
DN      0       13:34:29        test_CdList (EURUSD,H1) Nó #7, val=666 
GD      0       13:34:29        test_CdList (EURUSD,H1) Nó #8, val=13 
JK      0       13:34:29        test_CdList (EURUSD,H1) Nó #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) =======Lista #2 depois da ordenação=======
LE      0       13:34:29        test_CdList (EURUSD,H1) Nó #1, val=11 
KL      0       13:34:29        test_CdList (EURUSD,H1) Nó #2, val=13 
QS      0       13:34:29        test_CdList (EURUSD,H1) Nó #3, val=14 
NJ      0       13:34:29        test_CdList (EURUSD,H1) Nó #4, val=34 
NQ      0       13:34:29        test_CdList (EURUSD,H1) Nó #5, val=35 
NH      0       13:34:29        test_CdList (EURUSD,H1) Nó #6, val=36 
NO      0       13:34:29        test_CdList (EURUSD,H1) Nó #7, val=37 
NF      0       13:34:29        test_CdList (EURUSD,H1) Nó #8, val=38 
JN      0       13:34:29        test_CdList (EURUSD,H1) Nó #9, val=666 
RJ      0       13:34:29        test_CdList (EURUSD,H1) 
RE      0       13:34:29        test_CdList (EURUSD,H1)

Assim, o diagrama final de herança entre as classes das listas introduzidas se encontra a seguir (fig. 14).

Eu não tenho certeza se todas as classes precisam ser relacionados por herança, mas eu decidi deixar tudo como está.

Herança entre as classes da lista

Fig. 14 Herança entre as classes da lista

Completando esta seção do artigo, que foram mostrados as listas personalizadas, eu gostaria de salientar que praticamente não toquei no grupo das listas não-lineares, multi listas, etc. Como eu estou acumulando informações relevantes e ganhando experiência trabalhando com este tipos de estruturas dinâmicas, vou tentar escrever um outro artigo.


3. Listas na biblioteca padrão MQL5

Vamos dar uma olhada na classe lista disponível na biblioteca padrão (Fig. 15).

Ele pertence as Classes de dados.

Modelo da classe CList

Fig. 15 Modelo da classe CList

Curiosamente, a CList é um descendente da classe CObject. Ou seja, a lista herda dados e métodos da classe, que é um nó.

A classe de lista contém um impressionante conjunto de métodos. Para ser honesto, eu não esperava encontrar uma grande classe dessa na biblioteca padrão.

A classe CList tem 8 membros de dados. Eu gostaria de destacar algumas coisas. Os atributos da classe contêm o índice do nó atual (intm_curr_idx) e o ponteiro para o nó atual (CObject* m_curr_node). Pode-se dizer que a lista é "inteligente" - pode indicar o local onde o controle está localizado. Além disso, ela possui um mecanismo de gerenciamento de memória (que pode eliminar fisicamente um nó ou simplesmente excluí-lo da lista), flag de lista ordenada e modo de ordenação.

Falando de métodos, todos os métodos da classe CList são divididos nos seguintes grupos:

  • Atributos;
  • Criar métodos;
  • Adicionar métodos;
  • Remover métodos;
  • Navegação;
  • Métodos de ordenação;
  • Métodos de comparação;
  • Métodos de busca;
  • Entrada / Saída.

Como de costume, há um construtor padrão e destrutor.

O primeiro esvazia (NULL) todos os ponteiros. O estado do flag de gerenciamento de memória está definido para remover. A nova lista não será ordenada.

Em seu corpo, o destrutor só chama o método Clear() para esvaziar a lista de nós. O fim da existência da lista não implica necessariamente a "morte" de seus elementos (nós). Assim, o flag de gerenciamento de memória define quando excluir os elementos da lista transforma a relação da classe de composição para agregação.

Podemos lidar com este flag usando os métodos set- , get- e FreeMode().

Existem dois métodos na classe que lhe permitem aumentar a lista: Add() e Insert(). O primeiro é semelhante ao método AddRear() utilizado na primeira parte do artigo. O segundo método é semelhante ao método SetNodeByIndex().

Vamos começar com um pequeno exemplo. Precisamos primeiro criar uma classe de nó CNodeInt, um descendente da classe de interface CObject. Ele irá armazenar o valor do tipo inteiro.

//+------------------------------------------------------------------+
//|                        Classe CNodeInt                           |
//+------------------------------------------------------------------+
class  CNodeInt : public CObject
  {
private:
   int               m_val;  // dados do nó

public:
   void              CNodeInt(void){this.m_val=WRONG_VALUE;}; // construtor padrão
   void              CNodeInt(int _val);                      // construtor parametrizado
   void             ~CNodeInt(void){};                        // destrutor
   int               GetVal(void){return this.m_val;};        // método para obter os dados do nó
   void              SetVal(int _val){this.m_val=_val;};      // método para definir os dados do nó
  };
//+------------------------------------------------------------------+
//|                    Construtor parametrizado|
//+------------------------------------------------------------------+
void CNodeInt::CNodeInt(int _val):m_val(_val)
  {

  };

Vamos trabalhar com a lista CList no script test_MQL5_List.mq5.

Exemplo 1 demonstra uma criação dinâmica da lista e dos nós. A lista é então preenchida com nós e o valor do primeiro nó é verificado antes e depois de eliminar a lista.

//--- Exemplo 1 (testando o gerenciamento de memória)
   CList *myList=new CList;
// myList.FreeMode(false);  // redefine o flag
   bool _free_mode=myList.FreeMode();
   PrintFormat("\nList \"myList\" - flag de gerenciamento de memória: %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("Lista \"myList\" tem como muitos nós como: %d",myList.Total());
   Print("=======Antes de Remover \"myList\"=======");
   PrintFormat("O 1º valor do nó é: %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("=======Após Remover \"myList\"=======");
   PrintFormat("O 1º valor do nó é: %d",val_to_check);

Se a seqüência de redefinir o flag permanece comentada (inativo), teremos as seguintes entradas no log:

GS      0       14:00:16        test_MQL5_List (EURUSD,H1)      
EO      0       14:00:16        test_MQL5_List (EURUSD,H1)      Lista "myList" - flag de gerenciamento de memória: 1
FR      0       14:00:16        test_MQL5_List (EURUSD,H1)      Lista "myList" tem como muitos nós como: 10
JH      0       14:00:16        test_MQL5_List (EURUSD,H1)      =======Antes de remover "myList"=======
DO      0       14:00:16        test_MQL5_List (EURUSD,H1)      O  valor do nó é: 7189
KJ      0       14:00:16        test_MQL5_List (EURUSD,H1)      =======Após remover "myList"=======
QK      0       14:00:16        test_MQL5_List (EURUSD,H1)      O  valor do nó é: -1

Por favor, note que após a exclusão dinâmica da lista myList, todos os nós que também estavam lá foram apagados da memória.

No entanto, se descomentamos a linha do flag de redefinição:

// myList.FreeMode(false); // redefine o flag

a saída para o log será a seguinte:

NS      0       14:02:11        test_MQL5_List (EURUSD,H1)      
CN      0       14:02:11        test_MQL5_List (EURUSD,H1)      Lista "myList" - flag de gerenciamento de memória: 0
CS      0       14:02:11        test_MQL5_List (EURUSD,H1)      Lista "myList" tem como muitos nós como: 10
KH      0       14:02:11        test_MQL5_List (EURUSD,H1)      =======Antes de remover "myList"=======
NL      0       14:02:11        test_MQL5_List (EURUSD,H1)      O  valor do nó é: 20411
HJ      0       14:02:11        test_MQL5_List (EURUSD,H1)      =======Após remover "myList"=======
LI      0       14:02:11        test_MQL5_List (EURUSD,H1)      o  valor do nó é: 20411
QQ      1       14:02:11        test_MQL5_List (EURUSD,H1)      10 objetos que faltam remover
DD      1       14:02:11        test_MQL5_List (EURUSD,H1)      10 objetos que faltam do tipo CNodeInt
DL      1       14:02:11        test_MQL5_List (EURUSD,H1)      400 bytes de memória vazia

É fácil perceber que o nó principal conserva o seu valor tanto antes quanto depois da lista ser excluída. Neste caso, haverá também objetos restantes não removidos se o script não conter o código para excluí-los corretamente.

Vamos agora tentar trabalhar com o método de ordenação.

//--- Exemplo 2 (ordenação)

   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\" tem como muitos nós como: %d",myList.Total());
   Print("=======Lista \"myList\" antes da ordenação=======");
   for(int i=0;i<myList.Total();i++)
     {
      CNodeInt *p_node_int=myList.GetNodeAtIndex(i);
      int node_val=p_node_int.GetVal();
      PrintFormat("Nó #%d é igual a: %d",i+1,node_val);
     }
   myList.Sort(0);
   Print("\n=======Lista \"myList\" depois da ordenação=======");
   for(int i=0;i<myList.Total();i++)
     {
      CNodeInt *p_node_int=myList.GetNodeAtIndex(i);
      int node_val=p_node_int.GetVal();
      PrintFormat("Nó #%d é igual a: %d",i+1,node_val);
     }
   delete myList;

Como resultado, o log contém as seguintes entradas:

OR      0       22:47:01        test_MQL5_List (EURUSD,H1)      
FN      0       22:47:01        test_MQL5_List (EURUSD,H1)      Lista "myList" tem como muitos nós como: 10
FH      0       22:47:01        test_MQL5_List (EURUSD,H1)      =======Lista "myList" antes da ordenação=======
LG      0       22:47:01        test_MQL5_List (EURUSD,H1)      Nó #1 é igual a: 30511
CO      0       22:47:01        test_MQL5_List (EURUSD,H1)      Nó #2 é igual a: 17404
GF      0       22:47:01        test_MQL5_List (EURUSD,H1)      Nó #3 é igual a: 12215
KQ      0       22:47:01        test_MQL5_List (EURUSD,H1)      Nó #4 é igual a: 31574
NJ      0       22:47:01        test_MQL5_List (EURUSD,H1)      Nó #5 é igual a: 7285
HP      0       22:47:01        test_MQL5_List (EURUSD,H1)      Nó #6 é igual a: 23509
IH      0       22:47:01        test_MQL5_List (EURUSD,H1)      Nó #7 é igual a: 26991
NS      0       22:47:01        test_MQL5_List (EURUSD,H1)      Nó #8 é igual a: 414
MK      0       22:47:01        test_MQL5_List (EURUSD,H1)      Nó #9 é igual a: 18824
DR      0       22:47:01        test_MQL5_List (EURUSD,H1)      Nó #10 é igual a: 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)      Nó #1 é igual a: 26991
RE      0       22:47:01        test_MQL5_List (EURUSD,H1)      Nó #2 é igual a: 23509
ML      0       22:47:01        test_MQL5_List (EURUSD,H1)      Nó #3 é igual a: 18824
DD      0       22:47:01        test_MQL5_List (EURUSD,H1)      Nó #4 é igual a: 414
LL      0       22:47:01        test_MQL5_List (EURUSD,H1)      Nó #5 é igual a: 1560
IG      0       22:47:01        test_MQL5_List (EURUSD,H1)      Nó #6 é igual a: 17404
PN      0       22:47:01        test_MQL5_List (EURUSD,H1)      Nó #7 é igual a: 30511
II      0       22:47:01        test_MQL5_List (EURUSD,H1)      Nó #8 é igual a: 31574
OQ      0       22:47:01        test_MQL5_List (EURUSD,H1)      Nó #9 é igual a: 12215
JH      0       22:47:01        test_MQL5_List (EURUSD,H1)      Nó #10 é igual a: 7285

Mesmo que qualquer ordenação foi feita em tudo, a técnica de ordenação permanece um mistério para mim. Vou explicar o porquê. Sem entrar em muitos detalhes sobre a ordem de chamada, o metodo CList::Sort() chama o método virtual CObject::Compare() que não é implementado na classe de base de qualquer forma. Assim, o programador tem de lidar com a implementação de um método de classificação por conta própria.

E agora, algumas palavras sobre o método Total(). Ele retorna o número de elementos (nós) para o qual o membro de dados m_data_total é responsável. É um método conciso e muito curto. A contagem do elemento nesta implementação será muito mais rápido do que a que eu propus anteriormente. Na verdade, em vez de passar pela lista e fazer a contagem dos nós, o número exato pode ser definido na hora de adicionar ou remover os nós.

O exemplo 3 compara a velocidade de preenchimento das listas do tipo CList e CiSingleList e calcula o tempo para obter o tamanho de cada lista.

//--- Exemplo 3 (número de nós)

   int iterations=1e7;   // 10 milhões de iterações
//--- A nova CList 
   CList *p_mql_List=new CList;
   uint start=GetTickCount(); // Valor inicial
   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; // Tempo gasto, ms
   Print("\n=======A lista do tipo CList=======");
   PrintFormat("O Prenchendo da lista de %.3e nós levou %d ms",iterations,time);
//--- obtém o tamanho
   start=GetTickCount();
   int list_size=p_mql_List.Total(); 
   time=GetTickCount()-start;
   PrintFormat("Para obter o tamanho da lista levou-se %d ms",time);

   delete p_mql_List;

//---  A nova CiSingleList 
   CiSingleList *p_sList=new CiSingleList;

   start=GetTickCount(); // valor inicial
   for(int i=0;i<iterations;i++)
      p_sList.AddRear(rand());
   time=GetTickCount()-start; // tempo gasto, ms
   Print("\n=======A lista do tipo CiSingleList=======");
   PrintFormat("O preenchimento da lista de %.3e nós levou %d msec",iterations,time);
//--- obter o tamanho
   start=GetTickCount();
   list_size=(int)p_sList.Size();  
   time=GetTickCount()-start;
   PrintFormat("Para obter o tamanho da lista levou-se %d ms",time);
   delete p_sList;   

Isto é o que eu tenho no log:

KO      0       22:48:24        test_MQL5_List (EURUSD,H1)      
CK      0       22:48:24        test_MQL5_List (EURUSD,H1)      =======A lista do tipo CList=======
JL      0       22:48:24        test_MQL5_List (EURUSD,H1)      O preenchimento da lista de 1.000e+007 nós levou 2606 ms
RO      0       22:48:24        test_MQL5_List (EURUSD,H1)      Para obter o tamanho da lista levou-se 0 ms
LF      0       22:48:29        test_MQL5_List (EURUSD,H1)      
EL      0       22:48:29        test_MQL5_List (EURUSD,H1)      =======A lista do tipo CiSingleList=======
KK      0       22:48:29        test_MQL5_List (EURUSD,H1)      O preenchimento da lista de 1.000e+007 nós levou 2356 ms
NF      0       22:48:29        test_MQL5_List (EURUSD,H1)      Para obter o tamanho da lista levou-se 359 ms

O método para obter o tamanho funciona instantaneamente na lista CList. Por sinal, a adição de nós para a lista também é bem rápido.

No próximo bloco (Exemplo 4), sugiro que preste atenção a uma das principais desvantagens da lista como um contêiner de dados - a velocidade de acesso aos elementos. O fato é que os elementos da lista são acessados ​​de forma linear. Na classe CList, eles são acessados ​​de forma binária, o que diminui ligeiramente a complexidade do algoritmo.

Ao procurar linearmente, a complexidade é de O(N) iterações. A busca implementada de forma binária resulta em uma complexidade de log2(N) iterações.

Este é um exemplo do código ao acessar os elementos de um conjunto de dados:

//--- Exemplo 4 (velocidade de acesso do nó)

   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]; // Número de iterações     
      uint randArr[];                        // Array de números aleatórios
      uint idxArr[];                         // Array de índices
      //--- Define o tamanho do array    
      ArrayResize(randArr,cur_iterations);
      ArrayResize(idxArr,cur_iterations);
      CRandom myRand;                        // Gerador de números aleatórios
      //--- Preenche o array com números aleatórios
      for(uint t=0;t<cur_iterations;t++)
         randArr[t]=myRand.int32();
      //--- Preenche os índices do array com números aleatórios (de 0 a 10 milhões)
      int iter_log10=(int)log10(cur_iterations);
      for(uint r=0;r<cur_iterations;r++)
        {
         uint rand_val=myRand.int32(); // Valor aleatório (de 0 a 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);
           }
         //--- Verificar o limite
         if(rand_val>=cur_iterations)
           {
            Alert("Erro no valor aleatório!");
            return;
           }
         idxArr[r]=rand_val;
        }
      //--- Tempo gasto para o array
      uint start=GetTickCount();
      //--- Acessar os elementos do array 
      for(uint p=0;p<cur_iterations;p++)
         uint random_val=randArr[idxArr[p]];

      uint time=GetTickCount()-start; // Tempo gasto, ms
      Print("\n=======array do tipo uint=======");
      PrintFormat("Acesso aleatório do array de elementos %.1e levou %d ms",cur_iterations,time);
      //--- A lista do tipo CList
      CList *p_mql_List=new CList;
      //--- preencher a lista
      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();
      //--- Acessando os nós da lista
      for(uint w=0;w<cur_iterations;w++)
         CNodeInt *p_node_int=p_mql_List.GetNodeAtIndex(idxArr[w]);
      time=GetTickCount()-start; // tempo gasto, ms
      Print("\n=======A lista do tipo CList=======");
      PrintFormat("Acesso aleatório da lista %.1e de nós levou %d ms",cur_iterations,time);

      //--- Libera a memória
      ArrayFree(randArr);
      ArrayFree(idxArr);
      delete p_mql_List;
     }

Com base nos resultados de operação do bloco, as seguintes entradas foram impressos para o log: 

MR      0       22:51:22        test_MQL5_List (EURUSD,H1)      
QL      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======O array do tipo uint=======
IG      0       22:51:22        test_MQL5_List (EURUSD,H1)      Acesso aleatório do array de elementos 1.0e+003 levou 0 ms
QF      0       22:51:22        test_MQL5_List (EURUSD,H1)      
IQ      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======A lista do tipo CList=======
JK      0       22:51:22        test_MQL5_List (EURUSD,H1)      Acesso aleatório da lista de nós 1.0e+003 levou 0 ms
MJ      0       22:51:22        test_MQL5_List (EURUSD,H1)      
QD      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======O array do tipo uint=======
GO      0       22:51:22        test_MQL5_List (EURUSD,H1)      Acesso aleatório do array de elementos 3.0e+003 levou 0 ms
QN      0       22:51:22        test_MQL5_List (EURUSD,H1)      
II      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======A lista do tipo CList=======
EP      0       22:51:22        test_MQL5_List (EURUSD,H1)      Acesso aleatório da lista de nós 3.0e+003 levou 16 ms
OR      0       22:51:22        test_MQL5_List (EURUSD,H1)      
OL      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======O array do tipo uint=======
FG      0       22:51:22        test_MQL5_List (EURUSD,H1)      Acesso aleatório do array de elementos 6.0e+003 levou 0 ms
CF      0       22:51:22        test_MQL5_List (EURUSD,H1)      
GQ      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======A lista do tipo CList=======
CH      0       22:51:22        test_MQL5_List (EURUSD,H1)      Acesso aleatório da lista de nós 6.0e+003 levou 31 ms
QJ      0       22:51:22        test_MQL5_List (EURUSD,H1)      
MD      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======O array do tipo uint=======
MO      0       22:51:22        test_MQL5_List (EURUSD,H1)      Acesso aleatório do array de elementos 9.0e+003 levou 0 ms
EN      0       22:51:22        test_MQL5_List (EURUSD,H1)      
MJ      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======A lista do tipo CList=======
CP      0       22:51:22        test_MQL5_List (EURUSD,H1)      Acesso aleatório da lista de nós 9.0e+003 levou 47 ms
CR      0       22:51:22        test_MQL5_List (EURUSD,H1)      
KL      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======O array do tipo uint=======
JG      0       22:51:22        test_MQL5_List (EURUSD,H1)      Acesso aleatório do array de elementos 1.0e+004 levou 0 ms
GF      0       22:51:22        test_MQL5_List (EURUSD,H1)      
KR      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======A lista do tipo CList=======
MK      0       22:51:22        test_MQL5_List (EURUSD,H1)      Acesso aleatório da lista de nós 1.0e+004 levou 343 ms
GJ      0       22:51:22        test_MQL5_List (EURUSD,H1)      
GG      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======O array do tipo uint=======
LO      0       22:51:22        test_MQL5_List (EURUSD,H1)      Acesso aleatório do array de elementos 3.0e+004 levou 0 ms
QO      0       22:51:24        test_MQL5_List (EURUSD,H1)      
MJ      0       22:51:24        test_MQL5_List (EURUSD,H1)      =======A lista do tipo CList=======
NP      0       22:51:24        test_MQL5_List (EURUSD,H1)      Acesso aleatório da lista de nós 3.0e+004 levou 1217 ms
OS      0       22:51:24        test_MQL5_List (EURUSD,H1)      
KO      0       22:51:24        test_MQL5_List (EURUSD,H1)      =======O array do tipo uint=======
CP      0       22:51:24        test_MQL5_List (EURUSD,H1)      Acesso aleatório do array de elementos 6.0e+004 levou 0 ms
MG      0       22:51:26        test_MQL5_List (EURUSD,H1)      
ER      0       22:51:26        test_MQL5_List (EURUSD,H1)      =======A lista do tipo CList=======
PG      0       22:51:26        test_MQL5_List (EURUSD,H1)      Acesso aleatório da lista de nós 6.0e+004 levou 2387 ms
GK      0       22:51:26        test_MQL5_List (EURUSD,H1)      
OG      0       22:51:26        test_MQL5_List (EURUSD,H1)      =======O array do tipo uint=======
NH      0       22:51:26        test_MQL5_List (EURUSD,H1)      Acesso aleatório do array de elementos 9.0e+004 levou 0 ms
JO      0       22:51:30        test_MQL5_List (EURUSD,H1)      
NK      0       22:51:30        test_MQL5_List (EURUSD,H1)      =======A lista do tipo CList=======
KO      0       22:51:30        test_MQL5_List (EURUSD,H1)      Acesso aleatório da lista de nós 9.0e+004 levou 3619 ms
HS      0       22:51:30        test_MQL5_List (EURUSD,H1)      
DN      0       22:51:30        test_MQL5_List (EURUSD,H1)      =======O array do tipo uint=======
RP      0       22:51:30        test_MQL5_List (EURUSD,H1)      Acesso aleatório do array de elementos 1.0e+005 levou 0 ms
OD      0       22:52:05        test_MQL5_List (EURUSD,H1)      
GS      0       22:52:05        test_MQL5_List (EURUSD,H1)      =======A lista do tipo CList=======
DE      0       22:52:05        test_MQL5_List (EURUSD,H1)      Acesso aleatório da lista de nós 1.0e+005 levou 35631 ms
NH      0       22:52:06        test_MQL5_List (EURUSD,H1)      
RF      0       22:52:06        test_MQL5_List (EURUSD,H1)      =======O array do tipo uint=======
FI      0       22:52:06        test_MQL5_List (EURUSD,H1)      Acesso aleatório do array de elementos 3.0e+005 levou 0 ms
HL      0       22:54:20        test_MQL5_List (EURUSD,H1)      
PD      0       22:54:20        test_MQL5_List (EURUSD,H1)      =======A lista do tipo CList=======
FN      0       22:54:20        test_MQL5_List (EURUSD,H1)      Acesso aleatório da lista de nós 3.0e+005 levou 134379 ms
RQ      0       22:54:20        test_MQL5_List (EURUSD,H1)      
JI      0       22:54:20        test_MQL5_List (EURUSD,H1)      =======O array do tipo uint=======
MR      0       22:54:20        test_MQL5_List (EURUSD,H1)      Acesso aleatório do array de elementos 6.0e+005 levou 15 ms
NE      0       22:58:48        test_MQL5_List (EURUSD,H1)      
FL      0       22:58:48        test_MQL5_List (EURUSD,H1)      =======A lista do tipo CList=======
GE      0       22:58:48        test_MQL5_List (EURUSD,H1)      Acesso aleatório da lista de nós 6.0e+005 levou 267589 ms

Você pode ver que o acesso aleatório a lista de elementos leva mais tempo quando o tamanho da lista fica maior (Fig. 16).

Tempo gasto para o acesso aleatório ao array e para a lista de elementos

Figo. 16 Tempo gasto para o acesso aleatório ao array e para a lista de elementos

Vamos agora considerar métodos para salvar e carregar os dados.

A classe base da lista CList contém tais métodos, mas eles são virtuais. Assim, a fim de testar as suas operação através de um exemplo, nós precisamos fazer algumas preparações.

Devemos herdar as capacidades da classe CList usando a classe descendente CIntList. Este último terá apenas um método para criar um novo elemento CIntList::CreateElement().

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

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

Também será necessário adicionar os métodos virtuais CNodeInt::Save() e CNodeInt::Load() para o nó derivado do tipo CNodeInt. Eles serão chamados de funções membro CList::Save() e CList::Load(), respectivamente.

Como resultado, o exemplo esta a seguir (Exemplo 5):

//--- Exemplo 5 (salvando os dados na lista)
//--- A lista do tipo CIntList
   CList *p_int_List=new CIntList;
   int randArr[1000];                        // Array de números aleatórios
   ArrayInitialize(randArr,0);
//--- Preencher o array de números aleatórios 
   for(int t=0;t<1000;t++)
      randArr[t]=(int)myRand.int32();
//--- Preencher a lista
   for(uint q=0;q<1000;q++)
     {
      CNodeInt *p_node_int=new CNodeInt(randArr[q]);
      p_int_List.Add(p_node_int);
     }
//--- Salvar a lista para o arquivo 
   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();           
//--- Carregar a lista a partir do arquivo  
   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("Nós carregado a partir do arquivo: %d",Loaded_List_size);
//--- Livre da memória    
   delete p_int_List;

Depois de executar o script no gráfico, a seguinte entrada será adicionada ao Log:

ND      0       11:59:35        test_MQL5_List (EURUSD,H1)      Nós carregados a partir do arquivo: 1000.

Assim, vimos a implementação de métodos de entrada / saída para um membro de dados do nó do tipo CNodeInt.

Na próxima seção, vamos ver exemplos de como as listas podem ser usadas ​​para resolver os problemas quando se trabalha com MQL5.


4. Exemplos de utilização das listas em MQL5

Na seção anterior, eu dei alguns exemplos considerando os métodos da biblioteca padrão da classe CList.

Agora, eu vou considerar casos em que a lista é utilizada para resolver um problema específico. E aqui não posso deixar de ressaltar mais uma vez o benefício da lista como um recipiente de tipo de dados. Nós podemos trabalhar com o código de forma mais eficiente, aproveitando a flexibilidade de uma lista.


4.1 Manipulação de objetos gráficos

Imagine que nós precisamos criar programaticamente objetos gráficos no gráfico. Estes podem ser diferentes objetos que podem aparecer no gráfico devido a várias razões.

Lembro-me uma vez como que a lista me ajudou a resolver uma situação com objetos gráficos. E eu gostaria de compartilhar essa lembrança com você.

Eu tinha uma tarefa para criar linhas verticais por uma condição especificada. De acordo com a condição, as linhas verticais serviam como limites para um determinado intervalo de tempo que variaram em comprimento de um caso para outro. Dito isso, o intervalo não foi sempre completamente formado.

Eu estava estudando o comportamento de EMA21 e para efeito tive que coletar estatísticas.

Eu estava particularmente interessado no comprimento da inclinação da média móvel. Por exemplo, em um movimento de baixa o ponto de partida foi identificado por registrar um movimento negativo da média móvel (ou seja diminuição de valor), pelo qual uma linha vertical foi desenhada. Fig. 17 mostra tal ponto que foi identificado para o EURUSD, em H1 no dia 5 de setembro de 2013 as 16:00, após a abertura de uma vela.

Fig. 17 Primeiro ponto do intervalo descendente

Fig. 17 Primeiro ponto do intervalo descendente 


O segundo ponto que sugere o fim do movimento de queda foi identificado com base no princípio inverso - ao registrar um movimento positivo da média móvel, ou seja, aumento de valor (Fig. 18).


Fig. 18 Segundo ponto do intervalo descendente

Fig. 18 Segundo ponto do intervalo descendente


Assim, o intervalo alvo foi a partir das 16:00 do dia 05 de setembro de 2013 até 17:00 do dia 6 de setembro de 2013.

O sistema para a identificação dos intervalos diferentes podem ser mais complexo ou mais simples. Este não é o ponto. O que é importante é o fato de que esta técnica para trabalhar com objetos gráficos e ao mesmo tempo coletar dados estatísticos envolve uma das principais vantagens da lista - a flexibilidade de composição.

Quanto ao exemplo atual, primeiro eu criei um nó do tipo CVertLineNode o qual é responsável por 2 objetos gráficos de "linha vertical".

A definição da classe é o seguinte:

//+------------------------------------------------------------------+
//|                      Classe CVertLineNode                        |
//+------------------------------------------------------------------+
class CVertLineNode : public CObject
  {
private:
   SVertLineProperties m_vert_lines[2];      // Array de estruturas com propriedades de linha verticais
   uint              m_duration;             // duração do frame
   bool              m_IsFrameFormed;        // flag formação do frame

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;
   //--- desenha a linha
   bool              DrawLine(bool IsFirst=true) const;
  };

Basicamente, esta classe nó descreve um frame (aqui interpretado como um certo número de velas confinadas dentro de duas linhas verticais). Os limites do frame são representados por um par de estruturas com propriedades de linhas verticais, duração e formação do flag.

Além do construtor padrão e destrutor, a classe tem vários métodos get e set, assim como o método para desenhar a linha no gráfico.

Deixe-me lembrá-lo que o nó de linhas verticais (frame) no meu exemplo pode ser considerado formado quando há uma primeira linha vertical que indica o início do movimento de baixa e a segunda linha vertical que indica o início do movimento de alta.

Usando o script Stat_collector.mq5, Eu exibi todos os quadros no gráfico e contei quantos nós (frames) correspondem a um determinado limite de duração ao longo das últimos 2.000 barras.

Para ilustração, eu criei quatro listas que podem conter qualquer frame. A primeira lista continha frames com o número de até 5 velas, o segundo - até 10, o terceiro - até 15 e o quarto - número ilimitado. 

NS      0       15:27:32        Stat_collector (EURUSD,H1)      =======Lista #1=======
RF      0       15:27:32        Stat_collector (EURUSD,H1)      Limite de duração: 5
ML      0       15:27:32        Stat_collector (EURUSD,H1)      Número de nós: 65
HK      0       15:27:32        Stat_collector (EURUSD,H1)      
OO      0       15:27:32        Stat_collector (EURUSD,H1)      =======Lista #2=======
RI      0       15:27:32        Stat_collector (EURUSD,H1)      Limite de duração: 10
NP      0       15:27:32        Stat_collector (EURUSD,H1)      Número de nós: 15
RG      0       15:27:32        Stat_collector (EURUSD,H1)      
FH      0       15:27:32        Stat_collector (EURUSD,H1)      =======Lista #3=======
GN      0       15:27:32        Stat_collector (EURUSD,H1)      Limite de duração: 15
FG      0       15:27:32        Stat_collector (EURUSD,H1)      Número de nós: 6
FR      0       15:27:32        Stat_collector (EURUSD,H1)      
CD      0       15:27:32        Stat_collector (EURUSD,H1)      =======Lista #4=======
PS      0       15:27:32        Stat_collector (EURUSD,H1)      Número de nós: 20

Como resultado, eu tenho o gráfico a seguir (Fig. 19). Para maior comodidade, o frame da segunda linha vertical é exibido em azul.


Fig. 19 Exibindo os frames

Curiosamente, o último frame foi formado durante a última hora de sexta-feira, dia 13 de dezembro de 2013. Ele caiu sob a segunda lista, uma vez que foi de 6 horas de duração.


4.2 Lidar com negociação virtual

Imagine que você precisa criar um Expert Advisor que irá implementar várias estratégias independentes no que diz respeito a um instrumento em um fluxo de ticks. É claro que, na realidade, apenas uma estratégia pode ser aplicada a um tempo no que diz respeito a um instrumento. Todas as outras estratégias serão de natureza virtual. Assim, ele pode ser implementado apenas para fins de teste e otimização de uma idéia de negociação.

Aqui, eu tenho que fazer uma referência a um artigo fundamental que fornece uma descrição detalhada sobre os conceitos básicos relacionados à negociação em geral e, particularmente, para o terminal MetaTrader 5: "Ordens, posições e negócios no MetaTrader 5".

Assim, se na resolução deste problema, usamos o conceito de negociação, o sistema de gerenciamento de objetos de negociação e a metodologia de armazenamento de informações sobre objetos de negociação, que são habituais no ambiente MetaTrader 5, nós devemos provavelmente pensar em criar um banco de dados virtual.

Deixe-me lembrá-lo que um desenvolvedor classifica todos os objetos de troca em ordens, posições, negociações e histórico de ordens. Um olhar crítico pode notar que o termo "objeto de negociação 'foi colocado em uso aqui pelo próprio autor. Isto é verdade ...

Eu proponho usar uma abordagem similar na negociação virtual e obter os seguintes objetos virtuais de negociação: ordens virtuais, posições virtuais, negociações virtuais e histórico de ordens virtuais.

Acredito que este assunto merece uma discussão mais profunda e detalhada. Entretanto, voltando ao assunto do artigo, eu gostaria de dizer que o recipiente dos tipos de dados, incluindo listas, pode tornar a vida do programador mais fácil quando for implementar estratégias virtuais.

Pense em uma nova posição virtual que, naturalmente, não pode ser do lado do servidor de negociação. Isso significa que as informações sobre ele deve ser salvo do lado do terminal. Aqui, uma base de dados pode ser representada por uma lista, que por sua vez é constituída de várias listas, sendo que uma delas contêm os nós da posições virtuais.

Usando a abordagem do desenvolvedor, haverá as seguintes classes de negociação virtual:

Classe / Grupo

Descrição

CVirtualOrder

Classe para trabalhar com ordens pendentes virtuais

CVirtualHistoryOrder

Classe para trabalhar com histórico de ordens virtuais

CVirtualPosition

Classe para trabalhar com posições abertas virtualmente

CVirtualDeal

Classe para trabalhar com histórico virtual de negócios

CVirtualTrade

Classe para a realização de operações de negociação virtual

Tabela 1. Classes de negociação virtuais


Não vou discutir a composição de qualquer uma das classes de negociação virtuais. Mas ela provavelmente irá conter todos ou quase todos os métodos de uma classe de negociação padrão. Eu só quero ressaltar que o que o desenvolvedor usa não é uma classe de um determinado objeto de negociação em si, mas uma classe de suas propriedades.

Para utilizar as listas em seus algoritmos, você também vai precisar de nós. Portanto, precisamos encerrar a classe de um objeto de negócio virtual em um nó.

Suponha que o nó de uma posição virtual aberta é do tipo CVirtualPositionNode. A definição deste tipo pode, inicialmente, ser como se segue:

//+------------------------------------------------------------------+
//|                Classe CVirtualPositionNode                       |
//+------------------------------------------------------------------+
class CVirtualPositionNode : public CObject
  {
protected:
   CVirtualPositionNode *m_virt_position;         // ponteiro para a posição virtual

public:
   void              CVirtualPositionNode(void);  // construtor padrão
   void             ~CVirtualPositionNode(void);  // destrutor
  };

Agora, quando a posição virtual abrir, ela pode ser adicionada à lista de posições virtuais.

Eu também gostaria de notar que esta abordagem para trabalhar com objetos de negociação virtual não requerem o uso de memória cache, porque o banco de dados é armazenado na memória de acesso aleatório. Você pode, é claro, providenciar para que sejam armazenadas em outros meios de armazenamento.


Conclusão

Neste artigo, eu tentei demonstrar as vantagens de um recipiente de tipo de dados, como a lista. Mas eu não podia ir sem mencionar seus inconvenientes. Enfim, espero que esta informação seja útil para aqueles que estudam POO em geral e, particularmente, um dos seus princípios fundamentais, o polimorfismo.


Localização dos arquivos:

Na minha opinião, seria melhor criar e armazenar os arquivos na pasta do projeto. Por exemplo, assim: %MQL5\Projects\UserLists. Aqui é onde eu salvo todos os arquivos de código fonte. Se você usa os diretórios padrão, você vai precisar mudar o método de designação incluir arquivo (substitua as aspas com colchetes) no código de alguns arquivos.

#ArquivoLocalizaçãoDescrição
1 CiSingleNode.mqh  %MQL5\Projects\UserLists  Classe de um nó de lista encadeada simples
2 CDoubleNode.mqh  %MQL5\Projects\UserLists  Classe de um nó de lista duplamente encadeada
3 CiUnrollDoubleNode.mqh  %MQL5\Projects\UserLists  Classe de um nó de lista vetorial duplamente encadeada
4 test_nodes.mq5  %MQL5\Projects\UserLists  Script com exemplos de como trabalhar com os nós
5 CiSingleList.mqh  %MQL5\Projects\UserLists  Classe de uma lista encadeada simples
6 CDoubleList.mqh  %MQL5\Projects\UserLists  Classe de uma lista duplamente encadeada
7 CiUnrollDoubleList.mqh  %MQL5\Projects\UserLists  Classe de uma lista vetorial duplamente encadeada
 8 CiCircleDoublList.mqh %MQL5\Projects\UserLists Classe de uma lista circular duplamente encadeada
 9 test_sList.mq5 %MQL5\Projects\UserLists Script com exemplos de como trabalhar com uma lista encadeada simples
 10 test_dList.mq5 %MQL5\Projects\UserLists Script com exemplos de como trabalhar com uma lista duplamente encadeada
 11 test_UdList.mq5 %MQL5\Projects\UserLists Script com exemplos de como trabalhar com uma lista vetorial duplamente encadeada
 12 test_CdList.mq5 %MQL5\Projects\UserLists Script com exemplos de como trabalhar com uma lista circular duplamente encadeada
 13 test_MQL5_List.mq5 %MQL5\Projects\UserLists Script com exemplos de como trabalhar com a classe CList
 14 CNodeInt.mqh %MQL5\Projects\UserLists Classe do nó do tipo inteiro
 15 CIntList.mqh %MQL5\Projects\UserLists Classe da lista para os nós CNodeInt
 16 CRandom.mqh %MQL5\Projects\UserLists Classe de um gerador de números aleatórios
 17 CVertLineNode.mqh %MQL5\Projects\UserLists Classe nó para lidar com a estrutura de linhas verticais
 18 Stat_collector.mq5 %MQL5\Projects\UserLists Script com um exemplo de coleta de dados estatísticos


Referências:

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

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/709

Arquivos anexados |
files.zip (22.75 KB)
Resumo do Mercado MetaTrader (infográfico) Resumo do Mercado MetaTrader (infográfico)
Duas semanas atrás nós publicamos o infográfico do serviço Freelance. Nós também prometemos revelar algumas estatísticas sobre o mercado MetaTrader. Agora, nós convidamos você a examinar os dados que reunimos.
Como criar um robô de negociação rapidamente Como criar um robô de negociação rapidamente
Negociar em mercados financeiros envolve muitos riscos, incluindo o mais crítico destes - o risco de tomar uma decisão de negociação errada. O sonho de todo negociador é encontrar um robô de negociação, que está sempre em boa forma e não está sujeito às fraquezas humanas - medo, cobiça e impaciência.
Dicas para uma apresentação eficaz do produto no Mercado Dicas para uma apresentação eficaz do produto no Mercado
Vender programas para os traders de forma eficiente não exigem apenas uma boa escrita e um produto útil e, logo em seguida, publicá-lo no Mercado. É vital o fornecimento de uma descrição detalhada e abrangente e de boas ilustrações. Um logotipo de qualidade e boas imagens são tão importantes como o código em si. Tenha em mente esta simples fórmula: sem downloads = sem vendas.
Provedores de Sinal Johnpaul77: "Nossa estratégia continua a ser rentável por mais de três anos. Então, por que devemos mudar isso?" Provedores de Sinal Johnpaul77: "Nossa estratégia continua a ser rentável por mais de três anos. Então, por que devemos mudar isso?"
Vamos revelar um pequeno segredo: Os visitantes do site MQL5.com passam a maior parte do seu tempo na página do sinal Johnpaul77. Ele é líder em nosso ranking de sinais, com cerca de 900 assinantes e com fundos de contas reais no valor total de $5.7 milhões. Entrevistamos o provedor deste sinal. Acontece que há quatro deles! Como são distribuídos os deveres entre os membros da equipe? Quais são as ferramentas técnicas que eles usam? Por que eles se auto-denominam de John Paul? E, finalmente, como que simples jogadores da Indonésia se tornaram os principais provedores de sinais no MQL5.com? Descubra tudo neste artigo.