Data structures - STL
A data structure organizes and stores data in memory for efficient access and modification. A collection is a group of data items treated as a single unit, focusing on operations like adding, removing, and iterating over elements. While collections rely on data structures, they offer a more abstract way to manage groups of objects. All collections are based on data structures, but not all data structures are collections.
STL stands for Standard Template Library. It provides several types of containers (data structures), such as vectors, deques, stacks, queues, sets, and maps.
Vectors
Vectors are dynamic arrays, meaning we do not have to know their size at the beginning (but we have to determine the type of its elements). It works because the memory is reserved in advance based on how many elements we added in the past. That reserved in advance memory is the vector's capacity, and its size is how much of this memory is used. We must include the <vector>
library for all of this to work.
The push_back()
method will add an element at the end of a vector. If we want to use all of the vector's features, e.g., assigning values at declaration (vector<int> numbers {1, 2, 3, 4, 5};
), we have to enable C++ 11 in the editor. In Dev C++, we can do that here: Tools / Compiler Options / Settings / Code Generation / Language Standard (-std) - GNU C++11
. Vector's elements can be accessed by index (like with arrays).
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> integers;
integers.push_back(2); // adding an element at the end
integers.push_back(4);
cout << integers[0] << endl;
cout << integers.at(0) << endl; // the same as integers[0]
cout << integers.front() << endl; // the first value
cout << integers.back() << endl; // the last value
integers.at(0) = 3; // changing the value under the given index
cout << integers.at(0) << endl;
cout << integers.size() << endl; // the length of the vector
integers.pop_back(); // deleting the last element
return 0;
}
If we want to make a vector of arrays, we have to import the <array>
library and write, e.g., vector<array<string, 5>> data;
. In this example, each array in the vector has five string elements. Of course, we can also nest vectors.
Iterators
An iterator is an object pointing at a particular position inside a container (e.g., a vector). We use it to get to certain elements inside the container. We create iterators using this pattern: "type_of_the_container::iterator_name;". Not all elements inside a container are in a row inside the memory (this was the case with arrays). An iterator is basically an object that contains a countable number of values. The ++
operator is used to advance the iterator to the next item, and the dereference operator (*
) is used to retrieve the value at the current position.
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v = {1, 2, 3};
vector<int>::iterator iter = v.begin(); // it points at the first element of the vector
// Iterating through the vector
while (iter != v.end()) {
cout << *iter << endl; // dereferencing the iterator to get the value
++iter; // moving to the next element
}
// Restarting the iterator
iter = v.begin();
// Removing the first element returned by the iterator
if (iter != v.end())
iter = v.erase(iter); // moving to the first element and removing it (the iterator is now pointing to the next valid element - "2")
// Displaying the vector after removal
iter = v.begin();
while (iter != v.end()) {
cout << *iter << endl;
++iter;
}
return 0;
}
auto
type
auto
is a data type that decides which type a variable is based on its value. We can check the variable's type using the typeid(x).name()
expression from the <typeinfo>
library.
auto x = 5;
auto y = 5.5;
auto z = "z";
for (auto i = numbers.begin(); i != numbers.end(); i++)
cout << *i << endl;
Enhanced for
Enhanced for
is a type of the for
loop with the syntax shown below.
for (auto x : numbers) // where to save the values : from where to read the values
cout << x << endl;
for (auto& x : numbers) {
x *= 10;
cout << x << endl;
}
*Adding an ampersand after the auto
keyword allows us to edit the values of a vector inside the loop.
The <sstream>
library
Dividing a string with delimiters using the <sstream>
library.
#include <iostream>
#include <vector>
#include <sstream>
using namespace std;
int main() {
string str = "abc;de;f;ghij";
stringstream ss(str); // a stringstream object used to process the input string
string t; // a variable used to temporarily store each substring extracted from the input
vector<string> values;
// This loop will execute until getline() cannot extract any more substrings.
while (getline(ss, t, ';')) // getline() extracts a substring from the stringstream 'ss' until it encounters the specified delimiter (';') and stores the result in 't'
values.push_back(t); // adding the extracted value to the vector
for (int i = 0; i < values.size(); i++)
cout << values.at(i) << endl;
return 0;
}
Stacks & queues
Stacks and Queues are data structures for storing data in specific orders. Their elements can't be accessed by index. Stacks operate in a Last-In-First-Out (LIFO) order, where elements can only be added and deleted from the top of the stack. Queues operate in a First-In-First-Out (FIFO) order, where elements can be added at the end of the queue and removed from the front.
#include <iostream>
#include <stack>
#include <queue>
using namespace std;
int main()
{
stack<int> integers;
integers.push(1);
integers.push(2);
integers.pop(); // deleting an element
cout << integers.top() << endl; // the value from the top of the stack (1)
queue<int> integers2;
integers2.push(1);
integers2.push(2);
integers2.push(3);
integers2.pop();
cout << integers2.front() << endl; // the value from the front of the queue (2)
return 0;
}
Deques
Deque stands for Double-Ended Queue. With it, we can easily add and remove elements not only at the end (like with vectors) but also at the beginning. They share the same methods with vectors and can be handled the same way. Its elements also can be accessed by index.
#include <iostream>
#include <deque>
using namespace std;
int main()
{
deque<int> integers;
integers.push_back(2); // adding an element at the end
integers.push_back(4);
integers.pop_back(); // deleting an element at the end
integers.push_front(1); // adding an element at the beginning
integers.pop_front(); // deleting an element at the beginning
cout << integers.front() << endl; // integers.back() would return the same number as there is only one
for (int i = 0; i < integers.size(); i++)
cout << integers[i] << endl;
return 0;
}
Sets
Sets store unique elements (even if we add, e.g., the number 4 twice, it will keep only one) that are not accessible by index. An unordered set (unordered_set
) stores elements without an order (you could enter them like this: 1, 2, 3, and then receive 3, 1, 2).
#include <iostream>
#include <set>
using namespace std;
int main()
{
set<int> integers {1, 2, 3, 4};
integers.insert(1); // adding an element
integers.insert(5);
integers.erase(2); // deleting an element
for (int i : integers)
cout << i << endl;
return 0;
}
Maps
Maps work on the key-value coding rule, which states that searching for the key will give us the value. Using the map keys, we can fetch the assigned values. In the example below, "C++" and "map" are keys, and "programming language" and "data structure" are values.
#include <iostream>
#include <map>
using namespace std;
int main()
{
map<string, string> x;
x.insert({"C++", "programming language"});
x.insert({"map", "data structure"});
x.erase("map");
for (auto i : x) // accessing all elements of a map in a loop
cout << i.first << " - " << i.second << endl;
x["C++"] = "language"; // editing the value under a given key
cout << x.at("C++") << endl; // displaying the value under a given key
return 0;
}