// $Id: hash.mqh 125 2014-03-03 08:38:32Z ydrol $ #ifndef YDROL_HASH_MQH #define YDROL_HASH_MQH //#property strict /* This is losely ported from a C version I have which was in turn modified from hashtable.c by Christopher Clark. Copyright (C) 2014, Andrew Lord (NICKNAME=lordy) Copyright (C) 2002, 2004 Christopher Clark 2014/02/21 - Readded PrimeNumber sizes and auto rehashing when load factor hit. */ /// Any value stored in a Hash must be a subclass of HashValue class HashValue { }; /// Linked list of values - there will be one list for each hash value class HashEntry { public: string _key; HashValue * _val; HashEntry *_next; HashEntry() { _key=NULL; _val=NULL; _next=NULL; } HashEntry(string key,HashValue* val) { _key=key; _val=val; _next=NULL; } ~HashEntry() { } }; /// Convenience class for storing strings as hash values. class HashString : public HashValue { private: string val; public: HashString(string v) { val=v;} string getVal() { return val; } }; /// Convenience class for storing doubles as hash values. class HashDouble : public HashValue { private: double val; public: HashDouble(double v) { val=v;} double getVal() { return val; } }; /// Convenience class for storing ints as hash values. class HashInt : public HashValue { private: int val; public: HashInt(int v) { val=v;} int getVal() { return val; } }; /// Convenience class for storing longs as hash values. class HashLong : public HashValue { private: long val; public: HashLong(datetime v) { val=v;} long getVal() { return val; } }; /// Convenience class for storing datetimes as hash values. class HashDatetime : public HashValue { private: datetime val; public: HashDatetime(datetime v) { val=v;} datetime getVal() { return val; } }; /// /// Hash class allows objects to be stored in a table index by strings. /// the stored Objects must be a sub class of the HashValue class. /// /// There are some convenience classes to hold atomic types as values HashString,HashDouble,HashInt /// ///EXAMPLE: /// ///
/// class myClass: public HashValue {
///   public: int v;
///   myClass(int a) { v = a;}
/// };
///
/// // Create the objects as needed
///
///      myClass *a = new myClass(1);
///      myClass *b = new myClass(2);
///      myClass *c = new myClass(3);
///
/// // Then to insert into hash etc.
///
///      Hash* h = new Hash(193,true); 
///      // 'true' means when the hash will adopt the values and delete them when they are removed from the hash or when the hash is deleted.
///
///      h.hPut("a",a);
///      h.hPut("b",b);
///      h.hPut("c",c);
///
///      myClass *d = h.hGet("b");
///
///      etc.
///
/// // Iterate over hash
///    HashLoop *l
///    for (l = new HashLoop(h) ; l.hasNext() ; l.next()  ) {
///        string key = l.key();
///        MyClass *c = l.val();
///    }
///    delete l;
///
///    // Delete from hash - This will also delete 'a' because we set the 'adopt' flag on the hash.
///    h.hDel("a");
///
///    //Delete the hash - this will also delete 'b' and 'c' because of the adopt flag.
///    delete h;
/// 
class Hash : public HashValue { private: /// Number of slots in the hashtable. /// this should be approx number of elements to store. Depending on hash algorithm /// it may optimally be a prime or a power of two etc. but probably not important /// for MQL4 performance. A future optimisation might be to move the hashcode function to a DLL?? uint _hashSlots; /// Number of elements at which hash will get resized. int _resizeThreshold; /// number of things in the hash int _hashEntryCount; /// an array of linked lists (HashEntry). one for each hash value. /// To store an object against a string(key) - get the string hashcode, then insert pair (key,val) into the linked list for that hashcode. /// To fetch an object against a string(key) - get the string hashcode, get linked-list at that hashcode index, then search for the key and return the val. HashEntry* _buckets[]; /// If true the hash will free(delete) values as they are removed, or at cleanup. bool _adoptValues; int _errCode; string _errText; void init(uint size,bool adoptValues) { _hashSlots = 0; _hashEntryCount = 0; clearError(); setAdoptValues(adoptValues); rehash(size); } // Hash table distribution is better when size is prime, eg if hash function procduces numbers // that are multiples of x, then there may be grouping occuring around gcd(x,slots) gcd(2x,slots) etc // using a prime size helps spread the distribution. uint size2prime(uint size) { int pmax=ArraySize(_primes); for(int p=0 ; p= size) { return _primes[p]; } } return size; } /// Primes that approx double in size, used for hash table sizes to avoid gcd causing bunching static uint _primes[]; /// After reviewing quite a few hash functions I settled on the one below. /// http://www.cse.yorku.ca/~oz/hash.html /// this is the bottleneck function. Shame mql hash no default hash method for objects. uint hash(string s) { uchar c[]; uint h = 0; if (s != NULL) { h = 5381; int n = StringToCharArray(s,c); for(int i = 0 ; i < n ; i++ ) { h = ((h << 5 ) + h ) + c[i]; } } return h % _hashSlots; } void clearError() { setError(0,""); } void setError(int e,string m) { _errCode = e; _errText = m; //error((string)e,m); } public: /// Constructor: Create a Hash Object Hash() { init(17,true); } /// Constructor: Create a Hash Object /// @param adoptValues : If true the hash destructor will delete all dynamically allocated hash values. Hash(bool adoptValues) { init(17,adoptValues); } /// Constructor: Create a Hash Object /// @param size : Approximate size (actual size will be a larger prime number close to a power of 2) /// @param adoptValues : If true the hash destructor will delete all dynamically allocated hash values. Hash(int size,bool adoptValues) { init(size,adoptValues); } ~Hash() { // Free entries. for(uint i = 0 ; i< _hashSlots ; i++) { HashEntry *nextEntry = NULL; for(HashEntry *entry = _buckets[i] ; entry!= NULL ; entry = nextEntry ) { nextEntry = entry._next; if (_adoptValues && entry._val != NULL && CheckPointer(entry._val) == POINTER_DYNAMIC ) { delete entry._val; } delete entry; } _buckets[i] = NULL; } } /// Return any error that has occured. This should be used when /// retriving values in a Hash that may contain NULLs. hGet() /// methods can return NULL if not found, in which case getErrorCode /// will be set. int getErrCode() { return _errCode; } /// Return text of the error message. string getErrText() { return _errText; } /// If true the hash destructor will delete all dynamically allocated hash values. void setAdoptValues(bool v) { _adoptValues = v; } /// True if the hash destructor will delete all dynamically allocated hash values. bool getAdoptValues() { return _adoptValues; } private: uint _foundIndex; // After find() is called is set to hashindex for name whether found or not. HashEntry* _foundEntry; // After find() is called is set to the HashEntry that contains the key. HashEntry* _foundPrev; // After find() is called is set to the HashEntry before the entry // (could use double linked list but requires more memory). /// Look for the required entry for key 'name' true if found. bool find(string keyName) { //Alert("finding"); bool found = false; // Get the index using the hashcode of the string _foundIndex = hash(keyName); if (_foundIndex>_hashSlots ) { setError(1,"hGet: bad hashIndex="+(string)_foundIndex+" size "+(string)_hashSlots); } else { // Search the linked list determined by the index. for(HashEntry *e = _buckets[_foundIndex] ; e != NULL ; e = e._next ) { if (e._key == keyName) { _foundEntry = e; found=true; break; } // Track the item before the target item in case deleting from single linked list. _foundPrev = e; } } return found; } public: /// This is used by the HashLoop class to get start of LinkedList at bucket[i] HashEntry*getEntry(int i) { return _buckets[i]; } /// Return the number of slots/buckets (not number of elements) uint getSlots() { return _hashSlots; } /// Return the number of elements in the Hash int getCount() { return _hashEntryCount; } /// Change the hash size and re-allocate values to new buckets. bool rehash(uint newSize) { bool ret = false; HashEntry* oldTable[]; uint oldSize = _hashSlots; newSize = size2prime(newSize); //info("rehashing from "+(string)_hashSlots+" to "+(string)newSize+" "+(string)GetTickCount()); if (newSize <= getSlots()) { setError(2,"rehash "+(string)newSize+" <= "+(string)_hashSlots); } else if (ArrayResize(_buckets,newSize) != newSize) { setError(3,"unable to resize "); } else if (ArrayResize(oldTable,oldSize) != oldSize) { setError(4,"unable to resize old copy "); } else { //Copy old table. uint i; for(i = 0 ; i < oldSize ; i++ ) oldTable[i] = _buckets[i]; // Init new entries - not sure if MQL does this anyway for(i = 0 ; ikeyName key. This will overwrite any existing /// value. It adoptValues is set, it will also free the value if applicable. /// @param keyName : key name /// @param obj : Value to store /// @return the previous value of the key or NULL if there wasnt one HashValue *hPut(string keyName,HashValue *obj) { HashValue *ret = NULL; clearError(); if (find(keyName)) { // Return revious value ret = _foundEntry._val; /* // Replace entry contents if (_adoptValues && _foundEntry._val != NULL && CheckPointer(_foundEntry._val) == POINTER_DYNAMIC ) { delete _foundEntry._val; } */ _foundEntry._val = obj; } else { // Insert new entry at head of list HashEntry* e = new HashEntry(keyName,obj); HashEntry* first = _buckets[_foundIndex]; e._next = first; _buckets[_foundIndex] = e; _hashEntryCount++; //info((string)_hashEntryCount+" vs. "+(string)_resizeThreshold); // Auto Resize if number of entries hits _resizeThreshold if (_hashEntryCount > _resizeThreshold ) { rehash(_hashSlots/2*3); // this will snap to the next prime } } return ret; } /// Store a string as hash value (HashString) /// @return the previous value of the key or NULL if there wasnt one HashValue* hPutString(string keyName,string s) { HashString *v = new HashString(s); return hPut(keyName,v); } /// Store a double as hash value (HashDouble) /// @return the previous value of the key or NULL if there wasnt one HashValue* hPutDouble(string keyName,double d) { HashDouble *v = new HashDouble(d); return hPut(keyName,v); } /// Store an int as hash value (HashInt) /// @return the previous value of the key or NULL if there wasnt one HashValue* hPutInt(string keyName,int i) { HashInt *v = new HashInt(i); return hPut(keyName,v); } /// Store a datetime as hash value (HashLong) /// @return the previous value of the key or NULL if there wasnt one HashValue* hPutLong(string keyName,long i) { HashLong *v = new HashLong(i); return hPut(keyName,v); } /// Store a datetime as hash value (HashDatetime) /// @return the previous value of the key or NULL if there wasnt one HashValue* hPutDatetime(string keyName,datetime i) { HashDatetime *v = new HashDatetime(i); return hPut(keyName,v); } /// Delete an entry from the hash. bool hDel(string keyName) { bool found = false; clearError(); if (find(keyName)) { HashEntry *next = _foundEntry._next; if (_foundPrev != NULL) { //Remove entry from the middle of the list. _foundPrev._next = next; } else { // remove from head of list _buckets[_foundIndex] = next; } if (_adoptValues && _foundEntry._val != NULL&& CheckPointer(_foundEntry._val) == POINTER_DYNAMIC) { delete _foundEntry._val; } delete _foundEntry; _hashEntryCount--; found=true; } return found; } }; uint Hash::_primes[] = { 17, 53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, 49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741}; /// Class to iterate over a Hash using ... ///
///   HashLoop *l
///   for (l = new HashLoop(h) ; l.hasNext() ; l.next()  ) {
///       string key = l.key();
///       MyClass *c = l.val();
///   }
///   delete l;
/// 
class HashLoop { private: uint _index; HashEntry *_currentEntry; Hash *_hash; public: /// Create iterator for a hash - move to first item HashLoop(Hash *h) { setHash(h); } ~HashLoop() {}; /// Clear current state and move to first item (if any). void reset() { _index=0; _currentEntry = _hash.getEntry(_index); // Move to first item if (_currentEntry == NULL) { next(); } } /// Change the hash over which to iterate. void setHash(Hash *h) { _hash = h; reset(); } /// Check if more items. bool hasNext() { bool ret = ( _currentEntry != NULL); //config("hasNext=",ret); return ret; } /// Move to next item. void next() { //config("next : index = ",_index); // Advance if (_currentEntry != NULL) { _currentEntry = _currentEntry._next; } // Keep advancing if _currentEntry is null while (_currentEntry==NULL) { _index++; if (_index >= _hash.getSlots() ) return ; _currentEntry = _hash.getEntry(_index); } } /// Return the key name of the current item. string key() { if (_currentEntry != NULL) { return _currentEntry._key; } else { return NULL; } } /// Return the value. HashValue *val() { if (_currentEntry != NULL) { return _currentEntry._val; } else { return NULL; } } /// Convenience functions for retriving int from a current HashInt entry int valInt() { return ((HashInt *)val()).getVal(); } /// Convenience functions for retriving int from a current HashString entry string valString() { return ((HashString *)val()).getVal(); } /// Convenience functions for retriving int from a current HashDouble entry double valDouble() { return ((HashDouble *)val()).getVal(); } /// Convenience functions for retriving int from a current HashLong entry long valLong() { return ((HashLong *)val()).getVal(); } /// Convenience functions for retriving int from a current HashDatetime entry datetime valDatetime() { return ((HashDatetime *)val()).getVal(); } }; #endif