Bibliothèque de classes génériques - bogues, description, questions, caractéristiques d'utilisation et suggestions - page 19

 
Vasiliy Sokolov:
vous pouvez écrire votre propre fonction de spécialisation pour la classe, après tout.
 
Combinateur:
Vous pouvez écrire votre propre spécialisation d'une fonction pour une classe, après tout.

Vous ne pouvez pas le faire sans interface.

 
Vasiliy Sokolov:

Vous ne pouvez pas le faire sans interface.

Vous ne pouvez pas faire quoi sans interface ? )
 
Combinateur:
Vous ne pouvez pas le faire sans interface ? )

Eh bien, pensez-y. Bref, n'encombrez pas le fil, s'il vous plaît.

 
Vasiliy Sokolov:

Problème

De toute évidence, GetHashCode ne peut pas être mis en œuvre correctement dans l'environnement utilisateur MQL5. En effet, tous les objets ne sont pas accessibles. Et si l'implémentation fonctionne bien pour les membres primitifs comme double int, etc., pour les objets complexes (classes, structures et même énumérations) le hash est calculé par nom. Si nous avons des milliers de CObjects ou même ENUM_something_that, alors CHashMap et CHashSet dégénéreront en LinkedList :

Nous ne pouvons pas éviter cela car tout ce que nous avons au niveau de l'utilisateur est le nom de l'objet :

Par conséquent, les hachages de tous les objets de types complexes sont égaux les uns aux autres et le code de recherche implique ici une énumération complète des tableaux :

En fait, c'est tellement important que cela annule tout l'intérêt d'utiliser Generic à la racine. Le fait est que, dans la plupart des cas, vous devez associer ou stocker des objets complexes : énumérations, structures ou classes. Si vous aviez besoin d'opérer avec des types simples, vous pouviez vous contenter de quelque chose de plus simple.

Pour que les collections Generic fonctionnent correctement avec les objets de classe, ces classes doivent implémenter l'interface IEqualityComparable où les méthodes Equals et HashCode sont définies. Cela signifie que l'utilisateur doit définir lui-même les méthodes de calcul des codes de hachage, et c'est la seule option possible jusqu'à présent, car il est impossible de mettre en œuvre ces méthodes automatiquement, comme cela se fait en .Net, par exemple, au moyen de MQL5.

 
Roman Konopelko:

Pour que les collections Generic fonctionnent correctement avec les objets de classe, ces classes doivent implémenter l'interface IEqualityComparable, dans laquelle les méthodes Equals et HashCode sont définies. Cela signifie que l'utilisateur doit définir les méthodes de calcul des codes de hachage et c'est la seule option à ce jour, car il est impossible de mettre en œuvre ces méthodes automatiquement, comme cela a été fait dans .Net, par exemple, au moyen de MQL5.

Roman, tu as oublié de mentionner que dans MQL5 n'a pas d'interfaces. Cet article présente le concept d'interfaces dans MQL5.

p.s. Mais même si les interfaces apparaissaient dans MQL5, le problème des structures et des énumérations resterait non résolu.

 
Vasiliy Sokolov:

Roman, tu as oublié de mentionner que dans MQL5 n'a pas d'interfaces. Toute discussion sur les interfaces dans le MQL5 d'aujourd'hui est une insinuation malveillante et une démagogie.

p.s. Mais même si les interfaces étaient apparues dans MQL5, le problème des structures et des énumérations serait resté non résolu.

Dans MQL5, il n'est pas possible pour le moment d'écrire des méthodes modèles qui fonctionnent à la fois pour les classes, les structures et les énumérations, en raison des particularités du transfert de données.
 
Roman Konopelko:
Pour l'instant, dans MQL5, vous ne pouvez pas en principe écrire des méthodes modèles qui fonctionneraient pour les classes, les structures et les énumérations en même temps, en raison des particularités du transfert de données.

C'est de ça que je parle. Mais l'environnement MQL5 sait tout sur ses objets ! Il possède des pointeurs vers des objets et connaît tous les identifiants des enums (EnumToString). C'est pourquoi nous avons besoin de GetHashCode comme système et fonction omnivore.

Aussi, autorisez enfin l'héritage d'interfaces multiples. Réécrivez le générique pour les interfaces normales et vous aurez un point sensible.

 

La situation est évidente : les développeurs MQ ont été si souvent échaudés par l'héritage multiple en C++ qu'ils craignent désormais toute manifestation de celui-ci. Par conséquent, il est proposé d'éviter une connerie (l'héritage multiple) en utilisant une autre connerie : des chaînes d'héritage ridicules.

