//+------------------------------------------------------------------+ //| IndexMapT.mqh | //| https://www.mql5.com/en/articles/5706/ | //+------------------------------------------------------------------+ #define EMPTY ((int)EMPTY_VALUE) #ifdef HASHMAP_WARNING #define NULL_PLACEHOLDER "n/a" #else #define NULL_PLACEHOLDER "" #endif class Object { public: virtual string asString() const { return(__FUNCSIG__); } virtual string asCSVString() const { return(__FUNCSIG__); } virtual string getTypeName() const = 0; }; /** * Base container for indexed map. Can contain plain types and pointers. */ class Container: public Object { protected: enum datatype { null, s, d, t, i, o, u }; datatype type; public: datatype getType() const { return type; } // helper method to access plain type values from the base class template R get() const; // helper method to access object pointers from the base class template Object *getObject() const; }; /** * Variable type data for indexed map; plain types only. */ template class TypeContainer: public Container { private: int digits; int flags; protected: T v; TypeContainer() { digits = _Digits; flags = TIME_DATE | TIME_MINUTES; } public: TypeContainer(T _v, int precision = INT_MIN, int timeflags = TIME_DATE | TIME_MINUTES) { v = _v; digits = precision == INT_MIN ? _Digits : precision; flags = timeflags; if(typename(T) == "string") { type = datatype::s; } else if(typename(T) == "double" || typename(T) == "float") { type = datatype::d; } else if(typename(T) == "datetime") { type = datatype::t; } else if(typename(T) == "char" || typename(T) == "short" || typename(T) == "int" || typename(T) == "long") { type = datatype::i; } else { type = datatype::u; } } virtual T getValue() const { return v; } // represent data as a string, convert if necessary virtual string asString() const { switch(type) { case datatype::s: return (string)v; case datatype::d: return DoubleToString((double)v, digits); case datatype::t: return TimeToString((datetime)v, flags); case datatype::i: return IntegerToString((long)v); default: return (string)v; } } virtual string asCSVString() const { return asString(); } virtual string getTypeName() const override { return typename(this); } }; /** * Object pointer types for indexed map. * Pointer will be deleted automatically. */ template class ObjectContainer: public Container { protected: Object *o; public: ObjectContainer(T _v) { o = _v; type = datatype::o; } ~ObjectContainer() { if(CheckPointer(o) == POINTER_DYNAMIC) { delete(o); } } T getObject() const { return o; } virtual string asString() const override { return o.asString(); } virtual string asCSVString() const { return o.asCSVString(); } virtual string getTypeName() const override { return typename(this); } }; template R Container::get() const { const TypeContainer *ptr = dynamic_cast *>(&this); if(ptr != NULL) return (R)ptr.getValue(); return (R)NULL; } template // R is supposed to be an object pointer Object *Container::getObject() const { const ObjectContainer *obj = dynamic_cast *>(&this); if(obj != NULL) return obj.getObject(); return NULL; } /** * Indexed map with random access by key and index. */ template class IndexMapT: public Container // Object { private: T keys[]; Container *values[]; int count; string id; uchar delimiter; public: IndexMapT(): count(0), delimiter(',') {} IndexMapT(string obj): id (obj), count(0), delimiter(',') {} IndexMapT(uchar d): count(0), delimiter(d) {} ~IndexMapT() { reset(); } virtual string getTypeName() const override { return typename(this); } void add(const T key, Container *value) { ArrayResize(keys, count + 1); ArrayResize(values, count + 1); keys[count] = key; values[count] = value; count++; } void reset() { for(int i = 0; i < count; i++) { if(CheckPointer(values[i]) == POINTER_DYNAMIC) { delete(values[i]); } } ArrayResize(keys, 0); ArrayResize(values, 0); count = 0; } bool isKeyExisting(const T key) const { return (getIndex(key) != EMPTY); } int getIndex(const T key) const { for(int i = 0; i < count; i++) { if(keys[i] == key) return(i); } #ifdef HASHMAP_VERBOSE Print(__FUNCSIG__, ": no key=", key); #endif return EMPTY; } Container *operator[](const int index) const { if(index < 0 || index >= count) { #ifdef HASHMAP_VERBOSE Print(__FUNCSIG__, ": index=", index); #endif return(NULL); } return(GetPointer(values[index])); } Container *operator[](const T key) const { for(int i = 0; i < count; i++) { if(keys[i] == key) return(GetPointer(values[i])); } #ifdef HASHMAP_VERBOSE Print(__FUNCSIG__, ": no key=", key); #endif return(NULL); } T getKey(const int index) const { if(index < 0 || index >= count) { Print(__FUNCSIG__, ": index=", index); } return(keys[index]); } template void setValue(const T key, R value) { // NB: implementation specific // in HTML every attribute can occur only once in a tag, // all successive assignments are ignored if(!isKeyExisting(key)) { set(key, new TypeContainer(value)); } } void set(const T key, Container *value) { int index = getIndex(key); if(index != EMPTY) { #ifdef HASHMAP_WARNING Print(__FUNCSIG__, ": overwritten key=", key, ", old value=", (values[index] != NULL ? values[index].asString() : "null"), ", new value=", (value != NULL ? value.asString() : "null")); #endif values[index] = value; } else { add(key, value); } } void set(const T key) { int index = getIndex(key); if(index == EMPTY) { add(key, NULL); } } int getSize() const { return(count); } virtual string asString() const { string result = ""; for(int i = 0; i < count; i++) { result += (string)keys[i] + "=" + (CheckPointer(values[i]) == POINTER_INVALID ? NULL_PLACEHOLDER : values[i].asString()) + ";"; } return result; } virtual string asCSVString() const { string result = ""; string d = CharToString(delimiter); for(int i = 0; i < count; i++) { string v = NULL_PLACEHOLDER; if(CheckPointer(values[i]) != POINTER_INVALID) { v = values[i].asCSVString(); StringReplace(v, d, ""); } if(i < count - 1) { result += v + d; } else { result += v; } } return result; } }; class IndexMap: public IndexMapT { public: IndexMap(): IndexMapT() {} IndexMap(string obj): IndexMapT(obj) {} IndexMap(uchar d): IndexMapT(d) {} string get(const string key) { Container *c = this[key]; if(c == NULL) return NULL; return c.get(); } };