//+------------------------------------------------------------------+ //| HashMap.mqh | //| Copyright 2000-2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #include #include #include #include #include #include "HashSet.mqh" //+------------------------------------------------------------------+ //| Struct Entry. | //| Usage: Internal structure for organization CHashMap. | //+------------------------------------------------------------------+ template struct Entry: public Slot { public: TKey key; Entry(void): key((TKey)NULL) {} }; //+------------------------------------------------------------------+ //| Class CKeyValuePair. | //| Usage: Defines a key/value pair that can be set or retrieved. | //+------------------------------------------------------------------+ template class CKeyValuePair: public IComparable*> { protected: TKey m_key; TValue m_value; public: CKeyValuePair(void) { } CKeyValuePair(TKey key,TValue value): m_key(key), m_value(value) { } ~CKeyValuePair(void) { } //--- methods to access protected data TKey Key(void) { return(m_key); } void Key(TKey key) { m_key=key; } TValue Value(void) { return(m_value); } void Value(TValue value) { m_value=value; } //--- method to create clone of current instance CKeyValuePair*Clone(void) { return new CKeyValuePair(m_key,m_value); } //--- method to compare keys int Compare(CKeyValuePair*pair) { return ::Compare(m_key,pair.m_key); } //--- method for determining equality bool Equals(CKeyValuePair*pair) { return ::Equals(m_key,pair.m_key); } //--- method to calculate hash code int HashCode(void) { return ::GetHashCode(m_key); } }; //+------------------------------------------------------------------+ //| Class CKeyValuePairComparer. | //| Usage: Provides a comparer class for convertation IComparer| //| to the IComparer*> interface. | //+------------------------------------------------------------------+ template class CKeyValuePairComparer: public IComparer*> { private: IComparer*m_comparer; public: CKeyValuePairComparer(IComparer*comaprer) { m_comparer=comaprer; } int Compare(CKeyValuePair* x,CKeyValuePair* y) { return(m_comparer.Compare(x.Key(), y.Key())); } }; //+------------------------------------------------------------------+ //| Class CHashMap. | //| Usage: Represents a collection of keys and values. | //+------------------------------------------------------------------+ template class CHashMap: public IMap { protected: int m_buckets[]; Entrym_entries[]; int m_count; int m_capacity; int m_free_list; int m_free_count; IEqualityComparer*m_comparer; bool m_delete_comparer; public: CHashMap(void); CHashMap(const int capacity); CHashMap(IEqualityComparer*comparer); CHashMap(const int capacity,IEqualityComparer*comparer); CHashMap(IMap*map); CHashMap(IMap*map,IEqualityComparer*comparer); ~CHashMap(void); //--- methods of filling data bool Add(CKeyValuePair*pair); bool Add(TKey key,TValue value); //--- methods of access to protected data int Count(void) { return(m_count-m_free_count); } IEqualityComparer*Comparer(void) const { return(m_comparer); } bool Contains(CKeyValuePair*item); bool Contains(TKey key,TValue value); bool ContainsKey(TKey key); bool ContainsValue(TValue value); //--- methods of copy data from collection int CopyTo(CKeyValuePair*&dst_array[],const int dst_start=0); int CopyTo(TKey &dst_keys[],TValue &dst_values[],const int dst_start=0); //--- methods of cleaning and deleting void Clear(void); bool Remove(CKeyValuePair*item); bool Remove(TKey key); //--- method of access to the data bool TryGetValue(TKey key,TValue &value); bool TrySetValue(TKey key,TValue value); private: void Initialize(const int capacity); bool Resize(int new_size); int FindEntry(TKey key); bool Insert(TKey key,TValue value,const bool add); static int m_collision_threshold; }; //+------------------------------------------------------------------+ //| Initializes a new instance of the CHashMap class | //| that is empty, has the default initial capacity, and uses the | //| default equality comparer for the key type. | //+------------------------------------------------------------------+ template CHashMap::CHashMap(void): m_count(0), m_free_list(0), m_free_count(0), m_capacity(0) { //--- use default equality comaprer m_comparer=new CDefaultEqualityComparer(); m_delete_comparer=true; } //+------------------------------------------------------------------+ //| Initializes a new instance of the CHashMap class | //| that is empty, has the specified initial capacity, and uses the | //| default equality comparer for the key type. | //+------------------------------------------------------------------+ template CHashMap::CHashMap(const int capacity): m_count(0), m_free_list(0), m_free_count(0), m_capacity(0) { //--- set capacity if(capacity>0) Initialize(capacity); //--- use default equality comaprer m_comparer=new CDefaultEqualityComparer(); m_delete_comparer=true; } //+------------------------------------------------------------------+ //| Initializes a new instance of the CHashMap class | //| that is empty, has the default initial capacity, and uses the | //| specified IEqualityComparer. | //+------------------------------------------------------------------+ template CHashMap::CHashMap(IEqualityComparer*comparer): m_count(0), m_free_list(0), m_free_count(0), m_capacity(0) { //--- check equality comaprer if(CheckPointer(comparer)==POINTER_INVALID) { //--- use default equality comaprer m_comparer=new CDefaultEqualityComparer(); m_delete_comparer=true; } else { //--- use specified equality comaprer m_comparer=comparer; m_delete_comparer=false; } } //+------------------------------------------------------------------+ //| Initializes a new instance of the CHashMap class | //| that is empty, has the specified initial capacity, and uses the | //| specified IEqualityComparer. | //+------------------------------------------------------------------+ template CHashMap::CHashMap(const int capacity,IEqualityComparer*comparer): m_count(0), m_free_list(0), m_free_count(0), m_capacity(0) { if(capacity>0) Initialize(capacity); //--- check equality comaprer if(CheckPointer(comparer)==POINTER_INVALID) { //--- use default equality comaprer m_comparer=new CDefaultEqualityComparer(); m_delete_comparer=true; } else { //--- use specified equality comaprer m_comparer=comparer; m_delete_comparer=false; } } //+------------------------------------------------------------------+ //| Initializes a new instance of the CHashMap class | //| that contains elements copied from the specified | //| IMap and uses the default equality comparer for the | //| key type. | //+------------------------------------------------------------------+ template CHashMap::CHashMap(IMap*map): m_count(0), m_free_list(0), m_free_count(0), m_capacity(0) { //--- use default equality comaprer m_comparer=new CDefaultEqualityComparer(); m_delete_comparer=true; //--- check map if(CheckPointer(map)!=POINTER_INVALID && map.Count()>0) { //--- set capacity Initialize(map.Count()); TKey keys[]; TValue values[]; map.CopyTo(keys,values); //--- copy all keys and values from specified map to current map for(int i=0; i class | //| that contains elements copied from the specified | //| IMap and uses the specified IEqualityComparer.| //+------------------------------------------------------------------+ template CHashMap::CHashMap(IMap*map,IEqualityComparer*comparer): m_count(0), m_free_list(0), m_free_count(0), m_capacity(0) { //--- check equality comaprer if(CheckPointer(comparer)==POINTER_INVALID) { //--- use default equality comaprer m_comparer=new CDefaultEqualityComparer(); m_delete_comparer=true; } else { //--- use specified equality comaprer m_comparer=comparer; m_delete_comparer=false; } //--- check map if(CheckPointer(map)!=POINTER_INVALID && map.Count()>0) { //--- set capacity Initialize(map.Count()); TKey keys[]; TValue values[]; map.CopyTo(keys,values); //--- copy all keys and values from specified map to current map for(int i=0; i CHashMap::~CHashMap(void) { if(m_delete_comparer) delete m_comparer; } //+------------------------------------------------------------------+ //| Adds the specified key-value pair to the map. | //+------------------------------------------------------------------+ template bool CHashMap::Add(CKeyValuePair*pair) { //--- check pair if(CheckPointer(pair)==POINTER_INVALID) return(false); return(Add(pair.Key(),pair.Value())); } //+------------------------------------------------------------------+ //| Adds the specified key and value to the map. | //+------------------------------------------------------------------+ template bool CHashMap::Add(TKey key,TValue value) { return(Insert(key,value,true)); } //+------------------------------------------------------------------+ //| Determines whether the map contains the specified key-value pair.| //+------------------------------------------------------------------+ template bool CHashMap::Contains(CKeyValuePair*item) { //--- check pair if(CheckPointer(item)==POINTER_INVALID) return(false); //--- find pair with specified key int i=FindEntry(item.Key()); //--- create default equality value comparer CDefaultEqualityComparercomparer; //--- check value is equal value from the found pair if(i>=0 && comparer.Equals(m_entries[i].value,item.Value())) return(true); else return(false); } //+------------------------------------------------------------------+ //| Determines whether the map contains the specified key with value.| //+------------------------------------------------------------------+ template bool CHashMap::Contains(TKey key,TValue value) { //--- find pair with specified key int i=FindEntry(key); //--- create default equality value comparer CDefaultEqualityComparercomparer; //--- check value is equal value from the found pair if(i>=0 && comparer.Equals(m_entries[i].value,value)) return(true); else return(false); } //+------------------------------------------------------------------+ //| Determines whether the map contains the specified key. | //+------------------------------------------------------------------+ template bool CHashMap::ContainsKey(TKey key) { return(FindEntry(key)>=0); } //+------------------------------------------------------------------+ //| Determines whether the map contains the specified value. | //+------------------------------------------------------------------+ template bool CHashMap::ContainsValue(TValue value) { //--- create default equality value comparer CDefaultEqualityComparercomparer_value(); //--- try to find pair contains specified value for(int i=0; i=0 && comparer_value.Equals(m_entries[i].value,value)) return(true); return(false); } //+------------------------------------------------------------------+ //| Copies a range of elements from the map to a compatible | //| one-dimensional array. | //+------------------------------------------------------------------+ template int CHashMap::CopyTo(CKeyValuePair*&dst_array[],const int dst_start=0) { //--- resize array if(dst_start+m_count>ArraySize(dst_array)) ArrayResize(dst_array,dst_start+m_count); //--- start copy int index=0; for(int i=0; i=0) { //--- check indexes if(dst_start+index>=ArraySize(dst_array) || index>=m_count) return(index); dst_array[dst_start+index++]=new CKeyValuePair(m_entries[i].key,m_entries[i].value); } return(index); } //+------------------------------------------------------------------+ //| Copies a range of elements from the map to a compatible | //| one-dimensionals keys and values arrays. | //+------------------------------------------------------------------+ template int CHashMap::CopyTo(TKey &dst_keys[],TValue &dst_values[],const int dst_start=0) { int count=m_count-m_free_count; //--- resize keys array if(dst_start+count>ArraySize(dst_keys)) ArrayResize(dst_keys,dst_start+count); //--- resize values array if(dst_start+count>ArraySize(dst_values)) ArrayResize(dst_values,MathMin(ArraySize(dst_keys),dst_start+count)); //--- start copy int index=0; for(int i=0; i=0) { //--- check indexes if(dst_start+index>=ArraySize(dst_keys) || dst_start+index>=ArraySize(dst_values) || index>=count) return(index); dst_keys[dst_start+index]=m_entries[i].key; dst_values[dst_start+index]=m_entries[i].value; index++; } return(index); } //+------------------------------------------------------------------+ //| Removes all keys and values from the map. | //+------------------------------------------------------------------+ template void CHashMap::Clear(void) { //--- check count if(m_count>0) { ArrayFill(m_buckets,0,m_capacity,-1); ArrayFree(m_entries); m_count=0; m_free_list=-1; m_free_count=0; } } //+------------------------------------------------------------------+ //| Removes the specified key-value pair from map. | //+------------------------------------------------------------------+ template bool CHashMap::Remove(CKeyValuePair*item) { //--- check pair if(CheckPointer(item)==POINTER_INVALID) return(false); //--- find pair with specified key int i=FindEntry(item.Key()); //--- create default equality value comparer CDefaultEqualityComparercomparer_value(); //--- remove pair if(i>=0 && comparer_value.Equals(m_entries[i].value,item.Value())) return Remove(item.Key()); return(false); } //+------------------------------------------------------------------+ //| Removes the value with the specified key from the map. | //+------------------------------------------------------------------+ template bool CHashMap::Remove(TKey key) { if(m_capacity!=0) { int hash_code=m_comparer.HashCode(key)&0x7FFFFFFF; int bucket=hash_code%m_capacity; int last=-1; //--- search pair with specified key for(int i=m_buckets[bucket]; i>=0; last=i,i=m_entries[i].next) { if(m_entries[i].hash_code==hash_code && m_comparer.Equals(m_entries[i].key,key)) { if(last<0) m_buckets[bucket]=m_entries[i].next; else m_entries[last].next=m_entries[i].next; //--- remove pair m_entries[i].hash_code=-1; m_entries[i].next=m_free_list; m_entries[i].key=(TKey)NULL; m_entries[i].value=(TValue)NULL; //--- incremet free count m_free_list=i; m_free_count++; return(true); } } } return(false); } //+------------------------------------------------------------------+ //| Gets the value associated with the specified key. | //+------------------------------------------------------------------+ template bool CHashMap::TryGetValue(TKey key,TValue &value) { //--- find pair with specified key int i=FindEntry(key); //--- check index if(i>=0) { //--- get value value=m_entries[i].value; return(true); } return(false); } //+------------------------------------------------------------------+ //| Sets the value associated with the specified key. | //+------------------------------------------------------------------+ template bool CHashMap::TrySetValue(TKey key,TValue value) { return(Insert(key, value, false)); } //+------------------------------------------------------------------+ //| Initialize map with specified capacity. | //+------------------------------------------------------------------+ template void CHashMap::Initialize(const int capacity) { m_capacity=CPrimeGenerator::GetPrime(capacity); ArrayResize(m_buckets,m_capacity); ArrayFill(m_buckets,0,m_capacity,-1); ArrayResize(m_entries,m_capacity); m_free_list=-1; } //+------------------------------------------------------------------+ //| Resize map. | //+------------------------------------------------------------------+ template bool CHashMap::Resize(const int new_size) { //--- resize buckets if(ArrayResize(m_buckets,new_size)!=new_size) return(false); ArrayFill(m_buckets,0,new_size,-1); //--- resize entries if(ArrayResize(m_entries,new_size)!=new_size) return(false); //--- restore buckets for(int i=0; i=0) { int bucket=m_entries[i].hash_code%new_size; m_entries[i].next = m_buckets[bucket]; m_buckets[bucket] = i; } //--- restore capacity m_capacity=new_size; return(true); } //+------------------------------------------------------------------+ //| Find index of entry with specified key. | //+------------------------------------------------------------------+ template int CHashMap::FindEntry(TKey key) { if(m_capacity!=NULL) { //--- get hash code from key int hash_code=m_comparer.HashCode(key)&0x7FFFFFFF; //--- search pair with specified key for(int i=m_buckets[hash_code%m_capacity]; i>=0; i=m_entries[i].next) if(m_entries[i].hash_code==hash_code && m_comparer.Equals(m_entries[i].key,key)) return(i); } return(-1); } //+------------------------------------------------------------------+ //| Insert the value with the specified key from the map. | //+------------------------------------------------------------------+ template bool CHashMap::Insert(TKey key,TValue value,const bool add) { if(m_capacity==0) Initialize(0); //--- get hash code from key int hash_code=m_comparer.HashCode(key)&0x7FFFFFFF; int target_bucket=hash_code%m_capacity; //--- collisions count in one bucket with different hashes int collision_count=0; //--- search pair with specified key for(int i=m_buckets[target_bucket]; i>=0; i=m_entries[i].next) { //--- hash compare if(m_entries[i].hash_code!=hash_code) { collision_count++; continue; } //--- value compare if(m_comparer.Equals(m_entries[i].key,key)) { //--- adding duplicate if(add) return(false); m_entries[i].value=value; return(true); } } //--- check collision if(collision_count>=m_collision_threshold) { int new_size=CPrimeGenerator::ExpandPrime(m_count); if(!Resize(new_size)) return(false); target_bucket=hash_code%new_size; } //--- calculate index int index; if(m_free_count>0) { index=m_free_list; m_free_list=m_entries[index].next; m_free_count--; } else { if(m_count==ArraySize(m_entries)) { int new_size=CPrimeGenerator::ExpandPrime(m_count); if(!Resize(new_size)) return(false); target_bucket=hash_code%new_size; } index=m_count; m_count++; } //--- set pair m_entries[index].hash_code=hash_code; m_entries[index].next=m_buckets[target_bucket]; m_entries[index].key=key; m_entries[index].value=value; m_buckets[target_bucket]=index; return(true); } template static int CHashMap::m_collision_threshold=8; //+------------------------------------------------------------------+