Vous devez comprendre que les interfaces n'ont rien à voir avec l'héritage. Une interface est une déclaration par laquelle une classe s'engage à fournir une fonctionnalité donnée. Si deux classes implémentent la même fonctionnalité, elles ne doivent pas hériter l'une de l'autre. Héritage = mal.

 

Forum sur le trading, les systèmes de trading automatisé et les tests de stratégies de trading

Bibliothèque de classes génériques - bugs, description, questions, particularités d'utilisation et suggestions

Roman Konopelko, 2017.12.18 16:29

1) Le facteur de croissance du volume (capacité) n'est pas égal à 1,2, la méthode CPrimeGenerator::ExpandPrime est utilisée pour calculer le nouveau volume dans 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);
  }

Dans cette méthode, l'ancienne taille de la collection est multipliée par deux, puis pour la valeur résultante, nous trouvons le nombre premier le plus proche du sommet et le renvoyons comme nouveau volume.

Concernant la valeur initiale de la capacité - je suis d'accord, elle est très faible.

Mais d'un autre côté, il y a toujours des constructeurs, où vous pouvez spécifier explicitement la capacité initiale :

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

Je ne vois donc pas l'intérêt de changer quelque chose ici.


Oui, j'ai eu tort, je me repens.
En réalité, le facteur d'augmentation du volume (capacité) pour CHashMap est supérieur à 2.
Merci d'avoir signalé l'erreur et je m'excuse d'avoir perdu du temps.



D'autre part, j'ai réussi à prendre le temps d'étudier l'implémentation 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);
  }


Et il y a quelques suggestions, principalement pour améliorer les performances.


1. Éliminez les comportements ambigus :
Si nous passons "INT_MAX - 10" comme paramètre à CPrimeGenerator::ExpandPrime, le résultat "INT_MAX" sera renvoyé.
Si nous passons "INT_MAX - 10" comme paramètre à CPrimeGenerator::GetPrime, le même résultat sera renvoyé : "INT_MAX - 10".

De plus, dans les deux cas, la valeur renvoyée n'est pas un nombre premier, ce qui induit l'utilisateur en erreur.



2. Lorsque l'on appelleGetPrime pour des nombres supérieurs à7199369, l'économie de mémoire devient une priorité, mais cela ne justifie pas les performances relativement faibles et les calculs inutiles.

Suggestion :
- ajouter une comparaison du nombre avec la dernière valeur du tableauCPrimeGenerator::s_primes[] et ne pas effectuer une énumération inutile de tous les 72 éléments du tableau.
- Remplace la recherche dynamique pour un nombre simple (passe par tous les nombres dans une rangée) par un tableau de valeurs prédéfinies commeCPrimeGenerator::s_primes[], mais avec un incrément linéaire, et non quadratique.
L'incrément des valeurs sera d'environ 1 million (un chiffre similaire à l'incrément des s_primes sur les derniers éléments du tableau).
Le nombre d'éléments va jusqu'à 3000, les valeurs vont de 8M à INT_MAX.
Le tableau sera recherché par une recherche binaire de borne supérieure, le nombre d'itérations requises est de 12.


3. Lorsque vous appelezGetPrime pour des nombres inférieurs à7199369, le pire cas est une recherche linéaire de toutes les 72 valeurs du tableauCPrimeGenerator::s_primes[].

La suggestion est :
- réduire le nombre d'éléments du tableau à 70. (en supprimant les deux premiers, ou le premier et le dernier) :

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 la valeur d'entrée est inférieure ou égale à la 6ème valeur dans le nouveau tableauCPrimeGenerator::s_primes- alors itérer à travers les nombres linéairement (jusqu'à 6 comparaisons).
- Sinon, utiliser la recherche binaire de la borne supérieure entre la 7ème et la 70ème valeur du tableau (environ 6 itérations).

L'idée est de n'utiliser la recherche linéaire que dans la mesure où il n'y a pas de perte de performance par rapport à la recherche binaire.
Le nombre d'éléments suggéré - 6 - est utilisé à titre d'exemple. En réalité, tout dépend de la mise en œuvre concrète de la recherche binaire à limite supérieure.
Le gain de performance global dû à la faible intensité d'appel d'une fonction particulière peut ne pas être suffisamment bénéfique pour justifier tout travail d'amélioration de cette fonctionnalité.

Raison: