Biblioteca de clases genéricas - errores, descripción, preguntas, características de uso y sugerencias - página 19

 
Vasiliy Sokolov:
después de todo, puedes escribir tu propia función de especialización para la clase.
 
Combinador:
Después de todo, puedes escribir tu propia especialización de una función para una clase.

No se puede hacer sin una interfaz.

 
Vasiliy Sokolov:

No se puede hacer sin una interfaz.

¿Qué es lo que no puedes hacer sin una interfaz? )
 
Combinador:
¿Qué no puede hacer sin una interfaz? )

Bueno, piénsalo. En fin, no desordenen el hilo, por favor.

 
Vasiliy Sokolov:

Problema

Obviamente, GetHashCode no puede ser implementado correctamente en el entorno de usuario MQL5. Esto se debe a que no se puede acceder a todos los objetos. Y si la implementación funciona bien para miembros primitivos como double int, etc., para objetos complejos (clases, estructuras e incluso enumeraciones) el hash se calcula por nombre. Si tenemos miles de CObjects o incluso ENUM_something_that, entonces CHashMap y CHashSet degenerarán en LinkedList:

No podemos evitarlo porque lo único que tenemos a nivel de usuario es el nombre del objeto:

Por lo tanto, los hashes de todos los objetos de tipos complejos son iguales entre sí y este código de búsqueda implica aquí una enumeración completa del array:

De hecho, esto es tan crítico que anula todo el punto de usar Generic en la raíz. La cuestión es que en la mayoría de los casos es necesario asociar o almacenar objetos complejos: enumeraciones, estructuras o clases. Si necesita operar con tipos simples, puede hacerlo con algo más sencillo.

Para que las colecciones genéricas funcionen correctamente con los objetos de las clases, éstas deben implementar la interfaz IEqualityComparable donde se definen los métodos Equals y HashCode. Es decir, el usuario debe establecer los métodos de cálculo de los códigos hash por sí mismo, y esta es la única opción hasta ahora, porque es imposible implementar estos métodos de forma automática, como se hace en .Net, por ejemplo, mediante MQL5.

 
Roman Konopelko:

Para que las colecciones genéricas funcionen correctamente con objetos de clase, estas clases deben implementar la interfaz IEqualityComparable, en la que se definen los métodos Equals y HashCode. Es decir, el usuario tiene que establecer los métodos de cálculo de los códigos hash y esta es la única opción hasta ahora, ya que es imposible implementar estos métodos de forma automática, como se hacía en .Net, por ejemplo, mediante MQL5.

Roman, te olvidaste de mencionar que en MQL5 no tiene interfaces. Este artículo introducirá el concepto de interfaces en MQL5.

p.d. Pero incluso si las interfaces aparecieran en MQL5, el problema con las estructuras y las enumeraciones seguiría sin resolverse.

 
Vasiliy Sokolov:

Roman, te olvidaste de mencionar que en MQL5 no tiene interfaces. Cualquier discusión sobre las interfaces en el MQL5 actual es una insinuación maliciosa y una demagogia.

p.d. Pero aunque hubieran aparecido las interfaces en MQL5, el tema de las estructuras y las enumeraciones habría quedado sin resolver.

En MQL5 por el momento no es posible escribir métodos de plantilla que funcionen para clases, estructuras y enumeraciones al mismo tiempo, debido a las peculiaridades de la transferencia de datos.
 
Roman Konopelko:
Por ahora en MQL5 no se puede en principio escribir métodos de plantilla que funcionen para clases, estructuras y enumeraciones al mismo tiempo, debido a las peculiaridades de la transferencia de datos.

A eso me refiero. ¡Pero el entorno MQL5 lo sabe todo sobre sus objetos! Tiene punteros a objetos y conoce todos los identificadores de enums (EnumToString). Por eso necesitamos que GetHashCode sea una función omnívora y de sistema.

Además, finalmente permite la herencia de múltiples interfaces. Reescribe Generic para interfaces normales y tendrás un punto dulce.

 

La situación es obvia: los desarrolladores de MQ se quemaron tantas veces con la herencia múltiple en C++ que ahora temen cualquier manifestación de la misma. En consecuencia, se propone evitar una porquería (la herencia múltiple) utilizando otra porquería: las ridículas cadenas de herencia.

Hay que entender que las interfaces no tienen nada que ver con la herencia. Una interfaz es una declaración que una clase se compromete a proporcionar una funcionalidad determinada. Si dos clases implementan la misma funcionalidad, no deben heredar la una de la otra. Herencia = maldad.

 

Foro sobre trading, sistemas de trading automatizados y pruebas de estrategias de trading

Biblioteca de clases genéricas - errores, descripción, preguntas, peculiaridades de uso y sugerencias

Roman Konopelko, 2017.12.18 16:29

1) El factor de crecimiento del volumen (capacidad) no es igual a 1,2, se utiliza el método CPrimeGenerator::ExpandPrime para calcular el nuevo volumen en CHashMap:

int CPrimeGenerator::ExpandPrime(const int old_size)
  {
   int new_size=2*old_size;
   if((uint)new_size>INT_MAX && INT_MAX>old_size)
      return INT_MAX;
   else
      return GetPrime(new_size);
  }

En este método, el tamaño de la colección antigua se multiplica por dos, luego para el valor resultante encontramos el más cercano del número primo superior y lo devolvemos como el nuevo volumen.

Sobre el valor inicial de la capacidad - estoy de acuerdo, es muy pequeño.

Pero por otro lado siempre hay constructores, donde se puede especificar explícitamente la capacidad inicial:

class CHashMap: public IMap<TKey,TValue>
  {
public:
                     CHashMap(const int capacity);
                     CHashMap(const int capacity,IEqualityComparer<TKey>*comparer);
  }

Así que no veo mucho sentido a cambiar algo aquí.


Sí, me equivoqué, me arrepiento.
Realmente, el factor de incremento de volumen (capacidad) para CHashMap es superior a 2.
Gracias por señalar el error y pedir disculpas por perder el tiempo.



Por otro lado he conseguido sacar tiempo para estudiar la implementación de CPrimeGenerator.

//+------------------------------------------------------------------+
//| Fast generator of parime value.                                  |
//+------------------------------------------------------------------+
int CPrimeGenerator::GetPrime(const int min)
  {
//--- a typical resize algorithm would pick the smallest prime number in this array
//--- that is larger than twice the previous capacity. 
//--- get next prime value from table
   for(int i=0; i<ArraySize(s_primes); i++)
     {
      int prime=s_primes[i];
      if(prime>=min)
         return(prime);
     }
//--- outside of our predefined table
   for(int i=(min|1); i<INT_MAX;i+=2)
     {
      if(IsPrime(i) && ((i-1)%s_hash_prime!=0))
         return(i);
     }
   return(min);
  }


Y hay algunas sugerencias, sobre todo para mejorar el rendimiento.


1. Eliminar los comportamientos ambiguos:
Si pasamos "INT_MAX - 10" como parámetro a CPrimeGenerator::ExpandPrime, se devolverá el resultado "INT_MAX".
Si pasamos "INT_MAX - 10" como parámetro a CPrimeGenerator::GetPrime, se devolverá el mismo resultado: "INT_MAX - 10".

Además, en ambos casos, el valor devuelto no es un número primo, lo que induce a error al usuario.



2. Cuando se llama aGetPrime para números mayores de7199369, el ahorro de memoria se convierte en una prioridad, pero esto no justifica el relativo bajo rendimiento y los cálculos inútiles.

Sugerencia:
- añadir una comparación del número con el último valor del arrayCPrimeGenerator::s_primes[] y no realizar una enumeración innecesaria de los 72 elementos del array.
- Reemplaza la búsqueda dinámica de un número simple (recorre todos los números de una fila) por una matriz de valores predefinidos comoCPrimeGenerator::s_primes[], pero con un incremento lineal, no cuadrático.
El incremento de valores será de aproximadamente 1 millón (una cifra similar al incremento de s_primes en los últimos elementos del array).
El número de elementos es hasta 3000, los valores van desde 8M hasta INT_MAX.
El array se buscará mediante una búsqueda binaria de límite superior, el número de iteraciones necesarias es de 12.


3. Cuando se llama aGetPrime para números menores de7199369, el peor caso es una búsqueda lineal de los 72 valores del arrayCPrimeGenerator::s_primes[].

La sugerencia es:
- reducir el número de elementos de la matriz a 70. (borrando los dos primeros, o el primero y el último):

const static int  CPrimeGenerator::s_primes[]=
  {
   3,7,11,17,23,29,37,47,59,71,89,107,131,163,197,239,293,353,431,521,631,761,919,
   1103,1327,1597,1931,2333,2801,3371,4049,4861,5839,7013,8419,10103,12143,14591,
   17519,21023,25229,30293,36353,43627,52361,62851,75431,90523,108631,130363,156437,
   187751,225307,270371,324449,389357,467237,560689,672827,807403,968897,1162687,1395263,
   1674319,2009191,2411033,2893249,3471899,4166287,4999559,5999471,7199369
  };

- si el valor de entrada es menor o igual que el 6º valor de la nueva matrizCPrimeGenerator::s_primes- entonces itera a través de los números linealmente (hasta 6 comparaciones).
- En caso contrario, utilice la búsqueda binaria de límite superior entre el 7º y el 70º valor del array (unas 6 iteraciones).

La idea es utilizar la búsqueda lineal sólo mientras no haya pérdida de rendimiento en comparación con la búsqueda binaria.
El número de elementos sugerido - 6 se utiliza como ejemplo, en realidad todo depende de la implementación concreta de la búsqueda binaria de límite superior.
La ganancia de rendimiento global debida a la baja intensidad de llamadas de una función concreta puede no ser tan beneficiosa como para hacer cualquier trabajo de mejora de esta funcionalidad.

Razón de la queja: