Generische Klassenbibliothek - Bugs, Beschreibung, Fragen, Nutzungsmöglichkeiten und Vorschläge - Seite 19

 
Vasiliy Sokolov:
Sie können ja Ihre eigene Spezialisierungsfunktion für die Klasse schreiben.
 
Kombinator:
Sie können ja Ihre eigene Spezialisierung einer Funktion für eine Klasse schreiben.

Ohne eine Schnittstelle geht es nicht.

 
Vasiliy Sokolov:

Ohne eine Schnittstelle geht es nicht.

Was können Sie ohne eine Schnittstelle nicht tun? )
 
Kombinator:
Sie können es nicht ohne eine Schnittstelle tun? )

Nun, denken Sie darüber nach. Wie dem auch sei, bringen Sie den Thread nicht durcheinander, bitte.

 
Vasiliy Sokolov:

Problem

Offensichtlich kann GetHashCode in der MQL5-Benutzerumgebung nicht richtig implementiert werden. Dies liegt daran, dass nicht auf alle Objekte zugegriffen werden kann. Und wenn die Implementierung für primitive Mitglieder wie double int usw. gut funktioniert, wird der Hash für komplexe Objekte (Klassen, Strukturen und sogar Aufzählungen) nach Namen berechnet. Wenn wir Tausende von CObjects oder sogar ENUM_something_that haben, dann werden CHashMap und CHashSet zu LinkedList degenerieren:

Dies lässt sich nicht vermeiden, da wir auf der Benutzerebene nur den Namen des Objekts kennen:

Daher sind die Hashes aller Objekte komplexer Typen einander gleich, und dieser Suchcode hier beinhaltet eine vollständige Array-Aufzählung:

Dies ist sogar so wichtig, dass es den ganzen Sinn der Verwendung von Generic an der Wurzel aufhebt. Der Punkt ist, dass Sie in den meisten Fällen komplexe Objekte assoziieren oder speichern müssen: Aufzählungen, Strukturen oder Klassen. Wenn Sie mit einfachen Typen arbeiten müssen, können Sie auch mit etwas Einfacherem arbeiten.

Damit generische Sammlungen korrekt mit Klassenobjekten funktionieren, müssen diese Klassen die Schnittstelle IEqualityComparable implementieren, in der die Methoden Equals und HashCode definiert sind. Das bedeutet, dass der Benutzer die Methoden zur Berechnung der Hash-Codes selbst festlegen muss, und das ist bisher die einzige Möglichkeit, da es unmöglich ist, diese Methoden automatisch zu implementieren, wie es beispielsweise in .Net mit MQL5 geschieht.

 
Roman Konopelko:

Damit generische Sammlungen korrekt mit Klassenobjekten funktionieren, müssen diese Klassen die Schnittstelle IEqualityComparable implementieren, in der die Methoden Equals und HashCode definiert sind. Das bedeutet, dass der Benutzer Berechnungsmethoden für Hash-Codes festlegen muss, und das ist bisher die einzige Möglichkeit, da es unmöglich ist, diese Methoden automatisch zu implementieren, wie es beispielsweise in .Net mit Hilfe von MQL5 gemacht wurde.

Roman, du hast vergessen zu erwähnen, dass in MQL5 hat keine Schnittstellen. In diesem Artikel wird das Konzept der Schnittstellen in MQL5 vorgestellt.

p.s. Aber selbst wenn Schnittstellen in MQL5 erscheinen würden, würde das Problem mit Strukturen und Aufzählungen ungelöst bleiben.

 
Vasiliy Sokolov:

Roman, du hast vergessen zu erwähnen, dass in MQL5 hat keine Schnittstellen. Jede Diskussion über Schnittstellen im heutigen MQL5 ist eine böswillige Unterstellung und Demagogie.

p.s. Aber selbst wenn Schnittstellen in MQL5 erschienen wären, wäre das Problem mit Strukturen und Aufzählungen ungelöst geblieben.

In MQL5 ist es zur Zeit nicht möglich, Template-Methoden zu schreiben, die gleichzeitig für Klassen, Strukturen und Aufzählungen funktionieren, aufgrund von Besonderheiten der Datenübertragung.
 
Roman Konopelko:
Im Moment kann man in MQL5 im Prinzip keine Template-Methoden schreiben, die gleichzeitig für Klassen, Strukturen und Aufzählungen funktionieren würden, wegen der Besonderheiten der Datenübertragung.

Das ist es, wovon ich spreche. Aber die MQL5-Umgebung weiß alles über ihre Objekte! Es hat Zeiger auf Objekte und kennt alle Bezeichner von Enums (EnumToString). Deshalb brauchen wir GetHashCode als System- und Allesfresser-Funktion.

Erlauben Sie außerdem endlich die Vererbung mehrerer Schnittstellen. Schreiben Sie Generic für normale Schnittstellen um, und Sie haben einen Sweet Spot.

 

Die Situation ist offensichtlich: MQ-Entwickler wurden so oft durch Mehrfachvererbung in C++ verbrannt, dass sie nun jede Manifestation davon fürchten. Infolgedessen wird vorgeschlagen, einen Mist (Mehrfachvererbung) durch einen anderen Mist zu vermeiden: lächerliche Vererbungsketten.

