From ce9f460e2cb4d97dbf15d3dc5cc5443935e66bad Mon Sep 17 00:00:00 2001 From: Marshall Lochbaum Date: Mon, 27 Nov 2023 21:46:51 -0500 Subject: [PATCH] =?UTF-8?q?=E2=80=A2HashMap=20count=20and=20set=20function?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/builtins/search.c | 109 +++++++++++++++++++++++++++++------------- src/builtins/sysfn.c | 33 +++++++++---- test/cases/hash.bqn | 3 +- 3 files changed, 102 insertions(+), 43 deletions(-) diff --git a/src/builtins/search.c b/src/builtins/search.c index 22f05477..b6f580a0 100644 --- a/src/builtins/search.c +++ b/src/builtins/search.c @@ -611,48 +611,93 @@ typedef struct HashMap { u64 sz; // count of allocated entries, a power of 2 u64 a[]; } HashMap; +static u64 hashmap_size(usz sh) { return ((u64)1 << (64-sh)) + 32; } +static const u64 empty = ~(u64)0; +static const u64 hmask = ~(u32)0; -B hashmap_build(B keys, usz n) { - usz ext = 32; +usz hashmap_count(B hash) { return c(HashMap, hash)->pop; } + +HashMap* hashmap_alloc(usz pop, usz sh, u64 sz) { + HashMap* map = mm_alloc(fsizeof(HashMap,a,u64,sz), t_hashmap); + map->pop = pop; map->sh = sh; map->sz = sz; + memset64(map->a, empty, sz); + return map; +} + +NOINLINE HashMap* hashmap_resize(HashMap* old_map) { + usz sh = old_map->sh - 1; + if (sh < 32) thrM("•HashMap: hash size maximum exceeded"); + u64 sz = hashmap_size(sh); + HashMap* map = hashmap_alloc(old_map->pop, sh, sz); + for (u64 i=0, j=-(u64)1; isz; i++) { + u64 h = old_map->a[i]; + if (h == empty) continue; + j++; u64 jh = h>>sh; if (j < jh) j = jh; + map->a[j] = h; + } + mm_free((Value*)old_map); + return map->a[sz-1]==empty ? map : hashmap_resize(map); +} + +// Expects already defined: u64* hp, u64 sh, B* keys +#define HASHMAP_FIND(X, FOUND) \ + u64 h = bqn_hash(X, wy_secret); \ + u64 m = hmask; \ + u64 v = h &~ m; \ + u64 j = h >> sh; \ + u64 u; while ((u=hp[j]) < v) j++; \ + while (u < (v|m)) { \ + usz i = u&m; \ + if (equal(X, keys[i])) { FOUND } \ + u = hp[++j]; \ + } +// Expects usz i to be the index if new (i in FOUND is index where found) +#define HASHMAP_INSERT(X, FOUND) \ + HASHMAP_FIND(X, FOUND) \ + u64 je=j; while (u!=empty) { u64 s=u; je++; u=hp[je]; hp[je]=s; } \ + hp[j] = v | i; + +B hashmap_build(B key_arr, usz n) { usz sh = CLZ(n|16)-1; - u64 l = (u64)1 << (64-sh); - HashMap* map = mm_alloc(fsizeof(HashMap,a,u64,l+ext), t_hashmap); - map->pop = n; map->sh = sh; map->sz = l; + u64 sz = hashmap_size(sh); + HashMap* map = hashmap_alloc(n, sh, sz); u64* hp = map->a; - B* kp = harr_ptr(keys); - u64 e = ~(u64)0; - u64 m = ~(u32)0; - memset64(hp, e, l+ext); + B* keys = harr_ptr(key_arr); for (usz i=0; i>sh; u64 j = j0; - u64 u; while ((u=hp[j]) < v) j++; - if (u < (v|m) && equal(key, kp[u&m])) thrM("•HashMap: 𝕨 contained duplicate keys"); - u64 je=j; while (u!=e) { u64 s=u; je++; u=hp[je]; hp[je]=s; } - hp[j] = v; + B key = keys[i]; + HASHMAP_INSERT(key, thrM("•HashMap: 𝕨 contained duplicate keys");) + if (RARE(je == sz-1)) { + map=hashmap_resize(map); + hp=map->a; sh=map->sh; sz=map->sz; + } } return tag(map, OBJ_TAG); } B hashmap_lookup(B* vars, B w, B x) { HashMap* map = c(HashMap, vars[2]); - u64* hp = map->a; - u64 h = bqn_hash(x, wy_secret); - u64 m = ~(u32)0; - u64 v = h &~ m; - u64 j = h >> map->sh; - u64 u; while ((u=hp[j]) < v) j++; - B* k = harr_ptr(vars[0]); // keys - while (u < (v|m)) { - usz i = u&m; - if (equal(x, k[i])) { - dec(x); dec(w); - return inc(harr_ptr(vars[1])[i]); - } - u = hp[++j]; - } + u64* hp = map->a; u64 sh = map->sh; + B* keys = harr_ptr(vars[0]); + HASHMAP_FIND(x, dec(w); dec(x); return inc(harr_ptr(vars[1])[i]);) if (q_N(w)) thrM("(hashmap).Get: key not found"); dec(x); return w; } + +void hashmap_set(B* vars, B w, B x) { + HashMap* map = c(HashMap, vars[2]); + u64* hp = map->a; u64 sh = map->sh; + usz i = map->pop; + if (i>>(64-3-sh)>7 || hp[map->sz-1]!=empty) { // keep load <= 7/8 + map=hashmap_resize(map); + vars[2] = tag(map, OBJ_TAG); + hp=map->a; sh=map->sh; + } + B* keys = harr_ptr(vars[0]); + HASHMAP_INSERT( + w, + B* s = harr_ptr(vars[1])+i; dec(*s); dec(w); *s=x; return; + ) + map->pop++; + vars[0] = vec_addN(vars[0], w); + vars[1] = vec_addN(vars[1], x); +} diff --git a/src/builtins/sysfn.c b/src/builtins/sysfn.c index a55bcc41..493f686a 100644 --- a/src/builtins/sysfn.c +++ b/src/builtins/sysfn.c @@ -708,21 +708,34 @@ B currentError_c1(B t, B x) { thrM("•CurrentError: No errors as error catching #endif static Body* hashmap_ns; -static B hashmap_getName; static NFnDesc* hashmap_getDesc; +static B hashmap_getName; static NFnDesc* hashmap_getDesc; +static B hashmap_hasName; static NFnDesc* hashmap_hasDesc; +static B hashmap_setName; static NFnDesc* hashmap_setDesc; +static B hashmap_countName; static NFnDesc* hashmap_countDesc; // Hash object handling defined in search.c extern B hashmap_build(B keys, usz n); extern B hashmap_lookup(B* vars, B w, B x); -B hashmap_get_c2(B t, B w, B x) { - return hashmap_lookup(c(NS,nfn_objU(t))->sc->vars, w, x); -} -B hashmap_get_c1(B t, B x) { - return hashmap_lookup(c(NS,nfn_objU(t))->sc->vars, bi_N, x); +extern void hashmap_set(B* vars, B w, B x); +extern usz hashmap_count(B hash); +#define VARS c(NS,nfn_objU(t))->sc->vars +B hashmap_get_c1(B t, B x ) { return hashmap_lookup(VARS, bi_N, x); } +B hashmap_get_c2(B t, B w, B x) { return hashmap_lookup(VARS, w, x); } +B hashmap_set_c2(B t, B w, B x) { hashmap_set(VARS, w, x); return inc(nfn_objU(t)); } +B hashmap_count_c1(B t, B x) { dec(x); return m_usz(hashmap_count(VARS[2])); } +B hashmap_has_c1(B t, B x) { + B l = hashmap_lookup(VARS, bi_noVar, x); + if (l.u==bi_noVar.u) return m_f64(0); + dec(l); return m_f64(1); } +#undef VARS static NOINLINE void hashmap_init() { - hashmap_ns = m_nnsDesc("keys", "vals", "hash", "get"); + hashmap_ns = m_nnsDesc("keys", "vals", "hash", "get", "has", "set", "count"); NSDesc* d = hashmap_ns->nsDesc; for (usz i = 0; i < 3; i++) d->expGIDs[i] = -1; - hashmap_getName = m_c8vec_0("get"); gc_add(hashmap_getName); hashmap_getDesc = registerNFn(m_c8vec_0("(hashmap).Get"), hashmap_get_c1, hashmap_get_c2); + hashmap_getName = m_c8vec_0("get"); gc_add(hashmap_getName); hashmap_getDesc = registerNFn(m_c8vec_0("(hashmap).Get"), hashmap_get_c1, hashmap_get_c2); + hashmap_hasName = m_c8vec_0("has"); gc_add(hashmap_hasName); hashmap_hasDesc = registerNFn(m_c8vec_0("(hashmap).Has"), hashmap_has_c1, c2_bad); + hashmap_setName = m_c8vec_0("set"); gc_add(hashmap_setName); hashmap_setDesc = registerNFn(m_c8vec_0("(hashmap).Set"), c1_bad, hashmap_set_c2); + hashmap_countName = m_c8vec_0("count"); gc_add(hashmap_countName); hashmap_countDesc = registerNFn(m_c8vec_0("(hashmap).Count"), hashmap_count_c1, c2_bad); } B hashMap_c2(B t, B w, B x) { if (!isArr(w) || RNK(w)!=1 || !isArr(x) || RNK(x)!=1) thrF("•HashMap: Arguments must be lists (%H≡≢𝕨, %H≡≢𝕩)", w, x); @@ -731,9 +744,9 @@ B hashMap_c2(B t, B w, B x) { if (hashmap_ns==NULL) hashmap_init(); w = taga(toHArr(w)); x = taga(toHArr(x)); B h = hashmap_build(w, n); - B ns = m_nns(hashmap_ns, w, x, h, m_nfn(hashmap_getDesc, bi_N)); + B ns = m_nns(hashmap_ns, w, x, h, m_nfn(hashmap_getDesc, bi_N), m_nfn(hashmap_hasDesc, bi_N), m_nfn(hashmap_setDesc, bi_N), m_nfn(hashmap_countDesc, bi_N)); Scope* sc = c(NS,ns)->sc; - for (usz i = 3; i < 4; i++) nfn_swapObj(sc->vars[i], incG(ns)); + for (usz i = 3; i < 7; i++) nfn_swapObj(sc->vars[i], incG(ns)); return ns; } diff --git a/test/cases/hash.bqn b/test/cases/hash.bqn index 2baaf73a..7f571d49 100644 --- a/test/cases/hash.bqn +++ b/test/cases/hash.bqn @@ -45,6 +45,7 @@ a←(•ParseFloat¨ "1.2"‿"-0"‿"0")∾-⊸⋈0÷0 ⋄ ! (0‿3‿3‿3⊏a) !"⊐: Rank of 𝕩 must be at least the cell rank of 𝕨 (2‿2‿2 ≡ ≢𝕨, ⟨2⟩ ≡ ≢𝕩)" % (2‿2‿2⥊1‿2)⊐1‿2 !"⊒: Rank of 𝕩 must be at least the cell rank of 𝕨 (2‿2‿2 ≡ ≢𝕨, ⟨2⟩ ≡ ≢𝕩)" % (2‿2‿2⥊1‿2)⊒1‿2 -⟨"get"⟩ %% •ns.Keys "abc"‿"de"‿"fgh" •HashMap ↕3 ⟨2⟩ %% ("abc"‿"de"‿"fgh" •HashMap ⥊¨↕3).Get "fgh" ∞ %% ∞ ("abc"‿"de"‿"fgh" •HashMap ⥊¨↕3).Get "fghi" +3 %% ("abc"‿"de"‿"fgh" •HashMap ⥊¨↕3).Count@ +"B" %% ("b" ("a"‿"b" •HashMap "A"‿"C").Set "B").Get "b"