├── .gitignore ├── Makefile ├── example_custom_hash_function.f90 ├── LICENSE ├── example_multiple_tables.f90 ├── example_custom_types.f90 ├── example_benchmark.f90 ├── README.md └── ffhash_inc.f90 /.gitignore: -------------------------------------------------------------------------------- 1 | /example_benchmark 2 | /example_custom_hash_function 3 | /example_custom_types 4 | /example_multiple_tables 5 | /*.mod 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | FC := gfortran 2 | FFLAGS := -Wall -O3 -cpp -g 3 | PROGS := example_custom_hash_function example_multiple_tables \ 4 | example_custom_types example_benchmark 5 | 6 | .phony: all clean test 7 | 8 | all: $(PROGS) 9 | 10 | clean: 11 | $(RM) $(PROGS) *.o *.mod 12 | 13 | test: all 14 | @for prog in $(PROGS); do \ 15 | echo "Running $$prog"; \ 16 | ./$$prog; \ 17 | done 18 | 19 | %: %.f90 20 | $(FC) -o $@ $< $(FFLAGS) 21 | 22 | $(PROGS): ffhash_inc.f90 23 | -------------------------------------------------------------------------------- /example_custom_hash_function.f90: -------------------------------------------------------------------------------- 1 | module m_ffhash 2 | use iso_fortran_env 3 | implicit none 4 | #define FFH_STRING_KEY_TYPE character(len=20) 5 | #define FFH_VAL_TYPE integer 6 | #define FFH_CUSTOM_HASH_FUNCTION 7 | #include "ffhash_inc.f90" 8 | 9 | ! djb2 hash function 10 | pure integer function hash_function(key) result(hash) 11 | character(len=*), intent(in) :: key 12 | integer :: n 13 | 14 | hash = 5381 15 | do n = 1, len(key) 16 | ! hash * 33 + c 17 | hash = (shiftl(hash, 5) + hash) + iachar(key(n:n)) 18 | end do 19 | end function hash_function 20 | end module m_ffhash 21 | 22 | program test 23 | use m_ffhash 24 | 25 | implicit none 26 | 27 | type(ffh_t) :: h 28 | integer :: day, month, year 29 | 30 | call h%ustore_value("day", 20) 31 | call h%ustore_value("month", 2) 32 | call h%ustore_value("year", 2020) 33 | 34 | call h%uget_value("day", day) 35 | call h%uget_value("month", month) 36 | call h%uget_value("year", year) 37 | 38 | if (all([day, month, year] == [20, 2, 2020])) then 39 | print *, [day, month, year] 40 | print *, h%get_index(["day ", "month", "year "]) 41 | print *, "PASSED" 42 | else 43 | error stop "FAILED" 44 | end if 45 | 46 | call h%reset() 47 | end program test 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Hash table implementation in Fortran by Jannis Teunissen. The implementation is 2 | heavily inspired by khash, whose license is copied below. The included Murmur3 3 | hash function is in the public domain, with credits to the C port by Peter Scott 4 | (see https://github.com/PeterScott/murmur3). 5 | 6 | The MIT License 7 | 8 | Copyright (c) 2008, 2009, 2011 by Attractive Chaos 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining 11 | a copy of this software and associated documentation files (the 12 | "Software"), to deal in the Software without restriction, including 13 | without limitation the rights to use, copy, modify, merge, publish, 14 | distribute, sublicense, and/or sell copies of the Software, and to 15 | permit persons to whom the Software is furnished to do so, subject to 16 | the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 25 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 26 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 27 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | SOFTWARE. -------------------------------------------------------------------------------- /example_multiple_tables.f90: -------------------------------------------------------------------------------- 1 | module m_hash_a 2 | use iso_fortran_env 3 | implicit none 4 | #define FFH_KEY_TYPE integer 5 | #define FFH_STRING_VAL_TYPE character(len=15) 6 | #include "ffhash_inc.f90" 7 | end module m_hash_a 8 | 9 | module m_hash_b 10 | use iso_fortran_env 11 | implicit none 12 | #define FFH_STRING_KEY_TYPE character(len=20) 13 | #define FFH_VAL_TYPE integer 14 | #include "ffhash_inc.f90" 15 | end module m_hash_b 16 | 17 | module m_hash_c 18 | use iso_fortran_env 19 | implicit none 20 | #define FFH_ENABLE_INT64 21 | #define FFH_KEY_TYPE integer(int64) 22 | #define FFH_VAL_TYPE integer(int64) 23 | #include "ffhash_inc.f90" 24 | end module m_hash_c 25 | 26 | module m_hash_d 27 | use iso_fortran_env 28 | implicit none 29 | #define FFH_ENABLE_INT64 30 | #define FFH_KEY_TYPE integer(int64) 31 | #define FFH_STRING_VAL_TYPE character(len=31) 32 | #include "ffhash_inc.f90" 33 | end module m_hash_d 34 | 35 | program test 36 | use iso_fortran_env, only: int64 37 | use m_hash_a, ffha_t => ffh_t 38 | use m_hash_b, ffhb_t => ffh_t 39 | use m_hash_c, ffhc_t => ffh_t 40 | use m_hash_d, ffhd_t => ffh_t 41 | implicit none 42 | 43 | type(ffha_t) :: ha 44 | type(ffhb_t) :: hb 45 | type(ffhc_t) :: hc 46 | type(ffhd_t) :: hd 47 | integer :: i 48 | integer(int64) :: k64, v64 49 | 50 | ! int -> string 51 | call ha%ustore_value(1, "hello world") 52 | print *, ha%fget_value(1) 53 | if (ha%fget_value(1) /= "hello world") error stop "FAILED ha" 54 | 55 | ! string -> int 56 | call hb%ustore_value("first", 12345) 57 | print *, hb%fget_value("first") 58 | if (hb%fget_value("first") /= 12345) error stop "FAILED hb" 59 | 60 | call hb%store_key("first", i) 61 | call hb%store_key("first", i, existing_key_is_error = .true.) 62 | if (i /= -2) error stop "Expected -2 for duplicate key" 63 | 64 | ! int64 -> int64 65 | k64 = 9876543210123456_int64 66 | v64 = 1234567890123456_int64 67 | call hc%ustore_value(k64, v64) 68 | print *, hc%fget_value(k64) 69 | if (hc%fget_value(k64) /= v64) error stop "FAILED hc" 70 | 71 | ! int64 -> string 72 | call hd%ustore_value(k64, "64-bit key works") 73 | print *, hd%fget_value(k64) 74 | if (hd%fget_value(k64) /= "64-bit key works") error stop "FAILED hd" 75 | 76 | print *, "PASSED" 77 | 78 | call ha%reset() 79 | call hb%reset() 80 | call hc%reset() 81 | call hd%reset() 82 | end program test 83 | -------------------------------------------------------------------------------- /example_custom_types.f90: -------------------------------------------------------------------------------- 1 | module m_example 2 | use iso_fortran_env 3 | implicit none 4 | 5 | type, public :: my_type 6 | integer :: n 7 | end type my_type 8 | 9 | #define FFH_KEY_TYPE type(my_type) 10 | #define FFH_CUSTOM_KEYS_EQUAL 11 | #define FFH_VAL_TYPE integer(int64) 12 | #include "ffhash_inc.f90" 13 | 14 | pure logical function keys_equal(a, b) 15 | type(my_type), intent(in) :: a, b 16 | keys_equal = (a%n == b%n) 17 | end function keys_equal 18 | end module m_example 19 | 20 | module m_example_2 21 | use iso_fortran_env 22 | implicit none 23 | 24 | type, public :: my_type_2 25 | integer, allocatable :: x(:) 26 | end type my_type_2 27 | 28 | #define FFH_KEY_TYPE type(my_type_2) 29 | #define FFH_CUSTOM_KEYS_EQUAL 30 | #define FFH_CUSTOM_CONVERT_KEY 31 | #define FFH_VAL_TYPE integer(int64) 32 | #include "ffhash_inc.f90" 33 | 34 | pure logical function keys_equal(a, b) 35 | type(my_type_2), intent(in) :: a, b 36 | keys_equal = size(a%x) == size(b%x) 37 | if (keys_equal) keys_equal = all(a%x == b%x) 38 | end function keys_equal 39 | 40 | pure function convert_key(a) result(bstring) 41 | type(my_type_2), intent(in) :: a 42 | character(len=size(a%x)*4) :: bstring 43 | bstring = transfer(a%x, bstring) 44 | end function convert_key 45 | end module m_example_2 46 | 47 | program test 48 | use m_example 49 | use m_example_2, ffh2_t => ffh_t 50 | use iso_fortran_env 51 | 52 | implicit none 53 | 54 | call test_1() 55 | call test_2() 56 | 57 | contains 58 | 59 | subroutine test_1() 60 | type(ffh_t) :: h 61 | type(my_type) :: x, y 62 | 63 | x%n = 123 64 | y%n = 345 65 | 66 | call h%ustore_value(x, 1024_int64) 67 | call h%ustore_value(y, 2048_int64) 68 | 69 | if (h%fget_value(x) == 1024_int64) then 70 | print *, "PASSED" 71 | else 72 | error stop "FAILED" 73 | end if 74 | 75 | call h%reset() 76 | end subroutine test_1 77 | 78 | subroutine test_2() 79 | type(ffh2_t) :: h 80 | type(my_type_2) :: a, b, c 81 | 82 | allocate(a%x(5), b%x(3), c%x(3)) 83 | a%x = [1, 2, 3, 4, 5] 84 | b%x = [3, 2, 1] 85 | c%x = [1, 2, 3] 86 | 87 | call h%ustore_value(a, 1024_int64) 88 | call h%ustore_value(b, 2048_int64) 89 | call h%ustore_value(c, 4096_int64) 90 | 91 | if (h%fget_value(a) == 1024_int64) then 92 | print *, "PASSED" 93 | else 94 | error stop "FAILED" 95 | end if 96 | 97 | call h%reset() 98 | end subroutine test_2 99 | 100 | end program test 101 | -------------------------------------------------------------------------------- /example_benchmark.f90: -------------------------------------------------------------------------------- 1 | module m_ffhash 2 | use iso_fortran_env 3 | implicit none 4 | #define FFH_KEY_TYPE integer 5 | #include "ffhash_inc.f90" 6 | end module m_ffhash 7 | 8 | module m_ffhash_64 9 | use iso_fortran_env 10 | implicit none 11 | #define FFH_KEY_TYPE integer 12 | #define FFH_ENABLE_INT64 13 | #include "ffhash_inc.f90" 14 | end module m_ffhash_64 15 | 16 | program test 17 | use iso_fortran_env 18 | implicit none 19 | 20 | integer, parameter :: n_max = 5*1000*1000 21 | 22 | call test_32(n_max) 23 | call test_64(n_max) 24 | 25 | contains 26 | 27 | subroutine test_32(n_max) 28 | use iso_fortran_env, dp => real64 29 | use m_ffhash 30 | integer, intent(in) :: n_max 31 | 32 | type(ffh_t) :: h 33 | integer :: n, i, status 34 | integer, allocatable :: keys(:) 35 | integer, allocatable :: key_counts(:) 36 | real(dp) :: t_start, t_end 37 | real(dp), allocatable :: r_uniform(:) 38 | 39 | allocate(keys(n_max), r_uniform(n_max)) 40 | 41 | call random_number(r_uniform) 42 | keys = nint(r_uniform * n_max * 0.25_dp) 43 | 44 | call cpu_time(t_start) 45 | do n = 1, n_max 46 | i = h%get_index(keys(n)) 47 | 48 | if (i /= -1) then 49 | call h%delete_index(i, status) 50 | else 51 | call h%store_key(keys(n), i) 52 | end if 53 | end do 54 | call cpu_time(t_end) 55 | 56 | write(*, "(A)") "Results for 32-bit hashing and indexing" 57 | write(*, "(A,E12.4)") "Elapsed time (s) ", t_end - t_start 58 | write(*, "(A,E12.4)") "Entries/s ", n_max/(t_end - t_start) 59 | write(*, "(A,I12)") "n_keys_stored ", h%n_keys_stored 60 | write(*, "(A,I12)") "n_occupied ", h%n_occupied 61 | write(*, "(A,I12)") "n_buckets ", h%n_buckets 62 | 63 | ! Count number of keys that occur an odd number of times 64 | allocate(key_counts(minval(keys):maxval(keys))) 65 | key_counts = 0 66 | do n = 1, n_max 67 | key_counts(keys(n)) = key_counts(keys(n)) + 1 68 | end do 69 | n = sum(iand(key_counts, 1)) 70 | 71 | if (n /= h%n_keys_stored) then 72 | error stop "FAILED" 73 | else 74 | print *, "PASSED" 75 | end if 76 | 77 | ! Clean up allocated storage 78 | call h%reset() 79 | deallocate(key_counts) 80 | end subroutine test_32 81 | 82 | subroutine test_64(n_max) 83 | use iso_fortran_env, dp => real64 84 | use m_ffhash_64 85 | integer, intent(in) :: n_max 86 | 87 | type(ffh_t) :: h 88 | integer(int64) :: n, i, status 89 | integer, allocatable :: keys(:) 90 | integer, allocatable :: key_counts(:) 91 | real(dp) :: t_start, t_end 92 | real(dp), allocatable :: r_uniform(:) 93 | 94 | allocate(keys(n_max), r_uniform(n_max)) 95 | 96 | call random_number(r_uniform) 97 | keys = nint(r_uniform * n_max * 0.25_dp) 98 | 99 | call cpu_time(t_start) 100 | do n = 1, n_max 101 | i = h%get_index(keys(n)) 102 | 103 | if (i /= -1) then 104 | call h%delete_index(i, status) 105 | else 106 | call h%store_key(keys(n), i) 107 | end if 108 | end do 109 | call cpu_time(t_end) 110 | 111 | write(*, "(A)") "Results for 64-bit hashing and indexing" 112 | write(*, "(A,E12.4)") "Elapsed time (s) ", t_end - t_start 113 | write(*, "(A,E12.4)") "Entries/s ", n_max/(t_end - t_start) 114 | write(*, "(A,I12)") "n_keys_stored ", h%n_keys_stored 115 | write(*, "(A,I12)") "n_occupied ", h%n_occupied 116 | write(*, "(A,I12)") "n_buckets ", h%n_buckets 117 | 118 | ! Count number of keys that occur an odd number of times 119 | allocate(key_counts(minval(keys):maxval(keys))) 120 | key_counts = 0 121 | do n = 1, n_max 122 | key_counts(keys(n)) = key_counts(keys(n)) + 1 123 | end do 124 | n = sum(iand(key_counts, 1)) 125 | 126 | if (n /= h%n_keys_stored) then 127 | error stop "FAILED" 128 | else 129 | print *, "PASSED" 130 | end if 131 | 132 | ! Clean up allocated storage 133 | call h%reset() 134 | deallocate(key_counts) 135 | end subroutine test_64 136 | 137 | end program 138 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ffhash: Fast Fortran Hash Table 2 | == 3 | 4 | The implementation is heavily inspired by [khash](https://github.com/attractivechaos/klib/blob/master/khash.h). 5 | 6 | Properties of the hash table: 7 | * [Open addressing](https://en.wikipedia.org/wiki/Open_addressing) 8 | * [Quadratic probing](https://en.wikipedia.org/wiki/Quadratic_probing) (but can easily be adjusted to linear probing) 9 | * [Murmur3_x86_32 and MurmurHash3_x64_128](http://code.google.com/p/smhasher/wiki/MurmurHash3) as default hash functions, see also [murmur3-fortran](https://github.com/jannisteunissen/murmur3-fortran) 10 | * Generic: the keys and values can be of any type. 11 | 12 | Requirements 13 | == 14 | 15 | * Fortran 2008 compatible compiler, such as `gfortran` or `ifort` 16 | 17 | Preprocessing should be enabled (typically with `-cpp`). With older `gfortran` versions (version 7 or older) there may be warnings. 18 | 19 | List of examples 20 | == 21 | 22 | * [Using custom types](example_custom_types.f90) 23 | * [Using a custom hash function](example_custom_hash_function.f90) (also shows how to use strings as keys) 24 | * [Using multiple hash tables](example_multiple_tables.f90) (also shows how to use strings as keys/values) 25 | * [Simple benchmark](example_benchmark.f90) 26 | 27 | Usage 28 | == 29 | 30 | The file `ffhash_inc.f90` can for example be included like this: 31 | 32 | ```Fortran 33 | module m_example 34 | #define FFH_KEY_TYPE integer 35 | #include "ffhash_inc.f90" 36 | end module m_example 37 | ``` 38 | 39 | The following preprocessor commands can be used (on the input side): 40 | 41 | * `#define FFH_KEY_TYPE` (required) followed by a type name (e.g., `integer`, `real`, `type(my_type)`). 42 | This is the type of the keys stored in the hash table. 43 | * `#define FFH_STRING_KEY_TYPE` followed by a character type declaration (e.g., `character(len=15)`). 44 | This is an alternative for `FFH_KEY_TYPE` used for string keys. 45 | (If defined, `FFH_KEY_TYPE = FFH_STRING_KEY_TYPE` will be set automatically in `ffhash_inc.f90`.) 46 | * `#define FFH_VAL_TYPE` (optional) followed by a type name, so that keys can be associated with values. 47 | * `#define FFH_STRING_VAL_TYPE` (optional) followed by a character type declaration (e.g., `character(len=30)`). 48 | This is an alternative for `FFH_VAL_TYPE` used for string values. 49 | (`FFH_VAL_TYPE = FFH_STRING_VAL_TYPE` will be set automatically in `ffhash_inc.f90`.) 50 | * `#define FFH_ENABLE_INT64` (optional) to use 64-bit integers for bucket indices, counters, and hash values. 51 | This allows handling very large tables (more than 2 billion buckets or keys). 52 | If undefined, 32-bit integers are used by default, which is usually sufficient and slightly faster. 53 | * `#define FFH_CUSTOM_KEYS_EQUAL` (optional) to define a custom function for comparing keys 54 | (after the `#include "ffhash_inc.f90"` line). 55 | * `#define FFH_CUSTOM_CONVERT_KEY` (optional) to convert the key into a `character` buffer suitable for hashing. 56 | This gives full control over how keys are serialized (e.g., for derived types). 57 | If undefined, the library supplies default conversions for string and non-string keys. 58 | * `#define FFH_CUSTOM_HASH_FUNCTION` (optional) to define a custom hash function 59 | (after the `#include "ffhash_inc.f90"` line). 60 | 61 | A type `ffh_t` can be used after importing the created module, for example like this: 62 | 63 | ```Fortran 64 | type(ffh_t) :: h 65 | 66 | ! Store and retrieve a key/value pair 67 | call h%store_value(key, value, status) 68 | call h%get_value(key, value, status) 69 | 70 | ! Store and retrieve a key/value pair (will abort on failures) 71 | call h%ustore_value(key, value) 72 | call h%uget_value(key, value) 73 | 74 | ! The values can also be indexed directly 75 | i = h%get_index(key) 76 | if (i /= -1) h%vals(i) = value 77 | 78 | ! This is also possible 79 | value = h%fget_value(key) 80 | 81 | ! The index range is from 0 to n_buckets-1 82 | do i = 0, h%n_buckets-1 83 | if (h%valid_index(i)) ... 84 | end do 85 | ``` 86 | 87 | Below is the full list of (public) methods included. The variants starting with a `u` call `error stop` in case of errors. The variants without a `u` have an additional `status` argument, which is `-1` in case of errors. 88 | 89 | | name | description | 90 | |---|---| 91 | | `get_index` | Get index of a key | 92 | | `valid_index` | Check whether a valid key is present at index | 93 | | `store_key` | Store a new key | 94 | | `delete_key`, `udelete_key` | Delete a key | 95 | | `delete_index`, `udelete_index` | Delete key at an index | 96 | | `store_value`, `ustore_value` | Store a key-value pair | 97 | | `get_value`, `uget_value` | Get value for a key | 98 | | `fget_value` | Function to get value for a key (can perform error stop) | 99 | | `fget_value_or` | Function to get value for a key or a dummy if not found | 100 | | `resize` | Manually resize the hash table (happens automatically) | 101 | | `reset` | Reset the hash table to initial empty state | 102 | | `hash_function` | Hash function | 103 | | `convert_key` | Function to convert a key to a string buffer before hashing (optional, user-supplied) | 104 | 105 | Links 106 | == 107 | 108 | * [murmur3-fortran](https://github.com/jannisteunissen/murmur3-fortran) Murmur3 109 | hash function implemented in Fortran 110 | * [Hash tables on Fortran Wiki](http://fortranwiki.org/fortran/show/Hash+tables) 111 | -------------------------------------------------------------------------------- /ffhash_inc.f90: -------------------------------------------------------------------------------- 1 | ! This file should be included in a module ... end module block 2 | ! For example: 3 | ! module m_ffhash 4 | ! implicit none 5 | ! #define FFH_KEY_TYPE integer !use this for non-string keys 6 | ! #define FFH_STRING_KEY_TYPE character(len=15) !use this for string keys 7 | !(FFH_KEY_TYPE = FFH_STRING_KEY_TYPE 8 | !will be set in ffhash_inc.f90) 9 | ! 10 | ! #define FFH_VAL_TYPE integer (optional) !use this for non-string values 11 | ! #define FFH_STRING_VAL_TYPE character(len=30) !use this for string values 12 | !(FFH_VAL_TYPE = FFH_STRING_VAL_TYPE 13 | !will be set in ffhash_inc.f90) 14 | ! #include "ffhash_inc.f90" 15 | ! end module m_ffhash 16 | 17 | ! Defining FFH's integer kind 18 | #ifdef FFH_ENABLE_INT64 19 | #define FFH_INT_KIND int64 20 | #else 21 | #define FFH_INT_KIND int32 22 | #endif 23 | 24 | ! Special handling of strings (which can be shortened) 25 | #ifdef FFH_STRING_KEY_TYPE 26 | #define FFH_KEY_TYPE FFH_STRING_KEY_TYPE 27 | #define FFH_KEY_ARG character(len=*) 28 | #else 29 | #define FFH_KEY_ARG FFH_KEY_TYPE 30 | #endif 31 | 32 | #ifdef FFH_STRING_VAL_TYPE 33 | #define FFH_VAL_TYPE FFH_STRING_VAL_TYPE 34 | #define FFH_VAL_ARG character(len=*) 35 | #else 36 | #define FFH_VAL_ARG FFH_VAL_TYPE 37 | #endif 38 | 39 | 40 | 41 | private 42 | 43 | !> Type storing the hash table 44 | type, public :: ffh_t 45 | !> Number of buckets in hash table 46 | integer(FFH_INT_KIND) :: n_buckets = 0 47 | !> Number of keys stored in hash table 48 | integer(FFH_INT_KIND) :: n_keys_stored = 0 49 | !> Number of keys stored or deleted 50 | integer(FFH_INT_KIND) :: n_occupied = 0 51 | !> Maximum number of occupied buckets 52 | integer(FFH_INT_KIND) :: n_occupied_max = 0 53 | !> Mask to convert hash to index 54 | integer(FFH_INT_KIND) :: hash_mask = 0 55 | !> Maximum load factor for the hash table 56 | double precision :: max_load_factor = 0.7d0 57 | !> Flags indicating whether buckets are empty or deleted 58 | character, allocatable :: flags(:) 59 | !> Keys of the hash table 60 | FFH_KEY_TYPE, allocatable :: keys(:) 61 | #ifdef FFH_VAL_TYPE 62 | !> Values stored for the keys 63 | FFH_VAL_TYPE, allocatable :: vals(:) 64 | #endif 65 | contains 66 | !> Get index of a key 67 | procedure, non_overridable :: get_index 68 | !> Check whether a valid key is present at index 69 | procedure, non_overridable :: valid_index 70 | !> Store a new key 71 | procedure, non_overridable :: store_key 72 | !> Delete a key 73 | procedure, non_overridable :: delete_key 74 | !> Delete a key (can perform error stop) 75 | procedure, non_overridable :: udelete_key 76 | !> Delete key at an index 77 | procedure, non_overridable :: delete_index 78 | !> Delete key at an index (can perform error stop) 79 | procedure, non_overridable :: udelete_index 80 | !> Resize the hash table 81 | procedure, non_overridable :: resize 82 | !> Reset the hash table to initial empty state 83 | procedure, non_overridable :: reset 84 | #ifdef FFH_VAL_TYPE 85 | !> Store a key-value pair 86 | procedure, non_overridable :: store_value 87 | !> Store a key-value pair (can perform error stop) 88 | procedure, non_overridable :: ustore_value 89 | !> Get value for a key 90 | procedure, non_overridable :: get_value 91 | !> Get value for a key (can perform error stop) 92 | procedure, non_overridable :: uget_value 93 | !> Function to get value for a key (can perform error stop) 94 | procedure, non_overridable :: fget_value 95 | !> Function to get value for a key or a dummy if not found 96 | procedure, non_overridable :: fget_value_or 97 | #endif 98 | !> Hash function 99 | procedure, non_overridable, nopass :: hash_function 100 | end type ffh_t 101 | 102 | contains 103 | 104 | !> Get index corresponding to a key. If the key is not found, return -1. 105 | elemental pure function get_index(h, key) result(ix) 106 | class(ffh_t), intent(in) :: h 107 | FFH_KEY_ARG, intent(in) :: key 108 | integer(FFH_INT_KIND) :: ix, i, step 109 | 110 | ix = -1 111 | i = hash_index(h, key) 112 | 113 | do step = 1, h%n_buckets 114 | ! Exit when an empty bucket or the key is found 115 | if (bucket_empty(h, i)) then 116 | ! Key not found 117 | exit 118 | else if (h%valid_index(i)) then 119 | if (keys_equal(h%keys(i), key)) then 120 | ! Key found 121 | ix = i 122 | exit 123 | end if 124 | end if 125 | i = next_index(h, i, step) 126 | end do 127 | end function get_index 128 | 129 | #ifdef FFH_VAL_TYPE 130 | !> Get the value corresponding to a key 131 | pure subroutine get_value(h, key, val, status) 132 | class(ffh_t), intent(in) :: h 133 | FFH_KEY_ARG, intent(in) :: key 134 | FFH_VAL_ARG, intent(inout) :: val 135 | integer(FFH_INT_KIND), intent(out) :: status 136 | 137 | status = h%get_index(key) 138 | if (status >= 0) val = h%vals(status) 139 | end subroutine get_value 140 | 141 | !> Get the value corresponding to a key 142 | subroutine uget_value(h, key, val) 143 | class(ffh_t), intent(in) :: h 144 | FFH_KEY_ARG, intent(in) :: key 145 | FFH_VAL_ARG, intent(inout) :: val 146 | integer(FFH_INT_KIND) :: status 147 | call get_value(h, key, val, status) 148 | if (status < 0) error stop "Cannot get value" 149 | end subroutine uget_value 150 | 151 | !> Get the value corresponding to a key 152 | function fget_value(h, key) result(val) 153 | class(ffh_t), intent(in) :: h 154 | FFH_KEY_ARG, intent(in) :: key 155 | FFH_VAL_TYPE :: val 156 | integer(FFH_INT_KIND) :: status 157 | call get_value(h, key, val, status) 158 | if (status < 0) error stop "Cannot get value" 159 | end function fget_value 160 | 161 | !> Get the value corresponding to a key 162 | elemental pure function fget_value_or(h, key, not_found) result(val) 163 | class(ffh_t), intent(in) :: h 164 | FFH_KEY_ARG, intent(in) :: key 165 | FFH_VAL_ARG, intent(in) :: not_found 166 | FFH_VAL_TYPE :: val 167 | integer(FFH_INT_KIND) :: status 168 | call get_value(h, key, val, status) 169 | if (status < 0) val = not_found 170 | end function fget_value_or 171 | 172 | !> Store the value corresponding to a key 173 | subroutine store_value(h, key, val, ix, existing_key_is_error) 174 | class(ffh_t), intent(inout) :: h 175 | FFH_KEY_ARG, intent(in) :: key 176 | FFH_VAL_ARG, intent(in) :: val 177 | integer(FFH_INT_KIND), intent(out) :: ix !< Index (or -1 / -2) 178 | logical, optional, intent(in) :: existing_key_is_error 179 | 180 | call h%store_key(key, ix, existing_key_is_error) 181 | if (ix >= 0) h%vals(ix) = val 182 | end subroutine store_value 183 | 184 | subroutine ustore_value(h, key, val) 185 | class(ffh_t), intent(inout) :: h 186 | FFH_KEY_ARG, intent(in) :: key 187 | FFH_VAL_ARG, intent(in) :: val 188 | integer(FFH_INT_KIND) :: ix 189 | call store_value(h, key, val, ix) 190 | if (ix < 0) error stop "Cannot store value" 191 | end subroutine ustore_value 192 | #endif 193 | 194 | !> Store key in the table, and return index. A negative index is returned in 195 | !> case of an error. If resizing fails, -1 is returned. If the key was 196 | !> already present, and existing_key_is_error (an optional flag to decide 197 | !> whether storing an already existing key should throw an error) is true, 198 | !> the returned index is set to -2. 199 | subroutine store_key(h, key, i, existing_key_is_error) 200 | class(ffh_t), intent(inout) :: h 201 | FFH_KEY_ARG, intent(in) :: key 202 | integer(FFH_INT_KIND), intent(out) :: i 203 | logical, optional, intent(in) :: existing_key_is_error 204 | logical :: error_if_exists 205 | integer(FFH_INT_KIND) :: i_deleted, step, status 206 | 207 | ! Create a local copy of "existing_key_is_error" 208 | if(present(existing_key_is_error)) then 209 | error_if_exists = existing_key_is_error 210 | else 211 | error_if_exists = .false. 212 | endif 213 | 214 | i = -1 215 | 216 | if (h%n_occupied >= h%n_occupied_max) then 217 | if (h%n_keys_stored <= h%n_occupied_max/2) then 218 | ! Enough free space, but need to clean up the table 219 | call h%resize(h%n_buckets, status) 220 | if (status /= 0) return 221 | else 222 | ! Increase table size 223 | call h%resize(2*h%n_buckets, status) 224 | if (status /= 0) return 225 | end if 226 | end if 227 | 228 | i = hash_index(h, key) 229 | 230 | if (.not. bucket_empty(h, i)) then 231 | i_deleted = -1 232 | ! Skip over filled slots if they are deleted or have the wrong key. Skipping 233 | ! over deleted slots ensures that a key is not added twice, in case it is 234 | ! not at its 'first' hash index, and some keys in between have been deleted. 235 | do step = 1, h%n_buckets 236 | if (bucket_empty(h, i)) exit 237 | 238 | ! Check if key is already present 239 | if (.not. bucket_deleted(h, i) .and. & 240 | keys_equal(h%keys(i), key)) then 241 | if (error_if_exists) then 242 | ! Throw error 243 | i = -2 244 | return 245 | else 246 | exit 247 | end if 248 | end if 249 | 250 | if (bucket_deleted(h, i)) i_deleted = i 251 | i = next_index(h, i, step) 252 | end do 253 | 254 | if (bucket_empty(h, i) .and. i_deleted /= -1) then 255 | ! Use deleted location. By taking the last one, the deleted sequence 256 | ! is shrunk from the end. 257 | i = i_deleted 258 | end if 259 | end if 260 | 261 | if (bucket_empty(h, i)) then 262 | h%n_occupied = h%n_occupied + 1 263 | h%n_keys_stored = h%n_keys_stored + 1 264 | h%keys(i) = key 265 | call set_bucket_filled(h, i) 266 | else if (bucket_deleted(h, i)) then 267 | h%n_keys_stored = h%n_keys_stored + 1 268 | h%keys(i) = key 269 | call set_bucket_filled(h, i) 270 | end if 271 | 272 | end subroutine store_key 273 | 274 | !> Resize a hash table 275 | pure subroutine resize(h, new_n_buckets, status) 276 | class(ffh_t), intent(inout) :: h 277 | integer(FFH_INT_KIND), intent(in) :: new_n_buckets 278 | integer(FFH_INT_KIND), intent(out) :: status 279 | integer(FFH_INT_KIND) :: n_new, i, j, step 280 | type(ffh_t) :: hnew 281 | 282 | ! Make sure n_new is a power of two, and at least 4 283 | n_new = 4 284 | do while (n_new < new_n_buckets) 285 | n_new = 2 * n_new 286 | end do 287 | 288 | if (h%n_keys_stored >= nint(n_new * h%max_load_factor)) then 289 | ! Requested size is too small 290 | status = -1 291 | return 292 | end if 293 | 294 | ! Expand or shrink table 295 | #ifdef FFH_VAL_TYPE 296 | allocate(hnew%flags(0:n_new-1), hnew%keys(0:n_new-1), & 297 | hnew%vals(0:n_new-1), stat=status) 298 | #else 299 | allocate(hnew%flags(0:n_new-1), hnew%keys(0:n_new-1), stat=status) 300 | #endif 301 | if (status /= 0) then 302 | status = -1 303 | return 304 | end if 305 | 306 | hnew%flags(:) = achar(0) 307 | hnew%n_buckets = n_new 308 | hnew%n_keys_stored = h%n_keys_stored 309 | hnew%n_occupied = h%n_keys_stored 310 | hnew%n_occupied_max = nint(n_new * hnew%max_load_factor) 311 | hnew%hash_mask = n_new - 1 312 | hnew%max_load_factor = h%max_load_factor 313 | 314 | do j = 0, h%n_buckets-1 315 | if (h%valid_index(j)) then 316 | ! Find a new index 317 | i = hash_index(hnew, h%keys(j)) 318 | 319 | do step = 1, hnew%n_buckets 320 | if (bucket_empty(hnew, i)) exit 321 | i = next_index(hnew, i, step) 322 | end do 323 | #ifdef FFH_VAL_TYPE 324 | hnew%vals(i) = h%vals(j) 325 | #endif 326 | hnew%keys(i) = h%keys(j) 327 | call set_bucket_filled(hnew, i) 328 | end if 329 | end do 330 | 331 | h%n_buckets = hnew%n_buckets 332 | h%n_keys_stored = hnew%n_keys_stored 333 | h%n_occupied = hnew%n_occupied 334 | h%n_occupied_max = hnew%n_occupied_max 335 | h%hash_mask = hnew%hash_mask 336 | h%max_load_factor = hnew%max_load_factor 337 | 338 | call move_alloc(hnew%flags, h%flags) 339 | call move_alloc(hnew%keys, h%keys) 340 | #ifdef FFH_VAL_TYPE 341 | call move_alloc(hnew%vals, h%vals) 342 | #endif 343 | 344 | status = 0 345 | end subroutine resize 346 | 347 | !> Delete entry for given key 348 | pure subroutine delete_key(h, key, status) 349 | class(ffh_t), intent(inout) :: h 350 | FFH_KEY_ARG, intent(in) :: key 351 | integer(FFH_INT_KIND), intent(out) :: status 352 | integer(FFH_INT_KIND) :: ix 353 | 354 | ix = h%get_index(key) 355 | if (ix >= 0) then 356 | call set_bucket_deleted(h, ix) 357 | h%n_keys_stored = h%n_keys_stored - 1 358 | status = 0 359 | else 360 | status = -1 361 | end if 362 | end subroutine delete_key 363 | 364 | !> Delete entry for given key 365 | subroutine udelete_key(h, key) 366 | class(ffh_t), intent(inout) :: h 367 | FFH_KEY_ARG, intent(in) :: key 368 | integer(FFH_INT_KIND) :: status 369 | call h%delete_key(key, status) 370 | if (status < 0) error stop "Cannot delete key" 371 | end subroutine udelete_key 372 | 373 | !> Delete entry at index. A negative status indicates an error. 374 | pure subroutine delete_index(h, ix, status) 375 | class(ffh_t), intent(inout) :: h 376 | integer(FFH_INT_KIND), intent(in) :: ix 377 | integer(FFH_INT_KIND), intent(out) :: status 378 | 379 | if (ix < lbound(h%keys, 1) .or. ix > ubound(h%keys, 1)) then 380 | status = -1 381 | else if (.not. h%valid_index(ix)) then 382 | status = -1 383 | else 384 | call set_bucket_deleted(h, ix) 385 | h%n_keys_stored = h%n_keys_stored - 1 386 | status = 0 387 | end if 388 | end subroutine delete_index 389 | 390 | !> Delete entry at index 391 | subroutine udelete_index(h, ix) 392 | class(ffh_t), intent(inout) :: h 393 | integer(FFH_INT_KIND), intent(in) :: ix 394 | integer(FFH_INT_KIND) :: status 395 | call h%delete_index(ix, status) 396 | if (status < 0) error stop "Cannot delete key" 397 | end subroutine udelete_index 398 | 399 | !> Reset the hash table to initial empty state 400 | subroutine reset(h) 401 | class(ffh_t), intent(inout) :: h 402 | 403 | h%n_buckets = 0 404 | h%n_keys_stored = 0 405 | h%n_occupied = 0 406 | h%n_occupied_max = 0 407 | h%hash_mask = 0 408 | 409 | if (h%n_buckets > 0) then 410 | deallocate(h%flags) 411 | deallocate(h%keys) 412 | #ifdef FFH_VAL_TYPE 413 | deallocate(h%vals) 414 | #endif 415 | end if 416 | end subroutine reset 417 | 418 | pure logical function bucket_empty(h, i) 419 | type(ffh_t), intent(in) :: h 420 | integer(FFH_INT_KIND), intent(in) :: i 421 | bucket_empty = (iand(iachar(h%flags(i)), 1) == 0) 422 | end function bucket_empty 423 | 424 | pure logical function bucket_deleted(h, i) 425 | type(ffh_t), intent(in) :: h 426 | integer(FFH_INT_KIND), intent(in) :: i 427 | bucket_deleted = (iand(iachar(h%flags(i)), 2) /= 0) 428 | end function bucket_deleted 429 | 430 | !> Check if index is used and not deleted 431 | pure logical function valid_index(h, i) 432 | class(ffh_t), intent(in) :: h 433 | integer(FFH_INT_KIND), intent(in) :: i 434 | valid_index = (iachar(h%flags(i)) == 1) 435 | end function valid_index 436 | 437 | pure subroutine set_bucket_filled(h, i) 438 | type(ffh_t), intent(inout) :: h 439 | integer(FFH_INT_KIND), intent(in) :: i 440 | h%flags(i) = achar(1) 441 | end subroutine set_bucket_filled 442 | 443 | pure subroutine set_bucket_deleted(h, i) 444 | type(ffh_t), intent(inout) :: h 445 | integer(FFH_INT_KIND), intent(in) :: i 446 | h%flags(i) = achar(ior(iachar(h%flags(i)), 2)) 447 | end subroutine set_bucket_deleted 448 | 449 | !> Compute index for given key 450 | pure integer(FFH_INT_KIND) function hash_index(h, key) result(i) 451 | type(ffh_t), intent(in) :: h 452 | FFH_KEY_ARG, intent(in) :: key 453 | i = iand(h%hash_function(key), h%hash_mask) 454 | end function hash_index 455 | 456 | !> Compute next index inside a loop 457 | pure integer(FFH_INT_KIND) function next_index(h, i_prev, step) 458 | type(ffh_t), intent(in) :: h 459 | integer(FFH_INT_KIND), intent(in) :: i_prev 460 | integer(FFH_INT_KIND), intent(in) :: step 461 | next_index = iand(i_prev + step, h%hash_mask) 462 | end function next_index 463 | 464 | #ifndef FFH_CUSTOM_KEYS_EQUAL 465 | pure logical function keys_equal(a, b) 466 | FFH_KEY_ARG, intent(in) :: a, b 467 | keys_equal = (a == b) 468 | end function keys_equal 469 | #endif 470 | 471 | #ifndef FFH_CUSTOM_HASH_FUNCTION 472 | pure function hash_function(key) result(hash) 473 | FFH_KEY_ARG, intent(in) :: key 474 | integer(FFH_INT_KIND) :: hash 475 | integer(int32), parameter :: seed = 42 476 | 477 | #ifdef FFH_CUSTOM_CONVERT_KEY 478 | hash = default_hash_core(convert_key(key), seed) 479 | #elif defined(FFH_STRING_KEY_TYPE) 480 | hash = default_hash_core(trim(key), seed) 481 | #else 482 | integer, parameter :: n_bytes = ceiling(storage_size(key)*0.125d0) 483 | character(len=n_bytes) :: buf 484 | buf = transfer(key, buf) 485 | hash = default_hash_core(buf, seed) 486 | #endif 487 | end function hash_function 488 | 489 | !helper function managing the int64-based hashing 490 | pure function default_hash_core(buf, seed) result(hash) 491 | character(len=*), intent(in) :: buf 492 | integer(int32), intent(in) :: seed 493 | integer(FFH_INT_KIND) :: hash 494 | #ifdef FFH_ENABLE_INT64 495 | integer(int32) :: hash128(4) 496 | integer(int64) :: h1, h2 497 | call MurmurHash3_x64_128(buf, len(buf), seed, hash128) 498 | h1 = transfer(hash128(1:2), h1) 499 | h2 = transfer(hash128(3:4), h2) 500 | hash = ieor(h1, h2) 501 | #else 502 | call MurmurHash3_x86_32(buf, len(buf), seed, hash) 503 | #endif 504 | end function default_hash_core 505 | 506 | #ifdef FFH_ENABLE_INT64 507 | 508 | pure subroutine MurmurHash3_x64_128(key, klen, seed, hash) 509 | integer, intent(in) :: klen 510 | character(len=klen), intent(in) :: key 511 | integer(int32), intent(in) :: seed 512 | integer(int32), intent(out) :: hash(4) 513 | integer :: i, i0, n, nblocks 514 | integer(int64) :: h1, h2, k1, k2 515 | ! 0x87c37b91114253d5 516 | integer(int64), parameter :: c1 = -8663945395140668459_int64 517 | ! 0x4cf5ad432745937f 518 | integer(int64), parameter :: c2 = 5545529020109919103_int64 519 | integer, parameter :: shifts(15) = [(i*8, i=0,7), (i*8, i=0,6)] 520 | 521 | h1 = seed 522 | h2 = seed 523 | nblocks = shiftr(klen, 4) ! nblocks / 16 524 | 525 | ! body 526 | do i = 1, nblocks 527 | k1 = transfer(key(i*16-15:i*16-8), k1) 528 | k2 = transfer(key(i*16-7:i*16), k2) 529 | 530 | k1 = k1 * c1 531 | k1 = rotl64(k1,31_int64) 532 | k1 = k1 * c2 533 | 534 | h1 = ieor(h1, k1) 535 | h1 = rotl64(h1,27_int64) 536 | h1 = h1 + h2 537 | h1 = h1 * 5 + 1390208809_int64 ! 0x52dce729 538 | 539 | k2 = k2 * c2 540 | k2 = rotl64(k2,33_int64) 541 | k2 = k2 * c1 542 | 543 | h2 = ieor(h2, k2) 544 | h2 = rotl64(h2,31_int64) 545 | h2 = h1 + h2 546 | h2 = h2 * 5 + 944331445 ! 0x38495ab5 547 | end do 548 | 549 | ! tail 550 | k1 = 0 551 | k2 = 0 552 | i = iand(klen, 15) 553 | i0 = 16 * nblocks 554 | 555 | do n = i, 9, -1 556 | k2 = ieor(k2, shiftl(iachar(key(i0+n:i0+n), int64), shifts(n))) 557 | end do 558 | 559 | ! Check if the above loop was executed 560 | if (i >= 9) then 561 | k2 = k2 * c2 562 | k2 = rotl64(k2,33_int64) 563 | k2 = k2 * c1 564 | h2 = ieor(h2, k2) 565 | end if 566 | 567 | do n = min(i, 8), 1, -1 568 | k1 = ieor(k1, shiftl(iachar(key(i0+n:i0+n), int64), shifts(n))) 569 | end do 570 | 571 | ! Check if the above loop was executed 572 | if (i >= 1) then 573 | k1 = k1 * c1 574 | k1 = rotl64(k1,31_int64) 575 | k1 = k1 * c2 576 | h1 = ieor(h1, k1) 577 | end if 578 | 579 | ! finalization 580 | h1 = ieor(h1, int(klen, int64)) 581 | h2 = ieor(h2, int(klen, int64)) 582 | 583 | h1 = h1 + h2 584 | h2 = h2 + h1 585 | 586 | h1 = fmix64(h1) 587 | h2 = fmix64(h2) 588 | 589 | h1 = h1 + h2 590 | h2 = h2 + h1 591 | 592 | hash = transfer([h1, h2], hash) 593 | end subroutine MurmurHash3_x64_128 594 | 595 | pure integer(int64) function rotl64(x, r) 596 | integer(int64), intent(in) :: x 597 | integer(int64), intent(in) :: r 598 | rotl64 = ior(shiftl(x, r), shiftr(x, (64 - r))) 599 | end function rotl64 600 | 601 | pure integer(int64) function fmix64(k_in) result(k) 602 | integer(int64), intent(in) :: k_in 603 | k = k_in 604 | k = ieor(k, shiftr(k, 33)) 605 | k = k * (-49064778989728563_int64) !0xff51afd7ed558ccd 606 | k = ieor(k, shiftr(k, 33)) 607 | k = k * (-4265267296055464877_int64) !0xc4ceb9fe1a85ec53 608 | k = ieor(k, shiftr(k, 33)) 609 | end function fmix64 610 | 611 | #else 612 | 613 | pure subroutine MurmurHash3_x86_32(key, klen, seed, hash) 614 | integer, intent(in) :: klen 615 | character(len=klen), intent(in) :: key 616 | integer(int32), intent(in) :: seed 617 | integer(int32), intent(out) :: hash 618 | integer :: i, i0, n, nblocks 619 | integer(int32) :: h1, k1 620 | integer(int32), parameter :: c1 = -862048943 ! 0xcc9e2d51 621 | integer(int32), parameter :: c2 = 461845907 !0x1b873593 622 | integer, parameter :: shifts(3) = [0, 8, 16] 623 | 624 | h1 = seed 625 | nblocks = shiftr(klen, 2) ! nblocks/4 626 | 627 | ! body 628 | do i = 1, nblocks 629 | k1 = transfer(key(i*4-3:i*4), k1) 630 | 631 | k1 = k1 * c1 632 | k1 = rotl32(k1,15_int64) 633 | k1 = k1 * c2 634 | 635 | h1 = ieor(h1, k1) 636 | h1 = rotl32(h1,13_int64) 637 | h1 = h1 * 5 - 430675100 ! 0xe6546b64 638 | end do 639 | 640 | ! tail 641 | k1 = 0 642 | i = iand(klen, 3) 643 | i0 = 4 * nblocks 644 | 645 | do n = i, 1, -1 646 | k1 = ieor(k1, shiftl(iachar(key(i0+n:i0+n)), shifts(n))) 647 | end do 648 | 649 | ! Check if the above loop was executed 650 | if (i >= 1) then 651 | k1 = k1 * c1 652 | k1 = rotl32(k1,15_int64) 653 | k1 = k1 * c2 654 | h1 = ieor(h1, k1) 655 | end if 656 | 657 | ! finalization 658 | h1 = ieor(h1, klen) 659 | h1 = fmix32(h1) 660 | hash = h1 661 | end subroutine MurmurHash3_x86_32 662 | 663 | pure integer(int32) function rotl32(x, r) 664 | integer(int32), intent(in) :: x 665 | integer(int64), intent(in) :: r 666 | rotl32 = ior(shiftl(x, r), shiftr(x, (32 - r))) 667 | end function rotl32 668 | 669 | ! Finalization mix - force all bits of a hash block to avalanche 670 | pure integer(int32) function fmix32(h_in) result(h) 671 | integer(int32), intent(in) :: h_in 672 | h = h_in 673 | h = ieor(h, shiftr(h, 16)) 674 | h = h * (-2048144789) !0x85ebca6b 675 | h = ieor(h, shiftr(h, 13)) 676 | h = h * (-1028477387) !0xc2b2ae35 677 | h = ieor(h, shiftr(h, 16)) 678 | end function fmix32 679 | #endif 680 | #endif 681 | 682 | ! So that this file can be included multiple times 683 | #undef FFH_ENABLE_INT64 684 | #undef FFH_INT_KIND 685 | #undef FFH_KEY_TYPE 686 | #undef FFH_STRING_KEY_TYPE 687 | #undef FFH_KEY_ARG 688 | #undef FFH_VAL_TYPE 689 | #undef FFH_STRING_VAL_TYPE 690 | #undef FFH_VAL_ARG 691 | #undef FFH_CUSTOM_HASH_FUNCTION 692 | #undef FFH_CUSTOM_KEYS_EQUAL 693 | --------------------------------------------------------------------------------