-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce an
IndexedHashMap
, which has constant time lookup and sta…
…ble iteration order. This change introduces an `IndexedHashMap`, which has O(1) lookup but stable iteration order. Under the hood, this is just a combination of a `absl::flat_hash_map` from key to index and a `std::vector` containing the values. This can't really use a standard C++ interface due to the key and the value not being co-located, but we can provide a sensible alternative map interface for it. PiperOrigin-RevId: 501124236
- Loading branch information
Showing
3 changed files
with
415 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# Containers for use in Raksha. | ||
|
||
cc_library( | ||
name = "indexed_hash_map", | ||
hdrs = ["indexed_hash_map.h"], | ||
visibility = ["//src:__subpackages__"], | ||
deps = [ | ||
"@com_google_absl//absl/container:flat_hash_map", | ||
"@com_google_absl//absl/log:check", | ||
], | ||
) | ||
|
||
cc_test( | ||
name = "indexed_hash_map_test", | ||
srcs = ["indexed_hash_map_test.cc"], | ||
deps = [ | ||
":indexed_hash_map", | ||
"//src/common/testing:gtest", | ||
"//testing/fuzzing/common_domains:utf8", | ||
"@fuzztest//fuzztest", | ||
], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
#ifndef SRC_COMMON_CONTAINERS_INDEXED_HASH_MAP_H_ | ||
#define SRC_COMMON_CONTAINERS_INDEXED_HASH_MAP_H_ | ||
|
||
#include <utility> | ||
#include <vector> | ||
|
||
#include "absl/container/flat_hash_map.h" | ||
#include "absl/log/check.h" | ||
|
||
namespace raksha::common::containers { | ||
|
||
// A hash map with constant-time lookup, but which preserves the order of | ||
// elements from insertion. It does this by being actually a regular hash map | ||
// combined with a vector, where the map goes from key to vector index, where | ||
// the vector contains the map's values. | ||
// | ||
// This provides constant time lookup and stable iteration order matching the | ||
// order in which elements were inserted, at the cost of about 1 size_t of | ||
// memory per key-value pair plus a non-contiguous memory access from the map to | ||
// the vector. | ||
// | ||
// In addition, because the layout is relatively odd, it would be difficult to | ||
// use the traditional C++ map interface. Instead, we use something a little | ||
// more akin to the interface you'd see for a Java map. | ||
template <class Key, class Value> | ||
class IndexedHashMap { | ||
public: | ||
explicit IndexedHashMap<Key, Value>() = default; | ||
explicit IndexedHashMap( | ||
std::vector<std::pair<Key, Value>> key_value_pair_vector) { | ||
for (auto iter = std::make_move_iterator(key_value_pair_vector.begin()); | ||
iter != std::make_move_iterator(key_value_pair_vector.end()); ++iter) { | ||
auto [key, value] = std::move(*iter); | ||
InsertExpectSuccess(std::move(key), std::move(value)); | ||
} | ||
} | ||
|
||
class iterator { | ||
public: | ||
using InnerIterator = typename std::vector<Value>::const_iterator; | ||
iterator(InnerIterator inner_iterator) | ||
: inner_iterator_(std::move(inner_iterator)) {} | ||
|
||
iterator operator++() { | ||
++inner_iterator_; | ||
return *this; | ||
} | ||
|
||
iterator operator++(int) { | ||
iterator previous = *this; | ||
++(*this); | ||
return previous; | ||
} | ||
|
||
bool operator==(const iterator &other) const { | ||
return inner_iterator_ == other.inner_iterator_; | ||
} | ||
|
||
bool operator!=(const iterator &other) const { return !(*this == other); } | ||
|
||
const Value &operator*() const { return *inner_iterator_; } | ||
|
||
private: | ||
friend class IndexedHashMap<Key, Value>; | ||
InnerIterator inner_iterator_; | ||
}; | ||
|
||
iterator begin() const { return iterator(value_vector_.begin()); } | ||
|
||
iterator end() const { return iterator(value_vector_.end()); } | ||
|
||
// Associate `value` with `key` if `key` is not yet associated with any value. | ||
// If it is already associated with a value, do nothing. Return whether or not | ||
// the map was successfully modified. | ||
bool InsertIfNotPresent(Key key, Value value) { | ||
auto [iterator, insert_success] = | ||
key_to_index_map_.insert({std::move(key), value_vector_.size()}); | ||
if (insert_success) { | ||
value_vector_.push_back(std::move(value)); | ||
} | ||
return insert_success; | ||
} | ||
|
||
void InsertExpectSuccess(Key key, Value value) { | ||
CHECK(InsertIfNotPresent(std::move(key), std::move(value))) | ||
<< "Key was already associated with a value!"; | ||
} | ||
|
||
size_t GetIndex(Key key) const { | ||
auto find_result = key_to_index_map_.find(key); | ||
CHECK(find_result != key_to_index_map_.end()) << "Key not present in map!"; | ||
return find_result->second; | ||
} | ||
|
||
const Value &Get(Key key) const { return value_vector_.at(GetIndex(key)); } | ||
|
||
size_t Size() const { return key_to_index_map_.size(); } | ||
|
||
private: | ||
absl::flat_hash_map<Key, size_t> key_to_index_map_; | ||
std::vector<Value> value_vector_; | ||
}; | ||
} // namespace raksha::common::containers | ||
|
||
#endif // SRC_COMMON_CONTAINERS_INDEXED_HASH_MAP_H_ |
Oops, something went wrong.