Discusión sobre el artículo "Recetas de MQL5 - implementamos el array asociativo o el diccionario para el acceso rápido a los datos"

 

Artículo publicado Recetas de MQL5 - implementamos el array asociativo o el diccionario para el acceso rápido a los datos:

En este artículo se describe un algoritmo especial que permite acceder de manera eficaz a los elementos usando su clave única. Como clave se puede utilizar cualquier tipo básico de datos, por ejemplo, las cadenas o variables de números enteros. Este contenedor de datos suelen llamarlo el diccionario o array asociativo. La solución de muchas tareas con su ayuda resulta más simple y eficaz.

Este artículo describe una clase cómoda para almacenar la información: array asociativo o diccionario. Gracias a esta clase se puede acceder a la información por su clave.

El array asociativo recuerda un array común, pero en vez del índice utiliza una clave única, por ejemplo, la enumeración ENUM_TIMEFRAMES o cualquier texto. No importa qué clave es, lo importante es que esta clave sea única. Este algoritmo de almacenamiento de datos simplifica considerablemente muchos aspectos de programación.

Por ejemplo, la función para recibir el código del error y mostrar el equivalente textual de este error podría tener el siguiente aspecto:

//+---------------------------------------------------------------------+
//| Muestra en el terminal la descripción del error recibido.           |
//| Si el identificador del error se desconoce, muestra "Unknown error" |
//+---------------------------------------------------------------------+
void PrintError(int error)
 {
   Dictionary dict;
   CStringNode* node = dict.GetObjectByKey(error);
   if(node != NULL)
      printf(node.Value());
   else
      printf("Unknown error");
 }

Más tarde analizaremos la específica de este código.

Antes de pasar directamente a la descripción de la lógica interna del array asociativo, vamos a considerar con detalles dos modos principales de almacenamiento de datos: se trata de los arrays y de las listas. Nuestro diccionario va a basarse precisamente en estos dos tipos de datos, por eso tenemos que comprender muy bien las características específicas de su funcionamiento. El capítulo 1 está destinado a la descripción de los tipos de datos. El capítulo 2 describe el array asociativo y el modo de trabajo con él.

Autor: Vasiliy Sokolov

 

Buen trabajo, ¡felicitaciones al autor! Esto es algo que MQ debería haber incluido en \MQL5\IncludeArrays hace mucho tiempo, espero que se incluya en la librería en las próximas versiones. Por cierto, todo funciona bien en MQL4 también, aquí están las mediciones de la primera prueba. ¿Entiendo que es imposible incluir tipos de datos simples en lugar de *CObject debido a la falta de punteros completos? ¿O hay alguna forma de evitarlo?

2015.03.23 13:25:54.617 TestDict EURUSD,M1: 1000000 elements. Add: 1373; Get: 218
2015.03.23 13:25:52.644 TestDict EURUSD,M1: 950000 elements. Add: 1216; Get: 219
2015.03.23 13:25:50.833 TestDict EURUSD,M1: 900000 elements. Add: 1217; Get: 218
2015.03.23 13:25:49.069 TestDict EURUSD,M1: 850000 elements. Add: 1154; Get: 187
2015.03.23 13:25:47.424 TestDict EURUSD,M1: 800000 elements. Add: 1092; Get: 187
2015.03.23 13:25:45.844 TestDict EURUSD,M1: 750000 elements. Add: 1061; Get: 171
2015.03.23 13:25:44.320 TestDict EURUSD,M1: 700000 elements. Add: 1107; Get: 156
2015.03.23 13:25:42.761 TestDict EURUSD,M1: 650000 elements. Add: 1045; Get: 140
2015.03.23 13:25:41.304 TestDict EURUSD,M1: 600000 elements. Add: 1014; Get: 156
2015.03.23 13:25:39.915 TestDict EURUSD,M1: 550000 elements. Add: 920; Get: 125
2015.03.23 13:25:38.665 TestDict EURUSD,M1: 500000 elements. Add: 702; Get: 109
2015.03.23 13:25:37.693 TestDict EURUSD,M1: 450000 elements. Add: 593; Get: 93
2015.03.23 13:25:36.836 TestDict EURUSD,M1: 400000 elements. Add: 577; Get: 78
2015.03.23 13:25:36.025 TestDict EURUSD,M1: 350000 elements. Add: 561; Get: 78
2015.03.23 13:25:35.247 TestDict EURUSD,M1: 300000 elements. Add: 515; Get: 78
2015.03.23 13:25:34.557 TestDict EURUSD,M1: 250000 elements. Add: 343; Get: 63
2015.03.23 13:25:34.063 TestDict EURUSD,M1: 200000 elements. Add: 312; Get: 47
2015.03.23 13:25:33.632 TestDict EURUSD,M1: 150000 elements. Add: 281; Get: 31
2015.03.23 13:25:33.264 TestDict EURUSD,M1: 100000 elements. Add: 171; Get: 16
2015.03.23 13:25:33.038 TestDict EURUSD,M1: 50000 elements. Add: 47; Get: 16
 
VDev:

