No description
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
nique_372 2e5206115f a
2026-10-01 10:22:05 -05:00
Src/ColHash correcion de includes 2026-09-20 11:24:03 -05:00
Test 2026-10-01 10:21:09 -05:00
dependencies.json Añadir dependencies.json 2026-07-19 13:37:42 +00:00
FastCollectionsByLeo.mqproj Generated by MQL5 Wizard 2026-07-19 08:23:37 -05:00
LICENSE Añadir LICENSE 2026-07-19 13:37:10 +00:00
README.md 2026-10-01 10:21:09 -05:00

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 for CHashMapFast* and CHashSetFast. Slots are grouped 8 at a time; each group has one metadata ulong (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. The Fast (Nor) variant takes values by copy — fine for int, double, pointers, small structs. The R (Ref) variant takes values by reference in Add/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, with Add/ContainsKey/Remove/iteration.
  • CDict: a separate, dynamically-typed dictionary (string key → string, long, double or bool value 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 for CHashMapFast.
  • Pluggable hash strategy per key type (Generic.mqh): every CGenericHash_* class defines three static hooks — Hash, Eq, and Assing (defaults to a plain =, but overridable to something like a move/RobarDe when copying the key is expensive or unsafe). Built-in variants: string (FNV-1a default, djb2, xxHash64), ulong and double (splitmix64 default, fmix64, Fibonacci, Fibonacci-plus, Murmur-style each). double hashing also normalizes -0.0/+0.0 so both hash equal. Swap strategy per key type via the P/RP macro variants, or supply your own class as the third template parameter (TBase).
  • Low-level access (advanced): CFastHashTableBase exposes ReserveSlot/m_last_pos_tbl so you can do find-or-insert in a single table pass instead of Find + Add. See Src/ColHash/HashBases/README.md for 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