Commit 9b20a942 authored by Kenton Varda's avatar Kenton Varda

Support findOrCreate() (with only a single lookup).

This is a very common pattern in practice -- and annoyingly difficult with STL maps.

This required some refactoring so than index.insert() could be called before the row was actually constructed, based on the search parameters.

It also required some awful hacks to support putting the creation function at the end of the argument list to findOrCreate(), with a variable-width arg list before it.
parent 25a25704
......@@ -43,8 +43,17 @@ KJ_TEST("HashMap") {
KJ_EXPECT(KJ_ASSERT_NONNULL(map.find("foo"_kj)) == 321);
KJ_EXPECT(
map.findOrCreate("foo"_kj,
[]() -> HashMap<String, int>::Entry { KJ_FAIL_ASSERT("shouldn't have been called"); })
== 321);
KJ_EXPECT(map.findOrCreate("baz"_kj,
[](){ return HashMap<String, int>::Entry { kj::str("baz"), 654 }; }) == 654);
KJ_EXPECT(KJ_ASSERT_NONNULL(map.find("baz"_kj)) == 654);
KJ_EXPECT(map.erase("bar"_kj));
KJ_EXPECT(!map.erase("baz"_kj));
KJ_EXPECT(map.erase("baz"_kj));
KJ_EXPECT(!map.erase("qux"_kj));
KJ_EXPECT(KJ_ASSERT_NONNULL(map.find("foo"_kj)) == 321);
KJ_EXPECT(map.size() == 1);
......@@ -75,8 +84,17 @@ KJ_TEST("TreeMap") {
KJ_EXPECT(KJ_ASSERT_NONNULL(map.find("foo"_kj)) == 321);
KJ_EXPECT(
map.findOrCreate("foo"_kj,
[]() -> TreeMap<String, int>::Entry { KJ_FAIL_ASSERT("shouldn't have been called"); })
== 321);
KJ_EXPECT(map.findOrCreate("baz"_kj,
[](){ return TreeMap<String, int>::Entry { kj::str("baz"), 654 }; }) == 654);
KJ_EXPECT(KJ_ASSERT_NONNULL(map.find("baz"_kj)) == 654);
KJ_EXPECT(map.erase("bar"_kj));
KJ_EXPECT(!map.erase("baz"_kj));
KJ_EXPECT(map.erase("baz"_kj));
KJ_EXPECT(!map.erase("qux"_kj));
KJ_EXPECT(KJ_ASSERT_NONNULL(map.find("foo"_kj)) == 321);
KJ_EXPECT(map.size() == 1);
......
......@@ -82,6 +82,11 @@ public:
//
// Note that the default hasher for String accepts StringPtr.
template <typename KeyLike, typename Func>
Value& findOrCreate(KeyLike&& key, Func&& createEntry);
// Like find() but if the key isn't present then call createEntry() to create the corresponding
// entry and insert it. createEntry() must return type `Entry`.
template <typename KeyLike>
bool erase(KeyLike&& key);
// Erase the entry with the matching key.
......@@ -172,9 +177,12 @@ public:
template <typename KeyLike>
kj::Maybe<const Value&> find(KeyLike&& key) const;
// Search for a matching key. The input does not have to be of type `Key`; it merely has to
// be something that the Hasher accepts.
//
// Note that the default hasher for String accepts StringPtr.
// be something that can be compared against `Key`.
template <typename KeyLike, typename Func>
Value& findOrCreate(KeyLike&& key, Func&& createEntry);
// Like find() but if the key isn't present then call createEntry() to create the corresponding
// entry and insert it. createEntry() must return type `Entry`.
template <typename K1, typename K2>
auto range(K1&& k1, K2&& k2);
......@@ -342,6 +350,12 @@ kj::Maybe<const Value&> HashMap<Key, Value>::find(KeyLike&& key) const {
return table.find(key).map([](const Entry& e) -> const Value& { return e.value; });
}
template <typename Key, typename Value>
template <typename KeyLike, typename Func>
Value& HashMap<Key, Value>::findOrCreate(KeyLike&& key, Func&& createEntry) {
return table.findOrCreate(key, kj::fwd<Func>(createEntry)).value;
}
template <typename Key, typename Value>
template <typename KeyLike>
bool HashMap<Key, Value>::erase(KeyLike&& key) {
......@@ -428,6 +442,12 @@ kj::Maybe<const Value&> TreeMap<Key, Value>::find(KeyLike&& key) const {
return table.find(key).map([](const Entry& e) -> const Value& { return e.value; });
}
template <typename Key, typename Value>
template <typename KeyLike, typename Func>
Value& TreeMap<Key, Value>::findOrCreate(KeyLike&& key, Func&& createEntry) {
return table.findOrCreate(key, kj::fwd<Func>(createEntry)).value;
}
template <typename Key, typename Value>
template <typename K1, typename K2>
auto TreeMap<Key, Value>::range(K1&& k1, K2&& k2) {
......
......@@ -148,6 +148,37 @@ KJ_TEST("simple table") {
KJ_EXPECT(*iter++ == "corge");
KJ_EXPECT(iter == table.end());
}
auto& graultRow = table.begin()[1];
kj::StringPtr origGrault = graultRow;
KJ_EXPECT(&table.findOrCreate("grault",
[&]() -> kj::StringPtr { KJ_FAIL_ASSERT("shouldn't have called this"); }) == &graultRow);
KJ_EXPECT(graultRow.begin() == origGrault.begin());
KJ_EXPECT(&KJ_ASSERT_NONNULL(table.find("grault")) == &graultRow);
KJ_EXPECT(table.find("waldo") == nullptr);
KJ_EXPECT(table.size() == 4);
kj::String searchWaldo = kj::str("waldo");
kj::String insertWaldo = kj::str("waldo");
auto& waldo = table.findOrCreate(searchWaldo,
[&]() -> kj::StringPtr { return insertWaldo; });
KJ_EXPECT(waldo == "waldo");
KJ_EXPECT(waldo.begin() == insertWaldo.begin());
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find("grault")) == "grault");
KJ_EXPECT(&KJ_ASSERT_NONNULL(table.find("waldo")) == &waldo);
KJ_EXPECT(table.size() == 5);
{
auto iter = table.begin();
KJ_EXPECT(*iter++ == "garply");
KJ_EXPECT(*iter++ == "grault");
KJ_EXPECT(*iter++ == "qux");
KJ_EXPECT(*iter++ == "corge");
KJ_EXPECT(*iter++ == "waldo");
KJ_EXPECT(iter == table.end());
}
}
class BadHasher {
......@@ -300,6 +331,47 @@ KJ_TEST("double-index table") {
KJ_EXPECT(table.size() == 2);
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("foo")) == (SiPair {"foo", 123}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(123)) == (SiPair {"foo", 123}));
KJ_EXPECT(
table.findOrCreate<0>("foo",
[]() -> SiPair { KJ_FAIL_ASSERT("shouldn't have called this"); })
== (SiPair {"foo", 123}));
KJ_EXPECT(table.size() == 2);
KJ_EXPECT_THROW_MESSAGE("inserted row already exists in table",
table.findOrCreate<0>("corge", []() -> SiPair { return {"corge", 123}; }));
KJ_EXPECT(table.size() == 2);
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("foo")) == (SiPair {"foo", 123}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(123)) == (SiPair {"foo", 123}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("bar")) == (SiPair {"bar", 456}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(456)) == (SiPair {"bar", 456}));
KJ_EXPECT(table.find<0>("corge") == nullptr);
KJ_EXPECT(
table.findOrCreate<0>("corge", []() -> SiPair { return {"corge", 789}; })
== (SiPair {"corge", 789}));
KJ_EXPECT(table.size() == 3);
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("foo")) == (SiPair {"foo", 123}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(123)) == (SiPair {"foo", 123}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("bar")) == (SiPair {"bar", 456}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(456)) == (SiPair {"bar", 456}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("corge")) == (SiPair {"corge", 789}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(789)) == (SiPair {"corge", 789}));
KJ_EXPECT(
table.findOrCreate<1>(234, []() -> SiPair { return {"grault", 234}; })
== (SiPair {"grault", 234}));
KJ_EXPECT(table.size() == 4);
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("foo")) == (SiPair {"foo", 123}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(123)) == (SiPair {"foo", 123}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("bar")) == (SiPair {"bar", 456}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(456)) == (SiPair {"bar", 456}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("corge")) == (SiPair {"corge", 789}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(789)) == (SiPair {"corge", 789}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("grault")) == (SiPair {"grault", 234}));
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(234)) == (SiPair {"grault", 234}));
}
class UintHasher {
......@@ -667,6 +739,37 @@ KJ_TEST("simple tree table") {
KJ_EXPECT(*iter++ == "garply");
KJ_EXPECT(iter == range.end());
}
auto& graultRow = table.begin()[1];
kj::StringPtr origGrault = graultRow;
KJ_EXPECT(&table.findOrCreate("grault",
[&]() -> kj::StringPtr { KJ_FAIL_ASSERT("shouldn't have called this"); }) == &graultRow);
KJ_EXPECT(graultRow.begin() == origGrault.begin());
KJ_EXPECT(&KJ_ASSERT_NONNULL(table.find("grault")) == &graultRow);
KJ_EXPECT(table.find("waldo") == nullptr);
KJ_EXPECT(table.size() == 4);
kj::String searchWaldo = kj::str("waldo");
kj::String insertWaldo = kj::str("waldo");
auto& waldo = table.findOrCreate(searchWaldo,
[&]() -> kj::StringPtr { return insertWaldo; });
KJ_EXPECT(waldo == "waldo");
KJ_EXPECT(waldo.begin() == insertWaldo.begin());
KJ_EXPECT(KJ_ASSERT_NONNULL(table.find("grault")) == "grault");
KJ_EXPECT(&KJ_ASSERT_NONNULL(table.find("waldo")) == &waldo);
KJ_EXPECT(table.size() == 5);
{
auto iter = table.begin();
KJ_EXPECT(*iter++ == "garply");
KJ_EXPECT(*iter++ == "grault");
KJ_EXPECT(*iter++ == "qux");
KJ_EXPECT(*iter++ == "corge");
KJ_EXPECT(*iter++ == "waldo");
KJ_EXPECT(iter == table.end());
}
}
class UintCompare {
......
This diff is collapsed.
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