Buen trabajo, ¡felicitaciones al autor! Esto es algo que MQ debería haber incluido en MQL5/Include/ Arrays hace mucho tiempo, espero que se incluya en la librería en las próximas versiones. Por cierto, todo funciona bien en MQL4 también, aquí están las medidas de la primera prueba. ¿Entiendo que es imposible incluir tipos de datos simples en lugar de *CObject debido a la falta de punteros completos? ¿O hay alguna forma de hacerlo funcionar?

Funcionará. Con la ayuda del mecanismo boxing/unboxing y las plantillas. La idea es que cada tipo base se empaquete en un contenedor KeyValuePairBase. El desempaquetado y la devolución del tipo correspondiente se realiza mediante funciones internas del tipo GetObjectByKey:

template<typename Type, typename T>
Type GetObjectByKey(T key);

Es importante destacar que trabajar con tipos base no proporcionará ninguna ventaja de rendimiento, pero será mucho más cómodo.

 

Ahora intenté crear un CDictionaryBase almacenando uno de los tipos básicos de MQL en lugar de CObject en base a plantillas. Desafortunadamente, no funcionó, las funciones no permiten devolver un tipo de plantilla. Es una lástima:

//+------------------------------------------------------------------+
//|TestDictBase.mq5
//|Derechos de autor 2015, Vasiliy Sokolov. |
//|http://www.mql5.com
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, Vasiliy Sokolov."
#property link      "http://www.mql5.com"
#property version   "1.00"
#include <Dictionary.mqh>
#include <DictionaryBase.mqh>
//+------------------------------------------------------------------+
//| Función de inicio del programa de script|
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   CDictionaryBase base;
   base.AddValue("Pi", 3.14159);
   double pi = (double)base.GetValueByKey("Pi");
   printf(DoubleToString(pi, 5));
   //base.AddObject(
  }
//+------------------------------------------------------------------+
could not deduce template argument #1    TestDictBase.mq5        19      29
could not deduce template argument #0    DictionaryBase.mqh      404     25
possible loss of data due to type conversion    DictionaryBase.mqh      133     10
possible loss of data due to type conversion    DictionaryBase.mqh      135     10
possible loss of data due to type conversion    DictionaryBase.mqh      137     10
...

//+------------------------------------------------------------------+
//| Devuelve el objeto por clave.|
//+------------------------------------------------------------------+
template<typename T, typename C>
C CDictionaryBase::GetValueByKey(T key)
  {
   if(!ContainsKey(key))
      return NULL;
   return m_current_kvp.GetValue();
  }

Una pena.

Así que, para cada tipo base tendremos que crear un contenedor base, o simplemente crear contenedores de tipos base: CDouble, CLong, CInt, etc.

 
C-4:

Ahora intenté crear un CDictionaryBase almacenando uno de los tipos básicos de MQL en lugar de CObject en base a plantillas. Desafortunadamente no pude hacerlo, las funciones no permiten devolver un tipo de plantilla.

Lo hacen. Pero el tipo del valor devuelto no se puede deducir automáticamente, que en realidad está escrito por el compilador.

Usted puede utilizar una pequeña muleta en forma de un pseudo-parámetro.

template<typename T, typename C>
C CDictionaryBase::GetValueByKey(T key, C)
{
   if(!ContainsKey(key))
      return NULL;
   return m_current_kvp.GetValue();
}
 
TheXpert:

Lo hacen. Pero no se puede deducir automáticamente el tipo del valor devuelto, que es lo que realmente escribe el compilador.

Puedes utilizar una pequeña muleta en forma de pseudoparámetro.

¿Dónde está la muletilla real?
 
C-4:
¿Dónde está la muleta real?
Se ha añadido un segundo parámetro
 
TheXpert:
Se ha añadido un segundo parámetro
Ahora lo veo. Lo comprobaré mañana.
 
¿Has probado a comparar el rendimiento? ¿A partir de qué tamaño de datos comienza la ventaja sobre la búsqueda binaria en una matriz de cadenas ordenada?
 
Integer:
¿Has probado a comparar el rendimiento? ¿A partir de qué tamaño de datos comienza la ventaja sobre la búsqueda binaria en una matriz de cadenas ordenada?

No he hecho pruebas exactas, pero según mis observaciones la ventaja de velocidad empieza a aparecer a partir de decenas de miles de elementos. Es decir, en tareas cotidianas con 100-10 000 elementos no se puede obtener una ganancia de rendimiento.

Otra cosa es importante aquí, a saber, la conveniencia de trabajar con el contenedor. No es necesario escribir métodos adicionales para buscar elementos. Muchas tareas cotidianas con diccionarios se vuelven mucho más fáciles de implementar. No necesitas crear un elemento para buscar un índice, luego recuperar el necesario por el índice correspondiente, etc., etc.

s.s. Aunque aquí pensé que el rendimiento debería medirse como el tiempo total para insertar elementos y buscarlos. Y si buscar elementos ordenados en CArrayObj es una operación bastante rápida, entonces la inserción es un verdadero problema. Dado que la búsqueda rápida requiere ordenación, no podemos deshacernos de posibles inserciones, y esto ralentizará significativamente el rendimiento.

 

Muy interesante y está claro que el diccionario es muy útil y fácil de usar organizador de datos.


Gracias por compartirlo.