Skip to content

Commit

Permalink
Add extract(key) and extract(it) API.
Browse files Browse the repository at this point in the history
Similar to erase(), but the return value is the erased element, moved out.
This has practically the same performance behavior as erase(), except
for 2 additional moves. (once into a temporary variable), and then out.
  • Loading branch information
martinus committed Dec 24, 2023
1 parent 2df51fb commit 58eb6bc
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 7 deletions.
58 changes: 51 additions & 7 deletions include/ankerl/unordered_dense.h
Original file line number Diff line number Diff line change
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
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());
}

0 comments on commit 58eb6bc

Please sign in to comment.