ddn.columnar.hash
The hashing layer of ddn.df: per-row hashing, typed column equality and Series.unique/nUnique (D16; ADR-2026-10-18-15). The GroupTable itself lives in ddn.df.groupby, shaped by its two consumers (groupBy and join).
One 64-bit hash per row per key column: hashOf on the physical value (which coalesces -0.0/0.0 and all NaN payloads, fact 7), the byte slice for strings, a fixed sentinel for nulls with nulls tracked separately (they never match in joins and form their own group in groupBy). Equality is typed, per key column, through a function-pointer table selected once by kind — per-row delegate comparers measured 3.3 times slower (fact 12) — and total on floats.
Copyright
Types 2
The per-key-column equality function pointer: does row ia of chunk a equal row ib of chunk b? Both chunks are at the same dtype. Nulls equal nulls (they form one group); floats compare totally.
The distinct values of a Series in first-appearance order; nulls form one group and floats group under the total order (NaN with NaN, -0.0 with 0.0).
Parameters
s | the column |
Returns
size_t hashsize_t rowFunctions 29
bool columnEqFor(Kind K)(const ArrayData a, size_t ia,
const ArrayData b, size_t ib) @safe pure nothrowArrayData childOf(const ArrayData chunk, size_t i) @trusted pure nothrowSelects the typed equality function pointer for one key column's dtype — once per column, never per row (D16).bool columnEqList(const ArrayData a, size_t ia, const ArrayData b,
size_t ib) @safe pure nothrowbool columnEqStruct(const ArrayData a, size_t ia, const ArrayData b,
size_t ib) @safe pure nothrowbool columnEqCategorical(const ArrayData a, size_t ia,
const ArrayData b, size_t ib) @safe pure nothrowbool columnEqDecimal(const ArrayData a, size_t ia, const ArrayData b,
size_t ib) @safe pure nothrowvoid rowHashColumn(Kind k, const ArrayData[] chunks,
size_t[] hashes) @safe pure nothrowCombines one more key column's per-row hashes into the running `hashes` array, densely: one typed view per chunk and one validity test per row - no per-row view rebuilds, no `Nullable` traffic. Nul...void rowHashColumnRich(Kind k, const ArrayData[] chunks,
size_t[] hashes) @safe pure nothrowvoid rowHashColumnOf(Kind K)(const ArrayData[] chunks,
size_t[] hashes) @safe pure nothrowvoid rowHashChunkOf(Kind K)(const ArrayData chunk, size_t at,
size_t[] hashes) @safe pure nothrowvoid rowHashColumnParallel(Kind k,
const ArrayData[] chunks, size_t[] hashes) @safeThe chunk-parallel form of `rowHashColumn`: every chunk writes its own disjoint range of `hashes`, so the result is identical to the serial pass whatever the scheduling (ADR-2026-10-19-02).ArrayData[] isInRange(ArrayData[] input, ArrayData[] set_,
DataType dtype) @safeThe membership kernel for the rich kinds: `isInKernel`'s table and probe with the runtime-kind structural hash and equality (the column equality and row hash above). Null input slots stay null; nul...auto locateGlobal(const(ArrayData)[] chunks,
size_t global)Variables 1
NULL_HASH = 0x9E37_79B9_7F4A_7C15ULThe null sentinel of the row hash (fact 7).
Templates 1
The membership kernel of Series.isIn (ADR-2026-10-23-08): the set side is hashed into an open-addressed (hash, row) table by linear probing (null set slots never enter it - they cannot match), and every non-null input slot probes its hash chain, re-verifying each candidate through the typed total equality - a hash collision can never become a false match, and NaN/-0.0 coalescing in hashOf guarantees equal-under-total values share a chain. Null input slots stay null; the BOOLEAN result follows the input chunking.
Parameters
K | the kind of both sides |
input | the probed side's chunks |
set_ | the set side's chunks |