Skip to content

Commit

Permalink
Introduce an IndexedHashMap, which has constant time lookup and sta…
Browse files Browse the repository at this point in the history
…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
Mark Winterrowd authored and arcs-c3po committed Jan 11, 2023
1 parent caecd22 commit 905a7b1
Show file tree
Hide file tree
Showing 3 changed files with 415 additions and 0 deletions.
22 changes: 22 additions & 0 deletions src/common/containers/BUILD
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",
],
)
105 changes: 105 additions & 0 deletions src/common/containers/indexed_hash_map.h
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_
Loading

0 comments on commit 905a7b1

Please sign in to comment.