Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add extract(key) and extract(it) API #105

Merged
merged 4 commits into from
Dec 24, 2023
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
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.12)
project("unordered_dense"
VERSION 4.3.1
VERSION 4.4.0
DESCRIPTION "A fast & densely stored hashmap and hashset based on robin-hood backward shift deletion"
HOMEPAGE_URL "https://github.com/martinus/unordered_dense")

Expand Down
21 changes: 16 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ Additionally, there are `ankerl::unordered_dense::segmented_map` and `ankerl::un
- [3.2.6. Hash the Whole Memory](#326-hash-the-whole-memory)
- [3.3. Container API](#33-container-api)
- [3.3.1. `auto extract() && -> value_container_type`](#331-auto-extract----value_container_type)
- [3.3.2. `[[nodiscard]] auto values() const noexcept -> value_container_type const&`](#332-nodiscard-auto-values-const-noexcept---value_container_type-const)
- [3.3.3. `auto replace(value_container_type&& container)`](#333-auto-replacevalue_container_type-container)
- [3.3.2. `extract()` single Elements](#332-extract-single-elements)
- [3.3.3. `[[nodiscard]] auto values() const noexcept -> value_container_type const&`](#333-nodiscard-auto-values-const-noexcept---value_container_type-const)
- [3.3.4. `auto replace(value_container_type&& container)`](#334-auto-replacevalue_container_type-container)
- [3.4. Custom Container Types](#34-custom-container-types)
- [3.5. Custom Bucket Types](#35-custom-bucket-types)
- [3.5.1. `ankerl::unordered_dense::bucket_type::standard`](#351-ankerlunordered_densebucket_typestandard)
Expand Down Expand Up @@ -251,17 +252,27 @@ struct custom_hash_unique_object_representation {

### 3.3. Container API

In addition to the standard `std::unordered_map` API (see https://en.cppreference.com/w/cpp/container/unordered_map) we have additional API leveraging the fact that we're using a random access container internally:
In addition to the standard `std::unordered_map` API (see https://en.cppreference.com/w/cpp/container/unordered_map) we have additional API that is somewhat similar to the node API, but leverages the fact that we're using a random access container internally:

#### 3.3.1. `auto extract() && -> value_container_type`

Extracts the internally used container. `*this` is emptied.

#### 3.3.2. `[[nodiscard]] auto values() const noexcept -> value_container_type const&`
#### 3.3.2. `extract()` single Elements

Similar to `erase()` I have an API call `extract()`. It behaves exactly the same as `erase`, except that the return value is the moved element that is removed from the container:

* `auto extract(const_iterator it) -> value_type`
* `auto extract(Key const& key) -> std::optional<value_type>`
* `template <class K> auto extract(K&& key) -> std::optional<value_type>`

Note that the `extract(key)` API returns an `std::optional<value_type>` that is empty when the key is not found.

#### 3.3.3. `[[nodiscard]] auto values() const noexcept -> value_container_type const&`

Exposes the underlying values container.

#### 3.3.3. `auto replace(value_container_type&& container)`
#### 3.3.4. `auto replace(value_container_type&& container)`

Discards the internally held container and replaces it with the one passed. Non-unique elements are
removed, and the container will be partly reordered when non-unique elements are found.
Expand Down
64 changes: 54 additions & 10 deletions include/ankerl/unordered_dense.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
///////////////////////// ankerl::unordered_dense::{map, set} /////////////////////////

// A fast & densely stored hashmap and hashset based on robin-hood backward shift deletion.
// Version 4.3.1
// Version 4.4.0
// https://github.com/martinus/unordered_dense
//
// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
Expand Down Expand Up @@ -31,8 +31,8 @@

// see https://semver.org/spec/v2.0.0.html
#define ANKERL_UNORDERED_DENSE_VERSION_MAJOR 4 // NOLINT(cppcoreguidelines-macro-usage) incompatible API changes
#define ANKERL_UNORDERED_DENSE_VERSION_MINOR 3 // NOLINT(cppcoreguidelines-macro-usage) backwards compatible functionality
#define ANKERL_UNORDERED_DENSE_VERSION_PATCH 1 // NOLINT(cppcoreguidelines-macro-usage) backwards compatible bug fixes
#define ANKERL_UNORDERED_DENSE_VERSION_MINOR 4 // NOLINT(cppcoreguidelines-macro-usage) backwards compatible functionality
#define ANKERL_UNORDERED_DENSE_VERSION_PATCH 0 // NOLINT(cppcoreguidelines-macro-usage) backwards compatible bug fixes

// API versioning with inline namespace, see https://www.foonathan.net/2018/11/inline-namespaces/

Expand Down Expand Up @@ -86,6 +86,7 @@
# include <iterator> // for pair, distance
# include <limits> // for numeric_limits
# include <memory> // for allocator, allocator_traits, shared_ptr
# include <optional> // for optional
# include <stdexcept> // for out_of_range
# include <string> // for basic_string
# include <string_view> // for basic_string_view, hash
Expand Down Expand Up @@ -1008,7 +1009,8 @@ class table : public std::conditional_t<is_map_v<T>, base_table_type_map<T>, bas
clear_and_fill_buckets_from_values();
}

void do_erase(value_idx_type bucket_idx) {
template <typename Op>
void do_erase(value_idx_type bucket_idx, Op handle_erased_value) {
auto const value_idx_to_remove = at(m_buckets, bucket_idx).m_value_idx;

// shift down until either empty or an element with correct spot is found
Expand All @@ -1019,6 +1021,7 @@ class table : public std::conditional_t<is_map_v<T>, base_table_type_map<T>, bas
bucket_idx = std::exchange(next_bucket_idx, next(next_bucket_idx));
}
at(m_buckets, bucket_idx) = {};
handle_erased_value(std::move(m_values[value_idx_to_remove]));

// update m_values
if (value_idx_to_remove != m_values.size() - 1) {
Expand All @@ -1039,8 +1042,8 @@ class table : public std::conditional_t<is_map_v<T>, base_table_type_map<T>, bas
m_values.pop_back();
}

template <typename K>
auto do_erase_key(K&& key) -> size_t {
template <typename K, typename Op>
auto do_erase_key(K&& key, Op handle_erased_value) -> size_t {
if (empty()) {
return 0;
}
Expand All @@ -1056,7 +1059,7 @@ class table : public std::conditional_t<is_map_v<T>, base_table_type_map<T>, bas
if (dist_and_fingerprint != at(m_buckets, bucket_idx).m_dist_and_fingerprint) {
return 0;
}
do_erase(bucket_idx);
do_erase(bucket_idx, handle_erased_value);
return 1;
}

Expand Down Expand Up @@ -1619,15 +1622,37 @@ class table : public std::conditional_t<is_map_v<T>, base_table_type_map<T>, bas
bucket_idx = next(bucket_idx);
}

do_erase(bucket_idx);
do_erase(bucket_idx, [](value_type&& /*unused*/) {
});
return begin() + static_cast<difference_type>(value_idx_to_remove);
}

auto extract(iterator it) -> value_type {
auto hash = mixed_hash(get_key(*it));
auto bucket_idx = bucket_idx_from_hash(hash);

auto const value_idx_to_remove = static_cast<value_idx_type>(it - cbegin());
while (at(m_buckets, bucket_idx).m_value_idx != value_idx_to_remove) {
bucket_idx = next(bucket_idx);
}

auto tmp = std::optional<value_type>{};
do_erase(bucket_idx, [&tmp](value_type&& val) {
tmp = std::move(val);
});
return std::move(tmp).value();
}

template <typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
auto erase(const_iterator it) -> iterator {
return erase(begin() + (it - cbegin()));
}

template <typename Q = T, std::enable_if_t<is_map_v<Q>, bool> = true>
auto extract(const_iterator it) -> value_type {
return extract(begin() + (it - cbegin()));
}

auto erase(const_iterator first, const_iterator last) -> iterator {
auto const idx_first = first - cbegin();
auto const idx_last = last - cbegin();
Expand All @@ -1653,12 +1678,31 @@ class table : public std::conditional_t<is_map_v<T>, base_table_type_map<T>, bas
}

auto erase(Key const& key) -> size_t {
return do_erase_key(key);
return do_erase_key(key, [](value_type&& /*unused*/) {
});
}

auto extract(Key const& key) -> std::optional<value_type> {
auto tmp = std::optional<value_type>{};
do_erase_key(key, [&tmp](value_type&& val) {
tmp = std::move(val);
});
return tmp;
}

template <class K, class H = Hash, class KE = KeyEqual, std::enable_if_t<is_transparent_v<H, KE>, bool> = true>
auto erase(K&& key) -> size_t {
return do_erase_key(std::forward<K>(key));
return do_erase_key(std::forward<K>(key), [](value_type&& /*unused*/) {
});
}

template <class K, class H = Hash, class KE = KeyEqual, std::enable_if_t<is_transparent_v<H, KE>, bool> = true>
auto extract(K&& key) -> std::optional<value_type> {
auto tmp = std::optional<value_type>{};
do_erase_key(std::forward<K>(key), [&tmp](value_type&& val) {
tmp = std::move(val);
});
return tmp;
}

void swap(table& other) noexcept(noexcept(std::is_nothrow_swappable_v<value_container_type> &&
Expand Down
2 changes: 1 addition & 1 deletion meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
#

project('unordered_dense', 'cpp',
version: '4.3.1',
version: '4.4.0',
license: 'MIT',
default_options : [
'cpp_std=c++17',
Expand Down
36 changes: 36 additions & 0 deletions test/unit/extract.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include <app/counter.h>
#include <app/doctest.h>

#include <fmt/format.h>

TEST_CASE_MAP("extract", counter::obj, counter::obj) {
auto counts = counter();
INFO(counts);
Expand All @@ -24,3 +26,37 @@ TEST_CASE_MAP("extract", counter::obj, counter::obj) {
REQUIRE(container[i].second.get() == i);
}
}

TEST_CASE_MAP("extract_element", counter::obj, counter::obj) {
auto counts = counter();
INFO(counts);

counts("init");
auto map = map_t();
for (size_t i = 0; i < 100; ++i) {
map.try_emplace(counter::obj{i, counts}, i, counts);
}

// extract(key)
for (size_t i = 0; i < 20; ++i) {
auto query = counter::obj{i, counts};
counts("before remove 1");
auto opt = map.extract(query);
counts("after remove 1");
REQUIRE(opt);
REQUIRE(opt->first.get() == i);
REQUIRE(opt->second.get() == i);
}
REQUIRE(map.size() == 80);

// extract iterator
for (size_t i = 20; i < 100; ++i) {
auto query = counter::obj{i, counts};
auto it = map.find(query);
REQUIRE(it != map.end());
auto opt = map.extract(it);
REQUIRE(opt.first.get() == i);
REQUIRE(opt.second.get() == i);
}
REQUIRE(map.empty());
}
2 changes: 1 addition & 1 deletion test/unit/namespace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

#include <doctest.h>

namespace versioned_namespace = ankerl::unordered_dense::v4_3_1;
namespace versioned_namespace = ankerl::unordered_dense::v4_4_0;

static_assert(std::is_same_v<versioned_namespace::map<int, int>, ankerl::unordered_dense::map<int, int>>);
static_assert(std::is_same_v<versioned_namespace::hash<int>, ankerl::unordered_dense::hash<int>>);
Expand Down
2 changes: 1 addition & 1 deletion test/unit/tuple_hash.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ TEST_CASE("tuple_hash_with_stringview") {

// #include <absl/hash/hash.h>

TEST_CASE("bench_tuple_hash" * doctest::test_suite("bench")) {
TEST_CASE("bench_tuple_hash" * doctest::test_suite("bench") * doctest::skip()) {
using T = std::tuple<uint8_t, int, uint16_t, uint64_t>;

auto vecs = std::vector<T>(100);
Expand Down
Loading