Mentions légales du service

Skip to content
Snippets Groups Projects
Commit 1429666f authored by GILLES Sebastien's avatar GILLES Sebastien
Browse files

Cosmetics.

parent c811530b
Branches
No related tags found
1 merge request!120Fixes from my notes of the last day of March 2024 session
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Useful concepts and STL](./0-main.ipynb) - [Associative containers](./4-AssociativeContainers.ipynb)
%% Cell type:markdown id: tags:
## Introduction
A `std::vector` can be seen as an association between two types:
* A `std::size_t`
index, which value is in interval [0, size[, that acts as a key.
* The value actually stored.
The `operator[]` might be used to access one of them:
%% Cell type:code id: tags:
``` c++
#include <vector>
#include <iostream>
{
std::vector<int> prime { 2, 3, 5, 7, 11, 13, 17, 19 };
auto index = 3ul;
std::cout << "Element which key is " << index << " is " << prime[index] << std::endl;
}
```
%% Cell type:markdown id: tags:
An associative container is an extension: what if we could loosen the constraint upon the key and use something else?
%% Cell type:markdown id: tags:
## `std::map`
### Construction
`std::map` is a list of key/value pairs that is ordered through a relationship imposed on the keys.
%% Cell type:code id: tags:
``` c++
#include <map>
{
std::map<std::string, unsigned int> age_list
{
{ "Alice", 25 },
{ "Charlie", 31 },
{ "Bob", 22 },
};
auto index = "Charlie";
std::cout << "Element which key is " << index << " is " << age_list[index] << std::endl;
}
```
%% Cell type:markdown id: tags:
### Iteration
In this example, we set three people with their age. We may iterate through it; the actual storage of an item is here a `std::pair<std::string, unsigned int>`. We haven't seen `std::pair` so far, but think of it as a `std::tuple` with 2 elements (it existed prior to `std::tuple` in fact).
There are two handy attributes to access the respective first and second element: `first` and `second`.
%% Cell type:code id: tags:
``` c++
#include <map>
#include <iostream>
std::map<std::string, unsigned int> age_list
{
{ "Alice", 25 },
{ "Charlie", 31 },
{ "Bob", 22 },
};
for (const auto& pair : age_list)
std::cout << pair.first << " : " << pair.second << std::endl;
```
%% Cell type:markdown id: tags:
#### C++ 17: Structure binding
C++ 17 introduced an alternate new syntax I like a lot that is called **structure bindings**:
%% Cell type:code id: tags:
``` c++
for (const auto& [person, age] : age_list)
std::cout << person << " : " << age << std::endl;
```
%% Cell type:markdown id: tags:
As you see, the syntax allocates on the fly variable (here references) for the first and second element of the pair, making the code much more expressive.
You may read more on them [here](https://www.fluentcpp.com/2018/06/19/3-simple-c17-features-that-will-make-your-code-simpler/); we will use them again in this notebook.
%% Cell type:markdown id: tags:
### Provide another ordering rule
The output order is not an accident: as I said it is an **ordered** associative container, and the key must provide a relationship. The default one is `std::less` but you might specify another in template arguments:
%% Cell type:code id: tags:
``` c++
#include <map>
#include <iostream>
{
std::map<std::string, unsigned int, std::greater<std::string>> age_list
{
{ "Alice", 25 },
{ "Charlie", 31 },
{ "Bob", 22 },
};
for (const auto& [name, age] : age_list) // structure binding!
std::cout << name << " : " << age << std::endl;
}
```
%% Cell type:markdown id: tags:
### insert()
You may insert another element later with `insert()`:
%% Cell type:code id: tags:
``` c++
#include <map>
#include <iostream>
{
std::map<std::string, unsigned int> age_list
{
{ "Alice", 25 },
{ "Charlie", 31 },
{ "Bob", 22 },
};
age_list.insert({"Dave", 44});
age_list.insert({"Alice", 32});
for (const auto& [name, age] : age_list)
std::cout << name << " : " << age << std::endl;
}
```
%% Cell type:markdown id: tags:
See here that Dave was correctly inserted... but Alice was unchanged!
In fact `insert` returns a pair:
* First is an iterator to the newly inserted element, or to the position of the one that made the insertion fail.
* Second is a boolean that returns `true` if the insertion worked.
%% Cell type:code id: tags:
``` c++
#include <map>
#include <iostream>
std::map<std::string, unsigned int> age_list
{
{ "Alice", 25 },
{ "Charlie", 31 },
{ "Bob", 22 },
};
{
auto result = age_list.insert({"Dave", 44});
if (!result.second)
std::cerr << "Insertion of Dave failed" << std::endl;
}
{
auto result = age_list.insert({"Alice", 32});
if (!result.second)
std::cerr << "Insertion of Alice failed" << std::endl;
}
for (const auto& [name, age] : age_list)
std::cout << name << " : " << age << std::endl;
```
%% Cell type:markdown id: tags:
Or even better with structure bindings:
%% Cell type:code id: tags:
``` c++
%%cppmagics cppyy/cppdef
const auto& [iterator, was_properly_inserted] = age_list.insert({"Alice", 32});
```
%% Cell type:code id: tags:
``` c++
if (!was_properly_inserted)
std::cerr << "Insertion of Alice failed" << std::endl;
```
%% Cell type:markdown id: tags:
That's something I dislike in this very useful class: error handling is not up to my taste as you have to remember to check explicitly all went right... (this is the discussion we had previously about error codes all over again...)
### Access to one element: don't use `operator[]`!
And this is not the sole example: let's look for an element in a map:
%% Cell type:code id: tags:
``` c++
#include <map>
#include <iostream>
{
std::map<std::string, unsigned int> age_list
{
{ "Alice", 25 },
{ "Charlie", 31 },
{ "Bob", 22 },
};
std::cout << "Alice : " << age_list["Alice"] << std::endl;
std::cout << "Erin : " << age_list["Erin"] << std::endl;
std::cout << "========" << std::endl;
for (const auto& [person, age] : age_list)
std::cout << person << " : " << age << std::endl;
}
```
%% Cell type:markdown id: tags:
So if you provide a wrong key, it doesn't yell and instead creates a new entry on the spot, filling the associated value with the default constructor for the type...
To do it properly (but more verbose!), use the `find()` method (if you're intrigued by the use of iterator there, we will present them more in details in the notebook about [algorithms](./7-Algorithms.ipynb)):
%% Cell type:code id: tags:
``` c++
#include <map>
#include <iostream>
{
std::map<std::string, unsigned int> age_list
{
{ "Alice", 25 },
{ "Charlie", 31 },
{ "Bob", 22 },
};
auto it = age_list.find("Alice");
if (it == age_list.cend())
std::cerr << "No Alice found in the listing!" << std::endl;
else
std::cout << "Alice's age is " << it->second << std::endl;
it = age_list.find("Erin");
if (it == age_list.cend())
std::cerr << "No Erin found in the listing!" << std::endl;
else
std::cout << "Erin's age is " << it->second << std::endl;
for (const auto& [name, age] : age_list)
std::cout << name << " : " << age << std::endl;
}
```
%% Cell type:markdown id: tags:
A side note which will be useful to explain later the `std::unordered_map`: search is performed by dichotomy (~O(log N)).
%% Cell type:markdown id: tags:
### Unicity of key
`std::map` is built on the fact a key must be unique.
If you need to enable possible repetition of keys, you should look at `std::multimap` which provides this possibility with slightly different interface (rather obviously `find()` is replaced by methods that returns a range of iterators).
%% Cell type:markdown id: tags:
### Using objects as keys
You may use your own objects as keys, provided that:
* Either you define `operator<` for it. It is really important to grasp that `operator==` **doesn't matter**: even in `find` it is really `operator<` that is used!
* Or provide as template parameter the ordering relationship you intend to use.
**WARNING:** If you're using pointers as keys, make sure to provide an adequate relationship ordering, typically that takes the pointed object relationship. Otherwise from one run to another you might end with different results as the address won't probably be given in the same order...
%% Cell type:markdown id: tags:
## `std::set`
`std::set` is a special case in which you do not associate a value to the key. The interface is roughly the same.
It might be used for instance if you want to keep a list of stuff you have encountered at least once: you don't care about how many times, but you want to know if it was encountered at least once. A `std::vector` would be inappropriate: you would have to look up its whole content before each insertion. With a `std::set` it is already built-in in the class.
## std::unordered_map
## `std::unordered_map`
This is another associative container introduced in C++ 11, with a different trade-off (and closer to a `dict` in Python for instance):
* Access is much more efficient (~O(1), i.e. independent on the number of elements!).
* Memory imprint is bigger.
* Adding new elements is more expensive.
* The result is not ordered, and there are no rules whatsoever: two runs on the same computer might not yield the list in the same order.
The constraint on the key is different too: the key must be **hashable**, meaning that there must be a specialization of `std::hash` for the type used for key. It must also define `operator==`.
STL provides good such **hashing functions** for POD types (and few others like `std::string`); it is not trivial (but still possible - see for instance [The C++ Standard Library: A Tutorial and Reference](../bibliography.ipynb#The-C++-Standard-Library:-A-Tutorial-and-Reference) for a discussion on this topic) to add new ones.
So to put in a nutshell, if your key type is already handled by the STL and you spend more time reading data than inserting new ones, you should really use this type.
Just an additional note: [The C++ Standard Library: A Tutorial and Reference](../bibliography.ipynb#The-C++-Standard-Library:-A-Tutorial-and-Reference) recommends changing the default internal setting of the class for efficiency: there is an internal float value named `max_load_factor` which has a default value of 1; API of the class introduces a mutator to modify it. He says 0.7f or 0.8f is more efficient; I haven't benchmarked and trusted him on this and am using it in my library.
%% Cell type:code id: tags:
``` c++
#include <unordered_map>
{
std::unordered_map<int, double> list;
list.max_load_factor(0.7f);
}
```
%% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment