Commit 6bf7e04f authored by Christoph Schwering's avatar Christoph Schwering Committed by Commit Bot

[Autofill] Added type-safe bitset.

This CL adds a type-safe set container for dense integers, following
std::set's API.

Currently, there are multiple use-cases in Autofill of the following
two patterns to express sets of small, dense integers:

Pattern 1:
  enum class E { A = 1, B = 2, C = 3, ... };
  std::set<E> set;
  set.insert(B);

Pattern 2:
  enum E { A = 1 << 0, B = 1 << 2, C = 1 << 3, ... };
  uint32_t bitmask = 0;
  bitmask |= B;

This CL's container combines the type-safety of the former pattern
with the efficiency of the latter.

Bug: 1007974
Change-Id: Ida9b9cec244119a27eca2c171b4fb2f52443d099
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2350423
Commit-Queue: Christoph Schwering <schwering@google.com>
Reviewed-by: default avatarJan Wilken Dörrie <jdoerrie@chromium.org>
Cr-Commit-Position: refs/heads/master@{#820983}
parent f67d9901
......@@ -32,6 +32,7 @@ static_library("common") {
"autofill_tick_clock.h",
"autofill_util.cc",
"autofill_util.h",
"dense_set.h",
"field_data_manager.cc",
"field_data_manager.h",
"form_data.cc",
......@@ -89,6 +90,7 @@ source_set("unit_tests") {
"autofill_prefs_unittest.cc",
"autofill_regexes_unittest.cc",
"autofill_util_unittest.cc",
"dense_set_unittest.cc",
"field_data_manager_unittest.cc",
"form_data_unittest.cc",
"form_field_data_unittest.cc",
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_AUTOFILL_CORE_COMMON_DENSE_SET_H_
#define COMPONENTS_AUTOFILL_CORE_COMMON_DENSE_SET_H_
#include <bitset>
#include <cstddef>
#include <iterator>
#include <type_traits>
#include "base/check.h"
#include "base/check_op.h"
#include "base/numerics/safe_conversions.h"
namespace autofill {
// A set container with a std::set<T>-like interface for integral or enum types
// T that have a dense and small representation as unsigned integers.
//
// The order of the elements in the container corresponds to their integer
// representation.
//
// The lower and upper bounds of elements storable in a container are
// [T(0), kEnd).
//
// Internally, the set is represented as a std::bitset.
//
// Time and space complexity depend on std::bitset:
// - insert(), erase(), contains() should run in time O(1)
// - empty(), size(), iteration should run in time O(kEnd)
// - sizeof(DenseSet) should be ceil(kEnd / 8) bytes.
//
// Iterators are invalidated when the owning container is destructed or moved,
// or when the element the iterator points to is erased from the container.
template <typename T, T kEnd>
class DenseSet {
private:
using Index = std::make_unsigned_t<T>;
public:
// A bidirectional iterator for the DenseSet.
class Iterator {
public:
using iterator_category = std::bidirectional_iterator_tag;
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = void;
using reference = T;
constexpr Iterator() = default;
friend bool operator==(const Iterator& a, const Iterator& b) {
DCHECK(a.owner_);
DCHECK_EQ(a.owner_, b.owner_);
return a.index_ == b.index_;
}
friend bool operator!=(const Iterator& a, const Iterator& b) {
return !(a == b);
}
T operator*() const {
DCHECK(derefenceable());
return index_to_value(index_);
}
Iterator& operator++() {
++index_;
Skip(kForward);
return *this;
}
Iterator operator++(int) {
auto that = *this;
operator++();
return that;
}
Iterator& operator--() {
--index_;
Skip(kBackward);
return *this;
}
Iterator operator--(int) {
auto that = *this;
operator--();
return that;
}
private:
friend DenseSet;
enum Direction { kBackward = -1, kForward = 1 };
constexpr Iterator(const DenseSet* owner, Index index)
: owner_(owner), index_(index) {}
// Advances the index, starting from the current position, to the next
// non-empty one. std::bitset does not offer a find-next-set operation.
void Skip(Direction direction) {
DCHECK_LE(index_, owner_->max_size());
while (index_ < owner_->max_size() && !derefenceable()) {
index_ += direction;
}
}
bool derefenceable() const {
DCHECK_LT(index_, owner_->max_size());
return owner_->bitset_.test(index_);
}
const DenseSet* owner_ = nullptr;
// The current index is in the interval [0, owner_->max_size()].
Index index_ = 0;
};
using value_type = T;
using iterator = Iterator;
using const_iterator = Iterator;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
constexpr DenseSet() = default;
DenseSet(std::initializer_list<T> init) {
for (const auto& x : init) {
insert(x);
}
}
template <typename InputIt>
DenseSet(InputIt first, InputIt last) {
for (auto it = first; it != last; ++it) {
insert(*it);
}
}
friend bool operator==(const DenseSet& a, const DenseSet& b) {
return a.bitset_ == b.bitset_;
}
friend bool operator!=(const DenseSet& a, const DenseSet& b) {
return !(a == b);
}
// Iterators.
// Returns an iterator to the beginning.
iterator begin() const {
const_iterator it(this, 0);
it.Skip(Iterator::kForward);
return it;
}
const_iterator cbegin() const { return begin(); }
// Returns an iterator to the end.
iterator end() const { return iterator(this, max_size()); }
const_iterator cend() const { return end(); }
// Returns a reverse iterator to the beginning.
reverse_iterator rbegin() const { return reverse_iterator(end()); }
const_reverse_iterator crbegin() const { return rbegin(); }
// Returns a reverse iterator to the end.
reverse_iterator rend() const { return reverse_iterator(begin()); }
const_reverse_iterator crend() const { return rend(); }
// Capacity.
// Returns true if the set is empty, otherwise false.
bool empty() const { return bitset_.none(); }
// Returns the number of elements the set has.
size_t size() const { return bitset_.count(); }
// Returns the maximum number of elements the set can have.
size_t max_size() const { return bitset_.size(); }
// Modifiers.
// Clears the contents.
void clear() { bitset_.reset(); }
// Inserts value |x| if it is not present yet, and returns an iterator to the
// inserted or existing element and a boolean that indicates whether the
// insertion took place.
std::pair<iterator, bool> insert(T x) {
bool contained = contains(x);
bitset_.set(value_to_index(x));
return {find(x), !contained};
}
// Erases the element whose index matches the index of |x| and returns the
// number of erased elements (0 or 1).
size_t erase(T x) {
bool contained = contains(x);
bitset_.reset(value_to_index(x));
return contained ? 1 : 0;
}
// Erases the element |*it| and returns an iterator to its successor.
iterator erase(const_iterator it) {
DCHECK(it.owner_ == this && it.derefenceable());
bitset_.reset(it.index_);
it.Skip(const_iterator::kForward);
return it;
}
// Erases the elements [first,last) and returns |last|.
iterator erase(const_iterator first, const_iterator last) {
DCHECK(first.owner_ == this && last.owner_ == this);
while (first != last) {
bitset_.reset(first.index_);
++first;
}
return last;
}
// Lookup.
// Returns 1 if |x| is an element, otherwise 0.
size_t count(T x) const { return contains(x) ? 1 : 0; }
// Returns an iterator to the element |x| if it exists, otherwise end().
const_iterator find(T x) const {
return contains(x) ? const_iterator(this, value_to_index(x)) : cend();
}
// Returns true if |x| is an element, else |false|.
bool contains(T x) const { return bitset_.test(value_to_index(x)); }
// Returns an iterator to the first element not less than the |x|, or end().
const_iterator lower_bound(T x) const {
const_iterator it(this, value_to_index(x));
it.Skip(Iterator::kForward);
return it;
}
// Returns an iterator to the first element greater than |x|, or end().
const_iterator upper_bound(T x) const {
const_iterator it(this, value_to_index(x) + 1);
it.Skip(Iterator::kForward);
return it;
}
private:
friend Iterator;
struct Wrapper {
using type = T;
};
static constexpr Index value_to_index(T x) {
DCHECK(index_to_value(0) <= x && x < kEnd);
return base::checked_cast<Index>(x);
}
static constexpr T index_to_value(Index i) {
DCHECK_LT(i, base::checked_cast<Index>(kEnd));
using UnderlyingType =
typename std::conditional_t<std::is_enum<T>::value,
std::underlying_type<T>, Wrapper>::type;
return static_cast<T>(base::checked_cast<UnderlyingType>(i));
}
static_assert(std::is_integral<T>::value || std::is_enum<T>::value, "");
static_assert(index_to_value(0) <= kEnd, "");
std::bitset<base::checked_cast<Index>(kEnd)> bitset_{};
};
} // namespace autofill
#endif // COMPONENTS_AUTOFILL_CORE_COMMON_BITSET_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/autofill/core/common/dense_set.h"
#include "base/logging.h"
#include "base/rand_util.h"
#include "base/ranges/algorithm.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace autofill {
TEST(DenseSet, initialization) {
enum class T : size_t {
One = 1,
Two = 2,
Three = 3,
Four = 4,
Five = 5,
kEnd = 6
};
using DS = DenseSet<T, T::kEnd>;
DS s;
EXPECT_TRUE(s.empty());
EXPECT_EQ(s.size(), 0u);
EXPECT_EQ(DS(s.begin(), s.end()), s);
s.insert(T::Two);
s.insert(T::Four);
s.insert(T::One);
EXPECT_EQ(s.size(), 3u);
EXPECT_EQ(DS(s.begin(), s.end()), s);
EXPECT_EQ(DS(s.cbegin(), s.cend()), s);
EXPECT_EQ(DS(s.rbegin(), s.rend()), s);
EXPECT_EQ(DS(s.crbegin(), s.crend()), s);
EXPECT_EQ(DS({T::Four, T::Two, T::One}), s);
}
TEST(DenseSet, iterators_begin_end) {
enum class T : int {
One = 1,
Two = 2,
Three = 3,
Four = 4,
Five = 5,
kEnd = 6
};
using DS = DenseSet<T, T::kEnd>;
DS s;
s.insert(T::Two);
s.insert(T::Four);
s.insert(T::One);
EXPECT_EQ(s.size(), 3u);
EXPECT_EQ(std::distance(s.begin(), s.end()), 3);
{
auto it = s.begin();
auto x1 = *it++;
auto x2 = *it++;
auto x3 = *it++;
EXPECT_EQ(it, s.end());
EXPECT_EQ(x1, T::One);
EXPECT_EQ(x2, T::Two);
EXPECT_EQ(x3, T::Four);
}
{
auto it = s.begin();
auto x1 = *it;
auto x2 = *++it;
auto x3 = *++it;
EXPECT_NE(it, s.end());
EXPECT_EQ(x1, T::One);
EXPECT_EQ(x2, T::Two);
EXPECT_EQ(x3, T::Four);
}
EXPECT_THAT(s, ::testing::ElementsAre(T::One, T::Two, T::Four));
}
TEST(DenseSet, iterators_begin_end_reverse) {
enum class T : char {
One = 1,
Two = 2,
Three = 3,
Four = 4,
Five = 5,
kEnd = 6
};
using DS = DenseSet<T, T::kEnd>;
DS s;
s.insert(T::Two);
s.insert(T::Four);
s.insert(T::One);
EXPECT_EQ(s.size(), 3u);
{
auto it = s.end();
it--;
auto x3 = *it--;
auto x2 = *it--;
auto x1 = *it;
EXPECT_EQ(it, s.begin());
EXPECT_EQ(x1, T::One);
EXPECT_EQ(x2, T::Two);
EXPECT_EQ(x3, T::Four);
}
{
auto it = s.end();
auto x3 = *--it;
auto x2 = *--it;
auto x1 = *--it;
EXPECT_EQ(it, s.begin());
EXPECT_EQ(x1, T::One);
EXPECT_EQ(x2, T::Two);
EXPECT_EQ(x3, T::Four);
}
}
TEST(DenseSet, iterators_rbegin_rend) {
enum class T { One = 1, Two = 2, Three = 3, Four = 4, Five = 5, kEnd = 6 };
using DS = DenseSet<T, T::kEnd>;
DS s;
s.insert(T::Two);
s.insert(T::Four);
s.insert(T::One);
EXPECT_EQ(s.size(), 3u);
EXPECT_EQ(std::distance(s.rbegin(), s.rend()), 3);
{
auto it = s.rbegin();
auto x3 = *it++;
auto x2 = *it++;
auto x1 = *it++;
EXPECT_EQ(it, s.rend());
EXPECT_EQ(x1, T::One);
EXPECT_EQ(x2, T::Two);
EXPECT_EQ(x3, T::Four);
}
{
auto it = s.rbegin();
auto x3 = *it;
auto x2 = *++it;
auto x1 = *++it;
EXPECT_NE(it, s.rend());
EXPECT_EQ(x1, T::One);
EXPECT_EQ(x2, T::Two);
EXPECT_EQ(x3, T::Four);
}
EXPECT_THAT(std::vector<T>(s.rbegin(), s.rend()),
::testing::ElementsAre(T::Four, T::Two, T::One));
}
TEST(DenseSet, lookup) {
enum class T { One = 1, Two = 2, Three = 3, Four = 4, Five = 5, kEnd = 6 };
using DS = DenseSet<T, T::kEnd>;
DS s;
s.insert(T::Two);
s.insert(T::Four);
s.insert(T::One);
EXPECT_FALSE(s.contains(static_cast<T>(0)));
EXPECT_TRUE(s.contains(T::One));
EXPECT_TRUE(s.contains(T::Two));
EXPECT_FALSE(s.contains(T::Three));
EXPECT_TRUE(s.contains(T::Four));
EXPECT_FALSE(s.contains(T::Five));
EXPECT_EQ(s.contains(static_cast<T>(0)), 0u);
EXPECT_EQ(s.contains(T::One), 1u);
EXPECT_EQ(s.contains(T::Two), 1u);
EXPECT_EQ(s.contains(T::Three), 0u);
EXPECT_EQ(s.contains(T::Four), 1u);
EXPECT_EQ(s.contains(T::Five), 0u);
EXPECT_EQ(s.find(static_cast<T>(0)), s.end());
EXPECT_NE(s.find(T::One), s.end());
EXPECT_NE(s.find(T::Two), s.end());
EXPECT_EQ(s.find(T::Three), s.end());
EXPECT_NE(s.find(T::Four), s.end());
EXPECT_EQ(s.find(T::Five), s.end());
EXPECT_EQ(*s.find(T::One), T::One);
EXPECT_EQ(*s.find(T::Two), T::Two);
EXPECT_EQ(*s.find(T::Four), T::Four);
EXPECT_NE(s.find(static_cast<T>(0)), s.lower_bound(static_cast<T>(0)));
EXPECT_EQ(s.find(T::One), s.lower_bound(T::One));
EXPECT_EQ(s.find(T::Two), s.lower_bound(T::Two));
EXPECT_NE(s.find(T::Three), s.lower_bound(T::Three));
EXPECT_EQ(s.find(T::Four), s.lower_bound(T::Four));
EXPECT_EQ(s.find(T::Five), s.lower_bound(T::Five));
}
TEST(DenseSet, iterators_lower_upper_bound) {
enum class T { One = 1, Two = 2, Three = 3, Four = 4, Five = 5, kEnd = 6 };
using DS = DenseSet<T, T::kEnd>;
DS s;
s.insert(T::Two);
s.insert(T::Four);
s.insert(T::One);
EXPECT_EQ(s.size(), 3u);
EXPECT_EQ(s.lower_bound(static_cast<T>(0)), s.begin());
EXPECT_EQ(s.lower_bound(T::One), s.begin());
EXPECT_EQ(s.upper_bound(T::Four), s.end());
EXPECT_EQ(s.upper_bound(T::Five), s.end());
{
auto it = s.lower_bound(static_cast<T>(0));
auto jt = s.upper_bound(static_cast<T>(0));
EXPECT_EQ(it, jt);
}
{
auto it = s.lower_bound(T::One);
auto jt = s.upper_bound(T::One);
auto x1 = *it++;
EXPECT_EQ(it, jt);
EXPECT_EQ(x1, T::One);
}
{
auto it = s.lower_bound(T::Four);
auto jt = s.upper_bound(T::Four);
auto x3 = *it++;
EXPECT_EQ(it, jt);
EXPECT_EQ(x3, T::Four);
}
{
auto it = s.lower_bound(T::Five);
auto jt = s.upper_bound(T::Five);
EXPECT_EQ(it, jt);
}
{
auto it = s.lower_bound(T::One);
auto jt = s.upper_bound(T::Five);
auto x1 = *it++;
auto x2 = *it++;
auto x3 = *it++;
EXPECT_EQ(it, jt);
EXPECT_EQ(x1, T::One);
EXPECT_EQ(x2, T::Two);
EXPECT_EQ(x3, T::Four);
}
{
auto it = s.lower_bound(T::Three);
auto jt = s.upper_bound(T::Four);
auto x3 = *it++;
EXPECT_EQ(jt, s.end());
EXPECT_EQ(it, jt);
EXPECT_EQ(x3, T::Four);
}
EXPECT_EQ(static_cast<size_t>(std::distance(s.begin(), s.end())), s.size());
EXPECT_EQ(std::next(std::next(std::next(s.begin()))), s.end());
}
TEST(DenseSet, max_size) {
const int One = 1;
const int Two = 2;
// const int Three = 3;
const int Four = 4;
// const int Five = 5;
const int kEnd = 6;
using DS = DenseSet<int, kEnd>;
DS s;
EXPECT_TRUE(s.empty());
EXPECT_EQ(s.size(), 0u);
EXPECT_EQ(s.max_size(), 6u);
s.insert(Two);
EXPECT_FALSE(s.empty());
EXPECT_EQ(s.size(), 1u);
s.insert(Four);
EXPECT_FALSE(s.empty());
EXPECT_EQ(s.size(), 2u);
s.insert(One);
EXPECT_FALSE(s.empty());
EXPECT_EQ(s.size(), 3u);
EXPECT_EQ(s.max_size(), 6u);
}
TEST(DenseSet, modifiers) {
const size_t One = 1;
const size_t Two = 2;
const size_t Three = 3;
const size_t Four = 4;
// const size_t Five = 5;
const size_t kEnd = 6;
using DS = DenseSet<size_t, kEnd>;
DS s;
s.insert(Two);
s.insert(Four);
s.insert(One);
EXPECT_EQ(s.size(), 3u);
auto EXPECT_INSERTION = [](auto& set, auto value, bool took_place) {
auto it = set.insert(value);
EXPECT_EQ(it, std::make_pair(set.find(value), took_place));
};
DS t;
EXPECT_NE(s, t);
EXPECT_INSERTION(t, Two, true);
EXPECT_INSERTION(t, Two, false);
EXPECT_INSERTION(t, Four, true);
EXPECT_INSERTION(t, Four, false);
EXPECT_INSERTION(t, One, true);
EXPECT_INSERTION(t, One, false);
EXPECT_EQ(s, t);
EXPECT_EQ(t.size(), 3u);
EXPECT_INSERTION(t, Three, true);
EXPECT_INSERTION(t, Three, false);
EXPECT_EQ(t.erase(Three), 1u);
EXPECT_EQ(t.erase(Three), 0u);
EXPECT_EQ(s, t);
EXPECT_EQ(t.size(), 3u);
EXPECT_EQ(s.erase(One), 1u);
EXPECT_EQ(t.erase(Four), 1u);
EXPECT_NE(s, t);
EXPECT_EQ(s.size(), 2u);
EXPECT_EQ(t.size(), 2u);
EXPECT_INSERTION(s, One, true);
EXPECT_INSERTION(t, Four, true);
EXPECT_EQ(s, t);
EXPECT_EQ(s.size(), 3u);
EXPECT_EQ(t.size(), 3u);
EXPECT_EQ(s.erase(s.find(One)), s.find(Two));
EXPECT_EQ(t.erase(t.lower_bound(One), t.upper_bound(One)), t.find(Two));
EXPECT_FALSE(s.contains(One));
EXPECT_EQ(s, t);
EXPECT_EQ(s.size(), 2u);
EXPECT_EQ(t.size(), 2u);
EXPECT_INSERTION(s, One, true);
EXPECT_INSERTION(t, One, true);
EXPECT_EQ(s, t);
EXPECT_EQ(s.size(), 3u);
EXPECT_EQ(t.size(), 3u);
EXPECT_EQ(s.erase(s.find(Two), s.end()), s.end());
EXPECT_EQ(t.erase(t.lower_bound(Two), t.upper_bound(Four)), t.end());
EXPECT_TRUE(s.contains(One));
EXPECT_EQ(s, t);
EXPECT_EQ(s.size(), 1u);
EXPECT_EQ(t.size(), 1u);
EXPECT_INSERTION(s, Two, true);
EXPECT_INSERTION(t, Two, true);
EXPECT_INSERTION(s, Four, true);
EXPECT_INSERTION(t, Four, true);
EXPECT_EQ(s, t);
EXPECT_EQ(s.size(), 3u);
EXPECT_EQ(t.size(), 3u);
s.clear();
EXPECT_EQ(s, DS());
EXPECT_EQ(s.size(), 0u);
EXPECT_INSERTION(s, *t.begin(), true);
EXPECT_TRUE(s.contains(One));
EXPECT_INSERTION(s, *std::next(t.begin()), true);
EXPECT_TRUE(s.contains(Two));
EXPECT_INSERTION(s, *std::prev(t.end()), true);
EXPECT_TRUE(s.contains(Four));
EXPECT_EQ(s, t);
EXPECT_EQ(s.size(), 3u);
EXPECT_EQ(t.size(), 3u);
}
TEST(DenseSet, std_set) {
constexpr size_t kEnd = 50;
DenseSet<size_t, kEnd> dense_set;
std::set<size_t> std_set;
auto expect_equivalence = [&] {
EXPECT_EQ(dense_set.empty(), std_set.empty());
EXPECT_EQ(dense_set.size(), std_set.size());
EXPECT_TRUE(base::ranges::equal(dense_set, std_set));
};
auto random_insert = [&] {
expect_equivalence();
size_t value = base::RandUint64() % kEnd;
auto p = dense_set.insert(value);
auto q = std_set.insert(value);
EXPECT_EQ(p.second, q.second);
EXPECT_EQ(p.first == dense_set.end(), q.first == std_set.end());
EXPECT_TRUE(!p.second || p.first == dense_set.find(value));
EXPECT_TRUE(!q.second || q.first == std_set.find(value));
};
auto random_erase = [&] {
expect_equivalence();
size_t value = base::RandUint64() % kEnd;
EXPECT_EQ(dense_set.erase(value), std_set.erase(value));
};
auto random_erase_iterator = [&] {
expect_equivalence();
size_t value = base::RandUint64() % kEnd;
auto it = dense_set.find(value);
auto jt = std_set.find(value);
EXPECT_EQ(it == dense_set.end(), jt == std_set.end());
if (it == dense_set.end() || jt == std_set.end())
return;
auto succ_it = dense_set.erase(it);
auto succ_jt = std_set.erase(jt);
EXPECT_EQ(succ_it == dense_set.end(), succ_jt == std_set.end());
EXPECT_TRUE(succ_it == dense_set.upper_bound(value));
EXPECT_TRUE(succ_jt == std_set.upper_bound(value));
EXPECT_TRUE(succ_it == dense_set.end() || *succ_it == *succ_jt);
};
auto random_erase_range = [&] {
expect_equivalence();
size_t min_value = base::RandUint64() % kEnd;
size_t max_value = base::RandUint64() % kEnd;
min_value = std::min(min_value, max_value);
max_value = std::max(min_value, max_value);
dense_set.erase(dense_set.lower_bound(min_value),
dense_set.upper_bound(max_value));
std_set.erase(std_set.lower_bound(min_value),
std_set.upper_bound(max_value));
};
for (size_t i = 0; i < kEnd; ++i) {
random_insert();
}
for (size_t i = 0; i < kEnd / 2; ++i) {
random_erase();
}
expect_equivalence();
dense_set.clear();
std_set.clear();
expect_equivalence();
for (size_t i = 0; i < kEnd; ++i) {
random_insert();
}
for (size_t i = 0; i < kEnd; ++i) {
random_erase_iterator();
}
expect_equivalence();
dense_set.clear();
std_set.clear();
expect_equivalence();
for (size_t i = 0; i < kEnd; ++i) {
random_insert();
}
for (size_t i = 0; i < kEnd; ++i) {
random_erase_range();
}
expect_equivalence();
}
} // namespace autofill
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment