| /* stb_ds.h - v0.67 - public domain data structures - Sean Barrett 2019 |
| |
| This is a single-header-file library that provides easy-to-use |
| dynamic arrays and hash tables for C (also works in C++). |
| |
| For a gentle introduction: |
| http://nothings.org/stb_ds |
| |
| To use this library, do this in *one* C or C++ file: |
| #define STB_DS_IMPLEMENTATION |
| #include "stb_ds.h" |
| |
| TABLE OF CONTENTS |
| |
| Table of Contents |
| Compile-time options |
| License |
| Documentation |
| Notes |
| Notes - Dynamic arrays |
| Notes - Hash maps |
| Credits |
| |
| COMPILE-TIME OPTIONS |
| |
| #define STBDS_NO_SHORT_NAMES |
| |
| This flag needs to be set globally. |
| |
| By default stb_ds exposes shorter function names that are not qualified |
| with the "stbds_" prefix. If these names conflict with the names in your |
| code, define this flag. |
| |
| #define STBDS_SIPHASH_2_4 |
| |
| This flag only needs to be set in the file containing #define STB_DS_IMPLEMENTATION. |
| |
| By default stb_ds.h hashes using a weaker variant of SipHash and a custom hash for |
| 4- and 8-byte keys. On 64-bit platforms, you can define the above flag to force |
| stb_ds.h to use specification-compliant SipHash-2-4 for all keys. Doing so makes |
| hash table insertion about 20% slower on 4- and 8-byte keys, 5% slower on |
| 64-byte keys, and 10% slower on 256-byte keys on my test computer. |
| |
| #define STBDS_REALLOC(context,ptr,size) better_realloc |
| #define STBDS_FREE(context,ptr) better_free |
| |
| These defines only need to be set in the file containing #define STB_DS_IMPLEMENTATION. |
| |
| By default stb_ds uses stdlib realloc() and free() for memory management. You can |
| substitute your own functions instead by defining these symbols. You must either |
| define both, or neither. Note that at the moment, 'context' will always be NULL. |
| @TODO add an array/hash initialization function that takes a memory context pointer. |
| |
| #define STBDS_UNIT_TESTS |
| |
| Defines a function stbds_unit_tests() that checks the functioning of the data structures. |
| |
| Note that on older versions of gcc (e.g. 5.x.x) you may need to build with '-std=c++0x' |
| (or equivalentally '-std=c++11') when using anonymous structures as seen on the web |
| page or in STBDS_UNIT_TESTS. |
| |
| LICENSE |
| |
| Placed in the public domain and also MIT licensed. |
| See end of file for detailed license information. |
| |
| DOCUMENTATION |
| |
| Dynamic Arrays |
| |
| Non-function interface: |
| |
| Declare an empty dynamic array of type T |
| T* foo = NULL; |
| |
| Access the i'th item of a dynamic array 'foo' of type T, T* foo: |
| foo[i] |
| |
| Functions (actually macros) |
| |
| arrfree: |
| void arrfree(T*); |
| Frees the array. |
| |
| arrlen: |
| ptrdiff_t arrlen(T*); |
| Returns the number of elements in the array. |
| |
| arrlenu: |
| size_t arrlenu(T*); |
| Returns the number of elements in the array as an unsigned type. |
| |
| arrpop: |
| T arrpop(T* a) |
| Removes the final element of the array and returns it. |
| |
| arrput: |
| T arrput(T* a, T b); |
| Appends the item b to the end of array a. Returns b. |
| |
| arrins: |
| T arrins(T* a, int p, T b); |
| Inserts the item b into the middle of array a, into a[p], |
| moving the rest of the array over. Returns b. |
| |
| arrinsn: |
| void arrinsn(T* a, int p, int n); |
| Inserts n uninitialized items into array a starting at a[p], |
| moving the rest of the array over. |
| |
| arraddnptr: |
| T* arraddnptr(T* a, int n) |
| Appends n uninitialized items onto array at the end. |
| Returns a pointer to the first uninitialized item added. |
| |
| arraddnindex: |
| size_t arraddnindex(T* a, int n) |
| Appends n uninitialized items onto array at the end. |
| Returns the index of the first uninitialized item added. |
| |
| arrdel: |
| void arrdel(T* a, int p); |
| Deletes the element at a[p], moving the rest of the array over. |
| |
| arrdeln: |
| void arrdeln(T* a, int p, int n); |
| Deletes n elements starting at a[p], moving the rest of the array over. |
| |
| arrdelswap: |
| void arrdelswap(T* a, int p); |
| Deletes the element at a[p], replacing it with the element from |
| the end of the array. O(1) performance. |
| |
| arrsetlen: |
| void arrsetlen(T* a, int n); |
| Changes the length of the array to n. Allocates uninitialized |
| slots at the end if necessary. |
| |
| arrsetcap: |
| size_t arrsetcap(T* a, int n); |
| Sets the length of allocated storage to at least n. It will not |
| change the length of the array. |
| |
| arrcap: |
| size_t arrcap(T* a); |
| Returns the number of total elements the array can contain without |
| needing to be reallocated. |
| |
| Hash maps & String hash maps |
| |
| Given T is a structure type: struct { TK key; TV value; }. Note that some |
| functions do not require TV value and can have other fields. For string |
| hash maps, TK must be 'char *'. |
| |
| Special interface: |
| |
| stbds_rand_seed: |
| void stbds_rand_seed(size_t seed); |
| For security against adversarially chosen data, you should seed the |
| library with a strong random number. Or at least seed it with time(). |
| |
| stbds_hash_string: |
| size_t stbds_hash_string(char *str, size_t seed); |
| Returns a hash value for a string. |
| |
| stbds_hash_bytes: |
| size_t stbds_hash_bytes(void *p, size_t len, size_t seed); |
| These functions hash an arbitrary number of bytes. The function |
| uses a custom hash for 4- and 8-byte data, and a weakened version |
| of SipHash for everything else. On 64-bit platforms you can get |
| specification-compliant SipHash-2-4 on all data by defining |
| STBDS_SIPHASH_2_4, at a significant cost in speed. |
| |
| Non-function interface: |
| |
| Declare an empty hash map of type T |
| T* foo = NULL; |
| |
| Access the i'th entry in a hash table T* foo: |
| foo[i] |
| |
| Function interface (actually macros): |
| |
| hmfree |
| shfree |
| void hmfree(T*); |
| void shfree(T*); |
| Frees the hashmap and sets the pointer to NULL. |
| |
| hmlen |
| shlen |
| ptrdiff_t hmlen(T*) |
| ptrdiff_t shlen(T*) |
| Returns the number of elements in the hashmap. |
| |
| hmlenu |
| shlenu |
| size_t hmlenu(T*) |
| size_t shlenu(T*) |
| Returns the number of elements in the hashmap. |
| |
| hmgeti |
| shgeti |
| hmgeti_ts |
| ptrdiff_t hmgeti(T*, TK key) |
| ptrdiff_t shgeti(T*, char* key) |
| ptrdiff_t hmgeti_ts(T*, TK key, ptrdiff_t tempvar) |
| Returns the index in the hashmap which has the key 'key', or -1 |
| if the key is not present. |
| |
| hmget |
| hmget_ts |
| shget |
| TV hmget(T*, TK key) |
| TV shget(T*, char* key) |
| TV hmget_ts(T*, TK key, ptrdiff_t tempvar) |
| Returns the value corresponding to 'key' in the hashmap. |
| The structure must have a 'value' field |
| |
| hmgets |
| shgets |
| T hmgets(T*, TK key) |
| T shgets(T*, char* key) |
| Returns the structure corresponding to 'key' in the hashmap. |
| |
| hmgetp |
| shgetp |
| hmgetp_ts |
| hmgetp_null |
| shgetp_null |
| T* hmgetp(T*, TK key) |
| T* shgetp(T*, char* key) |
| T* hmgetp_ts(T*, TK key, ptrdiff_t tempvar) |
| T* hmgetp_null(T*, TK key) |
| T* shgetp_null(T*, char *key) |
| Returns a pointer to the structure corresponding to 'key' in |
| the hashmap. Functions ending in "_null" return NULL if the key |
| is not present in the hashmap; the others return a pointer to a |
| structure holding the default value (but not the searched-for key). |
| |
| hmdefault |
| shdefault |
| TV hmdefault(T*, TV value) |
| TV shdefault(T*, TV value) |
| Sets the default value for the hashmap, the value which will be |
| returned by hmget/shget if the key is not present. |
| |
| hmdefaults |
| shdefaults |
| TV hmdefaults(T*, T item) |
| TV shdefaults(T*, T item) |
| Sets the default struct for the hashmap, the contents which will be |
| returned by hmgets/shgets if the key is not present. |
| |
| hmput |
| shput |
| TV hmput(T*, TK key, TV value) |
| TV shput(T*, char* key, TV value) |
| Inserts a <key,value> pair into the hashmap. If the key is already |
| present in the hashmap, updates its value. |
| |
| hmputs |
| shputs |
| T hmputs(T*, T item) |
| T shputs(T*, T item) |
| Inserts a struct with T.key into the hashmap. If the struct is already |
| present in the hashmap, updates it. |
| |
| hmdel |
| shdel |
| int hmdel(T*, TK key) |
| int shdel(T*, char* key) |
| If 'key' is in the hashmap, deletes its entry and returns 1. |
| Otherwise returns 0. |
| |
| Function interface (actually macros) for strings only: |
| |
| sh_new_strdup |
| void sh_new_strdup(T*); |
| Overwrites the existing pointer with a newly allocated |
| string hashmap which will automatically allocate and free |
| each string key using realloc/free |
| |
| sh_new_arena |
| void sh_new_arena(T*); |
| Overwrites the existing pointer with a newly allocated |
| string hashmap which will automatically allocate each string |
| key to a string arena. Every string key ever used by this |
| hash table remains in the arena until the arena is freed. |
| Additionally, any key which is deleted and reinserted will |
| be allocated multiple times in the string arena. |
| |
| NOTES |
| |
| * These data structures are realloc'd when they grow, and the macro |
| "functions" write to the provided pointer. This means: (a) the pointer |
| must be an lvalue, and (b) the pointer to the data structure is not |
| stable, and you must maintain it the same as you would a realloc'd |
| pointer. For example, if you pass a pointer to a dynamic array to a |
| function which updates it, the function must return back the new |
| pointer to the caller. This is the price of trying to do this in C. |
| |
| * The following are the only functions that are thread-safe on a single data |
| structure, i.e. can be run in multiple threads simultaneously on the same |
| data structure |
| hmlen shlen |
| hmlenu shlenu |
| hmget_ts shget_ts |
| hmgeti_ts shgeti_ts |
| hmgets_ts shgets_ts |
| |
| * You iterate over the contents of a dynamic array and a hashmap in exactly |
| the same way, using arrlen/hmlen/shlen: |
| |
| for (i=0; i < arrlen(foo); ++i) |
| ... foo[i] ... |
| |
| * All operations except arrins/arrdel are O(1) amortized, but individual |
| operations can be slow, so these data structures may not be suitable |
| for real time use. Dynamic arrays double in capacity as needed, so |
| elements are copied an average of once. Hash tables double/halve |
| their size as needed, with appropriate hysteresis to maintain O(1) |
| performance. |
| |
| NOTES - DYNAMIC ARRAY |
| |
| * If you know how long a dynamic array is going to be in advance, you can avoid |
| extra memory allocations by using arrsetlen to allocate it to that length in |
| advance and use foo[n] while filling it out, or arrsetcap to allocate the memory |
| for that length and use arrput/arrpush as normal. |
| |
| * Unlike some other versions of the dynamic array, this version should |
| be safe to use with strict-aliasing optimizations. |
| |
| NOTES - HASH MAP |
| |
| * For compilers other than GCC and clang (e.g. Visual Studio), for hmput/hmget/hmdel |
| and variants, the key must be an lvalue (so the macro can take the address of it). |
| Extensions are used that eliminate this requirement if you're using C99 and later |
| in GCC or clang, or if you're using C++ in GCC. But note that this can make your |
| code less portable. |
| |
| * To test for presence of a key in a hashmap, just do 'hmgeti(foo,key) >= 0'. |
| |
| * The iteration order of your data in the hashmap is determined solely by the |
| order of insertions and deletions. In particular, if you never delete, new |
| keys are always added at the end of the array. This will be consistent |
| across all platforms and versions of the library. However, you should not |
| attempt to serialize the internal hash table, as the hash is not consistent |
| between different platforms, and may change with future versions of the library. |
| |
| * Use sh_new_arena() for string hashmaps that you never delete from. Initialize |
| with NULL if you're managing the memory for your strings, or your strings are |
| never freed (at least until the hashmap is freed). Otherwise, use sh_new_strdup(). |
| @TODO: make an arena variant that garbage collects the strings with a trivial |
| copy collector into a new arena whenever the table shrinks / rebuilds. Since |
| current arena recommendation is to only use arena if it never deletes, then |
| this can just replace current arena implementation. |
| |
| * If adversarial input is a serious concern and you're on a 64-bit platform, |
| enable STBDS_SIPHASH_2_4 (see the 'Compile-time options' section), and pass |
| a strong random number to stbds_rand_seed. |
| |
| * The default value for the hash table is stored in foo[-1], so if you |
| use code like 'hmget(T,k)->value = 5' you can accidentally overwrite |
| the value stored by hmdefault if 'k' is not present. |
| |
| CREDITS |
| |
| Sean Barrett -- library, idea for dynamic array API/implementation |
| Per Vognsen -- idea for hash table API/implementation |
| Rafael Sachetto -- arrpop() |
| github:HeroicKatora -- arraddn() reworking |
| |
| Bugfixes: |
| Andy Durdin |
| Shane Liesegang |
| Vinh Truong |
| Andreas Molzer |
| github:hashitaku |
| github:srdjanstipic |
| Macoy Madson |
| Andreas Vennstrom |
| Tobias Mansfield-Williams |
| */ |
| |
| #ifdef STBDS_UNIT_TESTS |
| #define _CRT_SECURE_NO_WARNINGS |
| #endif |
| |
| #ifndef INCLUDE_STB_DS_H |
| #define INCLUDE_STB_DS_H |
| |
| #include <stddef.h> |
| #include <string.h> |
| |
| #ifndef STBDS_NO_SHORT_NAMES |
| #define arrlen stbds_arrlen |
| #define arrlenu stbds_arrlenu |
| #define arrput stbds_arrput |
| #define arrpush stbds_arrput |
| #define arrpop stbds_arrpop |
| #define arrfree stbds_arrfree |
| #define arraddn stbds_arraddn // deprecated, use one of the following instead: |
| #define arraddnptr stbds_arraddnptr |
| #define arraddnindex stbds_arraddnindex |
| #define arrsetlen stbds_arrsetlen |
| #define arrlast stbds_arrlast |
| #define arrins stbds_arrins |
| #define arrinsn stbds_arrinsn |
| #define arrdel stbds_arrdel |
| #define arrdeln stbds_arrdeln |
| #define arrdelswap stbds_arrdelswap |
| #define arrcap stbds_arrcap |
| #define arrsetcap stbds_arrsetcap |
| |
| #define hmput stbds_hmput |
| #define hmputs stbds_hmputs |
| #define hmget stbds_hmget |
| #define hmget_ts stbds_hmget_ts |
| #define hmgets stbds_hmgets |
| #define hmgetp stbds_hmgetp |
| #define hmgetp_ts stbds_hmgetp_ts |
| #define hmgetp_null stbds_hmgetp_null |
| #define hmgeti stbds_hmgeti |
| #define hmgeti_ts stbds_hmgeti_ts |
| #define hmdel stbds_hmdel |
| #define hmlen stbds_hmlen |
| #define hmlenu stbds_hmlenu |
| #define hmfree stbds_hmfree |
| #define hmdefault stbds_hmdefault |
| #define hmdefaults stbds_hmdefaults |
| |
| #define shput stbds_shput |
| #define shputi stbds_shputi |
| #define shputs stbds_shputs |
| #define shget stbds_shget |
| #define shgeti stbds_shgeti |
| #define shgets stbds_shgets |
| #define shgetp stbds_shgetp |
| #define shgetp_null stbds_shgetp_null |
| #define shdel stbds_shdel |
| #define shlen stbds_shlen |
| #define shlenu stbds_shlenu |
| #define shfree stbds_shfree |
| #define shdefault stbds_shdefault |
| #define shdefaults stbds_shdefaults |
| #define sh_new_arena stbds_sh_new_arena |
| #define sh_new_strdup stbds_sh_new_strdup |
| |
| #define stralloc stbds_stralloc |
| #define strreset stbds_strreset |
| #endif |
| |
| #if defined(STBDS_REALLOC) && !defined(STBDS_FREE) || !defined(STBDS_REALLOC) && defined(STBDS_FREE) |
| #error "You must define both STBDS_REALLOC and STBDS_FREE, or neither." |
| #endif |
| #if !defined(STBDS_REALLOC) && !defined(STBDS_FREE) |
| #include <stdlib.h> |
| #define STBDS_REALLOC(c,p,s) realloc(p,s) |
| #define STBDS_FREE(c,p) free(p) |
| #endif |
| |
| #ifdef _MSC_VER |
| #define STBDS_NOTUSED(v) (void)(v) |
| #else |
| #define STBDS_NOTUSED(v) (void)sizeof(v) |
| #endif |
| |
| #ifdef __cplusplus |
| extern "C" { |
| #endif |
| |
| // for security against attackers, seed the library with a random number, at least time() but stronger is better |
| extern void stbds_rand_seed(size_t seed); |
| |
| // these are the hash functions used internally if you want to test them or use them for other purposes |
| extern size_t stbds_hash_bytes(void *p, size_t len, size_t seed); |
| extern size_t stbds_hash_string(char *str, size_t seed); |
| |
| // this is a simple string arena allocator, initialize with e.g. 'stbds_string_arena my_arena={0}'. |
| typedef struct stbds_string_arena stbds_string_arena; |
| extern char * stbds_stralloc(stbds_string_arena *a, char *str); |
| extern void stbds_strreset(stbds_string_arena *a); |
| |
| // have to #define STBDS_UNIT_TESTS to call this |
| extern void stbds_unit_tests(void); |
| |
| /////////////// |
| // |
| // Everything below here is implementation details |
| // |
| |
| extern void * stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap); |
| extern void stbds_arrfreef(void *a); |
| extern void stbds_hmfree_func(void *p, size_t elemsize); |
| extern void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode); |
| extern void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode); |
| extern void * stbds_hmput_default(void *a, size_t elemsize); |
| extern void * stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode); |
| extern void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode); |
| extern void * stbds_shmode_func(size_t elemsize, int mode); |
| |
| #ifdef __cplusplus |
| } |
| #endif |
| |
| #if defined(__GNUC__) || defined(__clang__) |
| #define STBDS_HAS_TYPEOF |
| #ifdef __cplusplus |
| //#define STBDS_HAS_LITERAL_ARRAY // this is currently broken for clang |
| #endif |
| #endif |
| |
| #if !defined(__cplusplus) |
| #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L |
| #define STBDS_HAS_LITERAL_ARRAY |
| #endif |
| #endif |
| |
| // this macro takes the address of the argument, but on gcc/clang can accept rvalues |
| #if defined(STBDS_HAS_LITERAL_ARRAY) && defined(STBDS_HAS_TYPEOF) |
| #if __clang__ |
| #define STBDS_ADDRESSOF(typevar, value) ((__typeof__(typevar)[1]){value}) // literal array decays to pointer to value |
| #else |
| #define STBDS_ADDRESSOF(typevar, value) ((typeof(typevar)[1]){value}) // literal array decays to pointer to value |
| #endif |
| #else |
| #define STBDS_ADDRESSOF(typevar, value) &(value) |
| #endif |
| |
| #define STBDS_OFFSETOF(var,field) ((char *) &(var)->field - (char *) (var)) |
| |
| #define stbds_header(t) ((stbds_array_header *) (t) - 1) |
| #define stbds_temp(t) stbds_header(t)->temp |
| #define stbds_temp_key(t) (*(char **) stbds_header(t)->hash_table) |
| |
| #define stbds_arrsetcap(a,n) (stbds_arrgrow(a,0,n)) |
| #define stbds_arrsetlen(a,n) ((stbds_arrcap(a) < (size_t) (n) ? stbds_arrsetcap((a),(size_t)(n)),0 : 0), (a) ? stbds_header(a)->length = (size_t) (n) : 0) |
| #define stbds_arrcap(a) ((a) ? stbds_header(a)->capacity : 0) |
| #define stbds_arrlen(a) ((a) ? (ptrdiff_t) stbds_header(a)->length : 0) |
| #define stbds_arrlenu(a) ((a) ? stbds_header(a)->length : 0) |
| #define stbds_arrput(a,v) (stbds_arrmaybegrow(a,1), (a)[stbds_header(a)->length++] = (v)) |
| #define stbds_arrpush stbds_arrput // synonym |
| #define stbds_arrpop(a) (stbds_header(a)->length--, (a)[stbds_header(a)->length]) |
| #define stbds_arraddn(a,n) ((void)(stbds_arraddnindex(a, n))) // deprecated, use one of the following instead: |
| #define stbds_arraddnptr(a,n) (stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), &(a)[stbds_header(a)->length-(n)]) : (a)) |
| #define stbds_arraddnindex(a,n)(stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), stbds_header(a)->length-(n)) : stbds_arrlen(a)) |
| #define stbds_arraddnoff stbds_arraddnindex |
| #define stbds_arrlast(a) ((a)[stbds_header(a)->length-1]) |
| #define stbds_arrfree(a) ((void) ((a) ? STBDS_FREE(NULL,stbds_header(a)) : (void)0), (a)=NULL) |
| #define stbds_arrdel(a,i) stbds_arrdeln(a,i,1) |
| #define stbds_arrdeln(a,i,n) (memmove(&(a)[i], &(a)[(i)+(n)], sizeof *(a) * (stbds_header(a)->length-(n)-(i))), stbds_header(a)->length -= (n)) |
| #define stbds_arrdelswap(a,i) ((a)[i] = stbds_arrlast(a), stbds_header(a)->length -= 1) |
| #define stbds_arrinsn(a,i,n) (stbds_arraddn((a),(n)), memmove(&(a)[(i)+(n)], &(a)[i], sizeof *(a) * (stbds_header(a)->length-(n)-(i)))) |
| #define stbds_arrins(a,i,v) (stbds_arrinsn((a),(i),1), (a)[i]=(v)) |
| |
| #define stbds_arrmaybegrow(a,n) ((!(a) || stbds_header(a)->length + (n) > stbds_header(a)->capacity) \ |
| ? (stbds_arrgrow(a,n,0),0) : 0) |
| |
| #define stbds_arrgrow(a,b,c) ((a) = stbds_arrgrowf_wrapper((a), sizeof *(a), (b), (c))) |
| |
| #define stbds_hmput(t, k, v) \ |
| ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, 0), \ |
| (t)[stbds_temp((t)-1)].key = (k), \ |
| (t)[stbds_temp((t)-1)].value = (v)) |
| |
| #define stbds_hmputs(t, s) \ |
| ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), &(s).key, sizeof (s).key, STBDS_HM_BINARY), \ |
| (t)[stbds_temp((t)-1)] = (s)) |
| |
| #define stbds_hmgeti(t,k) \ |
| ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_HM_BINARY), \ |
| stbds_temp((t)-1)) |
| |
| #define stbds_hmgeti_ts(t,k,temp) \ |
| ((t) = stbds_hmget_key_ts_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, &(temp), STBDS_HM_BINARY), \ |
| (temp)) |
| |
| #define stbds_hmgetp(t, k) \ |
| ((void) stbds_hmgeti(t,k), &(t)[stbds_temp((t)-1)]) |
| |
| #define stbds_hmgetp_ts(t, k, temp) \ |
| ((void) stbds_hmgeti_ts(t,k,temp), &(t)[temp]) |
| |
| #define stbds_hmdel(t,k) \ |
| (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_BINARY)),(t)?stbds_temp((t)-1):0) |
| |
| #define stbds_hmdefault(t, v) \ |
| ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1].value = (v)) |
| |
| #define stbds_hmdefaults(t, s) \ |
| ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1] = (s)) |
| |
| #define stbds_hmfree(p) \ |
| ((void) ((p) != NULL ? stbds_hmfree_func((p)-1,sizeof*(p)),0 : 0),(p)=NULL) |
| |
| #define stbds_hmgets(t, k) (*stbds_hmgetp(t,k)) |
| #define stbds_hmget(t, k) (stbds_hmgetp(t,k)->value) |
| #define stbds_hmget_ts(t, k, temp) (stbds_hmgetp_ts(t,k,temp)->value) |
| #define stbds_hmlen(t) ((t) ? (ptrdiff_t) stbds_header((t)-1)->length-1 : 0) |
| #define stbds_hmlenu(t) ((t) ? stbds_header((t)-1)->length-1 : 0) |
| #define stbds_hmgetp_null(t,k) (stbds_hmgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)]) |
| |
| #define stbds_shput(t, k, v) \ |
| ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ |
| (t)[stbds_temp((t)-1)].value = (v)) |
| |
| #define stbds_shputi(t, k, v) \ |
| ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ |
| (t)[stbds_temp((t)-1)].value = (v), stbds_temp((t)-1)) |
| |
| #define stbds_shputs(t, s) \ |
| ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (s).key, sizeof (s).key, STBDS_HM_STRING), \ |
| (t)[stbds_temp((t)-1)] = (s), \ |
| (t)[stbds_temp((t)-1)].key = stbds_temp_key((t)-1)) // above line overwrites whole structure, so must rewrite key here if it was allocated internally |
| |
| #define stbds_pshput(t, p) \ |
| ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (p)->key, sizeof (p)->key, STBDS_HM_PTR_TO_STRING), \ |
| (t)[stbds_temp((t)-1)] = (p)) |
| |
| #define stbds_shgeti(t,k) \ |
| ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ |
| stbds_temp((t)-1)) |
| |
| #define stbds_pshgeti(t,k) \ |
| ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_HM_PTR_TO_STRING), \ |
| stbds_temp((t)-1)) |
| |
| #define stbds_shgetp(t, k) \ |
| ((void) stbds_shgeti(t,k), &(t)[stbds_temp((t)-1)]) |
| |
| #define stbds_pshget(t, k) \ |
| ((void) stbds_pshgeti(t,k), (t)[stbds_temp((t)-1)]) |
| |
| #define stbds_shdel(t,k) \ |
| (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_STRING)),(t)?stbds_temp((t)-1):0) |
| #define stbds_pshdel(t,k) \ |
| (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_OFFSETOF(*(t),key), STBDS_HM_PTR_TO_STRING)),(t)?stbds_temp((t)-1):0) |
| |
| #define stbds_sh_new_arena(t) \ |
| ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_ARENA)) |
| #define stbds_sh_new_strdup(t) \ |
| ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_STRDUP)) |
| |
| #define stbds_shdefault(t, v) stbds_hmdefault(t,v) |
| #define stbds_shdefaults(t, s) stbds_hmdefaults(t,s) |
| |
| #define stbds_shfree stbds_hmfree |
| #define stbds_shlenu stbds_hmlenu |
| |
| #define stbds_shgets(t, k) (*stbds_shgetp(t,k)) |
| #define stbds_shget(t, k) (stbds_shgetp(t,k)->value) |
| #define stbds_shgetp_null(t,k) (stbds_shgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)]) |
| #define stbds_shlen stbds_hmlen |
| |
| typedef struct |
| { |
| size_t length; |
| size_t capacity; |
| void * hash_table; |
| ptrdiff_t temp; |
| } stbds_array_header; |
| |
| typedef struct stbds_string_block |
| { |
| struct stbds_string_block *next; |
| char storage[8]; |
| } stbds_string_block; |
| |
| struct stbds_string_arena |
| { |
| stbds_string_block *storage; |
| size_t remaining; |
| unsigned char block; |
| unsigned char mode; // this isn't used by the string arena itself |
| }; |
| |
| #define STBDS_HM_BINARY 0 |
| #define STBDS_HM_STRING 1 |
| |
| enum |
| { |
| STBDS_SH_NONE, |
| STBDS_SH_DEFAULT, |
| STBDS_SH_STRDUP, |
| STBDS_SH_ARENA |
| }; |
| |
| #ifdef __cplusplus |
| // in C we use implicit assignment from these void*-returning functions to T*. |
| // in C++ these templates make the same code work |
| template<class T> static T * stbds_arrgrowf_wrapper(T *a, size_t elemsize, size_t addlen, size_t min_cap) { |
| return (T*)stbds_arrgrowf((void *)a, elemsize, addlen, min_cap); |
| } |
| template<class T> static T * stbds_hmget_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) { |
| return (T*)stbds_hmget_key((void*)a, elemsize, key, keysize, mode); |
| } |
| template<class T> static T * stbds_hmget_key_ts_wrapper(T *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) { |
| return (T*)stbds_hmget_key_ts((void*)a, elemsize, key, keysize, temp, mode); |
| } |
| template<class T> static T * stbds_hmput_default_wrapper(T *a, size_t elemsize) { |
| return (T*)stbds_hmput_default((void *)a, elemsize); |
| } |
| template<class T> static T * stbds_hmput_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) { |
| return (T*)stbds_hmput_key((void*)a, elemsize, key, keysize, mode); |
| } |
| template<class T> static T * stbds_hmdel_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode){ |
| return (T*)stbds_hmdel_key((void*)a, elemsize, key, keysize, keyoffset, mode); |
| } |
| template<class T> static T * stbds_shmode_func_wrapper(T *, size_t elemsize, int mode) { |
| return (T*)stbds_shmode_func(elemsize, mode); |
| } |
| #else |
| #define stbds_arrgrowf_wrapper stbds_arrgrowf |
| #define stbds_hmget_key_wrapper stbds_hmget_key |
| #define stbds_hmget_key_ts_wrapper stbds_hmget_key_ts |
| #define stbds_hmput_default_wrapper stbds_hmput_default |
| #define stbds_hmput_key_wrapper stbds_hmput_key |
| #define stbds_hmdel_key_wrapper stbds_hmdel_key |
| #define stbds_shmode_func_wrapper(t,e,m) stbds_shmode_func(e,m) |
| #endif |
| |
| #endif // INCLUDE_STB_DS_H |
| |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // |
| // IMPLEMENTATION |
| // |
| |
| #ifdef STB_DS_IMPLEMENTATION |
| #include <assert.h> |
| #include <string.h> |
| |
| #ifndef STBDS_ASSERT |
| #define STBDS_ASSERT_WAS_UNDEFINED |
| #define STBDS_ASSERT(x) ((void) 0) |
| #endif |
| |
| #ifdef STBDS_STATISTICS |
| #define STBDS_STATS(x) x |
| size_t stbds_array_grow; |
| size_t stbds_hash_grow; |
| size_t stbds_hash_shrink; |
| size_t stbds_hash_rebuild; |
| size_t stbds_hash_probes; |
| size_t stbds_hash_alloc; |
| size_t stbds_rehash_probes; |
| size_t stbds_rehash_items; |
| #else |
| #define STBDS_STATS(x) |
| #endif |
| |
| // |
| // stbds_arr implementation |
| // |
| |
| //int *prev_allocs[65536]; |
| //int num_prev; |
| |
| void *stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap) |
| { |
| stbds_array_header temp={0}; // force debugging |
| void *b; |
| size_t min_len = stbds_arrlen(a) + addlen; |
| (void) sizeof(temp); |
| |
| // compute the minimum capacity needed |
| if (min_len > min_cap) |
| min_cap = min_len; |
| |
| if (min_cap <= stbds_arrcap(a)) |
| return a; |
| |
| // increase needed capacity to guarantee O(1) amortized |
| if (min_cap < 2 * stbds_arrcap(a)) |
| min_cap = 2 * stbds_arrcap(a); |
| else if (min_cap < 4) |
| min_cap = 4; |
| |
| //if (num_prev < 65536) if (a) prev_allocs[num_prev++] = (int *) ((char *) a+1); |
| //if (num_prev == 2201) |
| // num_prev = num_prev; |
| b = STBDS_REALLOC(NULL, (a) ? stbds_header(a) : 0, elemsize * min_cap + sizeof(stbds_array_header)); |
| //if (num_prev < 65536) prev_allocs[num_prev++] = (int *) (char *) b; |
| b = (char *) b + sizeof(stbds_array_header); |
| if (a == NULL) { |
| stbds_header(b)->length = 0; |
| stbds_header(b)->hash_table = 0; |
| stbds_header(b)->temp = 0; |
| } else { |
| STBDS_STATS(++stbds_array_grow); |
| } |
| stbds_header(b)->capacity = min_cap; |
| |
| return b; |
| } |
| |
| void stbds_arrfreef(void *a) |
| { |
| STBDS_FREE(NULL, stbds_header(a)); |
| } |
| |
| // |
| // stbds_hm hash table implementation |
| // |
| |
| #ifdef STBDS_INTERNAL_SMALL_BUCKET |
| #define STBDS_BUCKET_LENGTH 4 |
| #else |
| #define STBDS_BUCKET_LENGTH 8 |
| #endif |
| |
| #define STBDS_BUCKET_SHIFT (STBDS_BUCKET_LENGTH == 8 ? 3 : 2) |
| #define STBDS_BUCKET_MASK (STBDS_BUCKET_LENGTH-1) |
| #define STBDS_CACHE_LINE_SIZE 64 |
| |
| #define STBDS_ALIGN_FWD(n,a) (((n) + (a) - 1) & ~((a)-1)) |
| |
| typedef struct |
| { |
| size_t hash [STBDS_BUCKET_LENGTH]; |
| ptrdiff_t index[STBDS_BUCKET_LENGTH]; |
| } stbds_hash_bucket; // in 32-bit, this is one 64-byte cache line; in 64-bit, each array is one 64-byte cache line |
| |
| typedef struct |
| { |
| char * temp_key; // this MUST be the first field of the hash table |
| size_t slot_count; |
| size_t used_count; |
| size_t used_count_threshold; |
| size_t used_count_shrink_threshold; |
| size_t tombstone_count; |
| size_t tombstone_count_threshold; |
| size_t seed; |
| size_t slot_count_log2; |
| stbds_string_arena string; |
| stbds_hash_bucket *storage; // not a separate allocation, just 64-byte aligned storage after this struct |
| } stbds_hash_index; |
| |
| #define STBDS_INDEX_EMPTY -1 |
| #define STBDS_INDEX_DELETED -2 |
| #define STBDS_INDEX_IN_USE(x) ((x) >= 0) |
| |
| #define STBDS_HASH_EMPTY 0 |
| #define STBDS_HASH_DELETED 1 |
| |
| static size_t stbds_hash_seed=0x31415926; |
| |
| void stbds_rand_seed(size_t seed) |
| { |
| stbds_hash_seed = seed; |
| } |
| |
| #define stbds_load_32_or_64(var, temp, v32, v64_hi, v64_lo) \ |
| temp = v64_lo ^ v32, temp <<= 16, temp <<= 16, temp >>= 16, temp >>= 16, /* discard if 32-bit */ \ |
| var = v64_hi, var <<= 16, var <<= 16, /* discard if 32-bit */ \ |
| var ^= temp ^ v32 |
| |
| #define STBDS_SIZE_T_BITS ((sizeof (size_t)) * 8) |
| |
| static size_t stbds_probe_position(size_t hash, size_t slot_count, size_t slot_log2) |
| { |
| size_t pos; |
| STBDS_NOTUSED(slot_log2); |
| pos = hash & (slot_count-1); |
| #ifdef STBDS_INTERNAL_BUCKET_START |
| pos &= ~STBDS_BUCKET_MASK; |
| #endif |
| return pos; |
| } |
| |
| static size_t stbds_log2(size_t slot_count) |
| { |
| size_t n=0; |
| while (slot_count > 1) { |
| slot_count >>= 1; |
| ++n; |
| } |
| return n; |
| } |
| |
| static stbds_hash_index *stbds_make_hash_index(size_t slot_count, stbds_hash_index *ot) |
| { |
| stbds_hash_index *t; |
| t = (stbds_hash_index *) STBDS_REALLOC(NULL,0,(slot_count >> STBDS_BUCKET_SHIFT) * sizeof(stbds_hash_bucket) + sizeof(stbds_hash_index) + STBDS_CACHE_LINE_SIZE-1); |
| t->storage = (stbds_hash_bucket *) STBDS_ALIGN_FWD((size_t) (t+1), STBDS_CACHE_LINE_SIZE); |
| t->slot_count = slot_count; |
| t->slot_count_log2 = stbds_log2(slot_count); |
| t->tombstone_count = 0; |
| t->used_count = 0; |
| |
| #if 0 // A1 |
| t->used_count_threshold = slot_count*12/16; // if 12/16th of table is occupied, grow |
| t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild |
| t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink |
| #elif 1 // A2 |
| //t->used_count_threshold = slot_count*12/16; // if 12/16th of table is occupied, grow |
| //t->tombstone_count_threshold = slot_count* 3/16; // if tombstones are 3/16th of table, rebuild |
| //t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink |
| |
| // compute without overflowing |
| t->used_count_threshold = slot_count - (slot_count>>2); |
| t->tombstone_count_threshold = (slot_count>>3) + (slot_count>>4); |
| t->used_count_shrink_threshold = slot_count >> 2; |
| |
| #elif 0 // B1 |
| t->used_count_threshold = slot_count*13/16; // if 13/16th of table is occupied, grow |
| t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild |
| t->used_count_shrink_threshold = slot_count* 5/16; // if table is only 5/16th full, shrink |
| #else // C1 |
| t->used_count_threshold = slot_count*14/16; // if 14/16th of table is occupied, grow |
| t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild |
| t->used_count_shrink_threshold = slot_count* 6/16; // if table is only 6/16th full, shrink |
| #endif |
| // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2 |
| // Note that the larger tables have high variance as they were run fewer times |
| // A1 A2 B1 C1 |
| // 0.10ms : 0.10ms : 0.10ms : 0.11ms : 2,000 inserts creating 2K table |
| // 0.96ms : 0.95ms : 0.97ms : 1.04ms : 20,000 inserts creating 20K table |
| // 14.48ms : 14.46ms : 10.63ms : 11.00ms : 200,000 inserts creating 200K table |
| // 195.74ms : 196.35ms : 203.69ms : 214.92ms : 2,000,000 inserts creating 2M table |
| // 2193.88ms : 2209.22ms : 2285.54ms : 2437.17ms : 20,000,000 inserts creating 20M table |
| // 65.27ms : 53.77ms : 65.33ms : 65.47ms : 500,000 inserts & deletes in 2K table |
| // 72.78ms : 62.45ms : 71.95ms : 72.85ms : 500,000 inserts & deletes in 20K table |
| // 89.47ms : 77.72ms : 96.49ms : 96.75ms : 500,000 inserts & deletes in 200K table |
| // 97.58ms : 98.14ms : 97.18ms : 97.53ms : 500,000 inserts & deletes in 2M table |
| // 118.61ms : 119.62ms : 120.16ms : 118.86ms : 500,000 inserts & deletes in 20M table |
| // 192.11ms : 194.39ms : 196.38ms : 195.73ms : 500,000 inserts & deletes in 200M table |
| |
| if (slot_count <= STBDS_BUCKET_LENGTH) |
| t->used_count_shrink_threshold = 0; |
| // to avoid infinite loop, we need to guarantee that at least one slot is empty and will terminate probes |
| STBDS_ASSERT(t->used_count_threshold + t->tombstone_count_threshold < t->slot_count); |
| STBDS_STATS(++stbds_hash_alloc); |
| if (ot) { |
| t->string = ot->string; |
| // reuse old seed so we can reuse old hashes so below "copy out old data" doesn't do any hashing |
| t->seed = ot->seed; |
| } else { |
| size_t a,b,temp; |
| memset(&t->string, 0, sizeof(t->string)); |
| t->seed = stbds_hash_seed; |
| // LCG |
| // in 32-bit, a = 2147001325 b = 715136305 |
| // in 64-bit, a = 2862933555777941757 b = 3037000493 |
| stbds_load_32_or_64(a,temp, 2147001325, 0x27bb2ee6, 0x87b0b0fd); |
| stbds_load_32_or_64(b,temp, 715136305, 0, 0xb504f32d); |
| stbds_hash_seed = stbds_hash_seed * a + b; |
| } |
| |
| { |
| size_t i,j; |
| for (i=0; i < slot_count >> STBDS_BUCKET_SHIFT; ++i) { |
| stbds_hash_bucket *b = &t->storage[i]; |
| for (j=0; j < STBDS_BUCKET_LENGTH; ++j) |
| b->hash[j] = STBDS_HASH_EMPTY; |
| for (j=0; j < STBDS_BUCKET_LENGTH; ++j) |
| b->index[j] = STBDS_INDEX_EMPTY; |
| } |
| } |
| |
| // copy out the old data, if any |
| if (ot) { |
| size_t i,j; |
| t->used_count = ot->used_count; |
| for (i=0; i < ot->slot_count >> STBDS_BUCKET_SHIFT; ++i) { |
| stbds_hash_bucket *ob = &ot->storage[i]; |
| for (j=0; j < STBDS_BUCKET_LENGTH; ++j) { |
| if (STBDS_INDEX_IN_USE(ob->index[j])) { |
| size_t hash = ob->hash[j]; |
| size_t pos = stbds_probe_position(hash, t->slot_count, t->slot_count_log2); |
| size_t step = STBDS_BUCKET_LENGTH; |
| STBDS_STATS(++stbds_rehash_items); |
| for (;;) { |
| size_t limit,z; |
| stbds_hash_bucket *bucket; |
| bucket = &t->storage[pos >> STBDS_BUCKET_SHIFT]; |
| STBDS_STATS(++stbds_rehash_probes); |
| |
| for (z=pos & STBDS_BUCKET_MASK; z < STBDS_BUCKET_LENGTH; ++z) { |
| if (bucket->hash[z] == 0) { |
| bucket->hash[z] = hash; |
| bucket->index[z] = ob->index[j]; |
| goto done; |
| } |
| } |
| |
| limit = pos & STBDS_BUCKET_MASK; |
| for (z = 0; z < limit; ++z) { |
| if (bucket->hash[z] == 0) { |
| bucket->hash[z] = hash; |
| bucket->index[z] = ob->index[j]; |
| goto done; |
| } |
| } |
| |
| pos += step; // quadratic probing |
| step += STBDS_BUCKET_LENGTH; |
| pos &= (t->slot_count-1); |
| } |
| } |
| done: |
| ; |
| } |
| } |
| } |
| |
| return t; |
| } |
| |
| #define STBDS_ROTATE_LEFT(val, n) (((val) << (n)) | ((val) >> (STBDS_SIZE_T_BITS - (n)))) |
| #define STBDS_ROTATE_RIGHT(val, n) (((val) >> (n)) | ((val) << (STBDS_SIZE_T_BITS - (n)))) |
| |
| size_t stbds_hash_string(char *str, size_t seed) |
| { |
| size_t hash = seed; |
| while (*str) |
| hash = STBDS_ROTATE_LEFT(hash, 9) + (unsigned char) *str++; |
| |
| // Thomas Wang 64-to-32 bit mix function, hopefully also works in 32 bits |
| hash ^= seed; |
| hash = (~hash) + (hash << 18); |
| hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,31); |
| hash = hash * 21; |
| hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,11); |
| hash += (hash << 6); |
| hash ^= STBDS_ROTATE_RIGHT(hash,22); |
| return hash+seed; |
| } |
| |
| #ifdef STBDS_SIPHASH_2_4 |
| #define STBDS_SIPHASH_C_ROUNDS 2 |
| #define STBDS_SIPHASH_D_ROUNDS 4 |
| typedef int STBDS_SIPHASH_2_4_can_only_be_used_in_64_bit_builds[sizeof(size_t) == 8 ? 1 : -1]; |
| #endif |
| |
| #ifndef STBDS_SIPHASH_C_ROUNDS |
| #define STBDS_SIPHASH_C_ROUNDS 1 |
| #endif |
| #ifndef STBDS_SIPHASH_D_ROUNDS |
| #define STBDS_SIPHASH_D_ROUNDS 1 |
| #endif |
| |
| #ifdef _MSC_VER |
| #pragma warning(push) |
| #pragma warning(disable:4127) // conditional expression is constant, for do..while(0) and sizeof()== |
| #endif |
| |
| static size_t stbds_siphash_bytes(void *p, size_t len, size_t seed) |
| { |
| unsigned char *d = (unsigned char *) p; |
| size_t i,j; |
| size_t v0,v1,v2,v3, data; |
| |
| // hash that works on 32- or 64-bit registers without knowing which we have |
| // (computes different results on 32-bit and 64-bit platform) |
| // derived from siphash, but on 32-bit platforms very different as it uses 4 32-bit state not 4 64-bit |
| v0 = ((((size_t) 0x736f6d65 << 16) << 16) + 0x70736575) ^ seed; |
| v1 = ((((size_t) 0x646f7261 << 16) << 16) + 0x6e646f6d) ^ ~seed; |
| v2 = ((((size_t) 0x6c796765 << 16) << 16) + 0x6e657261) ^ seed; |
| v3 = ((((size_t) 0x74656462 << 16) << 16) + 0x79746573) ^ ~seed; |
| |
| #ifdef STBDS_TEST_SIPHASH_2_4 |
| // hardcoded with key material in the siphash test vectors |
| v0 ^= 0x0706050403020100ull ^ seed; |
| v1 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed; |
| v2 ^= 0x0706050403020100ull ^ seed; |
| v3 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed; |
| #endif |
| |
| #define STBDS_SIPROUND() \ |
| do { \ |
| v0 += v1; v1 = STBDS_ROTATE_LEFT(v1, 13); v1 ^= v0; v0 = STBDS_ROTATE_LEFT(v0,STBDS_SIZE_T_BITS/2); \ |
| v2 += v3; v3 = STBDS_ROTATE_LEFT(v3, 16); v3 ^= v2; \ |
| v2 += v1; v1 = STBDS_ROTATE_LEFT(v1, 17); v1 ^= v2; v2 = STBDS_ROTATE_LEFT(v2,STBDS_SIZE_T_BITS/2); \ |
| v0 += v3; v3 = STBDS_ROTATE_LEFT(v3, 21); v3 ^= v0; \ |
| } while (0) |
| |
| for (i=0; i+sizeof(size_t) <= len; i += sizeof(size_t), d += sizeof(size_t)) { |
| data = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); |
| data |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // discarded if size_t == 4 |
| |
| v3 ^= data; |
| for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j) |
| STBDS_SIPROUND(); |
| v0 ^= data; |
| } |
| data = len << (STBDS_SIZE_T_BITS-8); |
| switch (len - i) { |
| case 7: data |= ((size_t) d[6] << 24) << 24; // fall through |
| case 6: data |= ((size_t) d[5] << 20) << 20; // fall through |
| case 5: data |= ((size_t) d[4] << 16) << 16; // fall through |
| case 4: data |= (d[3] << 24); // fall through |
| case 3: data |= (d[2] << 16); // fall through |
| case 2: data |= (d[1] << 8); // fall through |
| case 1: data |= d[0]; // fall through |
| case 0: break; |
| } |
| v3 ^= data; |
| for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j) |
| STBDS_SIPROUND(); |
| v0 ^= data; |
| v2 ^= 0xff; |
| for (j=0; j < STBDS_SIPHASH_D_ROUNDS; ++j) |
| STBDS_SIPROUND(); |
| |
| #ifdef STBDS_SIPHASH_2_4 |
| return v0^v1^v2^v3; |
| #else |
| return v1^v2^v3; // slightly stronger since v0^v3 in above cancels out final round operation? I tweeted at the authors of SipHash about this but they didn't reply |
| #endif |
| } |
| |
| size_t stbds_hash_bytes(void *p, size_t len, size_t seed) |
| { |
| #ifdef STBDS_SIPHASH_2_4 |
| return stbds_siphash_bytes(p,len,seed); |
| #else |
| unsigned char *d = (unsigned char *) p; |
| |
| if (len == 4) { |
| unsigned int hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); |
| #if 0 |
| // HASH32-A Bob Jenkin's hash function w/o large constants |
| hash ^= seed; |
| hash -= (hash<<6); |
| hash ^= (hash>>17); |
| hash -= (hash<<9); |
| hash ^= seed; |
| hash ^= (hash<<4); |
| hash -= (hash<<3); |
| hash ^= (hash<<10); |
| hash ^= (hash>>15); |
| #elif 1 |
| // HASH32-BB Bob Jenkin's presumably-accidental version of Thomas Wang hash with rotates turned into shifts. |
| // Note that converting these back to rotates makes it run a lot slower, presumably due to collisions, so I'm |
| // not really sure what's going on. |
| hash ^= seed; |
| hash = (hash ^ 61) ^ (hash >> 16); |
| hash = hash + (hash << 3); |
| hash = hash ^ (hash >> 4); |
| hash = hash * 0x27d4eb2d; |
| hash ^= seed; |
| hash = hash ^ (hash >> 15); |
| #else // HASH32-C - Murmur3 |
| hash ^= seed; |
| hash *= 0xcc9e2d51; |
| hash = (hash << 17) | (hash >> 15); |
| hash *= 0x1b873593; |
| hash ^= seed; |
| hash = (hash << 19) | (hash >> 13); |
| hash = hash*5 + 0xe6546b64; |
| hash ^= hash >> 16; |
| hash *= 0x85ebca6b; |
| hash ^= seed; |
| hash ^= hash >> 13; |
| hash *= 0xc2b2ae35; |
| hash ^= hash >> 16; |
| #endif |
| // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2 |
| // Note that the larger tables have high variance as they were run fewer times |
| // HASH32-A // HASH32-BB // HASH32-C |
| // 0.10ms // 0.10ms // 0.10ms : 2,000 inserts creating 2K table |
| // 0.96ms // 0.95ms // 0.99ms : 20,000 inserts creating 20K table |
| // 14.69ms // 14.43ms // 14.97ms : 200,000 inserts creating 200K table |
| // 199.99ms // 195.36ms // 202.05ms : 2,000,000 inserts creating 2M table |
| // 2234.84ms // 2187.74ms // 2240.38ms : 20,000,000 inserts creating 20M table |
| // 55.68ms // 53.72ms // 57.31ms : 500,000 inserts & deletes in 2K table |
| // 63.43ms // 61.99ms // 65.73ms : 500,000 inserts & deletes in 20K table |
| // 80.04ms // 77.96ms // 81.83ms : 500,000 inserts & deletes in 200K table |
| // 100.42ms // 97.40ms // 102.39ms : 500,000 inserts & deletes in 2M table |
| // 119.71ms // 120.59ms // 121.63ms : 500,000 inserts & deletes in 20M table |
| // 185.28ms // 195.15ms // 187.74ms : 500,000 inserts & deletes in 200M table |
| // 15.58ms // 14.79ms // 15.52ms : 200,000 inserts creating 200K table with varying key spacing |
| |
| return (((size_t) hash << 16 << 16) | hash) ^ seed; |
| } else if (len == 8 && sizeof(size_t) == 8) { |
| size_t hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); |
| hash |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // avoid warning if size_t == 4 |
| hash ^= seed; |
| hash = (~hash) + (hash << 21); |
| hash ^= STBDS_ROTATE_RIGHT(hash,24); |
| hash *= 265; |
| hash ^= STBDS_ROTATE_RIGHT(hash,14); |
| hash ^= seed; |
| hash *= 21; |
| hash ^= STBDS_ROTATE_RIGHT(hash,28); |
| hash += (hash << 31); |
| hash = (~hash) + (hash << 18); |
| return hash; |
| } else { |
| return stbds_siphash_bytes(p,len,seed); |
| } |
| #endif |
| } |
| #ifdef _MSC_VER |
| #pragma warning(pop) |
| #endif |
| |
| |
| static int stbds_is_key_equal(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode, size_t i) |
| { |
| if (mode >= STBDS_HM_STRING) |
| return 0==strcmp((char *) key, * (char **) ((char *) a + elemsize*i + keyoffset)); |
| else |
| return 0==memcmp(key, (char *) a + elemsize*i + keyoffset, keysize); |
| } |
| |
| #define STBDS_HASH_TO_ARR(x,elemsize) ((char*) (x) - (elemsize)) |
| #define STBDS_ARR_TO_HASH(x,elemsize) ((char*) (x) + (elemsize)) |
| |
| #define stbds_hash_table(a) ((stbds_hash_index *) stbds_header(a)->hash_table) |
| |
| void stbds_hmfree_func(void *a, size_t elemsize) |
| { |
| if (a == NULL) return; |
| if (stbds_hash_table(a) != NULL) { |
| if (stbds_hash_table(a)->string.mode == STBDS_SH_STRDUP) { |
| size_t i; |
| // skip 0th element, which is default |
| for (i=1; i < stbds_header(a)->length; ++i) |
| STBDS_FREE(NULL, *(char**) ((char *) a + elemsize*i)); |
| } |
| stbds_strreset(&stbds_hash_table(a)->string); |
| } |
| STBDS_FREE(NULL, stbds_header(a)->hash_table); |
| STBDS_FREE(NULL, stbds_header(a)); |
| } |
| |
| static ptrdiff_t stbds_hm_find_slot(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode) |
| { |
| void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); |
| stbds_hash_index *table = stbds_hash_table(raw_a); |
| size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed); |
| size_t step = STBDS_BUCKET_LENGTH; |
| size_t limit,i; |
| size_t pos; |
| stbds_hash_bucket *bucket; |
| |
| if (hash < 2) hash += 2; // stored hash values are forbidden from being 0, so we can detect empty slots |
| |
| pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2); |
| |
| for (;;) { |
| STBDS_STATS(++stbds_hash_probes); |
| bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; |
| |
| // start searching from pos to end of bucket, this should help performance on small hash tables that fit in cache |
| for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { |
| if (bucket->hash[i] == hash) { |
| if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { |
| return (pos & ~STBDS_BUCKET_MASK)+i; |
| } |
| } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { |
| return -1; |
| } |
| } |
| |
| // search from beginning of bucket to pos |
| limit = pos & STBDS_BUCKET_MASK; |
| for (i = 0; i < limit; ++i) { |
| if (bucket->hash[i] == hash) { |
| if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { |
| return (pos & ~STBDS_BUCKET_MASK)+i; |
| } |
| } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { |
| return -1; |
| } |
| } |
| |
| // quadratic probing |
| pos += step; |
| step += STBDS_BUCKET_LENGTH; |
| pos &= (table->slot_count-1); |
| } |
| /* NOTREACHED */ |
| } |
| |
| void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) |
| { |
| size_t keyoffset = 0; |
| if (a == NULL) { |
| // make it non-empty so we can return a temp |
| a = stbds_arrgrowf(0, elemsize, 0, 1); |
| stbds_header(a)->length += 1; |
| memset(a, 0, elemsize); |
| *temp = STBDS_INDEX_EMPTY; |
| // adjust a to point after the default element |
| return STBDS_ARR_TO_HASH(a,elemsize); |
| } else { |
| stbds_hash_index *table; |
| void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); |
| // adjust a to point to the default element |
| table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; |
| if (table == 0) { |
| *temp = -1; |
| } else { |
| ptrdiff_t slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); |
| if (slot < 0) { |
| *temp = STBDS_INDEX_EMPTY; |
| } else { |
| stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; |
| *temp = b->index[slot & STBDS_BUCKET_MASK]; |
| } |
| } |
| return a; |
| } |
| } |
| |
| void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode) |
| { |
| ptrdiff_t temp; |
| void *p = stbds_hmget_key_ts(a, elemsize, key, keysize, &temp, mode); |
| stbds_temp(STBDS_HASH_TO_ARR(p,elemsize)) = temp; |
| return p; |
| } |
| |
| void * stbds_hmput_default(void *a, size_t elemsize) |
| { |
| // three cases: |
| // a is NULL <- allocate |
| // a has a hash table but no entries, because of shmode <- grow |
| // a has entries <- do nothing |
| if (a == NULL || stbds_header(STBDS_HASH_TO_ARR(a,elemsize))->length == 0) { |
| a = stbds_arrgrowf(a ? STBDS_HASH_TO_ARR(a,elemsize) : NULL, elemsize, 0, 1); |
| stbds_header(a)->length += 1; |
| memset(a, 0, elemsize); |
| a=STBDS_ARR_TO_HASH(a,elemsize); |
| } |
| return a; |
| } |
| |
| static char *stbds_strdup(char *str); |
| |
| void *stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode) |
| { |
| size_t keyoffset=0; |
| void *raw_a; |
| stbds_hash_index *table; |
| |
| if (a == NULL) { |
| a = stbds_arrgrowf(0, elemsize, 0, 1); |
| memset(a, 0, elemsize); |
| stbds_header(a)->length += 1; |
| // adjust a to point AFTER the default element |
| a = STBDS_ARR_TO_HASH(a,elemsize); |
| } |
| |
| // adjust a to point to the default element |
| raw_a = a; |
| a = STBDS_HASH_TO_ARR(a,elemsize); |
| |
| table = (stbds_hash_index *) stbds_header(a)->hash_table; |
| |
| if (table == NULL || table->used_count >= table->used_count_threshold) { |
| stbds_hash_index *nt; |
| size_t slot_count; |
| |
| slot_count = (table == NULL) ? STBDS_BUCKET_LENGTH : table->slot_count*2; |
| nt = stbds_make_hash_index(slot_count, table); |
| if (table) |
| STBDS_FREE(NULL, table); |
| else |
| nt->string.mode = mode >= STBDS_HM_STRING ? STBDS_SH_DEFAULT : 0; |
| stbds_header(a)->hash_table = table = nt; |
| STBDS_STATS(++stbds_hash_grow); |
| } |
| |
| // we iterate hash table explicitly because we want to track if we saw a tombstone |
| { |
| size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed); |
| size_t step = STBDS_BUCKET_LENGTH; |
| size_t pos; |
| ptrdiff_t tombstone = -1; |
| stbds_hash_bucket *bucket; |
| |
| // stored hash values are forbidden from being 0, so we can detect empty slots to early out quickly |
| if (hash < 2) hash += 2; |
| |
| pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2); |
| |
| for (;;) { |
| size_t limit, i; |
| STBDS_STATS(++stbds_hash_probes); |
| bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; |
| |
| // start searching from pos to end of bucket |
| for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { |
| if (bucket->hash[i] == hash) { |
| if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { |
| stbds_temp(a) = bucket->index[i]; |
| if (mode >= STBDS_HM_STRING) |
| stbds_temp_key(a) = * (char **) ((char *) raw_a + elemsize*bucket->index[i] + keyoffset); |
| return STBDS_ARR_TO_HASH(a,elemsize); |
| } |
| } else if (bucket->hash[i] == 0) { |
| pos = (pos & ~STBDS_BUCKET_MASK) + i; |
| goto found_empty_slot; |
| } else if (tombstone < 0) { |
| if (bucket->index[i] == STBDS_INDEX_DELETED) |
| tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); |
| } |
| } |
| |
| // search from beginning of bucket to pos |
| limit = pos & STBDS_BUCKET_MASK; |
| for (i = 0; i < limit; ++i) { |
| if (bucket->hash[i] == hash) { |
| if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { |
| stbds_temp(a) = bucket->index[i]; |
| return STBDS_ARR_TO_HASH(a,elemsize); |
| } |
| } else if (bucket->hash[i] == 0) { |
| pos = (pos & ~STBDS_BUCKET_MASK) + i; |
| goto found_empty_slot; |
| } else if (tombstone < 0) { |
| if (bucket->index[i] == STBDS_INDEX_DELETED) |
| tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); |
| } |
| } |
| |
| // quadratic probing |
| pos += step; |
| step += STBDS_BUCKET_LENGTH; |
| pos &= (table->slot_count-1); |
| } |
| found_empty_slot: |
| if (tombstone >= 0) { |
| pos = tombstone; |
| --table->tombstone_count; |
| } |
| ++table->used_count; |
| |
| { |
| ptrdiff_t i = (ptrdiff_t) stbds_arrlen(a); |
| // we want to do stbds_arraddn(1), but we can't use the macros since we don't have something of the right type |
| if ((size_t) i+1 > stbds_arrcap(a)) |
| *(void **) &a = stbds_arrgrowf(a, elemsize, 1, 0); |
| raw_a = STBDS_ARR_TO_HASH(a,elemsize); |
| |
| STBDS_ASSERT((size_t) i+1 <= stbds_arrcap(a)); |
| stbds_header(a)->length = i+1; |
| bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; |
| bucket->hash[pos & STBDS_BUCKET_MASK] = hash; |
| bucket->index[pos & STBDS_BUCKET_MASK] = i-1; |
| stbds_temp(a) = i-1; |
| |
| switch (table->string.mode) { |
| case STBDS_SH_STRDUP: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_strdup((char*) key); break; |
| case STBDS_SH_ARENA: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_stralloc(&table->string, (char*)key); break; |
| case STBDS_SH_DEFAULT: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = (char *) key; break; |
| default: memcpy((char *) a + elemsize*i, key, keysize); break; |
| } |
| } |
| return STBDS_ARR_TO_HASH(a,elemsize); |
| } |
| } |
| |
| void * stbds_shmode_func(size_t elemsize, int mode) |
| { |
| void *a = stbds_arrgrowf(0, elemsize, 0, 1); |
| stbds_hash_index *h; |
| memset(a, 0, elemsize); |
| stbds_header(a)->length = 1; |
| stbds_header(a)->hash_table = h = (stbds_hash_index *) stbds_make_hash_index(STBDS_BUCKET_LENGTH, NULL); |
| h->string.mode = (unsigned char) mode; |
| return STBDS_ARR_TO_HASH(a,elemsize); |
| } |
| |
| void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode) |
| { |
| if (a == NULL) { |
| return 0; |
| } else { |
| stbds_hash_index *table; |
| void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); |
| table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; |
| stbds_temp(raw_a) = 0; |
| if (table == 0) { |
| return a; |
| } else { |
| ptrdiff_t slot; |
| slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); |
| if (slot < 0) |
| return a; |
| else { |
| stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; |
| int i = slot & STBDS_BUCKET_MASK; |
| ptrdiff_t old_index = b->index[i]; |
| ptrdiff_t final_index = (ptrdiff_t) stbds_arrlen(raw_a)-1-1; // minus one for the raw_a vs a, and minus one for 'last' |
| STBDS_ASSERT(slot < (ptrdiff_t) table->slot_count); |
| --table->used_count; |
| ++table->tombstone_count; |
| stbds_temp(raw_a) = 1; |
| STBDS_ASSERT(table->used_count >= 0); |
| //STBDS_ASSERT(table->tombstone_count < table->slot_count/4); |
| b->hash[i] = STBDS_HASH_DELETED; |
| b->index[i] = STBDS_INDEX_DELETED; |
| |
| if (mode == STBDS_HM_STRING && table->string.mode == STBDS_SH_STRDUP) |
| STBDS_FREE(NULL, *(char**) ((char *) a+elemsize*old_index)); |
| |
| // if indices are the same, memcpy is a no-op, but back-pointer-fixup will fail, so skip |
| if (old_index != final_index) { |
| // swap delete |
| memmove((char*) a + elemsize*old_index, (char*) a + elemsize*final_index, elemsize); |
| |
| // now find the slot for the last element |
| if (mode == STBDS_HM_STRING) |
| slot = stbds_hm_find_slot(a, elemsize, *(char**) ((char *) a+elemsize*old_index + keyoffset), keysize, keyoffset, mode); |
| else |
| slot = stbds_hm_find_slot(a, elemsize, (char* ) a+elemsize*old_index + keyoffset, keysize, keyoffset, mode); |
| STBDS_ASSERT(slot >= 0); |
| b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; |
| i = slot & STBDS_BUCKET_MASK; |
| STBDS_ASSERT(b->index[i] == final_index); |
| b->index[i] = old_index; |
| } |
| stbds_header(raw_a)->length -= 1; |
| |
| if (table->used_count < table->used_count_shrink_threshold && table->slot_count > STBDS_BUCKET_LENGTH) { |
| stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count>>1, table); |
| STBDS_FREE(NULL, table); |
| STBDS_STATS(++stbds_hash_shrink); |
| } else if (table->tombstone_count > table->tombstone_count_threshold) { |
| stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count , table); |
| STBDS_FREE(NULL, table); |
| STBDS_STATS(++stbds_hash_rebuild); |
| } |
| |
| return a; |
| } |
| } |
| } |
| /* NOTREACHED */ |
| } |
| |
| static char *stbds_strdup(char *str) |
| { |
| // to keep replaceable allocator simple, we don't want to use strdup. |
| // rolling our own also avoids problem of strdup vs _strdup |
| size_t len = strlen(str)+1; |
| char *p = (char*) STBDS_REALLOC(NULL, 0, len); |
| memmove(p, str, len); |
| return p; |
| } |
| |
| #ifndef STBDS_STRING_ARENA_BLOCKSIZE_MIN |
| #define STBDS_STRING_ARENA_BLOCKSIZE_MIN 512u |
| #endif |
| #ifndef STBDS_STRING_ARENA_BLOCKSIZE_MAX |
| #define STBDS_STRING_ARENA_BLOCKSIZE_MAX (1u<<20) |
| #endif |
| |
| char *stbds_stralloc(stbds_string_arena *a, char *str) |
| { |
| char *p; |
| size_t len = strlen(str)+1; |
| if (len > a->remaining) { |
| // compute the next blocksize |
| size_t blocksize = a->block; |
| |
| // size is 512, 512, 1024, 1024, 2048, 2048, 4096, 4096, etc., so that |
| // there are log(SIZE) allocations to free when we destroy the table |
| blocksize = (size_t) (STBDS_STRING_ARENA_BLOCKSIZE_MIN) << (blocksize>>1); |
| |
| // if size is under 1M, advance to next blocktype |
| if (blocksize < (size_t)(STBDS_STRING_ARENA_BLOCKSIZE_MAX)) |
| ++a->block; |
| |
| if (len > blocksize) { |
| // if string is larger than blocksize, then just allocate the full size. |
| // note that we still advance string_block so block size will continue |
| // increasing, so e.g. if somebody only calls this with 1000-long strings, |
| // eventually the arena will start doubling and handling those as well |
| stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + len); |
| memmove(sb->storage, str, len); |
| if (a->storage) { |
| // insert it after the first element, so that we don't waste the space there |
| sb->next = a->storage->next; |
| a->storage->next = sb; |
| } else { |
| sb->next = 0; |
| a->storage = sb; |
| a->remaining = 0; // this is redundant, but good for clarity |
| } |
| return sb->storage; |
| } else { |
| stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + blocksize); |
| sb->next = a->storage; |
| a->storage = sb; |
| a->remaining = blocksize; |
| } |
| } |
| |
| STBDS_ASSERT(len <= a->remaining); |
| p = a->storage->storage + a->remaining - len; |
| a->remaining -= len; |
| memmove(p, str, len); |
| return p; |
| } |
| |
| void stbds_strreset(stbds_string_arena *a) |
| { |
| stbds_string_block *x,*y; |
| x = a->storage; |
| while (x) { |
| y = x->next; |
| STBDS_FREE(NULL, x); |
| x = y; |
| } |
| memset(a, 0, sizeof(*a)); |
| } |
| |
| #endif |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // |
| // UNIT TESTS |
| // |
| |
| #ifdef STBDS_UNIT_TESTS |
| #include <stdio.h> |
| #ifdef STBDS_ASSERT_WAS_UNDEFINED |
| #undef STBDS_ASSERT |
| #endif |
| #ifndef STBDS_ASSERT |
| #define STBDS_ASSERT assert |
| #include <assert.h> |
| #endif |
| |
| typedef struct { int key,b,c,d; } stbds_struct; |
| typedef struct { int key[2],b,c,d; } stbds_struct2; |
| |
| static char buffer[256]; |
| char *strkey(int n) |
| { |
| #if defined(_WIN32) && defined(__STDC_WANT_SECURE_LIB__) |
| sprintf_s(buffer, sizeof(buffer), "test_%d", n); |
| #else |
| sprintf(buffer, "test_%d", n); |
| #endif |
| return buffer; |
| } |
| |
| void stbds_unit_tests(void) |
| { |
| #if defined(_MSC_VER) && _MSC_VER <= 1200 && defined(__cplusplus) |
| // VC6 C++ doesn't like the template<> trick on unnamed structures, so do nothing! |
| STBDS_ASSERT(0); |
| #else |
| const int testsize = 100000; |
| const int testsize2 = testsize/20; |
| int *arr=NULL; |
| struct { int key; int value; } *intmap = NULL; |
| struct { char *key; int value; } *strmap = NULL, s; |
| struct { stbds_struct key; int value; } *map = NULL; |
| stbds_struct *map2 = NULL; |
| stbds_struct2 *map3 = NULL; |
| stbds_string_arena sa = { 0 }; |
| int key3[2] = { 1,2 }; |
| ptrdiff_t temp; |
| |
| int i,j; |
| |
| STBDS_ASSERT(arrlen(arr)==0); |
| for (i=0; i < 20000; i += 50) { |
| for (j=0; j < i; ++j) |
| arrpush(arr,j); |
| arrfree(arr); |
| } |
| |
| for (i=0; i < 4; ++i) { |
| arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); |
| arrdel(arr,i); |
| arrfree(arr); |
| arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); |
| arrdelswap(arr,i); |
| arrfree(arr); |
| } |
| |
| for (i=0; i < 5; ++i) { |
| arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); |
| stbds_arrins(arr,i,5); |
| STBDS_ASSERT(arr[i] == 5); |
| if (i < 4) |
| STBDS_ASSERT(arr[4] == 4); |
| arrfree(arr); |
| } |
| |
| i = 1; |
| STBDS_ASSERT(hmgeti(intmap,i) == -1); |
| hmdefault(intmap, -2); |
| STBDS_ASSERT(hmgeti(intmap, i) == -1); |
| STBDS_ASSERT(hmget (intmap, i) == -2); |
| for (i=0; i < testsize; i+=2) |
| hmput(intmap, i, i*5); |
| for (i=0; i < testsize; i+=1) { |
| if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); |
| else STBDS_ASSERT(hmget(intmap, i) == i*5); |
| if (i & 1) STBDS_ASSERT(hmget_ts(intmap, i, temp) == -2 ); |
| else STBDS_ASSERT(hmget_ts(intmap, i, temp) == i*5); |
| } |
| for (i=0; i < testsize; i+=2) |
| hmput(intmap, i, i*3); |
| for (i=0; i < testsize; i+=1) |
| if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); |
| else STBDS_ASSERT(hmget(intmap, i) == i*3); |
| for (i=2; i < testsize; i+=4) |
| hmdel(intmap, i); // delete half the entries |
| for (i=0; i < testsize; i+=1) |
| if (i & 3) STBDS_ASSERT(hmget(intmap, i) == -2 ); |
| else STBDS_ASSERT(hmget(intmap, i) == i*3); |
| for (i=0; i < testsize; i+=1) |
| hmdel(intmap, i); // delete the rest of the entries |
| for (i=0; i < testsize; i+=1) |
| STBDS_ASSERT(hmget(intmap, i) == -2 ); |
| hmfree(intmap); |
| for (i=0; i < testsize; i+=2) |
| hmput(intmap, i, i*3); |
| hmfree(intmap); |
| |
| #if defined(__clang__) || defined(__GNUC__) |
| #ifndef __cplusplus |
| intmap = NULL; |
| hmput(intmap, 15, 7); |
| hmput(intmap, 11, 3); |
| hmput(intmap, 9, 5); |
| STBDS_ASSERT(hmget(intmap, 9) == 5); |
| STBDS_ASSERT(hmget(intmap, 11) == 3); |
| STBDS_ASSERT(hmget(intmap, 15) == 7); |
| #endif |
| #endif |
| |
| for (i=0; i < testsize; ++i) |
| stralloc(&sa, strkey(i)); |
| strreset(&sa); |
| |
| { |
| s.key = "a", s.value = 1; |
| shputs(strmap, s); |
| STBDS_ASSERT(*strmap[0].key == 'a'); |
| STBDS_ASSERT(strmap[0].key == s.key); |
| STBDS_ASSERT(strmap[0].value == s.value); |
| shfree(strmap); |
| } |
| |
| { |
| s.key = "a", s.value = 1; |
| sh_new_strdup(strmap); |
| shputs(strmap, s); |
| STBDS_ASSERT(*strmap[0].key == 'a'); |
| STBDS_ASSERT(strmap[0].key != s.key); |
| STBDS_ASSERT(strmap[0].value == s.value); |
| shfree(strmap); |
| } |
| |
| { |
| s.key = "a", s.value = 1; |
| sh_new_arena(strmap); |
| shputs(strmap, s); |
| STBDS_ASSERT(*strmap[0].key == 'a'); |
| STBDS_ASSERT(strmap[0].key != s.key); |
| STBDS_ASSERT(strmap[0].value == s.value); |
| shfree(strmap); |
| } |
| |
| for (j=0; j < 2; ++j) { |
| STBDS_ASSERT(shgeti(strmap,"foo") == -1); |
| if (j == 0) |
| sh_new_strdup(strmap); |
| else |
| sh_new_arena(strmap); |
| STBDS_ASSERT(shgeti(strmap,"foo") == -1); |
| shdefault(strmap, -2); |
| STBDS_ASSERT(shgeti(strmap,"foo") == -1); |
| for (i=0; i < testsize; i+=2) |
| shput(strmap, strkey(i), i*3); |
| for (i=0; i < testsize; i+=1) |
| if (i & 1) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); |
| else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); |
| for (i=2; i < testsize; i+=4) |
| shdel(strmap, strkey(i)); // delete half the entries |
| for (i=0; i < testsize; i+=1) |
| if (i & 3) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); |
| else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); |
| for (i=0; i < testsize; i+=1) |
| shdel(strmap, strkey(i)); // delete the rest of the entries |
| for (i=0; i < testsize; i+=1) |
| STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); |
| shfree(strmap); |
| } |
| |
| { |
| struct { char *key; char value; } *hash = NULL; |
| char name[4] = "jen"; |
| shput(hash, "bob" , 'h'); |
| shput(hash, "sally" , 'e'); |
| shput(hash, "fred" , 'l'); |
| shput(hash, "jen" , 'x'); |
| shput(hash, "doug" , 'o'); |
| |
| shput(hash, name , 'l'); |
| shfree(hash); |
| } |
| |
| for (i=0; i < testsize; i += 2) { |
| stbds_struct s = { i,i*2,i*3,i*4 }; |
| hmput(map, s, i*5); |
| } |
| |
| for (i=0; i < testsize; i += 1) { |
| stbds_struct s = { i,i*2,i*3 ,i*4 }; |
| stbds_struct t = { i,i*2,i*3+1,i*4 }; |
| if (i & 1) STBDS_ASSERT(hmget(map, s) == 0); |
| else STBDS_ASSERT(hmget(map, s) == i*5); |
| if (i & 1) STBDS_ASSERT(hmget_ts(map, s, temp) == 0); |
| else STBDS_ASSERT(hmget_ts(map, s, temp) == i*5); |
| //STBDS_ASSERT(hmget(map, t.key) == 0); |
| } |
| |
| for (i=0; i < testsize; i += 2) { |
| stbds_struct s = { i,i*2,i*3,i*4 }; |
| hmputs(map2, s); |
| } |
| hmfree(map); |
| |
| for (i=0; i < testsize; i += 1) { |
| stbds_struct s = { i,i*2,i*3,i*4 }; |
| stbds_struct t = { i,i*2,i*3+1,i*4 }; |
| if (i & 1) STBDS_ASSERT(hmgets(map2, s.key).d == 0); |
| else STBDS_ASSERT(hmgets(map2, s.key).d == i*4); |
| //STBDS_ASSERT(hmgetp(map2, t.key) == 0); |
| } |
| hmfree(map2); |
| |
| for (i=0; i < testsize; i += 2) { |
| stbds_struct2 s = { { i,i*2 }, i*3,i*4, i*5 }; |
| hmputs(map3, s); |
| } |
| for (i=0; i < testsize; i += 1) { |
| stbds_struct2 s = { { i,i*2}, i*3, i*4, i*5 }; |
| stbds_struct2 t = { { i,i*2}, i*3+1, i*4, i*5 }; |
| if (i & 1) STBDS_ASSERT(hmgets(map3, s.key).d == 0); |
| else STBDS_ASSERT(hmgets(map3, s.key).d == i*5); |
| //STBDS_ASSERT(hmgetp(map3, t.key) == 0); |
| } |
| #endif |
| } |
| #endif |
| |
| |
| /* |
| ------------------------------------------------------------------------------ |
| This software is available under 2 licenses -- choose whichever you prefer. |
| ------------------------------------------------------------------------------ |
| ALTERNATIVE A - MIT License |
| Copyright (c) 2019 Sean Barrett |
| Permission is hereby granted, free of charge, to any person obtaining a copy of |
| this software and associated documentation files (the "Software"), to deal in |
| the Software without restriction, including without limitation the rights to |
| use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies |
| of the Software, and to permit persons to whom the Software is furnished to do |
| so, subject to the following conditions: |
| The above copyright notice and this permission notice shall be included in all |
| copies or substantial portions of the Software. |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| SOFTWARE. |
| ------------------------------------------------------------------------------ |
| ALTERNATIVE B - Public Domain (www.unlicense.org) |
| This is free and unencumbered software released into the public domain. |
| Anyone is free to copy, modify, publish, use, compile, sell, or distribute this |
| software, either in source code form or as a compiled binary, for any purpose, |
| commercial or non-commercial, and by any means. |
| In jurisdictions that recognize copyright laws, the author or authors of this |
| software dedicate any and all copyright interest in the software to the public |
| domain. We make this dedication for the benefit of the public at large and to |
| the detriment of our heirs and successors. We intend this dedication to be an |
| overt act of relinquishment in perpetuity of all present and future rights to |
| this software under copyright law. |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
| ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
| WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| ------------------------------------------------------------------------------ |
| */ |