English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Türkçe
Forum sulla programmazione MQL5 Liste

Forum sulla programmazione MQL5 Liste

MetaTrader 5Esempi | 12 gennaio 2022, 09:56
74 0
Denis Kirichenko
Denis Kirichenko

Introduzione

La nuova versione del linguaggio MQL ha fornito agli sviluppatori di sistemi di trading automatizzato strumenti efficaci per l'implementazione di attività complesse. Non si può negare il fatto che le funzionalità di programmazione del linguaggio siano state notevolmente ampliate. Anche solo le caratteristiche OOP di MQL5 valgono molto. Da non sottovalutare è l’importanza della Standard Library. A giudicare dal codice di errore 359, i modelli di classe saranno presto supportati.

In questo articolo, vorrei parlare di quella che potrebbe essere in qualche modo un'espansione o una continuazione degli argomenti che descrivono i tipi di dati e i loro insiemi. Vorrei qui fare riferimento ad un articolo pubblicato sul sito MQL5.community. Una descrizione completa molto dettagliata dei principi e della logica del lavoro con gli array è stata fornita da Dmitry Fedoseev (Integer) nel suo articolo "MQL5 Programming Basics: Arrays".

Quindi, oggi propongo di passare alle liste e, più precisamente, alle liste lineari collegate. Inizieremo con la struttura, il significato e la logica dell'elenco. Successivamente, prenderemo in considerazione gli strumenti correlati già disponibili nella Standard Library. In conclusione, fornirò esempi di come è possibile utilizzare gli elenchi quando si lavora con MQL5.

  1. Concetto di Lista e Nodo: Teoria
  2. Concetto di Lista e Nodo: Programmazione
  3. Lista nella libreria standard MQL5
  4. Esempi di utilizzo di liste in MQL5


1. Concetto di Lista e Nodo: Teoria

Quindi cos'è una lista per uno sviluppatore e come si fa? Farò riferimento alla fonte pubblica di informazione, Wikipedia, per una generica definizione di questo termine:

In informatica, una ista è un tipo di dati astratto che implementa una raccolta ordinata finita di valori, in cui lo stesso valore può verificarsi più di una volta. Un'istanza di una lista è una rappresentazione al computer del concetto matematico di una sequenza finita - una tupla. Ogni istanza di un valore nella lista viene solitamente chiamata elemento, voce o elemento della lista; se lo stesso valore si verifica più volte, ogni occorrenza è considerata un elemento distinto.

Il termine "lista" viene utilizzato anche per diverse strutture concrete di dati che possono essere utilizzate per implementare elenchi astratti, in particolare liste collegate.

Credo che sarete d'accordo sul fatto che questa definizione sia un po' troppo accademica.

Ai fini di questo articolo, siamo più interessati all'ultima frase di questa definizione. Quindi soffermiamoci su di essa:

In informatica, una lista concatenata è una struttura di dati dinamica di base costituita da nodi, dove ogni nodo è composto da dati e uno o due referenze ("links") al nodo successivo e/o precedente dell'elenco. [1] Il principale vantaggio di una lista collegata rispetto a un array convenzionale è una flessibilità strutturale: la sequenza degli elementi della lista collegata non deve necessariamente corrispondere alla sequenza degli elementi di dati nella memoria del computer, mentre i collegamenti interni degli elementi dell'elenco vengono sempre mantenuti per l'attraversamento dell'elenco.

Proviamo ad esaminarlo passo a passo.

In informatica, una lista in sé è un tipo di dati. Lo abbiamo stabilito. È piuttosto un tipo di dati sintetico in quanto include altri tipi di dati. Una lista è in qualche modo simile a un array. Se un array di dati di un tipo fosse mai classificato come un nuovo tipo di dati, quello sarebbe un elenco. Ma non del tutto.

Il vantaggio principale di un elenco è che consente l'inserimento o la rimozione di nodi in qualsiasi punto dell'elenco, a seconda del bisogno. Qui, la lista è simile a un array dinamico, tranne per il fatto che per una lista non è necessario utilizzare sempre la funzione ArrayResize().

Parlando in termini di ordine degli elementi di memoria, i nodi di lista non sono memorizzati e non devono essere memorizzati allo stesso modo degli elementi di matrice, memorizzati in aree di memoria adiacenti.

E questo è più o meno tutto quello che c’è da dire sull’argomento. Scendendo più in basso nell'elenco.


1.1 Nodo in una lista collegato singolarmente

Le liste ti consentono di memorizzare i nodi, anziché gli elementi. Il nodo è un tipo di dati costituito da due parti.

La prima parte è un campo dati e la seconda parte è utilizzata per i collegamenti con altri nodi (Fig. 1). Il primo nodo nell'elenco è chiamato 'testa' e l'ultimo nodo nell'elenco è chiamato 'coda'. Il campo del collegamento in coda contiene un riferimento NULL. Fondamentalmente viene utilizzato per indicare la mancanza di ulteriori nodi nella lista. Altre fonti specializzate si riferiscono al resto dell'elenco dopo la testa come 'coda'.

Fig. 1 Nodi in una lista collegata singolarmente

Fig. 1 Nodi in una lista collegata singolarmente

Oltre ai nodi di liste collegati singolarmente, esistono altri tipi di nodi. Un nodo in una lista doppiamente collegata è forse il più comune.


1.2 Nodo in una lista doppiamente collegata

Avremo anche bisogno di un nodo che servirà alle esigenze di una lista doppiamente collegata. È diverso dal tipo precedente in quanto contiene un altro collegamento che punta al nodo precedente. E naturalmente, il nodo dell’inizio della lista conterrà un riferimento NULL. Nel diagramma che mostra la struttura dell'elenco contenente tali nodi (Fig. 2), i collegamenti che puntano ai nodi precedenti sono visualizzati come frecce rosse.

Fig. 2 Nodi in una lista doppiamente collegata

Fig. 2 Nodi in una lista doppiamente collegata


Quindi le capacità di un nodo in una lista doppiamente collegata saranno simili a quelle di un nodo di una lista collegata singola. Dovrai solo gestire un ulteriore collegamento al nodo precedente.


1.3 Nodo in una lista circolare doppiamente collegata

Ci sono casi in cui i nodi di cui abbiamo parlato possono essere utilizzati anche in elenchi non lineari. E sebbene l'articolo descriverà principalmente elenchi lineari, fornirò anche un esempio di elenchi circolari.

Fig. 3 Nodi in una lista circolare doppiamente collegata

Fig. 3 Nodi in una lista circolare doppiamente collegata


