401 lines
17 KiB
Plaintext
401 lines
17 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| 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<typename R>
|
|
R get() const;
|
|
|
|
// helper method to access object pointers from the base class
|
|
template<typename R>
|
|
Object *getObject() const;
|
|
};
|
|
|
|
/**
|
|
* Variable type data for indexed map; plain types only.
|
|
*/
|
|
template<typename T>
|
|
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<typename T>
|
|
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<typename R>
|
|
R Container::get() const
|
|
{
|
|
const TypeContainer<R> *ptr = dynamic_cast<const TypeContainer<R> *>(&this);
|
|
if(ptr != NULL) return (R)ptr.getValue();
|
|
return (R)NULL;
|
|
}
|
|
|
|
template<typename R> // R is supposed to be an object pointer
|
|
Object *Container::getObject() const
|
|
{
|
|
const ObjectContainer<R> *obj = dynamic_cast<const ObjectContainer<R> *>(&this);
|
|
if(obj != NULL) return obj.getObject();
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* Indexed map with random access by key and index.
|
|
*/
|
|
template<typename T>
|
|
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<typename R>
|
|
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<R>(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<string>
|
|
{
|
|
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<string>();
|
|
}
|
|
}; |