Sie müssen verstehen, dass Schnittstellen nichts mit Vererbung zu tun haben. Eine Schnittstelle ist eine Erklärung, die eine Klasse verpflichtet, eine bestimmte Funktionalität bereitzustellen. Wenn zwei Klassen die gleiche Funktionalität implementieren, sollten sie nicht voneinander erben. Vererbung = böse.

 

Forum zum Thema Handel, automatisierte Handelssysteme und Testen von Handelsstrategien

Generische Klassenbibliothek - Bugs, Beschreibung, Fragen, Besonderheiten der Nutzung und Vorschläge

Roman Konopelko, 2017.12.18 16:29

1) Der Volumenwachstumsfaktor (Kapazität) ist nicht gleich 1,2, die Methode CPrimeGenerator::ExpandPrime wird zur Berechnung des neuen Volumens in CHashMap verwendet:

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);
  }

Bei dieser Methode wird die alte Sammlungsgröße mit zwei multipliziert, dann wird für den resultierenden Wert die nächstgelegene von der obersten Primzahl gefunden und als neues Volumen zurückgegeben.

Was den Anfangswert der Kapazität betrifft, so stimme ich zu, dass er sehr gering ist.

Andererseits gibt es immer Konstruktoren, bei denen man die Anfangskapazität explizit angeben kann:

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

Ich sehe also nicht viel Sinn darin, hier etwas zu ändern.


Ja, ich habe mich geirrt, ich bereue es.
Der Volumensteigerungsfaktor (Kapazität) für die CHashMap beträgt tatsächlich mehr als 2.
Ich danke für den Hinweis auf den Fehler und entschuldige mich für die Zeitverschwendung.



Andererseits habe ich es geschafft, mir Zeit zu nehmen, um die Implementierung von CPrimeGenerator zu studieren.

//+------------------------------------------------------------------+
//| 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);
  }


Und es gibt ein paar Vorschläge, vor allem zur Verbesserung der Leistung.


1. Beseitigen Sie zweideutiges Verhalten:
Wenn wir "INT_MAX - 10" als Parameter an CPrimeGenerator::ExpandPrime übergeben, wird das Ergebnis "INT_MAX" zurückgegeben.
Wenn wir "INT_MAX - 10" als Parameter an CPrimeGenerator::GetPrime übergeben, wird das gleiche Ergebnis zurückgegeben: "INT_MAX - 10".

Außerdem ist in beiden Fällen der zurückgegebene Wert keine Primzahl, was den Benutzer in die Irre führt.



2. Beim Aufruf vonGetPrime für Zahlen größer als7199369 wird die Speicherplatzersparnis zur Priorität, aber das rechtfertigt nicht die relativ schlechte Leistung und die unnützen Berechnungen.

Anregung:
- einen Vergleich der Zahl mit dem letzten Wert des ArraysCPrimeGenerator::s_primes[] hinzufügen und keine unnötige Aufzählung aller 72 Arrayelemente durchführen.
- Ersetzen der dynamischen Suche nach einfachen Zahlen (geht durch alle Zahlen in einer Reihe) durch ein Array von vordefinierten Werten wieCPrimeGenerator::s_primes[], aber mit linearer, nicht quadratischer, Steigerung.
Das Inkrement der Werte wird etwa 1 Million betragen (eine Zahl, die dem Inkrement von s_primes bei den letzten Elementen des Arrays entspricht).
Die Anzahl der Elemente kann bis zu 3000 betragen, die Werte reichen von 8M bis INT_MAX.
Das Array wird durch eine binäre Suche mit oberer Grenze durchsucht, die Anzahl der erforderlichen Iterationen beträgt 12.


3. WennGetPrime für Zahlen kleiner als7199369 aufgerufen wird, ist der schlimmste Fall eine lineare Suche über alle 72 Werte des ArraysCPrimeGenerator::s_primes[].

Die Anregung lautet:
- die Anzahl der Elemente im Array auf 70 reduzieren. (durch Streichen der ersten beiden oder der ersten und der letzten):

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
  };

- wenn der Eingabewert kleiner oder gleich dem 6. Wert im neuen ArrayCPrimeGenerator::s_primes ist - dann werden die Zahlen linear durchlaufen (bis zu 6 Vergleiche).
- Andernfalls verwenden Sie die obere Grenze der binären Suche zwischen dem 7. und 70.

Die Idee ist, die lineare Suche nur so lange zu verwenden, wie es keine Leistungseinbußen im Vergleich zur binären Suche gibt.
Die vorgeschlagene Anzahl der Elemente - 6 - wird als Beispiel verwendet, in Wirklichkeit hängt alles von der konkreten Implementierung der binären Suche mit oberen Grenzen ab.
Der allgemeine Leistungsgewinn aufgrund der geringen Aufrufintensität einer bestimmten Funktion ist möglicherweise nicht so vorteilhaft, dass es sich lohnt, an der Verbesserung dieser Funktion zu arbeiten.