Il diagramma di una lista circolare doppiamente collegata (Fig. 3) mostra che i nodi con due campi di collegamento sono semplicemente collegati circolarmente. Questo viene fatto usando le frecce arancione e verde. Pertanto, il nodo della testa sarà collegato alla coda (come l'elemento precedente). E il campo di collegamento del nodo di coda non sarà vuoto poiché punterà alla testa.


1.4 Operazioni principali dell'elenco

Come riportato nella letteratura specializzata, tutte le operazioni di lista possono essere suddivise in 3 gruppi di base:

  1. Aggiunta (di un nuovo nodo alla lista);
  2. Cancellazione (di un nodo dalla lista);
  3. Verifica (dati di un nodo).

I metodi di aggiunta includono:

  • aggiungere un nuovo nodo all'inizio della lista;
  • aggiungere un nuovo nodo alla fine della lista;
  • aggiungere un nodo alla posizione specificata nella lista;
  • aggiungere un nodo a una lista vuota;
  • costruttore parametrizzato.

Per quanto riguarda le operazioni di cancellazione, esse rispecchiano virtualmente le corrispondenti operazioni del gruppo di addizione:

  • eliminare il nodo di testa;
  • eliminare il nodo di coda;
  • eliminare un nodo dalla posizione specificata nell'elenco;
  • Distruttore.

Qui, vorrei farvi notare che il distruttore serve non solo a completare e terminare correttamente l'operazione di un elenco, ma anche a eliminare correttamente tutti i suoi elementi.

Il terzo gruppo di varie operazioni di controllo infatti dà accesso ai nodi o ai valori dei nodi nell'elenco:

  • ricerca di un dato valore;
  • controllando che l'elenco sia vuoto;
  • ottenere il valore dell'i-esimo nodo nella lista;
  • ottenere il puntatore all'i-esimo nodo nell'elenco;
  • ottenere la dimensione dell'elenco;
  • stampa dei valori degli elementi della lista.

Oltre ai gruppi di base, separerei anche il quarto gruppo di servizio. Esso serve i gruppi precedenti:

  • Operatore di assegnazione
  • costruttore di copie;
  • lavorare con il puntatore dinamico;
  • copia dell'elenco per valori;
  • ordinamento.

Questo è tutto. Lo sviluppatore può ovviamente espandere le funzionalità e le caratteristiche di una classe di lista in qualsiasi momento, se necessario.


2. Concetto di Lista e Nodo: Programmazione

In questa parte, suggerisco di procedere direttamente alla programmazione di nodi e liste. Se necessario, verranno fornite illustrazioni al codice.

2.1 Nodo in una lista collegata singolarmente

Gettiamo le basi per la classe del nodo (Fig. 4) che soddisfa le esigenze di una lista concatenata singolarmente. Puoi familiarizzare con la notazione del diagramma di classe (modello) nell'articolo intitolato "Come sviluppare un Expert Advisor utilizzando gli strumenti UML" (vedi Fig. 5. modello UML della classe CTradeExpert).

 Modello di classe CiSingleNode

Fig. 4 Modello di classe CiSingleNode

Proviamo ora a lavorare con il codice. Si basa sull'esempio fornito nel libro di Art Friedman e altri autori. "Archivi annotati C/C++".

//+------------------------------------------------------------------+
//|                     CiSingleNode class                           |
//+------------------------------------------------------------------+
class CiSingleNode
  {
protected:
   int               m_val;   // data
   CiSingleNode     *m_next;  // pointer to the next node
public:
   void              CiSingleNode(void);                             // default constructor
   void              CiSingleNode(int _node_val);                    // parameterized constructor
   void             ~CiSingleNode(void);                             // destructor
   void              SetVal(int _node_val);                          // set-method for data
   void              SetNextNode(CiSingleNode *_ptr_next);           // set-method for the next node
   virtual void      SetPrevNode(CiSingleNode *_ptr_prev){};         // set-method for the previous node
   virtual CiSingleNode *GetPrevNode(void) const {return NULL;};     // get-method for the previous node
   CiSingleNode     *GetNextNode(void) const;                        // get-method for the next node 
   int               GetVal(void){TRACE_CALL(_t_flag) return m_val;} // get-method for data 
  };

Non spiegherò ogni metodo della classe CiSingleNode. Potrai prenderne visione più da vicino nel file CiSingleNode.mqh in allegato. Tuttavia, vorrei attirare la vostra attenzione su una sfumatura interessante. La classe contiene metodi virtuali che funzionano con i nodi precedenti. Sono infatti fittizzi e la loro presenza è piuttosto ai fini del polimorfismo per i futuri discendenti.

Il codice utilizza la direttiva del preprocessore TRACE_CALL(f) necessaria per tracciare le chiamate di ciascun metodo utilizzato.

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

Con solo la classe CiSingleNode disponibile, sei in grado di creare una lista concatenata singolarmente. Ti faccio un esempio del codice.

//=========== Example 1 (processing the CiSingleNode type )
 
   CiSingleNode *p_sNodes[3];                             // #1
   p_sNodes[0]=NULL;
   srand(GetTickCount()); // initialize a random number 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 

Nella stringa #1, dichiariamo un array di puntatori a oggetti di tipo CiSingleNode. Nella stringa n. 2, l'array viene riempito con i puntatori creati. Per i dati di ciascun nodo, prendiamo un intero pseudocasuale compreso tra 0 e 32767 utilizzando la rand() function. I nodi sono collegati al puntatore successivo nella stringa #3. Nelle stringhe #4-5, controlliamo i valori dei nodi e nelle stringhe #9-11 controlliamo le prestazioni dei collegamenti. I puntatori vengono eliminati nella stringa #12.

Questo è ciò che è stato stampato sul registro.

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

La struttura del nodo risultante può essere schematicamente mostrata come segue (Fig. 5).

Fig. 5 Collegamenti tra i nodi nell'array CiSingleNode *p_sNodes[3]

Fig. 5 Collegamenti tra i nodi nell'array CiSingleNode *p_sNodes[3]

Procediamo ora ai nodi in una lista doppiamente collegata.


2.2 Nodo in una lista doppiamente collegata

Innanzitutto, dobbiamo rispolverare il fatto che un nodo in una lista doppiamente collegata è distinto in quanto ha due puntatori: puntatore al nodo successivo e puntatore al nodo precedente. Ad esempio, oltre al collegamento al nodo successivo, è necessario aggiungere un puntatore al nodo precedente a un nodo di elenco con collegamento singolo.

Nel fare ciò, propongo di utilizzare l'ereditarietà come relazione di classe. Quindi il modello di classe per un nodo in una lista doppiamente collegata può apparire come segue (Fig. 6).

 Modello di classe DoubleNode

Fig. 6 Modello di classe CDDoubleNode

Ora è il momento di dare un'occhiata al codice.

//+------------------------------------------------------------------+
//|                    CDoubleNode class                             |
//+------------------------------------------------------------------+
class CDoubleNode : public CiSingleNode
  {
protected:
   CiSingleNode     *m_prev;  // pointer to the previous node 

public:
   void              CDoubleNode(void);                     // default constructor
   void              CDoubleNode(int node_val);             // parameterized constructor
   void             ~CDoubleNode(void){TRACE_CALL(_t_flag)};// destructor
   virtual void      SetPrevNode(CiSingleNode *_ptr_prev);  // set-method for the previous node
   virtual CiSingleNode *GetPrevNode(void) const;           // get-method for the previous node CDoubleNode
  };

Ci sono pochissimi metodi aggiuntivi: sono virtuali e sono legati al lavoro con il nodo precedente. La descrizione completa della classe è fornita in CDoubleNode.mqh.

Proviamo a creare una lista doppiamente collegata basata sulla classe CDoubleNode. Ti faccio un esempio del codice.

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

   CiSingleNode *p_dNodes[3];                             // #1
   p_dNodes[0]=NULL;
   srand(GetTickCount()); // initialize a random number 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 

In linea di principio, questo è simile alla creazione di una lista collegata singolarmente, ma ci sono alcune peculiarità. Nota come l'array di puntatori p_dNodes[] è dichiarato nella stringa #1. Il tipo di puntatori può essere impostato identico alla classe base. Il principio del polimorfismo nella stringa n. 2 ci aiuterà a riconoscerli in futuro. I nodi precedenti vengono controllati nelle stringhe #12-14.

Le seguenti informazioni sono state aggiunte al registro.

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

La struttura del nodo risultante può essere schematicamente mostrata come segue (Fig. 7):

Fig. 7 Collegamenti tra i nodi nell'array CDoubleNode *p_sNodes[3]

Fig. 7 Collegamenti tra i nodi nell'array CDoubleNode *p_sNodes[3]

Suggerisco ora di considerare un nodo che sarà richiesto nella creazione di una lista srotolata a doppio collegamento.


2.3 Nodo in una lista non rotolata doppiamente collegata

Si pensi a un nodo che conterrebbe un dato che, invece di un singolo valore, è attribuibile all'intero array, cioè contiene e descrive l'intero array. Tale nodo può quindi essere utilizzato per creare una lista srotolata. Ho deciso di non fornire alcuna illustrazione qui poiché questo nodo è esattamente lo stesso di un nodo standard in una lista doppiamente collegata. L'unica differenza è che l'attributo 'data' incapsula l'intero array.

Userò di nuovo l'ereditarietà. La classe CDoubleNode fungerà da classe base per un nodo in un elenco srotolato a doppio collegamento. E il modello di classe per un nodo in una lista doppiamente collegata srotolata apparirà come segue (Fig. 8).

Modello di classe CiUnrollDoubleNode

Fig. 8 Modello di classe CiUnrollDoubleNode

La classe CiUnrollDoubleNode può essere definita utilizzando il seguente codice:

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

public:
   void              CiUnrollDoubleNode(void);               // default constructor 
   void              CiUnrollDoubleNode(int &_node_arr[]);   // parameterized constructor
   void             ~CiUnrollDoubleNode(void);               // destructor
   bool              GetArrVal(int &_dest_arr_val[])const;   // get-method for data array
   bool              SetArrVal(const int &_node_arr_val[]);  // set-method for data array
  };

Puoi esaminare ogni metodo in modo più dettagliato in CiUnrollDoubleNode.mqh.

Consideriamo un costruttore parametrizzato come esempio.

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

Qui, usando l'elenco di inizializzazione, inseriamo la dimensione di un array unidimensionale nel membro dati this.m_val.

Dopodiché creiamo "manualmente" una lista srotolata a doppio collegamento e controlliamo i collegamenti al suo interno.

//=========== 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 number 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 array 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
  }

La quantità di codice è diventata leggermente più grande. Questo ha a che fare con il fatto che dobbiamo creare e riempire un array per ogni nodo.

Il lavoro con gli array di dati inizia nella stringa #1. È sostanzialmente simile a quello che avevamo nei nodi precedenti considerati. È solo che abbiamo bisogno di stampare i valori dei dati di ciascun nodo per l'intero array (ad esempio, stringa #9).

Questo è quello che abbiamo:

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

Suggerisco di tracciare una linea sotto il lavoro con i nodi e procedere direttamente alle definizioni di classe di liste diverse. Gli esempi 1-3 possono essere trovati nello script test_nodes.mq5.


2.4 Lista collegata singolarmente

È giunto il momento di creare un modello di classe di una lista collegata singolarmente dai principali gruppi di operazioni di elenco (Fig. 9).

Modello di classe CiSingleList

Fig. 9 Modello di classe CiSingleList

È facile vedere che la classe CiSingleList utilizza il nodo di tipo CiSingleNode. Parlando di tipi di relazioni tra classi, possiamo dire che:

  1. la classe CiSingleList contiene la classe CiSingleNode (composizione);
  2. la classe CiSingleList utilizza i metodi della classe CiSingleNode (dipendenza).

L'illustrazione delle relazioni di cui sopra è fornita in Fig. 10.

Fig. 10 Tipi di relazioni tra la classe CiSingleList e la classe CiSingleNode

Fig. 10 Tipi di relazioni tra la classe CiSingleList e la classe CiSingleNode

Creiamo una nuova classe - CiSingleList. Guardando al futuro, tutte le altre classi di elenco utilizzate nell'articolo saranno basate su questa classe. Ecco perché è così 'ricco'.

//+------------------------------------------------------------------+
//|                     CiSingleList class                           |
//+------------------------------------------------------------------+
class CiSingleList
  {
protected:
   CiSingleNode     *m_head;    // head
   CiSingleNode     *m_tail;    // tail
   uint              m_size;    // number of nodes in the list
public:
   //--- constructor and destructor
   void              CiSingleList();                              // default constructor 
   void              CiSingleList(int _node_val);                 // parameterized constructor 
   void             ~CiSingleList();                              // destructor                

   //--- adding nodes   
   void              AddFront(int _node_val);                         // add a new node to the beginning of the list
   void              AddRear(int _node_val);                          // add a new node to the end of the list   
   virtual void      AddFront(int &_node_arr[]){TRACE_CALL(_t_flag)}; // add a new node to the beginning of the list
   virtual void      AddRear(int &_node_arr[]){TRACE_CALL(_t_flag)};  // add a new node to the end of the list
   //--- deleting nodes     
   int               RemoveFront(void);                           // delete the head node       
   int               RemoveRear(void);                            // delete the node from the end of the list
   void              DeleteNodeByIndex(const uint _idx);          // delete the ith node from the list

   //--- checking   
   virtual bool      Find(const int _node_val) const;             // find the required value    
   bool              IsEmpty(void) const;                         // check the list for being empty
   virtual int       GetValByIndex(const uint _idx) const;        // value of the ith node in the list
   virtual CiSingleNode *GetNodeByIndex(const uint _idx) const;   // get the ith node in the list
   virtual bool      SetNodeByIndex(CiSingleNode *_new_node,const uint _idx); // insert the new ith node in the list
   CiSingleNode     *GetHeadNode(void) const;                     // get the head node
   CiSingleNode     *GetTailNode(void) const;                     // get the tail node
   virtual uint      Size(void) const;                            // list size
   //--- service
   virtual void      PrintList(string _caption=NULL);             // print the list
   virtual bool      CopyByValue(const CiSingleList &_sList);     // copy the list by values
   virtual void      BubbleSort(void);                            // bubble sorting
   //---templates
   template<typename dPointer>
   bool              CheckDynamicPointer(dPointer &_p);           // template for checking a dynamic pointer
   template<typename dPointer>
   bool              DeleteDynamicPointer(dPointer &_p);          // template for deleting a dynamic pointer

protected:
   void              operator=(const CiSingleList &_sList) const; // assignment operator
   void              CiSingleList(const CiSingleList &_sList);    // copy constructor
   virtual bool      AddToEmpty(int _node_val);                   // add a new node to an empty list
   virtual void      addFront(int _node_val);                     // add a new "native" node to the beginning of the list
   virtual void      addRear(int _node_val);                      // add a new "native" node to the end of the list
   virtual int       removeFront(void);                           // delete the "native" head node
   virtual int       removeRear(void);                            // delete the "native" node from the end of the list
   virtual void      deleteNodeByIndex(const uint _idx);          // delete the "native" ith node from the list
   virtual CiSingleNode *newNode(int _val);                       // new "native" node
   virtual void      CalcSize(void) const;                        // calculate the list size
  };

La definizione completa dei metodi di classe è fornita in CiSingleList.mqh.

Quando ho iniziato a sviluppare questa classe, c'erano solo 3 membri dati e solo pochi metodi. Ma poiché questa classe fungeva da base per altre classi, ho dovuto aggiungere diverse funzioni membro virtuale. Non ho intenzione di descrivere questi metodi in dettaglio. Un esempio dell'utilizzo di questa classe di elenchi collegati singolarmente può essere trovato nello script test_sList.mq5.

Se viene eseguito senza il flag di traccia, nel registro verranno visualizzate le seguenti voci:

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)  

Lo script ha riempito 2 liste collegate singolarmente e poi ha esteso e ordinato la seconda lista.


2.5 Lista doppiamente collegata

Proviamo ora a creare una lista doppiamente collegata in base alla lista del tipo precedente. L'illustrazione del modello di classe di una lista doppiamente collegata è fornita in Fig. 11:

Modello di classe CDDoubleList

Fig. 11 Modello di classe CDDoubleList

La classe discendente contiene molti meno metodi, mentre i membri dei dati sono del tutto assenti. Di seguito è riportata la definizione della classe CDoubleList.

//+------------------------------------------------------------------+
//|                      CDoubleList class                           |
//+------------------------------------------------------------------+
class CDoubleList : public CiSingleList
  {
public:
   void              CDoubleList(void);                  // default constructor    
   void              CDoubleList(int _node_val);         // parameterized constructor   
   void             ~CDoubleList(void){};                // destructor                  
   virtual bool      SetNodeByIndex(CiSingleNode *_new_node,const uint _idx); // insert the new ith node in the list  

protected:
   virtual bool      AddToEmpty(int _node_val);          // add a node to an empty list
   virtual void      addFront(int _node_val);            // add a new "native" node to the beginning of the list
   virtual void      addRear(int _node_val);             // add a new "native" node to the end of the list 
   virtual int       removeFront(void);                  // delete the "native" head node
   virtual int       removeRear(void);                   // delete the "native" tail node
   virtual void      deleteNodeByIndex(const uint _idx); // delete the "native" ith node from the list
   virtual CiSingleNode *newNode(int _node_val);         // new "native" node
  };

La descrizione completa dei metodi della classe CDoubleList è fornita in CDoubleList.mqh.

In generale, le funzioni virtuali vengono utilizzate qui solo per soddisfare le esigenze del puntatore al nodo precedente che non esiste nelle liste concatenate singolarmente.

Un esempio di utilizzo dell'elenco di tipo CDoubleList può essere trovato nello script test_dList.mq5. Essp dimostra tutte le operazioni di elenco comuni relative a questo tipo di elenco. Il codice dello script contiene una stringa particolare:

CiSingleNode *_new_node=new CDoubleNode(666);     // create a new node of CDoubleNode type

Non c'è alcun errore perché tale costruzione è abbastanza accettabile nei casi in cui il puntatore della classe base descrive un oggetto della classe discendente. Questo è uno dei vantaggi dell'ereditarietà.

In MQL5, così come in С++, il puntatore alla classe base può puntare all'oggetto della sottoclasse derivata da quella classe base. Ma il contrario non è valido.

Se scrivi la stringa come segue:

CDoubleNode*_new_node=new CiSingleNode(666);

il compilatore non segnalerà un errore o un avviso ma il programma verrà eseguito finché non raggiunge questa stringa. In questo caso, vedrai un messaggio sul casting errato dei tipi a cui fanno riferimento i puntatori. Poiché il meccanismo di associazione tardiva entra in azione solo quando il programma è in esecuzione, è necessario considerare attentamente la gerarchia delle relazioni tra le classi.

Dopo aver eseguito lo script, il registro conterrà le seguenti voci:

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)  

Come nel caso di una lista concatenata singola, lo script ha riempito la prima lista (doppiamente collegata), l'ha copiata e passata alla seconda lista. Quindi ha aumentato il numero di nodi nel secondo elenco, ordinato e stampato l'elenco.


2.6 Lista doppiamente collegata srotolata

Questo tipo di lista è conveniente in quanto consente di memorizzare non solo un valore ma un intero array.

Gettiamo le basi per l'elenco di tipo CiUnrollDoubleList (Fig. 12).

 Modello di classe CiUnrollDoubleList

Fig. 12 Modello di classe CiUnrollDoubleList

Poiché qui ci occuperemo di un array di dati, dovremo ridefinire i metodi definiti nella classe base indiretta CiSingleList.

Di seguito è riportata la definizione della classe CiUnrollDoubleList.

//+------------------------------------------------------------------+
//|                     CiUnrollDoubleList class                     |
//+------------------------------------------------------------------+
class CiUnrollDoubleList : public CDoubleList
  {
public:
   void              CiUnrollDoubleList(void);                      // default constructor
   void              CiUnrollDoubleList(int &_node_arr[]);          // parameterized constructor
   void             ~CiUnrollDoubleList(void){TRACE_CALL(_t_flag)}; // destructor
   //---
   virtual void      AddFront(int &_node_arr[]);                    // add a new node to the beginning of the list
   virtual void      AddRear(int &_node_arr[]);                     // add a new node to the end of the list
   virtual bool      CopyByValue(const CiSingleList &_udList);      // copy by values
   virtual void      PrintList(string _caption=NULL);               // print the list
   virtual void      BubbleSort(void);                              // bubble sorting

protected:
   virtual bool      AddToEmpty(int &_node_arr[]);                  // add a node to an empty list
   virtual void      addFront(int &_node_arr[]);                    // add a new "native" node to the beginning of the list
   virtual void      addRear(int &_node_arr[]);                     // add a new "native" node to the end of the list
   virtual int       removeFront(void);                             // delete the "native" node from the beginning of the list
   virtual int       removeRear(void);                              // delete the "native" node from the end of the list
   virtual void      deleteNodeByIndex(const uint _idx);            // delete the "native" ith node from the list
   virtual CiSingleNode *newNode(int &_node_arr[]);                 // new "native" node
  };

La definizione completa dei metodi di classe è fornita in CiUnrollDoubleList.mqh.

Lanciamo lo script test_UdList.mq5 per verificare il funzionamento dei metodi della classe. Qui, le operazioni sui nodi sono simili a quelle usate negli script precedenti. Dovremmo forse spendere qualche parola sui metodi di smistamento e stampa. Il metodo di ordinamento ordina i nodi in base al numero di elementi in modo che il nodo contenente l'array di valori della dimensione più piccola sia all'inizio dell'elenco.

Il metodo di stampa stampa una stringa di valori di array contenuti in un determinato nodo.

Dopo aver eseguito lo script, il registro avrà le seguenti voci:

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) 

Come puoi vedere, dopo essere stato ordinato, la lista udList2 è stata stampata partendo dal nodo con l'array più piccolo fino al nodo che contiene l'array più grande.


2.7 Lista circolare doppiamente collegata

Sebbene le liste non lineari non siano considerate in questo articolo, suggerisco di lavorare anche con loro. Come sia possibile collegare i nodi circolarmente è già stato mostrato sopra (Fig. 3).

Creiamo un modello della classe CiCircleDoubleList class (Fig. 13). Questa classe sarà una classe discendente della classe CDoubleList.

 Modello di classe CiCircleDoubleList

Fig. 13 Modello di classe CiCircleDoubleList

A causa del fatto che i nodi in questa lista sono di carattere specifico (la testa e la coda sono collegate), quasi tutti i metodi della classe base di origine CiSingleList dovranno essere resi virtuali.

//+------------------------------------------------------------------+
//|                      CiCircleDoubleList class                    |
//+------------------------------------------------------------------+
class CiCircleDoubleList : public CDoubleList
  {
public:
   void              CiCircleDoubleList(void);                       // default constructor
   void              CiCircleDoubleList(int _node_val);              // parameterized constructor
   void             ~CiCircleDoubleList(void){TRACE_CALL(_t_flag)};  // destructor
   //---
   virtual uint      Size(void) const;                                        // list size
   virtual bool      SetNodeByIndex(CiSingleNode *_new_node,const uint _idx); // insert the new ith node in the list
   virtual int       GetValByIndex(const uint _idx) const;           // value of the ith node in the list
   virtual CiSingleNode *GetNodeByIndex(const uint _idx) const;      // get the ith node in the list
   virtual bool      Find(const int _node_val) const;                // find the required value
   virtual bool      CopyByValue(const CiSingleList &_sList);        // copy the list by values

protected:
   virtual void      addFront(int _node_val);                        // add a new "native" node to the beginning of the list
   virtual void      addRear(int _node_val);                         // add a new "native" node to the end of the list
   virtual int       removeFront(void);                              // delete the "native" head node
   virtual int       removeRear(void);                               // delete the "native" tail node
   virtual void      deleteNodeByIndex(const uint _idx);             // delete the "native" ith node from the list

protected:
   void              CalcSize(void) const;                           // calculate the list size
   void              LinkHeadTail(void);                             // link head to tail
  };

La descrizione completa della classe è fornita in CiCircleDoubleList.mqh.

Consideriamo alcuni metodi della classe. Il metodo CiCircleDoubleList::LinkHeadTail() collega il nodo di coda al nodo di testa. Dovrebbe essere chiamato quando c'è una nuova coda o testa e il collegamento precedente viene perso.

//+------------------------------------------------------------------+
//|                  Linking head to tail                            |
//+------------------------------------------------------------------+
void CiCircleDoubleList::LinkHeadTail(void)
  {
   TRACE_CALL(_t_flag)
   this.m_head.SetPrevNode(this.m_tail);      // link head to tail
   this.m_tail.SetNextNode(this.m_head);      // link tail to head
  }

Pensa a come sarebbe questo metodo se avessimo a che fare con un elenco circolare collegato singolarmente.

Si consideri, ad esempio, il metodo CiCircleDoubleList::addFront().

//+------------------------------------------------------------------+
//|                New "native" node to the beginning of the list    |
//+------------------------------------------------------------------+
void CiCircleDoubleList::addFront(int _node_val)
  {
   TRACE_CALL(_t_flag)
   CDoubleList::addFront(_node_val); // call a similar method of the base class
   this.LinkHeadTail();              // link head and tail
  }

Puoi vedere nel corpo del metodo che viene chiamato un metodo simile della classe base CDoubleList. A questo punto, potremmo completare l'operazione del metodo (il metodo in quanto tale non è sostanzialmente necessario qui), se non fosse per una cosa. Il collegamento tra la testa e la coda è perso e l'elenco non può essere collegato circolarmente senza di esso. Questo è il motivo per cui dobbiamo chiamare il metodo di collegamento della testa e della coda.

L'utilizzo della lista circolare doppiamente collegata è verificato nello script test_UdList.mq5.

In termini di compiti e obiettivi, gli altri metodi utilizzati sono gli stessi degli esempi precedenti.

Di conseguenza, il registro contiene le seguenti voci:

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)

Quindi il diagramma finale dell'ereditarietà tra le classi di liste introdotte è il seguente (Fig. 14).

Non sono sicuro che tutte le classi debbano essere collegate per eredità, ma ho deciso di lasciare tutto così com'è.

Ereditarietà tra le classi della lista

Fig. 14 Ereditarietà tra le classi della lista

Tracciando la linea sotto questa sezione dell'articolo che si è occupata della descrizione degli elenchi personalizzati, vorrei farvi notare che abbiamo appena accennato al gruppo di elenchi non lineari, elenchi concatenati multipli e altri. Man mano che raccolgo le informazioni pertinenti e acquisisco più esperienza lavorando con strutture di dati così dinamiche, proverò a scrivere un altro articolo.


3. Elenchi nella libreria standard (Standard Library) MQL5

Diamo un'occhiata alla classe della lista disponibile nella Standard Library (Fig. 15).

Appartiene alle classi di dati.

Modello di classe CList

Fig. 15 Modello classe CList

Curiosamente, CList è un discendente della classe CObject. Cioè l'elenco eredita dati e metodi della classe, che è un nodo.

La classe list contiene un insieme impressionante di metodi. Ad essere onesti, non mi aspettavo di trovare una classe così grande nella Standard Library.

