blob: 2ac0565440e183cb7bcc2a5ae131699af0386f6d [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef NET_DISK_CACHE_SQL_INDEXED_PAIR_SET_H_
#define NET_DISK_CACHE_SQL_INDEXED_PAIR_SET_H_
#include <vector>
#include "base/check.h"
#include "net/base/net_export.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_map.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_set.h"
namespace disk_cache {
// IndexedPairSet is a memory-efficient data structure that stores a set of
// unique (Key, Value) pairs. It is optimized for cases where keys typically
// have only one associated value, but it can accommodate multiple values per
// key.
//
// To conserve memory, this class avoids the overhead of a nested container
// (like absl::flat_hash_map<Key, absl::flat_hash_set<Value>>) for the
// common case of a single value per key. It achieves this by storing the first
// value for a key in a primary map (`primary_map_`). Subsequent, unique values
// for the same key are stored in a secondary map (`secondary_map_`) that maps
// keys to a set of additional values.
//
// This design enables a fast `Contains(key)` lookup, as it only requires
// checking the primary map. However, this optimization makes `Insert` and
// `Remove` operations more complex. For instance, if the representative value
// in the primary map is removed, a new value from the secondary map must be
// promoted to take its place, if one exists.
template <class Key, class Value>
class NET_EXPORT_PRIVATE IndexedPairSet {
public:
IndexedPairSet() = default;
~IndexedPairSet() = default;
IndexedPairSet(const IndexedPairSet&) = delete;
IndexedPairSet& operator=(const IndexedPairSet&) = delete;
IndexedPairSet(IndexedPairSet&& other) noexcept
: primary_map_(std::move(other.primary_map_)),
secondary_map_(std::move(other.secondary_map_)),
size_(other.size_) {
other.size_ = 0;
}
IndexedPairSet& operator=(IndexedPairSet&& other) noexcept {
if (this == &other) {
return *this;
}
primary_map_ = std::move(other.primary_map_);
secondary_map_ = std::move(other.secondary_map_);
size_ = other.size_;
other.size_ = 0;
return *this;
}
// Inserts a key-value pair if it does not already exist.
// Returns true if the pair was inserted, false if it already existed.
bool Insert(Key key, Value value) {
auto primary_it = primary_map_.find(key);
if (primary_it == primary_map_.end()) {
// Key is new, insert into primary_map.
primary_map_.insert({key, value});
size_++;
return true;
}
if (primary_it->second == value) {
// Exact pair already exists in primary_map.
return false;
}
auto insert_result = secondary_map_[key].insert(value);
if (insert_result.second) {
size_++;
return true;
}
// Exact pair already exists in secondary_map_.
return false;
}
// Finds all values associated with a given key.
std::vector<Value> Find(Key key) const {
std::vector<Value> results;
auto primary_it = primary_map_.find(key);
if (primary_it == primary_map_.end()) {
return results;
}
results.push_back(primary_it->second);
auto secondary_it = secondary_map_.find(key);
if (secondary_it != secondary_map_.end()) {
results.insert(results.end(), secondary_it->second.begin(),
secondary_it->second.end());
}
return results;
}
// Removes a specific key-value pair. Returns true if the pair was found and
// removed, false otherwise.
bool Remove(Key key, Value value) {
auto primary_it = primary_map_.find(key);
if (primary_it == primary_map_.end()) {
return false; // Key does not exist.
}
if (primary_it->second == value) {
// The value to remove is in the primary_map.
auto secondary_it = secondary_map_.find(key);
if (secondary_it != secondary_map_.end()) {
// Promote a value from secondary_map_.
CHECK(!secondary_it->second.empty());
auto& secondary_set = secondary_it->second;
Value new_base_value = *secondary_set.begin();
secondary_set.erase(secondary_set.begin());
primary_it->second = new_base_value;
if (secondary_set.empty()) {
secondary_map_.erase(secondary_it);
}
} else {
// No additional values, just remove from primary_map.
primary_map_.erase(primary_it);
}
size_--;
return true;
}
// The value to remove is not in primary_map, check secondary_map_.
auto secondary_it = secondary_map_.find(key);
if (secondary_it != secondary_map_.end()) {
if (secondary_it->second.erase(value) > 0) {
if (secondary_it->second.empty()) {
secondary_map_.erase(secondary_it);
}
size_--;
return true;
}
}
// Value not found.
return false;
}
// Returns true if the given key exists. This is a fast lookup.
bool Contains(Key key) const { return primary_map_.contains(key); }
// Returns the total number of elements in the set.
size_t size() const { return size_; }
// Returns true if the set is empty.
bool empty() const { return size_ == 0; }
// Removes all elements from the set.
void Clear() {
primary_map_.clear();
secondary_map_.clear();
size_ = 0;
}
// Test-only methods
bool SecondaryMapContainsKeyForTesting(const Key& key) const {
return secondary_map_.contains(key);
}
private:
absl::flat_hash_map<Key, Value> primary_map_;
absl::flat_hash_map<Key, absl::flat_hash_set<Value>> secondary_map_;
size_t size_ = 0;
};
} // namespace disk_cache
#endif // NET_DISK_CACHE_SQL_INDEXED_PAIR_SET_H_