- MQL5 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| Src/ColHash | ||
| Test | ||
| dependencies.json | ||
| FastCollectionsByLeo.mqproj | ||
| LICENSE | ||
| README.md | ||
Fast generic collections for MQL5 (HashMap, HashSet, dynamic Dict), based on a Swiss-table-style design.
Groups of 8 slots with a metadata byte array, scanned via SWAR bit-tricks instead of comparing keys one by one.
Main Features
- Swiss-table hash engine (
CFastHashTableBase): shared base forCHashMapFast*andCHashSetFast. Slots are grouped 8 at a time; each group has one metadataulong(one control byte per slot: empty, deleted, or 7 bits of the key's hash). Lookups scan a whole group in one SWAR pass instead of probing slot by slot. CHashMapFast(key, value)/CHashMapFastR(key, value): two flavors of the same generic hash map. TheFast(Nor) variant takes values by copy — fine forint,double, pointers, small structs. TheR(Ref) variant takes values by reference inAdd/TrySet— use it for structs/classes you don't want copied on every insert, or that shouldn't be copied by value at all.CHashSetFast(key): generic hash set on the same engine, key-only, withAdd/ContainsKey/Remove/iteration.CDict: a separate, dynamically-typed dictionary (stringkey →string,long,doubleorboolvalue chosen at runtime). Not built on the Swiss-table engine — bucket-based with in-place chaining. Meant for config-like or loosely-typed data, not as a replacement forCHashMapFast.- Pluggable hash strategy per key type (
Generic.mqh): everyCGenericHash_*class defines three static hooks —Hash,Eq, andAssing(defaults to a plain=, but overridable to something like a move/RobarDewhen copying the key is expensive or unsafe). Built-in variants:string(FNV-1a default, djb2, xxHash64),ulonganddouble(splitmix64 default, fmix64, Fibonacci, Fibonacci-plus, Murmur-style each).doublehashing also normalizes-0.0/+0.0so both hash equal. Swap strategy per key type via theP/RPmacro variants, or supply your own class as the third template parameter (TBase). - Low-level access (advanced):
CFastHashTableBaseexposesReserveSlot/m_last_pos_tblso you can do find-or-insert in a single table pass instead ofFind+Add. SeeSrc/ColHash/HashBases/README.mdfor the full field-by-field breakdown — most users won't need it. - Depends on TsnTables for the SWAR macros and De Bruijn tables that make the group scan fast.
Usage examples
HashMap (by value):
void OnStart()
{
CHashMapFast(string, int) map;
map.Add("hola", 10);
map.Add("cinco", 10);
map.Add("My key:", 20);
map.Add("Yo: ", 20);
CHashMapFastIterator(string, int) it = map.BeginIteration();
while(it.IsValid())
{
Print(it.Key(), " = ", it.Val());
it.Next();
}
}
HashMap (by reference — for structs/classes you don't want copied):
struct SHeavyConfig
{
double coefficients[64];
string label;
};
void OnStart()
{
CHashMapFastR(string, SHeavyConfig) map;
SHeavyConfig cfg;
cfg.label = "strategy_a";
map.Add("A", cfg); // passed by reference, no extra copy inside Add
SHeavyConfig out;
if(map.TryGet("A", out))
Print(out.label);
}
HashSet:
void OnStart()
{
CHashSetFast(string) set;
set.Add("hola");
set.Add("como");
set.Add("siuu");
Print(set.ContainsKey("hola"));
}
Using a different hash function for the same key type:
CHashMapFastP(ulong, int, _murmur) map; // uses CGenericHash_ulong_murmur instead of the default hash
CHashMapFastRP(string, SHeavyConfig, _xxhash64) map2; // "R" (by reference) variant + custom hash
Custom Assing (move instead of copy) for a non-trivial key:
Assing is the third hook a hash strategy class can define, alongside Hash and Eq. It runs whenever the engine relocates an entry internally (Reserve/Rehash), and the default (CGenericHash_eq_simple::Assing) is a plain a = b. If your key type owns a dynamic array, a plain = means a full copy every time the table grows — you can override Assing to move the array instead.
Say your key is a small buffer wrapper:
struct SBitBuffer
{
ulong bits[];
int size;
};
A plain Eq/Hash pair for it is straightforward. The interesting part is Assing: instead of copying bits[] element by element, it swaps the underlying arrays, so the data moves without being duplicated:
class CGenericHash_SBitBuffer
{
public:
static ulong Hash(const SBitBuffer &v) { /* ... */ }
static bool Eq(const SBitBuffer &a, const SBitBuffer &b) { /* ... */ }
//--- Called by the engine when an entry is relocated (Reserve/Rehash).
//--- "a" is the new slot, "b" is the old one being vacated.
template <typename TPar>
static void Assing(TPar &a, TPar &b)
{
ArraySwap(a.key.bits, b.key.bits); // moves the array, no per-element copy
a.key.size = b.key.size;
a.value = b.value;
a.hash = b.hash;
}
};
After the swap, b.key.bits is left empty and a.key.bits owns the data — the same array, not a duplicate.
CDict — dynamically-typed dictionary:
void OnStart()
{
CDict d;
d.Set("name", "EA Alpha");
d.Set("period", (long)14);
d.Set("threshold", 0.75);
d.Set("enabled", true);
string s;
long n;
if(d.TryGet("name", s)) Print(s);
if(d.TryGet("period", n)) Print(n);
}
Low-level find-or-insert in one pass (advanced):
if(!hashmap.ReserveSlot(key))
{
// already existed -> read/replace directly
hashmap.m_table[hashmap.m_last_pos_tbl].value = new_value;
}
else
{
// did not exist -> the slot is already reserved, just fill it in
hashmap.m_table[hashmap.m_last_pos_tbl].key = key;
hashmap.m_table[hashmap.m_last_pos_tbl].value = new_value;
}
Full details and re-entrancy warnings in
HashBases/README.md.
Hash map benchmarks
- Access test over 100,000 iterations
- Converting
ENUM_TIMEFRAMES(string) to integer - Same PC used for all tests
| Class | Time (microseconds, 100,000 iterations) |
|---|---|
| Perfect Hash | ~1534-1537 |
| CHashMapFast | 1843-1845 |
| CDictSValue | 2319-2329 |
| CHashMap | 8806-8867 |
| Linear | 10964-11048 |
Test in Final.mq5
Repository Structure
FastCollectionsByLeo/
└── Src/ColHash/
├── HashBases/ # Swiss-table engine shared by HashMap and HashSet
├── HashMap/ # CHashMapFast (by value) and CHashMapFastR (by reference)
├── HashSet/ # CHashSetFast
├── Hash/ # Pluggable hash/eq/assing strategies (string, ulong, double)
└── Dict/ # CDict, dynamically-typed string-keyed dictionary
Test/ # Benchmarks and usage scripts
Requirements
See dependencies.json for the full list.
- MetaTrader 5, build 5430+
- TsnTables (SWAR macros)
Installation
cd "C:\Users\YOUR_USER\AppData\Roaming\MetaQuotes\Terminal\YOUR_ID\MQL5\Shared Projects"
tsndep install "https://forge.mql5.io/nique_372/FastCollectionsByLeo.git"
Requires the tsndep package, available on PyPI. It automatically downloads and installs all declared dependencies.
Quick Start
1. Include what you need:
// HashMap (both flavors: by value and by reference)
#include <TSN\\GCol\\HashMap\\All.mqh>
#include <TSN\\GCol\\Hash\\Generic.mqh>
// HashSet
#include <TSN\\GCol\\HashSet\\Main.mqh>
// CDict (dynamic dictionary, does not need Generic.mqh)
#include <TSN\\GCol\\Dict\\Main.mqh>
2. Use it:
CHashMapFast(string, int) map;
map.Add("key", 1);
CHashMapFastR(string, MyHeavyStruct) map_ref;
CHashSetFast(string) set;
set.Add("key");
CDict dict;
dict.Set("key", "value");
License
Read Full License By downloading or using this repository, you accept the license terms.
Contact
- Platform: MQL5 Community
- Profile: https://www.mql5.com/es/users/nique_372
- Articles: https://www.mql5.com/es/users/nique_372/publications