ml-classifier
C++ Standard Library Containers
The C++ Standard Library provides several container types.
Ordered containers:
std::array
: A fixed-size ordered container, based on a raw C++ array.std::vector
: A dynamically-sized ordered container, based on a contiguous block of dynamic memory.std::list
: A dynamically-sized ordered container, based on a doubly-linked list.
Associative containers:
std::set
: A container of unique elements that automatically ignores duplicates.std::map
: A container mapping keys to values.
std::array
A std::array
is defined with template parameters for the element type and number of elements, which is fixed - elements may not be added or removed.
// An array to hold the pattern for dealing cards in Euchre
std::array<int, 8> deal_pattern = {3,2,3,2,2,3,2,3};
// Caution! A default-initialized std::array is not empty.
std::array<int, 5> nums; // Contains 5 ints with memory junk values
// Use indexing to access elements
cout << nums[0] << endl;
nums[3] = 10;
std::vector
std::vector
is defined with a template parameter for the element type. It is dynamically-sized, meaning elements may be added or removed.
Some key properties of std::vector
:
- Efficient push/pop operations are only available at the back.
- Efficient random access via indexing with
[]
or.at()
is available. - Underlying elements are guaranteed to be stored in a contiguous memory allocation.
- A
std::vector
may be sorted and searched efficiently. - Iterators provided by
std::vector
are “random-access iterators”, which support additional operations such as+
,-
, and<
. - Iterators provided by
std::vector
are less stable than other containers - they are often invalidated by operations that modify the vector.
// Default constructor creates an empty vector
std::vector<int> vec;
// Push/Pop only at the back
vec.push_back(1);
vec.pop_back();
// vec.push_front(2); NOT AVAILABLE
// vec.pop_front(); NOT AVAILABLE
// Indexing
vec[i] = x; // no bounds checking
vec.at(i) = x; // throws exception if out of bounds
// Traversal by index
for(int i = 0; i < vec.size(); ++i) {
cout << vec[i] << endl;
}
// Traversal by iterator
for(auto it = vec.begin(); it != vec.end(); ++it) {
cout << *it << endl;
}
// Traversal with range-based for loop
for(auto &elem : vec) {
cout << elem << endl;
}
// std::vector provides "random-access iterators" that may use +, -, <, etc.
auto it1 = vec.begin() + 3;
auto it2 = vec.end() - 1;
cout << *it1 << endl; // prints the element at index 3
cout << it2 - it1 << endl; // prints the distance between the iterators
cout << it1 < it2 << endl; // prints true if it1 comes before it2
// Some std::vector functions use an iterator interface.
// .insert()/.erase() have linear time complexity since they must shift elements.
vec.insert(vec.begin() + 2, 5); // insert 5 at index 2
vec.erase(vec.begin() + 3); // remove element at index 3
// Many algorithms in the standard library operate via iterators.
auto it = min_element(vec.begin(), vec.end()); // find minimum element
sort(vec.begin(), vec.end()); // Sort the vector in ascending order
auto it = lower_bound(vec.begin(), vec.end(), 5); // binary search, finds the first element >= 5
std::list
std::list
is defined with a template parameter for the element type. It is dynamically-sized, meaning elements may be added or removed.
Some key properties of std::list
:
- Efficient push/pop operations are available at the front and back.
std::list
does not support efficient random access.[]
and.at()
are not provided.- Assuming you already have an iterator to the correct location, insert/erase operations are efficient (no shifting required).
- Iterators provided by
std::list
are very stable and only invalidated if the element they point to is removed.
// Default constructor creates an empty list
std::list<int> list;
// Push/Pop at front or back
list.push_back(1);
list.pop_back();
list.push_front(2);
list.pop_front();
// Indexing is NOT available
// list[i] = x; NOT AVAILABLE
// list.at(i) = x; NOT AVAILABLE
// Traversal by iterator
for(auto it = list.begin(); it != list.end(); ++it) {
cout << *it << endl;
}
// Traversal with range-based for loop
for(auto &elem : list) {
cout << elem << endl;
}
// std::list provides "bidirectional iterators" supporting the standard
// operations *, ->, ++, --, ==, and != (but not +, -, <, etc).
auto it = list.begin();
++it;
++it;
++it;
cout << *it << endl; // prints the element at index 3
// Some std::list functions use an iterator interface.
// .insert()/.erase() have constant time complexity, assuming
// you already have an iterator to the desired location.
list.insert(it, 5); // insert 5 at before the element it points to
list.erase(it); // remove element it points to
// Algorithms that only require forward iterators can be used with lists.
auto it = min_element(list.begin(), list.end()); // find minimum element
// std::sort requires random-access iterators and is not available for lists.
// sort(list.begin(), list.end()); NOT AVAILABLE
// lower_bound is available, but it has linear time complexity, since an
// implementation based on binary search would require random access.
auto it = lower_bound(list.begin(), list.end(), 5);
std::set
A std::set
provides a container of unique elements that automatically ignores duplicates. A template parameter defines the element type.
// A set of uniqnames of students taking a course
std::set<string> students;
Use the .find()
, .insert()
, and .erase()
functions to manipulate a set. They use an iterator interface.
auto it = students.find("spongbob");
if (it != students.end()) {
// spongbob is in the set
}
else {
// spongbob is not in the set
}
// Add "patrick" (does nothing if already in the set)
students.insert("patrick");
// Remove "squidward" (does nothing if not present)
students.erase("squidwrd");
Traversal through a std::set
is facilitated via iterators. The set is internally sorted.
// Example 1: Traversal by iterator in ascending sorted order
for(auto it = students.begin(); it != students.end(); ++it) {
cout << "Uniqname=" << *it << endl;
}
// Example 2: Traverse with range-based for loop in ascending sorted order
for(auto &uniqname : students) {
cout << "Uniqname=" << uniqname << endl;
}
The C++ standard library also provides an unordered_set
container. It supports the same operations as above except traversals are not in any particular order.
std::map
A std::map
provides an associative mapping of key-value pairs. Template parameters define the key and value types.
// A map storing the number of each kind of fruit on the countertop
std::map<string, int> fruit_counts;
The .find()
function returns an iterator to the entry (a key-value pair, e.g. std::pair<const string,int>
) for a given key. If no entry is present for the given key, an end()
iterator is returned.
auto it = fruit_counts.find("apple");
// Print key and value if the entry exists
if (it != fruit_counts.end()) {
cout << "Key=" << it->first << endl;
cout << "Value=" << it->second << endl;
}
The .insert()
function attempts to add a new key-value pair as an entry, if one did not previously exist. It returns a pair containing an iterator and a boolean indicating whether the insert was successful.
// Attempt to insert an entry with key="pineapple", value=2
// Use a structured binding to unpack the returned pair.
// it is an iterator, success is a boolean.
auto [it, success] = fruit_counts.insert({"pineapple", 2});
if (success) {
// In this case, it is an iterator to the newly inserted entry
}
else {
// In this case, it is an iterator to the previous entry with
// the given key (i.e. the one that blocked the attempted insert)
}
The []
can be used to read or write the value for a given key. If a key was not previously present, an entry is automatically created (with a placeholder value initialized to 0
for numeric types). Compound assignment operators like +=
may be used with []
.
fruit_counts["apple"] = 5; // store the value 5 for the key "apple"
cout << fruit_counts["apple"]; // prints 5, the stored value for the key "apple"
cout << fruit_counts["banana"]; // prints 0 (the placeholder value created)
string fruit_name;
cin >> fruit_name; // ask the user for an arbitrary fruit name
fruit_counts[fruit_name] += 2; // Add 2 more
// ^^ If the user had entered "apple", makes 5 + 2 = 7 apples
// ^^ If the user had entered "orange", makes 0 + 2 = 2 oranges
Because the []
operator potentially modifies the map
(it may add placeholders), it is not allowed on const-qualified maps
.
The .erase()
function removes an entry from a map. It may be passed a key or an iterator to the entry.
fruit_counts.erase(it);
fruit_counts.erase("banana");
Traversal through a std::map
is facilitated via iterators. The map is internally sorted by key.
// Example 1: Traversal by iterator in ascending sorted order by key
for(auto it = fruit_counts.begin(); it != fruit_counts.end(); ++it) {
cout << "Key=" << it->first << endl;
cout << "Value=" << it->second << endl;
}
// Example 2: Traverse with range-based for loop in ascending sorted
// order by key. Use a structured binding to unpack the key-value pair.
// The & avoids unnecessary copying.
for(auto & [fruit_name, count] : fruit_counts) {
cout << "Key=" << fruit_name << endl;
cout << "Value=" << count << endl;
}
The C++ standard library also provides an unordered_map
container. It supports the same operations as above except traversals are not in any particular order.