CArrayList ist schneller als die Hash-Variante. Ich bin in die Eingeweide von CArrayList eingedrungen, und dort gibt es folgendes
template<typename T>
class CArrayList: public IList<T>
{
protected:
T m_items[];int m_size;
Ich fühle mich jetzt betrogen. CArrayList erwies sich als Wrapper für ein gewöhnliches Array. Ich dachte, es sei eine normale Liste mit Prev/Next-Zeigern, aber sie sieht so aus
template<typename T>
bool CArrayList::TryGetValue(constint index,T &value)
{
//--- check indexif((uint)index<(uint)m_size)
{
//--- get value by indexvalue=m_items[index];return(true);
}
return(false);
}
Neben den eigentlichen Algorithmen für die Arbeit mit Strukturen stellt sich das Problem der Auswahl des richtigen Containers je nach den Besonderheiten der Aufgabe.
Kombinator: Abgesehen von den Algorithmen, die bei der Arbeit mit Strukturen zum Einsatz kommen, stellt sich auch das Problem der Auswahl des richtigen Containers je nach den Besonderheiten der Aufgabe.
Die Schnittstelle kann also bei verschiedenen Implementierungen dieselbe sein. Eine Enttäuschung, nicht von der Schnittstelle, sondern von der spezifischen Implementierung - dem Array. Verglichen mit dem Haschisch ist das der Himmel auf Erden.
Ich fühle mich jetzt betrogen. CArrayList erwies sich als Wrapper für ein normales Array. Ich dachte, es handele sich um eine normale Liste mit Prev/Next-Zeigern, aber hier ist sie
Was für ein DBMS, sagen Sie zu einem Mann, der nichts überDatenstrukturen weiß. Wenn es kein Konzept von ArrayList (Vektor aus C++) gibt, worüber können wir dann hier sprechen.....
CArrayList ist schneller als die Hash-Variante. Ich bin in die Eingeweide von CArrayList eingedrungen, und dort gibt es folgendes
Ich fühle mich jetzt betrogen. CArrayList erwies sich als Wrapper für ein gewöhnliches Array. Ich dachte, es sei eine normale Liste mit Prev/Next-Zeigern, aber sie sah so aus
Historisch gesehen ist List gar kein Blatt, sondern ein Array. Es ist also alles in Ordnung. Wenn Sie eine Liste im generischen Format erhalten, wird sie wahrscheinlich LinkedList oder so ähnlich heißen.
Eine gewisse Frustration, nicht mit der Schnittstelle, sondern mit der spezifischen Implementierung - dem Array.
Nun... dieser Container ist ein Array (d.h. ein Analogon des Vektors in C++), und natives Mql-Array ist sehr gut, also ist arraylist eher ein nachträglicher Einfall, ein bisschen bequemer zu verwenden.
Es gibt einige Vorschläge für mögliche Verbesserungen:
1) Meiner bescheidenen Meinung nach enthält die Implementierung eine nicht ganz korrekte Wahl der Kapazität - sowohl der anfänglichen Größe 3 als auch der nachfolgenden beim Wiederaufbau der Hashtabelle. Ja, es ist richtig, dass für eine gleichmäßige Verteilung eine Primzahl gewählt werden sollte. Die Implementierung von CPrimeGenerator entspricht jedoch nicht den Erwartungen und enthält Auslassungen von Primzahlen. Der Zweck eines solchen "Generators" ist klar: Er soll einen Steigerungsfaktor in der Größenordnung von 1,2 mit automatischer Generierung von Primzahlen liefern. Aber ist dieser Koeffizient nicht zu klein? In C++ für Vektoren beträgt der Koeffizient normalerweise 1,5-2,0, je nach Bibliothek (da er die durchschnittliche Komplexität der Operation stark beeinflusst). Der Ausweg könnte ein konstanter Koeffizient sein, gefolgt von einer Rundung der Zahl auf eine Primzahl über eine binäre Suche in einer Liste von Primzahlen. Und eine Anfangsgröße von 3 ist zu klein, wir leben nicht im letzten Jahrhundert.
2) Derzeit wird die Hash-Tabelle nur dann neu aufgebaut (Resize), wenn die Kapazität zu 100% voll ist (alle m_entries[] sind gefüllt). Aus diesem Grund kann es bei nicht sehr gleichmäßig verteilten Schlüsseln zu einer erheblichen Anzahl von Kollisionen kommen. Als Option kann die Einführung eines Füllfaktors in Betracht gezogen werden, der 100 % um den für den Wiederaufbau der Hash-Tabelle erforderlichen Grenzwert reduziert.
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(constint old_size)
{
int new_size=2*old_size;if((uint)new_size>INT_MAX && INT_MAX>old_size)
returnINT_MAX;
elsereturn 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(constint capacity);
CHashMap(constint capacity,IEqualityComparer<TKey>*comparer);
}
Ich sehe also nicht viel Sinn darin, hier etwas zu ändern.
2) Das Hinzufügen eines Füllfaktors würde in einigen Fällen wirklich helfen, Kollisionen zu vermeiden. Am wahrscheinlichsten ist, dass sie umgesetzt werden.
1) Der Kapazitätskoeffizient ist ungleich 1,2, zur Berechnung des neuen Volumens in CHashMap wird die Methode CPrimeGenerator::ExpandPrime verwendet:
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:
Deshalb sehe ich auch keinen Grund, hier etwas zu ändern.
2) Die Hinzufügung des Parameters Füllfaktor wird in einigen Fällen wirklich helfen, Kollisionen zu vermeiden. Höchstwahrscheinlich wird sie umgesetzt werden.
Roman, ich glaube, Sie haben da einen falschen Eindruck gewonnen. Nun enthalten einige Klassen nicht einmal die notwendigsten Methoden. Haben Sie jemals versucht, sie zu benutzen? Nehmen wir zum Beispiel CRedBlackTree, die klassische Implementierung eines rot-schwarzen Baums. Ziemlich cooler Algorithmus, aber ohne die Möglichkeit der Iteration. Was meinen Sie damit? Es gibt noch viele andere Dinge, die Sie vielleicht brauchen, aber niemand hat sich die Mühe gemacht, sie aus irgendeinem Grund zu implementieren. Das GetHashCode-Zeug ist furchtbar. Tut mir leid, aber das ist nicht auf dem Niveau von MQ!
Roman, ich glaube nicht, dass du hier das Richtige tust. Nun enthalten einige generische Klassen nicht einmal die notwendigsten Methoden. Haben Sie schon einmal versucht, sie zu benutzen? Nehmen Sie zum Beispiel CRedBlackTree - eine klassische Implementierung eines rot-schwarzen Baums. Ziemlich cooler Algorithmus, aber ohne die Möglichkeit der Iteration. Was meinen Sie damit? Es gibt noch viele andere Dinge, die Sie vielleicht brauchen, aber niemand hat sich die Mühe gemacht, sie aus irgendeinem Grund zu implementieren. Das GetHashCode-Zeug ist furchtbar. Entschuldigung, aber das ist nicht das Niveau von MQ!
Ich bin mir der Notwendigkeit von Iteratoren für alle Vorlagensammlungen der Genric-Bibliothek durchaus bewusst, aber ohne die Möglichkeit, verschachtelte Klassen und Mehrfachvererbung von den Schnittstellen zu erstellen, kann man sie nicht richtig implementieren.
Ich würde gerne genauere Kommentare zu GetHashCode hören.
Was GetHashCode betrifft, so würde ich gerne genauere Bemerkungen hören.
Problem
Offensichtlich kann GetHashCode in einer benutzerdefinierten MQL5-Umgebung nicht richtig implementiert werden. Dies liegt daran, dass nicht auf alle Objekte zugegriffen werden kann. Und während die Implementierung für primitive Mitglieder wie double int 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:
#include <Generic\HashSet.mqh>
//+------------------------------------------------------------------+//| Безобидное перечисление, но может быть класс или структура |//+------------------------------------------------------------------+enum ENUM_NUMBER
{
NUMBER_FIRTS, // First
NUMBER_SECOND, // Second
NUMBER_THIRD // Third
};
//+------------------------------------------------------------------+//| Script program start function |//+------------------------------------------------------------------+voidOnStart()
{
CHashSet<ENUM_NUMBER> set_enum;
for(int i = 0; i < 3; i++)
set_enum.Add((ENUM_NUMBER)i);
//-- Поздравляю, мы выродились в LinkedList.... for(int i = 0; i < 3; i++)
{
//-- Здесь дикий оверхед по производительности, который тчательно скрывается...string is = set_enum.Contains((ENUM_NUMBER)i) ? " присутствует " : " отсутствует ";
//...
}
}
Dies lässt sich nicht vermeiden, da wir auf der Benutzerebene nur den Namen des Objekts kennen:
//+------------------------------------------------------------------+//| Returns a hashcode for custom object. |//+------------------------------------------------------------------+template<typename T>
int GetHashCode(T value)
{
//--- try to convert to equality comparable object
IEqualityComparable<T>*equtable=dynamic_cast<IEqualityComparable<T>*>(value);// <-- Здесь будет получено название класса, структуры или перечислениеif(equtable)
{
//--- calculate hash by specied method return equtable.HashCode();
}
else
{
//--- calculate hash from name of objectreturn GetHashCode(typename(value));
}
}
Daher sind die Hashes aller Objekte komplexer Typen einander gleich, und dieser Suchcode hier beinhaltet eine vollständige Array-Aufzählung:
//+------------------------------------------------------------------+//| Determines whether a set contains the specified element. |//+------------------------------------------------------------------+template<typename T>
bool CHashSet::Contains(T item)
{
//--- check bucketsif(ArraySize(m_buckets)!=0)
{
//--- get hash code for item int hash_code=m_comparer.HashCode(item);
//--- search item in the slots for(int i=m_buckets[hash_code%ArraySize(m_buckets)]-1; i>=0; i=m_slots[i].next)if(m_slots[i].hash_code==hash_code && m_comparer.Equals(m_slots[i].value,item))
return(true);
}
return(false);
}
Dies ist sogar so wichtig, dass es den Sinn der Verwendung aller Generika an der Wurzel zunichte macht. Der Punkt ist, dass Sie in den meisten Fällen komplexe Objekte assoziieren oder speichern müssen: Aufzählungen, Strukturen oder Klassen. Wenn Sie nur mit einfachen Typen arbeiten müssen, können Sie mit etwas Einfacherem auskommen.
Lösung
Offensichtlich ist MQL5 eine typsichere benutzerdefinierte Programmiersprache, die in einer internen virtuellen Maschine läuft. Etwas wie Net. Das bedeutet, dass der notwendige Zugang zu den Objekten weiterhin besteht, allerdings auf einer höheren Systemebene. Folglich,vielleicht die Systemfunktion GetHashCode schreiben mit dem nächsten Prototyp:
intGetHashCode(void sample);
Wie soll das funktionieren? Erstens sollte er ein Allesfresser und schnell sein. Sie erhalten eine gleichmäßige Verteilung. Zum Beispiel:
Dies ausführen
und erhielt dies.
CArrayList ist schneller als die Hash-Variante. Ich bin in die Eingeweide von CArrayList eingedrungen, und dort gibt es folgendes
Ich fühle mich jetzt betrogen. CArrayList erwies sich als Wrapper für ein gewöhnliches Array. Ich dachte, es sei eine normale Liste mit Prev/Next-Zeigern, aber sie sieht so aus
Abgesehen von den Algorithmen, die bei der Arbeit mit Strukturen zum Einsatz kommen, stellt sich auch das Problem der Auswahl des richtigen Containers je nach den Besonderheiten der Aufgabe.
Die Schnittstelle kann also bei verschiedenen Implementierungen dieselbe sein. Eine Enttäuschung, nicht von der Schnittstelle, sondern von der spezifischen Implementierung - dem Array. Verglichen mit dem Haschisch ist das der Himmel auf Erden.
Ich fühle mich jetzt betrogen. CArrayList erwies sich als Wrapper für ein normales Array. Ich dachte, es handele sich um eine normale Liste mit Prev/Next-Zeigern, aber hier ist sie
Forum zum Thema Handel, automatisierte Handelssysteme und Strategietests
Algorithmen, Lösungsmethoden, Leistungsvergleich
Sergey Dzyublik, 2017.12.10 20:52
Was für ein DBMS, sagen Sie zu einem Mann, der nichts überDatenstrukturen weiß.
Wenn es kein Konzept von ArrayList (Vektor aus C++) gibt, worüber können wir dann hier sprechen.....
Es geht eher um Ihre Unaufmerksamkeit...
Alle verfügbaren Listen lesen -https://www.mql5.com/ru/docs/standardlibrary/generic
CArrayList ist ein Analogon zu std::vector aus C++
CLinkedList ist höchstwahrscheinlich ein Analogon zu std::list aus C++
Dies ausführen
und erhielt dies.
CArrayList ist schneller als die Hash-Variante. Ich bin in die Eingeweide von CArrayList eingedrungen, und dort gibt es folgendes
Ich fühle mich jetzt betrogen. CArrayList erwies sich als Wrapper für ein gewöhnliches Array. Ich dachte, es sei eine normale Liste mit Prev/Next-Zeigern, aber sie sah so aus
Historisch gesehen ist List gar kein Blatt, sondern ein Array. Es ist also alles in Ordnung. Wenn Sie eine Liste im generischen Format erhalten, wird sie wahrscheinlich LinkedList oder so ähnlich heißen.
Eine gewisse Frustration, nicht mit der Schnittstelle, sondern mit der spezifischen Implementierung - dem Array.
Forum zum Thema Handel, automatisierte Handelssysteme und Testen von Handelsstrategien
Generische Klassenbibliothek - Bugs, Beschreibung, Fragen, Besonderheiten der Nutzung und Vorschläge
Sergey Dzyublik, 2017.12.09 01:12
Es gibt einige Vorschläge für mögliche Verbesserungen:
1) Meiner bescheidenen Meinung nach enthält die Implementierung eine nicht ganz korrekte Wahl der Kapazität - sowohl der anfänglichen Größe 3 als auch der nachfolgenden beim Wiederaufbau der Hashtabelle.
Ja, es ist richtig, dass für eine gleichmäßige Verteilung eine Primzahl gewählt werden sollte.
Die Implementierung von CPrimeGenerator entspricht jedoch nicht den Erwartungen und enthält Auslassungen von Primzahlen.
Der Zweck eines solchen "Generators" ist klar: Er soll einen Steigerungsfaktor in der Größenordnung von 1,2 mit automatischer Generierung von Primzahlen liefern.
Aber ist dieser Koeffizient nicht zu klein? In C++ für Vektoren beträgt der Koeffizient normalerweise 1,5-2,0, je nach Bibliothek (da er die durchschnittliche Komplexität der Operation stark beeinflusst).
Der Ausweg könnte ein konstanter Koeffizient sein, gefolgt von einer Rundung der Zahl auf eine Primzahl über eine binäre Suche in einer Liste von Primzahlen.
Und eine Anfangsgröße von 3 ist zu klein, wir leben nicht im letzten Jahrhundert.
2) Derzeit wird die Hash-Tabelle nur dann neu aufgebaut (Resize), wenn die Kapazität zu 100% voll ist (alle m_entries[] sind gefüllt).
Aus diesem Grund kann es bei nicht sehr gleichmäßig verteilten Schlüsseln zu einer erheblichen Anzahl von Kollisionen kommen.
Als Option kann die Einführung eines Füllfaktors in Betracht gezogen werden, der 100 % um den für den Wiederaufbau der Hash-Tabelle erforderlichen Grenzwert reduziert.
1) Der Volumenwachstumsfaktor (Kapazität) ist nicht gleich 1,2, die Methode CPrimeGenerator::ExpandPrime wird zur Berechnung des neuen Volumens in CHashMap verwendet:
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:
Ich sehe also nicht viel Sinn darin, hier etwas zu ändern.
2) Das Hinzufügen eines Füllfaktors würde in einigen Fällen wirklich helfen, Kollisionen zu vermeiden. Am wahrscheinlichsten ist, dass sie umgesetzt werden.
1) Der Kapazitätskoeffizient ist ungleich 1,2, zur Berechnung des neuen Volumens in CHashMap wird die Methode CPrimeGenerator::ExpandPrime verwendet:
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:
Deshalb sehe ich auch keinen Grund, hier etwas zu ändern.
2) Die Hinzufügung des Parameters Füllfaktor wird in einigen Fällen wirklich helfen, Kollisionen zu vermeiden. Höchstwahrscheinlich wird sie umgesetzt werden.
Roman, ich glaube, Sie haben da einen falschen Eindruck gewonnen. Nun enthalten einige Klassen nicht einmal die notwendigsten Methoden. Haben Sie jemals versucht, sie zu benutzen? Nehmen wir zum Beispiel CRedBlackTree, die klassische Implementierung eines rot-schwarzen Baums. Ziemlich cooler Algorithmus, aber ohne die Möglichkeit der Iteration. Was meinen Sie damit? Es gibt noch viele andere Dinge, die Sie vielleicht brauchen, aber niemand hat sich die Mühe gemacht, sie aus irgendeinem Grund zu implementieren. Das GetHashCode-Zeug ist furchtbar. Tut mir leid, aber das ist nicht auf dem Niveau von MQ!
Roman, ich glaube nicht, dass du hier das Richtige tust. Nun enthalten einige generische Klassen nicht einmal die notwendigsten Methoden. Haben Sie schon einmal versucht, sie zu benutzen? Nehmen Sie zum Beispiel CRedBlackTree - eine klassische Implementierung eines rot-schwarzen Baums. Ziemlich cooler Algorithmus, aber ohne die Möglichkeit der Iteration. Was meinen Sie damit? Es gibt noch viele andere Dinge, die Sie vielleicht brauchen, aber niemand hat sich die Mühe gemacht, sie aus irgendeinem Grund zu implementieren. Das GetHashCode-Zeug ist furchtbar. Entschuldigung, aber das ist nicht das Niveau von MQ!
Ich bin mir der Notwendigkeit von Iteratoren für alle Vorlagensammlungen der Genric-Bibliothek durchaus bewusst, aber ohne die Möglichkeit, verschachtelte Klassen und Mehrfachvererbung von den Schnittstellen zu erstellen, kann man sie nicht richtig implementieren.
Ich würde gerne genauere Kommentare zu GetHashCode hören.
...
Was GetHashCode betrifft, so würde ich gerne genauere Bemerkungen hören.
Problem
Offensichtlich kann GetHashCode in einer benutzerdefinierten MQL5-Umgebung nicht richtig implementiert werden. Dies liegt daran, dass nicht auf alle Objekte zugegriffen werden kann. Und während die Implementierung für primitive Mitglieder wie double int 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 Sinn der Verwendung aller Generika an der Wurzel zunichte macht. Der Punkt ist, dass Sie in den meisten Fällen komplexe Objekte assoziieren oder speichern müssen: Aufzählungen, Strukturen oder Klassen. Wenn Sie nur mit einfachen Typen arbeiten müssen, können Sie mit etwas Einfacherem auskommen.
Lösung
Offensichtlich ist MQL5 eine typsichere benutzerdefinierte Programmiersprache, die in einer internen virtuellen Maschine läuft. Etwas wie Net. Das bedeutet, dass der notwendige Zugang zu den Objekten weiterhin besteht, allerdings auf einer höheren Systemebene. Folglich,vielleicht die Systemfunktion GetHashCode schreiben mit dem nächsten Prototyp:
Wie soll das funktionieren? Erstens sollte er ein Allesfresser und schnell sein. Sie erhalten eine gleichmäßige Verteilung. Zum Beispiel:
Diese Funktion könnte im Bereich der Systemfunktionen zu finden sein. Ich sehe keine anderen Lösungen.
s.e. Ich werde andere Vorschläge über Schnittstellen, Mehrfachvererbung und andere C++-Legacy-Sachen ein wenig später machen.