Ch10.7: Maps: Keys With Values
1. Reading a Map Element
A map element has two halves: the key, which never changes, and the mapped value, which is yours to read and write. Both are reached through named members:
#include <fast_io_dsal/str_btree_map.h>
#include <fast_io.h>
using namespace ::fast_io::io;
int main()
{
::fast_io::str_btree_map<::std::size_t> m{{"c", 3zu}, {"a", 1zu}, {"b", 2zu}};
auto it{m.find("b")};
if (it != m.cend())
{
println(it->key(), " = ", it->mapped());
}
for (auto e : m)
{
println(" ", e.key(), " = ", e.mapped());
}
}
b = 2
a = 1
b = 2
c = 3
key() returns a ::fast_io::cstring_view into the
container’s own copy of the key. mapped() returns a
reference to the value, so it is also how you write it:
for (auto e : m)
{
e.mapped() *= 10zu;
}
println(m.find("c")->mapped()); // 30
for (auto e : m) while the
Swiss equivalent says for (auto &e : sm) (Ch10.9). Both let
you write through e.mapped(); the proxy is simply a
(node, position) pair, so it is copied by value.
2. Writing a Value
A read never changes the map, so it is find plus
mapped(). A write says what it means with one of the assign
functions:
::fast_io::str_btree_map<::std::size_t> m{{"a", 1zu}};
m.insert_key_or_assign("a", 100zu); // replace the value of an existing key
println(m.find("a")->mapped()); // 100
m.insert_key_or_assign("b", 2zu); // insert, because "b" is new
println(m.find("b")->mapped()); // 2
100
2
The call site says “insert or assign”, so there is no question
about whether a missing key gets created: it does, and the value you passed
is the value it gets. When you need to know which of the two happened, the
return value tells you — true means the map grew,
false means an existing value was replaced:
println(m.insert_key_or_assign("b", 3zu)); // 0 — replaced
println(m.insert_key_or_assign("c", 4zu)); // 1 — inserted
If you want to insert only when the key is absent and leave an existing value
untouched, that is insert_key, which does exactly one thing:
m.insert_key("c", 999zu); // ignored: "c" is already there
println(m.find("c")->mapped()); // 4
find/find_key, and every write is an
insert_key / insert_key_or_assign /
emplace_key. One call does one thing, so a typo in a key can
never silently grow your map, and nothing is ever built that you did not ask
for.
3. The Insert Family
Three functions cover every combination of “what should happen if the key is already there”:
| Call | Key exists | Key is new |
|---|---|---|
insert_key(k, v) |
Keeps the old value, does nothing. | Inserts v. |
insert_key_or_assign(k, v) |
Overwrites with v. |
Inserts v. |
emplace_key(k, args...) |
Keeps the old value, does nothing. | Constructs the value from args in place. |
emplace_key matters when the value is expensive to build: the
value is constructed only if the key turns out to be new, whereas
insert_key(k, make_value()) builds the temporary first and throws
it away on a duplicate.
::fast_io::str_swiss_map<::std::size_t> sm;
sm.insert_key_or_assign("a", 1zu); // inserts, because "a" is new
sm.insert_key_or_assign("a", 100zu); // replaces the old value
println(sm.find_key("a")->mapped()); // 100
4. Modifying Values
When the value is not being replaced wholesale but updated in place
(+=, push_back, a counter), iterate. A B-tree map
hands out a small proxy, so the loop binds by value and still writes through
mapped():
for (auto e : m)
{
e.mapped() *= 10zu;
}
println(m.find("c")->mapped()); // 40
A Swiss map hands out a reference, so the loop binds by reference:
::fast_io::str_swiss_map<::std::size_t> sm;
sm.insert_key_or_assign("a", 1zu);
for (auto &e : sm)
{
e.mapped() += 41zu;
}
println(sm.find_key("a")->mapped()); // 42
Outside an iteration, a Swiss map value is written by assigning a new one:
sm.insert_key_or_assign("a", 100zu). Reading it is
sm.find_key("a")->mapped().
5. Counting
Counting occurrences is the classic map loop: look the key up, then either insert the first count or bump the value you already found.
#include <fast_io_dsal/str_btree_map.h>
#include <fast_io.h>
using namespace ::fast_io::io;
int main()
{
::fast_io::str_btree_map<::std::size_t> counts;
for (auto w : {::fast_io::cstring_view("red"), ::fast_io::cstring_view("blue"),
::fast_io::cstring_view("red"), ::fast_io::cstring_view("green"),
::fast_io::cstring_view("red")})
{
auto it{counts.find(w)};
if (it == counts.cend())
{
counts.emplace_key(w, 1zu);
}
else
{
++it->mapped();
}
}
for (auto const &e : counts)
{
println(e.key(), " appears ", e.mapped(), " time(s)");
}
}
blue appears 1 time(s)
green appears 1 time(s)
red appears 3 time(s)
Because str_btree_map iterates in sorted order, the report comes
out sorted by key without a second pass. Two properties of the B-tree map
make the loop this short: find gives an iterator whose
mapped() is writable, so the second occurrence is just
++it->mapped().
On a Swiss map find_key returns a const_iterator, so
the same loop has to pick the element to bump by iterating (Ch10.9), or the
value is replaced through insert_key_or_assign:
::fast_io::str_swiss_map<::std::size_t> scounts;
for (auto w : {::fast_io::cstring_view("red"), ::fast_io::cstring_view("blue"),
::fast_io::cstring_view("red")})
{
if (scounts.emplace_key(w, 1zu).inserted)
{
continue;
}
for (auto &e : scounts)
{
if (e.key() == w)
{
++e.mapped();
break;
}
}
}
Key takeaways
- Map elements expose
key()(read-only view) andmapped()(reference) — neverfirst/second. - Write through
e.mapped(); B-tree maps iterate by proxy value, Swiss maps by reference. - Reads are
find/find_key; writes areinsert_key,insert_key_or_assignoremplace_*— one call does one thing. insert_keykeeps the old value,insert_key_or_assignoverwrites,emplace_*constructs in place.insert_key_or_assignreturnstrueonly when it inserted, so it also answers “was this key new?”.find_keygives aconst_iterator, so it only reads.::fast_io::zu_str_btree_map/zu_str_swiss_mapsave typing when the value is::std::size_t.