La classe CList ha 8 membri di dati. Vorrei sottolineare un paio di cose. Gli attributi della classe contengono l'indice del nodo corrente (int m_curr_idx) e il puntatore al nodo corrente (CObject* m_curr_node). Si può dire che l'elenco è "intelligente" - può indicare il luogo in cui è localizzato il controllo. Inoltre, è dotato di meccanismo di gestione della memoria (possiamo eliminare fisicamente un nodo o semplicemente escluderlo dall'elenco), flag di elenco ordinato e modalità di ordinamento.

Parlando di metodi, tutti i metodi della classe CList sono suddivisi nei seguenti gruppi:

  • attributi;
  • Crea metodi;
  • Aggiungi metodi;
  • Elimina metodi;
  • Navigazione;
  • Modalità di ordinazione;
  • Confronta metodi;
  • Metodi di ricerca;
  • Input Output.

Come al solito, c'è un costruttore e un distruttore standard.

Il primo svuota (NULL) tutti i puntatori. Lo stato del flag di gestione della memoria è impostato su eliminazione. Il nuovo elenco non sarà ordinato.

Nel suo corpo, il distruttore chiama solo il metodo Clear() per svuotare l'elenco dei nodi. La fine dell'esistenza della lista non comporta necessariamente la "morte" dei suoi elementi (nodi). Pertanto, il flag di gestione della memoria impostato durante l'eliminazione degli elementi della lista trasforma la relazione di classe da composizione ad aggregazione.

Possiamo gestire questo flag usando i metodi set e get FreeMode().

Ci sono due metodi nella classe che ti permettono di estendere l'elenco: Add() e Insert). Il primo è simile al metodo AddRear() utilizzato nella prima sezione dell'articolo. Il secondo metodo è simile al metodo SetNodeByIndex().

Cominciamo con un piccolo esempio. Dobbiamo prima creare una classe del nodo CNodeInt, un discendente della classe dell'interfaccia CObject. Memorizzerà il valore del tipo intero.

//+------------------------------------------------------------------+
//|                        CNodeInt class                            |
//+------------------------------------------------------------------+
class  CNodeInt : public CObject
  {
private:
   int               m_val;  // node data

public:
   void              CNodeInt(void){this.m_val=WRONG_VALUE;}; // default constructor
   void              CNodeInt(int _val);                      // parameterized constructor
   void             ~CNodeInt(void){};                        // destructor
   int               GetVal(void){return this.m_val;};        // get-method for node data
   void              SetVal(int _val){this.m_val=_val;};      // set-method for node data
  };
//+------------------------------------------------------------------+
//|                    Parameterized constructor                     |
//+------------------------------------------------------------------+
void CNodeInt::CNodeInt(int _val):m_val(_val)
  {

  };

Lavoreremo con l'elenco CList nello script test_MQL5_List.mq5.

L'esempio 1 mostra una creazione dinamica dell'elenco e dei nodi. L'elenco viene quindi riempito con i nodi e il valore del primo nodo viene verificato prima e dopo l'eliminazione dell'elenco.

//--- 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 many 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);

Se la stringa che ripristina il flag rimane commentata (inattiva), otterremo le seguenti voci nel registro:

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 many 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

Si noti che dopo aver cancellato dinamicamente la lista myList, anche tutti i nodi in essa contenuti sono stati cancellati dalla memoria.

Tuttavia, se decommentiamo la stringa del flag di ripristino:

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

l'output nel log sarà il seguente:

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 many 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

È facile notare che il nodo head mantiene il suo valore sia prima che dopo che la lista è stata cancellata. In questo caso, rimarranno anche oggetti non eliminati se lo script non contiene il codice per eliminarli correttamente.

Proviamo ora a lavorare con il metodo di ordinamento.

//--- 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 many 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;

Di conseguenza, il registro contiene le seguenti voci:

OR      0       22:47:01        test_MQL5_List (EURUSD,H1)      
FN      0       22:47:01        test_MQL5_List (EURUSD,H1)      List "myList" has as many 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

Anche se non è stato fatto alcun ordinamento, la tecnica di smistamento rimane un mistero per me. Spiegherò perché. Senza entrare nei dettagli sull'ordine delle chiamate, il metodo CList::Sort() chiama il metodo virtuale CObject::Compare() che non è in alcun modo implementato nella classe base. Quindi, il programmatore deve occuparsi da solo dell'implementazione di un metodo di ordinamento.

E ora, qualche parola sul metodo Total(). Restituisce il numero di elementi (nodi) di cui è responsabile il membro di dati m_data_total. È un metodo molto breve e conciso. Il conteggio degli elementi in questa implementazione sarà molto più veloce rispetto a quello che ho proposto in precedenza. Infatti, perché ogni volta passano attraverso la lista e contano i nodi, mentre il numero esatto di nodi nell'elenco può essere impostato quando si aggiungono o si eliminano nodi.

L'esempio 3 confronta la velocità di riempimento delle liste di tipo CList e CiSingleList e calcola il tempo per ottenere la dimensione di ciascuna 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; // time spent, 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; // time spent, 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;   

Questo è quello che ho nel registro:

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

Il metodo per ottenere la dimensione funziona istantaneamente nell'elenco CList. A proposito, anche l'aggiunta di nodi alla lista è abbastanza veloce.

Nel blocco successivo (Esempio 4), suggerisco di prestare attenzione a uno dei principali svantaggi della lista come contenitore di dati, ovvero la poca velocità di accesso agli elementi. Il fatto è che si accede agli elementi della lista in modo lineare. Nella classe CList, vi si accede in modo binario, il che diminuisce leggermente la laboriosità dell'algoritmo.

Quando si effettua una ricerca lineare, la laboriosità è O(N). Una ricerca implementata in modo binario risulta nella laboriosità di log2(N).

Questo è un esempio del codice per accedere agli elementi di un set di dati:

//--- 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[];                        // array of random numbers
      uint idxArr[];                         // array of indexes
      //--- set the arrays size    
      ArrayResize(randArr,cur_iterations);
      ArrayResize(idxArr,cur_iterations);
      CRandom myRand;                        // random number generator
      //--- fill the array of random numbers
      for(uint t=0;t<cur_iterations;t++)
         randArr[t]=myRand.int32();
      //--- fill the array of indexes with random numbers (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; // time spent, 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; // time spent, 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;
     }

Sulla base dei risultati dell'operazione di blocco, le seguenti voci sono state stampate nel registro: 

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

Puoi vedere che l'accesso casuale agli elementi dell'elenco richiede più tempo man mano che la dimensione dell'elenco aumenta (Fig. 16).

 Tempo speso per l'accesso casuale a array ed elementi di lista

Fig. 16 Tempo impiegato per l'accesso casuale agli elementi di array e di lista

Consideriamo ora i metodi per salvare e caricare i dati.

La classe dell'elenco di base CList contiene tali metodi ma essi sono virtuali. Quindi, per testare il loro funzionamento usando un esempio, dobbiamo fare un po' di preparazione.

Dovremmo ereditare le capacità della classe CList usando la classe discendente CIntList. Quest'ultimo avrà solo 1 metodo per creare un nuovo elemento CIntList::CreateElement().

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

public:
   virtual CObject *CreateElement(void);
  };
//+------------------------------------------------------------------+
//|                    New element of the list                       |
//+------------------------------------------------------------------+
CObject *CIntList::CreateElement(void)
  {
   CObject *new_node=new CNodeInt();
   return new_node;
  }

Sarà inoltre necessario aggiungere i metodi virtuali CNodeInt::Save() e CNodeInt::Load() al tipo di nodo derivato CNodeInt. Verranno chiamati dalle funzioni membro CList::Save() e CList::Load(), rispettivamente.

Di conseguenza, l'esempio sarà il seguente (Esempio 5):

//--- Example 5 (saving list data)
//--- the CIntList type list
   CList *p_int_List=new CIntList;
   int randArr[1000];                        // array of random numbers
   ArrayInitialize(randArr,0);
//--- fill the array of random numbers 
   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 to 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("Nodes loaded from the file: %d",Loaded_List_size);
//--- free the memory     
   delete p_int_List;

Dopo aver eseguito lo script sul grafico, la seguente voce verrà aggiunta al registro:

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

Pertanto, abbiamo visto l'implementazione di metodi di input/output per un membro dati del tipo di nodo CNodeInt.

Nella prossima sezione, vedremo esempi di come gli elenchi possono essere utilizzati per risolvere i problemi quando si lavora con MQL5.


4. Esempi di utilizzo di liste in MQL5

Nella sezione precedente, parlando dei metodi della classe CList della libreria standard, ho fornito alcuni esempi.

Ora prenderò in considerazione i casi in cui la lista viene utilizzata per risolvere un problema specifico. E qui non posso non sottolineare ancora una volta il vantaggio della lista come tipo di dati contenitore. Possiamo rendere più efficiente il lavoro con il codice, sfruttando la flessibilità di una lista.


4.1 Gestione di oggetti grafici

Immagina di dover creare a livello di codice oggetti grafici nel grafico. Questi possono essere oggetti diversi che possono apparire nel grafico per vari motivi.

Ricordo come una volta l'elenco mi abbia aiutato a risolvere un problema con oggetti grafici. E vorrei condividere con voi questa mia esperienza.

Avevo il compito di creare linee verticali in base alla condizione specificata. A seconda della condizione, le linee verticali fungevano da limiti per un dato intervallo di tempo che variava in lunghezza caso per caso. Detto questo, l'intervallo non era sempre completamente formato.

Dovevo raccogliere statistiche per lo studio del comportamento di EMA21.

Ero particolarmente interessato alla lunghezza della pendenza della media mobile. Ad esempio, in un movimento verso il basso il punto di partenza è stato individuato registrando il movimento negativo della media mobile (cioè diminuzione di valore) per cui è stata tracciata una linea verticale. La Fig. 17 mostra tale punto identificato per EURUSD, H1 il 5 settembre 2013 alle 16:00, all'apertura di una candela.

Fig. 17 Primo punto dell'intervallo discendente

Fig. 17 Primo punto dell'intervallo discendente 


Il secondo punto che suggerisce la fine del movimento al ribasso è stato individuato in base al principio inverso, registrando un movimento positivo della media mobile, cioè un aumento di valore (Fig. 18).


Fig. 18 Secondo punto dell'intervallo discendente

Fig. 18 Secondo punto dell'intervallo discendente


Pertanto, l'intervallo di destinazione era dal 5 settembre 2013 alle 16:00 al 6 settembre 2013 alle 17:00.

Il sistema di identificazione dei diversi intervalli può essere più complesso o più semplice. Questo non è il punto. Ciò che è importante è il fatto che questa tecnica per lavorare con oggetti grafici e raccogliere contemporaneamente dati statistici comporta uno dei maggiori vantaggi della lista: la flessibilità della composizione.

Per quanto riguarda l'esempio attuale, ho prima creato un nodo di tipo CVertLineNode che è responsabile di 2 oggetti grafici "Linea verticale".

La definizione della classe è la seguente:

//+------------------------------------------------------------------+
//|                      CVertLineNode class                         |
//+------------------------------------------------------------------+
class CVertLineNode : public CObject
  {
private:
   SVertLineProperties m_vert_lines[2];      // array of structures of vertical line properties
   uint              m_duration;             // frame duration
   bool              m_IsFrameFormed;        // flag of frame formation

public:
   void              CVertLineNode(void);
   void             ~CVertLineNode(void){};
   //--- set-methods   
   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;};
   //--- get-methods   
   void              GetLine(SVertLineProperties &_vert_line_out,bool IsFirst=true) const;
   uint              GetDuration(void) const;
   bool              GetFrameFlag(void) const;
   //--- draw the line
   bool              DrawLine(bool IsFirst=true) const;
  };

