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
B-tree maps hand out a small proxy; Swiss maps hand out a reference. That is why the B-tree loop above says 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
There is no shorthand that reads and inserts at the same time. Every read is a 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