Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 30 additions & 33 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ name: CI

on:
push:
branches: [ master ]
branches: [master]
pull_request:
branches: [ master ]
branches: [master]

env:
BUILD_DIR: ${{ github.workspace }}/build

jobs:
build:
Expand All @@ -14,35 +17,29 @@ jobs:
matrix:
os: [ubuntu-latest]
compiler: [gcc, clang]
build_type: [debug, release]
build_type: [Debug, Release]

steps:
- uses: actions/checkout@v4
- name: install-clang
if: matrix.compiler == 'clang'
run: |
sudo apt-get install -y clang
sudo update-alternatives --remove-all cc
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang 14
- name: configure-debug
if: matrix.build_type == 'debug'
run: |
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Debug -DHASHMAP_BUILD_TESTS=ON -DHASHMAP_BUILD_EXAMPLES=ON ${GITHUB_WORKSPACE}
- name: configure-release
if: matrix.build_type == 'release'
run: |
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DHASHMAP_BUILD_TESTS=ON -DHASHMAP_BUILD_EXAMPLES=ON ${GITHUB_WORKSPACE}
- name: make
run: |
make VERBOSE=1 -C build
- name: install
run: |
cd build
sudo make install
- name: test
run: |
cd build
ctest --output-on-failure
- uses: actions/checkout@v4

- name: Create build directory
run: mkdir -p ${{ env.BUILD_DIR }}

- name: Install Clang
if: matrix.compiler == 'clang'
run: |
sudo apt-get install -y clang
sudo update-alternatives --remove-all cc
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang 15

- name: Configure CMake
run: cmake -B ${{ env.BUILD_DIR }} -S ${{ github.workspace }} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DHASHMAP_BUILD_TESTS=ON -DHASHMAP_BUILD_EXAMPLES=ON

- name: Build
run: cmake --build ${{ env.BUILD_DIR }} --config ${{ matrix.build_type }}

- name: Install
run: sudo cmake --install ${{ env.BUILD_DIR }} --config ${{ matrix.build_type }}

- name: Test
run: ctest --output-on-failure --test-dir ${{ env.BUILD_DIR }}
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.5)
project(hashmap VERSION 2.0.0 LANGUAGES C)

set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD 11)

##############################################
# Build options
Expand Down Expand Up @@ -108,7 +108,7 @@ export(PACKAGE HashMap)

if(HASHMAP_BUILD_TESTS)
enable_testing()
add_subdirectory(test)
add_subdirectory(tests)
endif()

##############################################
Expand Down
105 changes: 81 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
# hashmap

[![ci](https://github.com/DavidLeeds/hashmap/workflows/CI/badge.svg)](https://github.com/DavidLeeds/hashmap/actions/workflows/ci.yml)

Templated type-safe hashmap implementation in C using open addressing and linear probing for collision resolution.

## Summary
This project came into existence because there are a notable lack of flexible and easy to use data structures available in C. C data structures with efficient, type-safe interfaces are virtually non-existent. Higher level languages have built-in libraries and templated classes, but plenty of embedded projects or higher level libraries are implemented in C. When it is undesireable to depend on a bulky library like Glib or grapple with a restrictive license agreement, this is the library for you.

This project came into existence because there are a notable lack of flexible and easy to use data structures available in C. C data structures with efficient, type-safe interfaces are virtually non-existent. Higher level languages have built-in libraries and templated classes, but plenty of embedded projects or higher level libraries are implemented in C. When it is undesireable to depend on a bulky library like Glib or grapple with a restrictive license agreement, this is the library for you.

## Goals

* **To scale gracefully to the full capacity of the numeric primitives in use.** We should be able to load enough entries to consume all memory on the system without hitting any bugs relating to integer overflows. Lookups on a hashtable with a hundreds of millions of entries should be performed in close to constant time, no different than lookups in a hashtable with 20 entries. Automatic rehashing occurs and maintains a load factor of 0.75 or less.
* **To provide a clean and easy-to-use interface.** C data structures often struggle to strike a balance between flexibility and ease of use. To this end, I wrapped a generic C backend implementation with light-weight pre-processor macros to create a templated interface that enables the compiler to type-check all function arguments and return values. All required type information is encoded in the hashmap declaration using the`HASHMAP()` macro. Unlike with header-only macro libraries, there is no code duplication or performance disadvantage over a traditional library with a non-type-safe `void *` interface.
* **To enable easy iteration and safe entry removal during iteration.** Applications often need these features, and the data structure should not hold them back. Easy to use `hashmap_foreach()` macros and a more flexible iterator interface are provided. This hashmap also uses an open addressing scheme, which has superior iteration performance to a similar hashmap implemented using separate chaining (buckets with linked lists). This is because fewer instructions are needed per iteration, and array traversal has superior cache performance than linked list traversal.
* **To use a very unrestrictive software license.** Using no license was an option, but I wanted to allow the code to be tracked, simply for my own edification. I chose the MIT license because it is the most common open source license in use, and it grants full rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell the code. Basically, take this code and do what you want with it. Just be nice and leave the license comment and my name at top of the file. Feel free to add your name if you are modifying and redistributing.
* **To use an unrestrictive software license.** I chose the MIT license because it is the most common open source license in use, and it grants full rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell the code. Basically, take this code and do what you want with it. Just be nice and leave the license comment and my name at top of the file. Feel free to add your name as a contributor if you are significantly modifying and redistributing.

## API Examples

Expand All @@ -28,13 +30,15 @@ HASHMAP(uint64_t, struct my_value) map2;
```

The structure defined by the `HASHMAP()` macro may be used directly, or named using `typedef`. For example:

```C
typedef HASHMAP(char, struct my_value) value_map_t;
```

### Initialization and cleanup

Maps must be initialized with a key hash function and a key comparator.
Maps must be initialized with a key hash function and a key comparator.

```C
/* Initialize the map structure */
hashmap_init(&map, my_key_hash, my_key_compare);
Expand All @@ -46,6 +50,7 @@ hashmap_cleanup(&map);
```

This library provides some hash functions, so you may not have to write your own:

* [hashmap_hash_string()](https://github.com/DavidLeeds/hashmap/blob/137d60b3818c22c79d2be5560150eb2eff981a68/include/hashmap_base.h#L54) - Case sensitive string hash
* [hashmap_hash_string_i()](https://github.com/DavidLeeds/hashmap/blob/137d60b3818c22c79d2be5560150eb2eff981a68/include/hashmap_base.h#L55) - Case insensitive string hash
* [hashmap_hash_default()](https://github.com/DavidLeeds/hashmap/blob/137d60b3818c22c79d2be5560150eb2eff981a68/include/hashmap_base.h#L53) - Hash function for arbitrary bytes that can be used by a user-defined hash function
Expand All @@ -59,15 +64,32 @@ hashmap_init(&map, hashmap_hash_string, strcmp);

Note that memory associated with map keys and values is not managed by the map, so you may need to free this before calling `hashmap_cleanup()`. Keys are often stored in the same structure as the value, but it is possible to have the map manage key memory allocation internally, by calling `hashmap_set_key_alloc_funcs()`.


### Value insertion and access
### Value insertion

```C
/* Insert a my_value (fails and returns -EEXIST if the key already exists) */
int result = hashmap_put(&map, "KeyABC", val);
struct my_value *val = /* ... */;

/* Add a my_value (fails and returns -EEXIST if the key already exists) */
int result1 = hashmap_put(&map, "KeyABC", val);

/* Add or update a my_value (assigns previous value to old_data if the key already exists) */
struct my_value *old_val;
int result2 = hashmap_insert(&map, "KeyABC", val, &old_val);
```

### Value access

```C
/* Access the value with a given key */
struct my_value *val = hashmap_get(&map, "KeyABC");
struct my_value *val1 = hashmap_get(&map, "KeyABC");

/* Access the key or value with an iterator */
HASHMAP_ITER(map) iter = hashmap_iter_find(&map, "keyABC");
const char *key = hashmap_iter_get_key(&iter);
struct my_value *val2 = hashmap_iter_get_data(&iter);

/* Check if an entry with the given key exists */
bool present = hashmap_contains(&map, "KeyABC");
```

### Value removal
Expand All @@ -76,10 +98,14 @@ struct my_value *val = hashmap_get(&map, "KeyABC");
/* Erase the entry with the given key */
struct my_value *val = hashmap_remove(&map, "KeyABC");

/* Erase the entry with an iterator */
HASHMAP_ITER(map) iter = hashmap_iter_find(&map, "keyABC");
hashmap_iter_remove(&iter);

/* Erase all entries */
hashmap_clear(&map);

/* Erase all entries and reset the hash table to its initial size */
/* Erase all entries and reset the hash table heap allocation to its initial size */
hashmap_reset(&map);
```

Expand Down Expand Up @@ -108,48 +134,79 @@ hashmap_foreach_data(val, &map) {
```

The above iteration macros are only safe for read-only access. To safely remove the current element during iteration, use the macros with a `_safe` suffix. These require an additional pointer parameter. For example:

```C
const char *key;
struct my_value *val;
void *temp;
void *pos;

/* Okay */
hashmap_foreach_key_safe(key, &map, temp) {
hashmap_foreach_key_safe(key, &map, pos) {
hashmap_remove(&map, key);
}
```

Iteration using the iterator interface.

```C
HASHMAP_ITER(map) it;

for (it = hashmap_iter(&map); hashmap_iter_valid(&it); hashmap_iter_next(&it) {
/*
* Access entry using:
* hashmap_iter_get_key()
* hashmap_iter_get_data()
* hashmap_iter_set_data()
*/
for (it = hashmap_iter(&map); hashmap_iter_valid(&it); hashmap_iter_next(&it)) {
/*
* Access entry using:
* hashmap_iter_get_key()
* hashmap_iter_get_data()
* hashmap_iter_set_data()
*/
}
```

### Additional examples
Are located in the `examples` directory in the source tree.

Are located in the [examples](https://github.com/DavidLeeds/hashmap/tree/master/examples) directory in the source tree.

## How to Build and Install
This project uses CMake to orchestrate the build and installallation process. To build and install on your host system, follow these easy steps:

This project uses CMake to orchestrate the build and installallation process.

### CMake Options

* `HASHMAP_BUILD_TESTS` - Set to `ON` to generate unit tests.
* `HASHMAP_BUILD_EXAMPLES` - Set to `ON` to build example code.

### How to build from source

To build and install on your host system, follow these easy steps:

1. `git clone https://github.com/DavidLeeds/hashmap.git` - download the source
2. `mkdir build-hashmap && cd build-hashmap` - create a build directory outside the source tree
3. `cmake ../hashmap` - run CMake to setup the build
4. `make` - compile the code
5. `make test` - run the unit tests (if enabled)
6. `sudo make install` - _OPTIONAL_ install the library on this system

##### CMake Options
### How to integrate with an existing CMake project

* `HASHMAP_BUILD_TESTS` - Set to `ON` to generate unit tests.
* `HASHMAP_BUILD_EXAMPLES` - Set to `ON` to build example code.
Clone and build this repository:

```cmake
include(FetchContent)

FetchContent_Declare(
hashmap
GIT_REPOSITORY https://github.com/DavidLeeds/hashmap.git
GIT_SHALLOW ON
)
FetchContent_MakeAvailable(hashmap)
```

Add `HashMap::HashMap` as a dependnecy, e.g.:

```cmake
add_executable(my_app main.c)
target_link_libraries(my_app PRIVATE HashMap::HashMap)
```

## Contibutions and Questions
I welcome all questions and contributions. Feel free to e-mail me, or put up a pull request. The core algorithm is stable, but I'm happy to consider CMake improvements, compiler compatibility fixes, or API additions.

I welcome all questions and contributions. Feel free to e-mail me, or put up a pull request. The core algorithm is stable, but I'm happy to consider CMake improvements, compiler compatibility fixes, or API additions.
4 changes: 2 additions & 2 deletions clib.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"name": "templated-hashmap",
"version": "v2.0.3",
"version": "v2.1.0",
"repo": "DavidLeeds/hashmap",
"description": " Templated type-safe hashmap implementation in C using open addressing and linear probing for collision resolution. ",
"description": " Templated type-safe hashmap implementation in C using open addressing and linear probing for collision resolution.",
"keywords": ["hashmap", "dictionary", "templated"],
"license": "MIT",
"src": ["src/hashmap.c", "include/hashmap.h", "include/hashmap_base.h"]
Expand Down
2 changes: 1 addition & 1 deletion examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ cmake_minimum_required(VERSION 3.5)

# Hashmap example
add_executable(hashmap_example hashmap_example.c)
target_compile_options(hashmap_example PRIVATE $<$<C_COMPILER_ID:GNU>:-Wall -Werror>)
target_compile_options(hashmap_example PRIVATE -Wall -Werror)
target_link_libraries(hashmap_example PRIVATE HashMap::HashMap)

Loading