Fondamentalmente, questa classe di nodi descrive un frame (qui interpretato come un numero di candele confinate all'interno di due linee verticali). I limiti del frame sono rappresentati da una coppia di strutture di proprietà della linea verticale, durata e flag di formazione.

Oltre al costruttore e al distruttore standard, la classe ha diversi metodi set e get, nonché il metodo per disegnare la linea nel grafico.

Vi ricordo che il nodo delle linee verticali (cornice) nel mio esempio si può considerare formato quando c'è una prima linea verticale che indica l'inizio del movimento discendente e la seconda linea verticale che indica l'inizio del movimento ascendente.

Utilizzando lo script Stat_collector.mq5, ho visualizzato tutti i frame nel grafico e ho contato quanti nodi (frame) corrispondono a un certo limite di durata nelle ultime 2mila barre.

A titolo illustrativo, ho creato 4 elenchi che potrebbero contenere qualsiasi frame. Il primo elenco conteneva frame con il numero di candele fino a 5, il secondo - fino a 10, il terzo - fino a 15 e il quarto - numero illimitato. 

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

Di conseguenza, ho ottenuto il seguente grafico (Fig. 19). Per comodità, la seconda linea di cornice verticale viene visualizzata in blu.


Fig. 19 Visualizzazione delle cornici (frames)

Curiosamente, l'ultimo frame si è formato durante l'ultima ora di venerdì 13 dicembre 2013. Rientrava nella seconda lista poiché era della durata di 6 ore.


4.2 Gestire il trading virtuale

Immagina di dover creare un Expert Advisor che implementi diverse strategie indipendenti rispetto a uno strumento in un flusso di tick. È chiaro che in realtà può essere attuata una sola strategia alla volta rispetto ad uno strumento. Tutte le altre strategie saranno di natura virtuale. Quindi, può essere implementato esclusivamente allo scopo di testare e ottimizzare un'idea di trading.

Qui, devo fare riferimento ad un articolo fondamentale che fornisce una descrizione dettagliata dei concetti base relativi al trading in generale e, in particolare, al terminale MetaTrader 5: Ordini, posizioni e deals(affari) in MetaTrader 5.

Quindi, se nel risolvere questo problema usiamo il concetto di trading, il sistema di gestione degli oggetti di trading e la metodologia di memorizzazione delle informazioni sugli oggetti di trading che sono consueti nell'ambiente MetaTrader 5, dovremmo probabilmente pensare di creare un database virtuale.

Ti ricordo che uno sviluppatore classifica tutti gli oggetti di trading in ordini, posizioni, accordi e ordini storici. Un occhio critico potrebbe notare che il termine 'oggetto di scambio' è stato qui utilizzato dall'autore stesso. Questo è vero...

Propongo di utilizzare un approccio simile nel trading virtuale e di ottenere i seguenti oggetti di trading virtuale: ordini virtuali, posizioni virtuali, offerte virtuali e ordini storici virtuali.

Questo argomento merita una trattazione approfondita e più dettagliata. Nel frattempo, tornando all'argomento dell'articolo, vorrei dire che i tipi di dati del contenitore, incluse le liste, possono semplificare la vita del programmatore durante l'implementazione di strategie virtuali.

Pensa a una nuova posizione virtuale che, naturalmente, non può essere dal lato del server di trading. Ciò significa che le informazioni a riguardo dovrebbero essere salvate sul lato terminale. Qui un database può essere rappresentato da una lista che a sua volta è composta da più liste, una delle quali conterrà i nodi della posizione virtuale.

Utilizzando l'approccio dello sviluppatore, ci saranno le seguenti classi di trading virtuale:

Classe/Gruppo

Descrizione

COrdine Virtuale

Classe per lavorare con ordini in sospeso virtuali

CVirtualHistoryOrder

Classe per lavorare con ordini di "storia" virtuale

CVirtualPosition

Classe per lavorare con posizioni aperte virtuali

CVirtualDeal

Classe per lavorare con le offerte di "storia" virtuale

CVirtualTrade

Classe per eseguire operazioni di trading virtuale

Tabella 1. Classi di trading virtuale


Non discuterò la composizione delle classi di trading virtuali. Ma probabilmente conterrà tutti o quasi tutti i metodi di una classe di trading standard. Voglio solo far notare che ciò che utilizza lo sviluppatore non è una classe di un determinato oggetto di trading, ma una classe delle sue proprietà.

Per utilizzare le liste nei tuoi algoritmi, avrai bisogno anche di nodi. Pertanto, abbiamo bisogno di concludere la classe di un oggetto di trading virtuale in un nodo.

Si supponga che il nodo di una posizione aperta virtuale sia di tipo CVirtualPositionNode. La definizione di questo tipo potrebbe inizialmente essere la seguente:

//+------------------------------------------------------------------+
//|                Class CVirtualPositionNode                        |
//+------------------------------------------------------------------+
class CVirtualPositionNode : public CObject
  {
protected:
   CVirtualPositionNode *m_virt_position;         // pointer to the virtual function

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

Ora, quando la posizione virtuale si apre, può essere aggiunta all'elenco delle posizioni virtuali.

Vorrei anche far notare che questo approccio al lavoro con oggetti di trading virtuale non richiede l'uso della memoria cache perché il database è archiviato nella memoria ad accesso casuale. Puoi, ovviamente, fare in modo che venga archiviato su altri supporti di memorizzazione.


Conclusione

In questo articolo ho cercato di dimostrare i vantaggi di un tipo di dati contenitore, le liste. Ma non potevo fare a meno di menzionare i suoi svantaggi. Ad ogni modo, spero che queste informazioni siano utili per chi studia l'OOP in generale, e in particolare, uno dei suoi principi fondamentali, il polimorfismo.


Posizione dei file:

A mio parere, sarebbe meglio creare e archiviare file nella cartella del progetto. Ad esempio, in questo modo: %MQL5\Projects\UserLists. Qui è dove ho salvato tutti i file del codice sorgente. Se si utilizzano directory predefinite, sarà necessario modificare il metodo di designazione dei file di inclusione (sostituire le virgolette con le parentesi angolari) nel codice di alcuni file.

#FileLa tua posizioneDescrizione
1 CiSingleNode.mqh  %MQL5\Projects\UserLists  Classe di un nodo di lista concatenato singolarmente
2 CDoubleNode.mqh  %MQL5\Projects\UserLists  Classe di un nodo di lista doppiamente concatenato
3 CiUnrollDoubleNode.mqh  %MQL5\Projects\UserLists  Classe di un nodo di lista doppiamente concatenato srotolato
4 test_nodes.mq5  %MQL5\Projects\UserLists  Script con esempi di lavoro con i nodi
5 CiSingleList.mqh  %MQL5\Projects\UserLists  Classe di una lista concatenata singolarmente
6 CDoubleList.mqh  %MQL5\Projects\UserLists  Classe di una lista doppiamente collegata
7 CiUnrollDoubleList.mqh  %MQL5\Projects\UserLists  Classe di una lista doppiamente collegata srotolata
 8 CiCircleDoublList.mqh %MQL5\Projects\UserLists Classe di una lista circolare doppiamente concatenata
 9 test_sList.mq5 %MQL5\Projects\UserLists Script con esempi di lavoro con un elenco collegato singolarmente
 10 test_dList.mq5 %MQL5\Projects\UserLists Script con esempi di lavoro con una lista doppiamente collegata
 11 test_UdList.mq5 %MQL5\Projects\UserLists Script con esempi di lavoro con un elenco srotolato a doppio collegamento
 12 test_CdList.mq5 %MQL5\Projects\UserLists Script con esempi di lavoro con una lista circolare doppiamente collegata
 13 test_MQL5_List.mq5 %MQL5\Projects\UserLists Script con esempi di lavoro con la classe CList
 14 CNodeInt.mqh %MQL5\Projects\UserLists Classe del nodo di tipo intero
 15 CIntList.mqh %MQL5\Projects\UserLists Elenca la classe per i nodi CNodeInt
 16 CRandom.mqh %MQL5\Projects\UserLists Classe di un generatore di numeri casuali
 17 CVertLineNode.mqh %MQL5\Projects\UserLists Classe nodo per la gestione del frame di linee verticali
 18 Stat_collector.mq5 %MQL5\Projects\UserLists Script con un esempio di raccolta di statistiche


Riferimenti:

  1. A. Friedman, L. Klander, M. Michaelis, H. Schildt. Archivi con annotazioni C/C++. McGraw-Hill Osborne Media, 1999. 1008 pagine.
  2. V.D. Daleka, A.S. Derevyanko, O.G. Kravets, L.E. Timanovskaya. Modelli e strutture di dati. Guida allo studio. Kharkov, KhGPU, 2000. 241 pagine (in russo).


Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/709

File allegati |
files.zip (22.75 KB)
Manuale MQL5: Sviluppo di un indicatore multi-simbolo per l’analisi della divergenza dei prezzi Manuale MQL5: Sviluppo di un indicatore multi-simbolo per l’analisi della divergenza dei prezzi
In questo articolo, considereremo lo sviluppo di un indicatore multi-simbolo per analizzare la divergenza dei prezzi in un determinato periodo di tempo. Gli argomenti principali sono già stati discussi nel precedente articolo sulla programmazione degli indicatori multi-valuta "MQL5 Cookbook: Sviluppo di un indicatore di volatilità multi-simbolo in MQL5". Quindi questa volta ci soffermeremo solo su quelle nuove caratteristiche e funzioni che sono state cambiate radicalmente. Se sei un neofita della programmazione di indicatori multi-valuta, ti consiglio di leggere prima l'articolo precedente.
Econometrics Previsioni EURUSD One-Step-Ahead Econometrics Previsioni EURUSD One-Step-Ahead
L'articolo si concentra sulla previsione anticipata per EURUSD utilizzando il software EViews e un'ulteriore valutazione dei risultati delle previsioni utilizzando i programmi in EViews. La previsione prevede modelli di regressione e viene valutata tramite un Expert Advisor sviluppato per MetaTrader 4.
MQL5 Cookbook - Consulente esperto multi-valuta e il lavoro con ordini in sospeso in MQL5 MQL5 Cookbook - Consulente esperto multi-valuta e il lavoro con ordini in sospeso in MQL5
Questa volta creeremo un Expert Advisor multi-valuta con un algoritmo di trading basato sul lavoro con gli ordini in sospeso Buy Stop e Sell Stop. Questo articolo considera le seguenti questioni: fare trading in un intervallo di tempo specificato, inserire/modificare/eliminare ordini in sospeso, verificare se l'ultima posizione è stata chiusa a Take Profit o Stop Loss e controllo della cronologia delle operazioni per ciascun simbolo.
Indicatore per Kagi Charting Indicatore per Kagi Charting
L'articolo presenta l'indicatore grafico Kagi con varie opzioni per la creazione di grafici e funzioni aggiuntive. Inoltre, vengono considerati il principio del grafico dell'indicatore e le sue funzionalità di implementazione MQL5. Vengono visualizzati i casi più popolari della sua implementazione nel trading: strategia di scambio Yin / Yang, allontanandosi dalla linea di tendenza e aumentando costantemente le "spalle" / diminuendo la "vita".