Libraries: CDictionary class

 

CDictionary class:

An implementation of the dictionary (associative array) data structure in MQL5, based on CArrayObj and CList.


Hash Function

FNV-1a


Usage

Primitives:

CDictionary *dict=new CDictionary();
//setting
dict.Set<string>("costarring","liquid");
dict.Set<double>("liquid",0.1);

//getting - variant 1
string variant1 = dict.Get<string>("costarring");
Print(variant1);

//getting - variant 2
double variant2 = 0;
dict.Get<double>("liquid",variant2);
Print(variant2);

It can also store pointers to objects (but not objects or structures):

dict.Set<CObject*>("object",new CObject());

Author: Enrico Lambino

 
Automated-Trading:

CDictionary class:

Author: Enrico Lambino

Hi Enrico,

Very nice work mate.

Thanks,

Shep

 
Shephard Mukachi:

Hi Enrico,

Very nice work mate.

Thanks,

Shep

You're welcome. I uploaded as promised :)

Also, you were quite right about the serialization part. I overlooked the CreateElement() method in CArrayObj. It is only possible to save and load objects if you know in advance what type of objects to save and load, or if MQ introduces a new feature that converts string to generic type. That is why I uploaded with no Save() and Load() methods in it.

 

Very clever implementation. Nice work! I made some teaks to it to make it easier to work with (for my needs). Instead of holding multiple data-types this tweaked version only holds one but doesn't require specifying the data-type when calling the get method, and it also doesn't require a string key input since the key is templated and casts to string inside the dictionary class. Example: 


   CDictionary<double> dict;
   for(int i=0;i<Bars;i++)
      dict.Add(Time[i],Close[i]);
   for(int i=0;i<Bars;i++)
      if(dict[Time[i]] != Close[i])
         Print("Collision Error ",++cnt); 
Files:
Dictionary.mqh  16 kb
 
nicholishen:

Very clever implementation. Nice work! I made some teaks to it to make it easier to work with (for my needs). Instead of holding multiple data-types this tweaked version only holds one but doesn't require specifying the data-type when calling the get method, and it also doesn't require a string key input since the key is templated and casts to string inside the dictionary class. Example: 


Interesting version. Thank you for sharing your ideas.

The earlier versions of this class has Dictionary<T> on it. But later on I changed it. For my own needs, I needed a something where I can store various types of data at the same, but I can also see some applications where your version is better suited for the task (and less cumbersome, I agree).

 

Very nice work.

For me, I just don't understand the benefit of extending CArrayObj, as most things to do with CArrayObj are forbidden in CDictionary (like Sort() etc.) and can mess up in case of malusage.
Actually, CDictionary is not a CArrayObj (defies the liskov principle of inheritance). So there is no real use of the CArrayObj.
I would extend CObject instead and have m_data[] inside instead. 

*-New version of DictionarySingle: Added serial iteration and memory management, some minor refactoring
Files:
 
Amir Yacoby:

Very nice work.

For me, I just don't understand the benefit of extending CArrayObj, as most things to do with CArrayObj are forbidden in CDictionary (like Sort() etc.) and can mess up in case of malusage.
Actually, CDictionary is not a CArrayObj (defies the liskov principle of inheritance). So there is no real use of the CArrayObj.
I would extend CObject instead and have m_data[] inside instead. 

I assume that's because MQL doesn't allow

class SubClass : private SuperClass


But no need to throw the baby out with the bathwater. You could simple wrap a CArrayObj object instead of implementing it thru derivation. 
 
nicholishen:

I assume that's because MQL doesn't allow

class SubClass : private SuperClass


But no need to throw the baby out with the bathwater. You could simple wrap a CArrayObj object instead of implementing it thru derivation. 

Wrapping was my first choice.
But then I noticed that it's not that trivial, as the m_data_max and m_data_total both protected in CArrayObj and have only a getter, and the algorithm needs a setter.
The m_data_max also is used a little differently in CArrayObj.

But anyway, 

class SubClass : private SuperClass

is allowed in MQL5, which can be a solution.
The only reason I see to use CArrayObj is because of freeing memory, which is pretty simple to do. 
Because I don't see nothing else is used of CArrayObj.

It is mostly used as CObject *m_data[] - and that is not a sufficient reason to extend it in my opinion.

 
Amir Yacoby:

Wrapping was my first choice.
But then I noticed that it's not that trivial, as the m_data_max and m_data_total both protected in CArrayObj and have only a getter, and the algorithm needs a setter.
The m_data_max also is used a little differently in CArrayObj.

But anyway, 

is allowed in MQL5, which can be a solution.
The only reason I see to use CArrayObj is because of freeing memory, which is pretty simple to do. 
Because I don't see nothing else is used of CArrayObj.

It is mostly used as CObject *m_data[] - and that is not a sufficient reason to extend it in my opinion.

I appreciate your inputs. I agree. Using CArrayObj is somewhat optional in this case.
There are many alternatives. You can make the superclass private as you stated, or directly inherit from CObject (your sample code), or meet halfway: extend CArray instead of CArrayObj so it would be safe against accidental misuse (no actual sorting is done).
Most of the needed methods from CArrayObj are not that difficult to re-implement. Mine is somewhat an opinionated solution (I chose to reuse as much code as I can for this class).
Feel free to modify as you see fit. 

 
updated DictionarySingle version - added serial iteration in the order of objects addition, added memory auto management and a little refactoring.
Example for iteration:
   CDictionary<double> dict;
   datetime time[];
   double close[];
   ulong bars=Bars(_Symbol,_Period);
   ArraySetAsSeries(time,true);
   ArraySetAsSeries(close,true);
   CopyTime(_Symbol,_Period,0,Bars(_Symbol,_Period),time);
   CopyClose(_Symbol,_Period,0,Bars(_Symbol,_Period),close);
   for(int i=0;i<20;i++) //Bars(_Symbol,_Period);i++)
     {
      dict.Add(time[i],close[i]);
      //dict.Add(time[i],close[i]*100);
     }

   //--- forward
   CDictionaryEntry<string> *node=dict.GetFirstNode();
   for(int i=0; node!=NULL; i++)
     {
      printf((string)i+":\t"+node.Value());
      node=dict.GetNextNode();
     }
   //--- backward
   node=dict.GetLastNode();
   for(int i=0; node!=NULL; i++)
     {
      printf((string)i+":\t"+node.Value());
      node=dict.GetPrevNode();
     }
Files:
 

I've discovered a bug in CDictionary. When you use the Reset Method it deletes the CList object in m_data. Subsequent calls to other methods that fetch the point from the dictionary receive a bad pointer in return. Example:


#include "Dictionary.mqh"

void OnStart()
{
   CDictionary d;
   d.Set("key", 1);
   d.Reset();
   Print(d.Contains<int>("key"));
}


I would propose the following fix for all methods which use the key hash to access a CList object from the m_data array. 


template<typename T>
bool CDictionary::Contains(string key)
  {
   bool res=false;
   T value=NULL;
   int index=Index(Hash(key+typename(T)));
   if(CheckPointer(m_data[index]) == POINTER_INVALID)
      m_data[index] = new CList;
   CList *list=m_data[index];
   if(CheckPointer(list))
     {
      CDictionaryEntryBase *model=new CDictionaryEntry<T>(key,value);
      if(CheckPointer(list.Search(model)))
         res=true;
      delete model;
     }
   return res;
  }
Reason: