Skip to content
Snippets Groups Projects
Commit cd68b973 authored by Marcus Mohr's avatar Marcus Mohr
Browse files

Adds small updates to notebooks #6 and #7 after class today.

parent ae1d0356
No related branches found
No related tags found
No related merge requests found
{"metadata":{"orig_nbformat":4,"kernelspec":{"display_name":"C++14","language":"C++14","name":"xcpp14"},"language_info":{"codemirror_mode":"text/x-c++src","file_extension":".cpp","mimetype":"text/x-c++src","name":"c++","version":"14"}},"nbformat_minor":5,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Standard Template Library (STL)\n","metadata":{},"id":"0921615f"},{"cell_type":"markdown","source":"#### Executive Summary\n* Since its inclusion in 1994 the STL forms an integral part of the **Standard C++ Library**.\n* The STL is composed of three major components:\n 1. Definition of several **container types** for data storage\n 1. Access functions for those containers, especially so called **iterators**\n 1. **Algorithms** for working with the containers.\n* The following table lists the header files to include for different aspects of the STL\n \n| STL containers | Iterators, Functors, Algorithms |\n| :------------------ | :------------------------------ |\n| **```<deque>```** | **```<iterator>```** |\n| **```<list>```** | **```<algorithm>```** |\n| **```<map>```** | **```<functional>```** |\n| **```<queue>```** | |\n| **```<set>```** | |\n| **```<stack>```** | |\n| **```<vector>```** | |\n\n* As its name suggests the STL makes heavy use of *generic programming* based on **templates**.","metadata":{},"id":"eacfe313"},{"cell_type":"markdown","source":"#### Vector\n* Probably the STL container most often used is **```std::vector```**.\n* It provides an array data-structure that\n 1. can grow and shrink dynamically at runtime\n 2. provides classical index-based access\n 3. allows dynamic bounds checking","metadata":{},"id":"e3d3641e"},{"cell_type":"code","source":"#include <vector>\n#include <iostream>\n#include <iomanip> // required for setw() below\n\nint main() {\n\n const int n = 5;\n \n std::vector< int > vec; // need to tell compiler what we will\n // store inside vec: < int > = template argument\n \n for( int k = 1; k <= n; ++k ) {\n vec.push_back( k*k ); // append new entries at the end\n }\n\n for( int k = 1; k <= n; ++k ) {\n std::cout << \"Square of \" << k << \" is \" << std::setw(2)\n << vec[k-1] << std::endl; // this container allows standard 0-based subscript access\n }\n}","metadata":{"trusted":true},"execution_count":1,"outputs":[],"id":"213d8360"},{"cell_type":"code","source":"main();","metadata":{"trusted":true},"execution_count":2,"outputs":[{"name":"stdout","text":"Square of 1 is 1\nSquare of 2 is 4\nSquare of 3 is 9\nSquare of 4 is 16\nSquare of 5 is 25\n","output_type":"stream"}],"id":"bd290a8a"},{"cell_type":"markdown","source":"Of course we can also store data of other types in a vector.","metadata":{},"id":"aa2f2d9d"},{"cell_type":"code","source":"#include <string>\n\nint main() {\n\n std::vector< double > dVec;\n dVec.push_back( 22.0 );\n dVec.push_back( -1.0 );\n \n std::vector< std::string > sVec;\n sVec.push_back( \"Helena\");\n sVec.push_back( \"Odysseus\" );\n}","metadata":{"trusted":true},"execution_count":3,"outputs":[],"id":"0bb0723b"},{"cell_type":"markdown","source":"So let's try an example with a **home-made class**. We make the con/destructor/s of our class a little talkative, to better understand what's happening.","metadata":{},"id":"aca19e34"},{"cell_type":"code","source":"class Dummy {\n\npublic:\n\n Dummy() = delete;\n \n Dummy( const std::string& name ) : name_(name) {\n std::cout << \"Object '\" << name_ << \"' was created\" << std::endl;\n }\n \n Dummy( const Dummy& other ) {\n name_ = other.getName() + \"_copy\";\n std::cout << \"Object '\" << name_ << \"' was created\" << std::endl;\n }\n\n ~Dummy() {\n std::cout << \"Object '\" << name_ << \"' was destroyed\" << std::endl;\n }\n\n const std::string& getName() const {\n return name_;\n }\n\nprivate:\n \n std::string name_;\n\n};","metadata":{"trusted":true},"execution_count":4,"outputs":[],"id":"e5b2a349"},{"cell_type":"code","source":"int main() {\n\n std::cout << \"-> Creating single objects\" << std::endl;\n Dummy obj1( \"one\" );\n Dummy obj2( \"two\" );\n\n std::cout << \"\\n-> Putting objects in container\" << std::endl;\n std::vector< Dummy > vec;\n vec.push_back( obj1 );\n vec.push_back( obj2 );\n\n std::cout << \"\\n-> Auto clean-up at program end\" << std::endl;\n}","metadata":{"trusted":true},"execution_count":5,"outputs":[],"id":"bd331ee4"},{"cell_type":"code","source":"main();","metadata":{"trusted":true},"execution_count":6,"outputs":[{"name":"stdout","text":"-> Creating single objects\nObject 'one' was created\nObject 'two' was created\n\n-> Putting objects in container\nObject 'one_copy' was created\nObject 'two_copy' was created\nObject 'one_copy_copy' was created\nObject 'one_copy' was destroyed\n\n-> Auto clean-up at program end\nObject 'one_copy_copy' was destroyed\nObject 'two_copy' was destroyed\nObject 'two' was destroyed\nObject 'one' was destroyed\n","output_type":"stream"}],"id":"a04654d2"},{"cell_type":"markdown","source":"**Observations:**\n1. Apparently ```std::vector``` stores **copies** of the objects we append.\n1. But why is a second copy of **one**, i.e. **one_copy_copy** created?","metadata":{},"id":"e9db8ff6"},{"cell_type":"code","source":" // Suggestions anyone?\n\n\n","metadata":{"trusted":true},"execution_count":7,"outputs":[],"id":"5d4ba36f"},{"cell_type":"markdown","source":"This has to do with the fact that an ```std::vector``` can dynamically grow and shrink. Consequently it has two different important properties\n\n* size = number of elements currently stored inside the vector\n* capacity = currently available slots to store entries inside the vector\n\nThe capacity of the vector is managed dynamically. We can see this in the following experiment:","metadata":{},"id":"e36f2bb9"},{"cell_type":"code","source":"#include <vector>\n#include <iostream>\n#include <iomanip>\n\nint main() {\n\n std::vector< int > vec;\n\n std::cout << \"vec: length = \" << std::setw(2) << vec.size()\n << \", capacity = \" << std::setw(2) << vec.capacity()\n << std::endl;\n\n for( int k = 1; k <= 33; k++ ) {\n vec.push_back( k );\n std::cout << \"vec: length = \" << std::setw(2) << vec.size()\n << \", capacity = \" << std::setw(2) << vec.capacity()\n << std::endl;\n }\n}\n\nmain();","metadata":{"trusted":true},"execution_count":8,"outputs":[{"name":"stdout","text":"vec: length = 0, capacity = 0\nvec: length = 1, capacity = 1\nvec: length = 2, capacity = 2\nvec: length = 3, capacity = 4\nvec: length = 4, capacity = 4\nvec: length = 5, capacity = 8\nvec: length = 6, capacity = 8\nvec: length = 7, capacity = 8\nvec: length = 8, capacity = 8\nvec: length = 9, capacity = 16\nvec: length = 10, capacity = 16\nvec: length = 11, capacity = 16\nvec: length = 12, capacity = 16\nvec: length = 13, capacity = 16\nvec: length = 14, capacity = 16\nvec: length = 15, capacity = 16\nvec: length = 16, capacity = 16\nvec: length = 17, capacity = 32\nvec: length = 18, capacity = 32\nvec: length = 19, capacity = 32\nvec: length = 20, capacity = 32\nvec: length = 21, capacity = 32\nvec: length = 22, capacity = 32\nvec: length = 23, capacity = 32\nvec: length = 24, capacity = 32\nvec: length = 25, capacity = 32\nvec: length = 26, capacity = 32\nvec: length = 27, capacity = 32\nvec: length = 28, capacity = 32\nvec: length = 29, capacity = 32\nvec: length = 30, capacity = 32\nvec: length = 31, capacity = 32\nvec: length = 32, capacity = 32\nvec: length = 33, capacity = 64\n","output_type":"stream"}],"id":"261a8327"},{"cell_type":"markdown","source":"**What happens, when the capacity is exhausted and must be enlarged?**\n\n* ```std::vector<T>``` is basically a wrapper class around a dynamic array ``T* data`` \n *(we can actually access this via the data() member function, e.g. to interface with C or Fortran)* \n* When the capacity is exhausted, but we want to add another entry three steps need to happen\n 1. Dynamical allocation of a new internal data-array with a new larger capacity.\n 1. **Copying** of all exisisting entries from the old data-array to the new one. \n *[That's what gave us \"Object 'one_copy_copy' was created\" above]*\n 1. Destruction of objects in old data-array and its de-allocation. \n *[That's what gave us \"Object 'one_copy' was destroyed\" above]*\n* Afterwards the new entry can be appended.\n\n**Performance issue** \nThe memory management, but especially the copying constitutes a significant overhead, especially for large arrays.\nWhen we already know what the maximal size of our vector will be, we can avoid this, by **reserving** a large enough\ncapacity.","metadata":{},"id":"b9d88e30"},{"cell_type":"code","source":"#include <vector>\n#include <iostream>\n#include <iomanip>\n\nint main() {\n\n std::vector< int > vec;\n\n // make the vector allocate a data-array of sufficient length\n vec.reserve( 100 );\n \n std::cout << \"vec: length = \" << std::setw(2) << vec.size()\n << \", capacity = \" << std::setw(2) << vec.capacity()\n << std::endl;\n\n for( int k = 1; k <= 33; k++ ) {\n vec.push_back( k );\n std::cout << \"vec: length = \" << std::setw(2) << vec.size()\n << \", capacity = \" << std::setw(2) << vec.capacity()\n << std::endl;\n }\n}\n\nmain();","metadata":{"trusted":true},"execution_count":9,"outputs":[{"name":"stdout","text":"vec: length = 0, capacity = 100\nvec: length = 1, capacity = 100\nvec: length = 2, capacity = 100\nvec: length = 3, capacity = 100\nvec: length = 4, capacity = 100\nvec: length = 5, capacity = 100\nvec: length = 6, capacity = 100\nvec: length = 7, capacity = 100\nvec: length = 8, capacity = 100\nvec: length = 9, capacity = 100\nvec: length = 10, capacity = 100\nvec: length = 11, capacity = 100\nvec: length = 12, capacity = 100\nvec: length = 13, capacity = 100\nvec: length = 14, capacity = 100\nvec: length = 15, capacity = 100\nvec: length = 16, capacity = 100\nvec: length = 17, capacity = 100\nvec: length = 18, capacity = 100\nvec: length = 19, capacity = 100\nvec: length = 20, capacity = 100\nvec: length = 21, capacity = 100\nvec: length = 22, capacity = 100\nvec: length = 23, capacity = 100\nvec: length = 24, capacity = 100\nvec: length = 25, capacity = 100\nvec: length = 26, capacity = 100\nvec: length = 27, capacity = 100\nvec: length = 28, capacity = 100\nvec: length = 29, capacity = 100\nvec: length = 30, capacity = 100\nvec: length = 31, capacity = 100\nvec: length = 32, capacity = 100\nvec: length = 33, capacity = 100\n","output_type":"stream"}],"id":"664eec8e"},{"cell_type":"markdown","source":"***\nThis will also work for our original example with the class Dummy:","metadata":{},"id":"2352d31e"},{"cell_type":"code","source":"int main() {\n\n std::cout << \"-> Creating single objects\" << std::endl;\n Dummy obj1( \"one\" );\n Dummy obj2( \"two\" );\n\n std::cout << \"\\n-> Putting objects in container\" << std::endl;\n std::vector< Dummy > vec;\n vec.reserve( 2 );\n vec.push_back( obj1 );\n vec.push_back( obj2 );\n\n std::cout << \"\\n-> Auto clean-up at program end\" << std::endl;\n}\n\nmain();","metadata":{"trusted":true},"execution_count":10,"outputs":[{"name":"stdout","text":"-> Creating single objects\nObject 'one' was created\nObject 'two' was created\n\n-> Putting objects in container\nObject 'one_copy' was created\nObject 'two_copy' was created\n\n-> Auto clean-up at program end\nObject 'one_copy' was destroyed\nObject 'two_copy' was destroyed\nObject 'two' was destroyed\nObject 'one' was destroyed\n","output_type":"stream"}],"id":"a5dffa46-65b8-477b-bbd8-a0ad0801d1fb"},{"cell_type":"markdown","source":"**But:** Wouldn't it be nicer/easier to have the reservation be part of the instantiation? Let's check:","metadata":{},"id":"41bf0d83"},{"cell_type":"code","source":"std::vector< Dummy > vec( 2 );","metadata":{"trusted":true},"execution_count":11,"outputs":[{"name":"stderr","text":"In file included from input_line_5:1:\nIn file included from /srv/conda/envs/notebook/include/xeus/xinterpreter.hpp:15:\nIn file included from /srv/conda/envs/notebook/bin/../lib/gcc/../../x86_64-conda-linux-gnu/include/c++/9.4.0/vector:65:\n/srv/conda/envs/notebook/bin/../lib/gcc/../../x86_64-conda-linux-gnu/include/c++/9.4.0/bits/stl_construct.h:75:38: error: call to deleted constructor of '__cling_N55::Dummy'\n { ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); }\n ^\n/srv/conda/envs/notebook/bin/../lib/gcc/../../x86_64-conda-linux-gnu/include/c++/9.4.0/bits/stl_uninitialized.h:545:8: note: in instantiation of function template specialization 'std::_Construct<__cling_N55::Dummy>' requested here\n std::_Construct(std::__addressof(*__cur));\n ^\n/srv/conda/envs/notebook/bin/../lib/gcc/../../x86_64-conda-linux-gnu/include/c++/9.4.0/bits/stl_uninitialized.h:601:2: note: in instantiation of function template specialization 'std::__uninitialized_default_n_1<false>::__uninit_default_n<__cling_N55::Dummy *, unsigned long>' requested here\n __uninit_default_n(__first, __n);\n ^\n/srv/conda/envs/notebook/bin/../lib/gcc/../../x86_64-conda-linux-gnu/include/c++/9.4.0/bits/stl_uninitialized.h:663:19: note: in instantiation of function template specialization 'std::__uninitialized_default_n<__cling_N55::Dummy *, unsigned long>' requested here\n { return std::__uninitialized_default_n(__first, __n); }\n ^\n/srv/conda/envs/notebook/bin/../lib/gcc/../../x86_64-conda-linux-gnu/include/c++/9.4.0/bits/stl_vector.h:1603:9: note: in instantiation of function template specialization 'std::__uninitialized_default_n_a<__cling_N55::Dummy *, unsigned long, __cling_N55::Dummy>' requested here\n std::__uninitialized_default_n_a(this->_M_impl._M_start, __n,\n ^\n/srv/conda/envs/notebook/bin/../lib/gcc/../../x86_64-conda-linux-gnu/include/c++/9.4.0/bits/stl_vector.h:509:9: note: in instantiation of member function 'std::vector<__cling_N55::Dummy, std::allocator<__cling_N55::Dummy> >::_M_default_initialize' requested here\n { _M_default_initialize(__n); }\n ^\ninput_line_21:2:23: note: in instantiation of member function 'std::vector<__cling_N55::Dummy, std::allocator<__cling_N55::Dummy> >::vector' requested here\n std::vector< Dummy > vec( 2 );\n ^\ninput_line_12:3:5: note: 'Dummy' has been explicitly marked deleted here\n Dummy() = delete;\n ^\n","output_type":"stream"},{"ename":"Interpreter Error","evalue":"","traceback":["Interpreter Error: "],"output_type":"error"}],"id":"351ace20"},{"cell_type":"markdown","source":"Problem is that this version of the ```std::vector<T>``` constructor will try to fill/initialise the vector with objects of type ```T``` using their default constructor.","metadata":{},"id":"ec2de34b"},{"cell_type":"markdown","source":"Let us take a look at other ways to construct an std::vector:","metadata":{},"id":"d1cdc134"},{"cell_type":"code","source":"int main() {\n\n std::cout << \"-> Creating single objects\" << std::endl;\n Dummy obj1( \"one\" );\n Dummy obj2( \"two\" );\n\n std::cout << \"\\n-> Putting objects in container\" << std::endl;\n std::vector< Dummy > vec;\n\n // Will fail, as we have no default constructor\n // std::vector< Dummy > vec2( 2 );\n\n vec.push_back( obj1 );\n // std::vector< Dummy > vec2( vec ); // -> construction by copying\n // std::vector< Dummy > vec2 = vec; // -> construction by copying (but move semantics?)\n // std::vector< Dummy > vec2( 5, obj1 ); // -> vector with 5 copies of object one\n // std::vector< Dummy > vec2( {obj1, obj2} ); // -> using initialiser list (but is a temporary generated here?)\n // std::vector< Dummy > vec2{obj1, obj2}; // -> clearer version with initialiser list\n\n vec.emplace_back( \"three\" ); // <- what will this do?\n\n std::cout << \"\\n-> Auto clean-up at program end\" << std::endl;\n\n}","metadata":{"trusted":true},"execution_count":12,"outputs":[],"id":"b3dfe338"},{"cell_type":"code","source":"main();","metadata":{"trusted":true},"execution_count":13,"outputs":[{"name":"stdout","text":"-> Creating single objects\nObject 'one' was created\nObject 'two' was created\n\n-> Putting objects in container\nObject 'one_copy' was created\nObject 'three' was created\nObject 'one_copy_copy' was created\nObject 'one_copy' was destroyed\n\n-> Auto clean-up at program end\nObject 'one_copy_copy' was destroyed\nObject 'three' was destroyed\nObject 'two' was destroyed\nObject 'one' was destroyed\n","output_type":"stream"}],"id":"ad928c13"},{"cell_type":"markdown","source":"* ```emplace()``` allows to **generate** a new entry at a specified place inside the vector, passing the given arguments to the entry's constructor.\n* ```emplace_back()``` does the same, but **appends at the end**.","metadata":{},"id":"eebc1269"},{"cell_type":"markdown","source":"***\nWe have already seen that we have read/write access to entries of our vector with the **```[ ]```** operator. Another alternative is the **```at()```** method.\n\nThe difference is that **```at()```** will throw an exception, when we commit an out-of-bounds access error.","metadata":{},"id":"dc64b2c9"},{"cell_type":"code","source":"%%file exception.cpp\n\n#include <iostream>\n#include <vector>\n\nint main() {\n\n std::vector< double > vec( 10, 2.0 );\n\n#ifdef OUT_OF_BOUNDS\n for( int k = 0; k <= 10; ++k ) {\n std::cout << \"vec[ \" << k << \" ] = \" << vec[k] << std::endl;\n }\n std::cout << \"Too bad we reached this line :-(\" << std::endl;\n#else\n for( int k = 0; k <= 10; ++k ) {\n std::cout << \"vec[ \" << k << \" ] = \" << vec.at( k ) << std::endl;\n }\n#endif\n\n}","metadata":{"trusted":true},"execution_count":14,"outputs":[{"name":"stdout","text":"Overwriting exception.cpp\n","output_type":"stream"}],"id":"7e537a6b"},{"cell_type":"code","source":"!g++ -Wall -DOUT_OF_BOUNDS exception.cpp","metadata":{"trusted":true},"execution_count":15,"outputs":[],"id":"0513b187"},{"cell_type":"code","source":"!./a.out","metadata":{"trusted":true},"execution_count":16,"outputs":[{"name":"stdout","text":"vec[ 0 ] = 2\nvec[ 1 ] = 2\nvec[ 2 ] = 2\nvec[ 3 ] = 2\nvec[ 4 ] = 2\nvec[ 5 ] = 2\nvec[ 6 ] = 2\nvec[ 7 ] = 2\nvec[ 8 ] = 2\nvec[ 9 ] = 2\nvec[ 10 ] = 0\nToo bad we reached this line :-(\n","output_type":"stream"}],"id":"af593caa"},{"cell_type":"code","source":"!g++ -Wall exception.cpp","metadata":{"trusted":true},"execution_count":17,"outputs":[],"id":"131dd4f5"},{"cell_type":"code","source":"!./a.out","metadata":{"trusted":true},"execution_count":18,"outputs":[{"name":"stdout","text":"vec[ 0 ] = 2\nvec[ 1 ] = 2\nvec[ 2 ] = 2\nvec[ 3 ] = 2\nvec[ 4 ] = 2\nvec[ 5 ] = 2\nvec[ 6 ] = 2\nvec[ 7 ] = 2\nvec[ 8 ] = 2\nvec[ 9 ] = 2\nterminate called after throwing an instance of 'std::out_of_range'\n what(): vector::_M_range_check: __n (which is 10) >= this->size() (which is 10)\nAborted\n","output_type":"stream"}],"id":"e6ce23bd"},{"cell_type":"markdown","source":"### Iterators and range-based access","metadata":{},"id":"8e0987ec"},{"cell_type":"markdown","source":"Our **std::vector** belongs to the class of sequence containers, i.e. the ordering of entries $e_k$ is important\n\n$$ \\Big( e_1, e_2, \\ldots, e_n \\Big)$$\n\nand the same value $v$ can show up at multiple positions ($e_i = v = e_j$ with $i\\neq j$).\n\nAnother type of sequence container is the **linked list**. The **std::list** implements a doubly-linked list, i.e. each entry/element is composed of three parts\n\n$$ \\text{entry} = \\Big( \\text{payload}, \\text{prev}, \\text{next} \\Big)$$\n\nwhere\n * payload = value of the entry\n * prev = information on where to find the predecessor of this entry\n * next = information on where to find the successor of this entry\n \nContrary to a vector, a list does not offer index-based access to its elements. Instead we can do list-traversal.\n\nNow consider the following piece of code:","metadata":{},"id":"80b33721"},{"cell_type":"code","source":"#include <iostream>\n#include <vector>\n\nint main() {\n using Container = std::vector< int >;\n Container box;\n\n const int n = 5;\n \n // fill the box with odd numbers\n for( int k = 0; k < n; k++ ) {\n box.push_back( 2*k+1 );\n }\n \n // print its contents\n for( int k = 0; k < n; k++ ) {\n std::cout << \"box(\" << k << \") = \" << box[k] << std::endl;\n }\n}\n\nmain();","metadata":{"trusted":true},"execution_count":19,"outputs":[{"name":"stdout","text":"box(0) = 1\nbox(1) = 3\nbox(2) = 5\nbox(3) = 7\nbox(4) = 9\n","output_type":"stream"}],"id":"eee3b34a"},{"cell_type":"markdown","source":"For what we are doing in the program the type of underlying container does not really matter. \nHow about replacing vector with list for a change?","metadata":{},"id":"33c83653"},{"cell_type":"code","source":"#include <iostream>\n#include <list> // okay, need to adapt the include\n\nint main() {\n using Container = std::list< int >; // need to alter this statement\n Container box;\n\n const int n = 5;\n \n // fill the box with odd numbers\n for( int k = 0; k < n; k++ ) {\n box.push_back( 2*k+1 );\n }\n \n // print its contents\n for( int k = 0; k < n; k++ ) {\n std::cout << \"box(\" << k << \") = \" << box[k] << std::endl;\n }\n}\n\nmain();","metadata":{"trusted":true},"execution_count":20,"outputs":[{"name":"stderr","text":"input_line_27:13:50: error: type 'Container' (aka 'list<int>') does not provide a subscript operator\n std::cout << \"box(\" << k << \") = \" << box[k] << std::endl;\n ~~~^~\n","output_type":"stream"},{"ename":"Interpreter Error","evalue":"","traceback":["Interpreter Error: "],"output_type":"error"}],"id":"d2806a4f"},{"cell_type":"markdown","source":"***\nNow that is, where **iterators** come into play. They abstract away the details of the underlying container.","metadata":{},"id":"40d19e9a"},{"cell_type":"code","source":"#include <iostream>\n#include <vector>\n\nint main() {\n using Container = std::vector< int >;\n Container box;\n\n const int n = 5;\n \n // fill the box with odd numbers\n for( int k = 0; k < n; k++ ) {\n box.push_back( 2*k+1 );\n }\n \n // use an iterator-based loop\n int k = 0;\n for( Container::iterator it = box.begin(); it != box.end(); it++ ) {\n std::cout << \"box(\" << k << \") = \" << *it << std::endl;\n k++;\n }\n}","metadata":{"trusted":true},"execution_count":21,"outputs":[],"id":"4a6ba619"},{"cell_type":"code","source":"main();","metadata":{"trusted":true},"execution_count":22,"outputs":[{"name":"stdout","text":"box(0) = 1\nbox(1) = 3\nbox(2) = 5\nbox(3) = 7\nbox(4) = 9\n","output_type":"stream"}],"id":"907c51a5"},{"cell_type":"markdown","source":"**Remarks:**\n* The iterator is no pointer, but 'feels' like one, as it overloads the dereferencing operator ```*```.\n* ```box.begin()``` points to the first entry in the container.\n* ```box.end()``` points **after** the last entry in the container. Thus, dereferencing it would be illegal.\n* Do not test for ```it < box.end()```, but for (in)equality only.","metadata":{},"id":"5b9766a5"},{"cell_type":"markdown","source":"Since our loop is using an iterator now, we can with two small modifications switch from a **std::vector** to a **std::list**.","metadata":{},"id":"ba14f11e"},{"cell_type":"code","source":"#include <iostream>\n#include <list> // mod #1\n\nint main() {\n using Container = std::list< int >; // mod #2\n Container box;\n\n const int n = 5;\n \n // fill the box with odd numbers\n for( int k = 0; k < n; k++ ) {\n box.push_back( 2*k+1 );\n }\n \n // use an iterator-based loop\n int k = 0;\n for( Container::iterator it = box.begin(); it != box.end(); it++ ) {\n std::cout << \"box(\" << k << \") = \" << *it << std::endl;\n k++;\n }\n}","metadata":{"trusted":true},"execution_count":23,"outputs":[],"id":"9f8daf26"},{"cell_type":"code","source":"main();","metadata":{"trusted":true},"execution_count":24,"outputs":[{"name":"stdout","text":"box(0) = 1\nbox(1) = 3\nbox(2) = 5\nbox(3) = 7\nbox(4) = 9\n","output_type":"stream"}],"id":"53211059"},{"cell_type":"markdown","source":"#### 'Reverse mode' \nUp above we have used a forward iterator. However, we can also ask for a reverse iterator to go through the container from end to start (if the container supports it).","metadata":{},"id":"44f9f4e2"},{"cell_type":"code","source":"#include <iostream>\n\nstd::vector< short > vec;\n\nvec.push_back( 1 );\nvec.push_back( 2 );\nvec.push_back( 3 );\n\nfor( std::vector< short >::reverse_iterator it = vec.rbegin(); it != vec.rend(); it++ ) {\n std::cout << *it << std::endl;\n}","metadata":{"trusted":true},"execution_count":25,"outputs":[{"name":"stdout","text":"3\n2\n1\n","output_type":"stream"}],"id":"158950d1"},{"cell_type":"markdown","source":"#### 'Const mode'\nWhen we do not intend to change the element in the loop body, we can also employ a ```const_iterator```.","metadata":{},"id":"96bed53d"},{"cell_type":"code","source":"#include <iostream>\nstd::list< double > lc;\nusing cIter = std::list< double >::const_iterator;\nlc.push_back( 4.0 );\nlc.push_back( 5.0 );\nlc.push_back( 6.0 );\nfor( cIter it = lc.begin(); it != lc.end(); it++ ) {\n std::cout << *it << std::endl;\n}","metadata":{"trusted":true},"execution_count":26,"outputs":[{"name":"stdout","text":"4\n5\n6\n","output_type":"stream"}],"id":"44a9bf28"},{"cell_type":"markdown","source":"#### 'Free Functions'\nC++ introduced free-function forms of ```begin()``` and ```end()``` which we can use alternatively to the member functions above:","metadata":{},"id":"da22b078"},{"cell_type":"code","source":"for( cIter it = begin( lc ), e = end( lc ); it != e; it++ ) {\n std::cout << *it << std::endl;\n}","metadata":{"trusted":true},"execution_count":27,"outputs":[{"name":"stdout","text":"4\n5\n6\n","output_type":"stream"}],"id":"ceb3de09"},{"cell_type":"markdown","source":"Why did we introduce ```e```? Is it necessary?","metadata":{},"id":"14632b71"},{"cell_type":"markdown","source":"### Range-based access\nC++ nowadays also supports an even simpler way to encode a loop over a container:","metadata":{},"id":"c731df99"},{"cell_type":"code","source":"#include <iostream>\n#include <list>\n\nint main() {\n using Container = std::list< int >;\n Container box;\n\n const int n = 5;\n \n // fill the box with odd numbers\n for( int k = 0; k < n; k++ ) {\n box.push_back( 2*k+1 );\n }\n \n // change that to even, using a range-based loop\n for( int& entry: box ) {\n entry -= 1;\n }\n \n // print using a range-based loop (in this case we can do w/o a ref)\n int k = 0;\n for( int value: box ) {\n std::cout << \"box(\" << k << \") = \" << value << std::endl;\n k++;\n }\n}","metadata":{"trusted":true},"execution_count":28,"outputs":[],"id":"2497a826"},{"cell_type":"code","source":"main();","metadata":{"trusted":true},"execution_count":29,"outputs":[{"name":"stdout","text":"box(0) = 0\nbox(1) = 2\nbox(2) = 4\nbox(3) = 6\nbox(4) = 8\n","output_type":"stream"}],"id":"a8918a0e"},{"cell_type":"markdown","source":"Note that the last loop version involved a copy operation!","metadata":{},"id":"f4d8fd5e"},{"cell_type":"code","source":"std::cout << \"-> Put objects into container\" << std::endl;\nstd::vector< Dummy > vec;\nvec.reserve(2);\nvec.emplace_back( \"one\" );\nvec.emplace_back( \"two\" );\n\nstd::cout << \"\\n-> Run a loop\" << std::endl;\nfor( Dummy x: vec ) {\n std::cout << \"* \" << x.getName() << std::endl;\n}","metadata":{"trusted":true},"execution_count":30,"outputs":[{"name":"stdout","text":"-> Put objects into container\nObject 'one' was created\nObject 'two' was created\n\n-> Run a loop\nObject 'one_copy' was created\n* one_copy\nObject 'one_copy' was destroyed\nObject 'two_copy' was created\n* two_copy\nObject 'two_copy' was destroyed\n","output_type":"stream"}],"id":"d1c64b51"},{"cell_type":"markdown","source":"Now let's try the same, but use a reference","metadata":{},"id":"eaf4c60a-ce19-410a-a81e-163d292de58d"},{"cell_type":"code","source":"std::cout << \"-> Put objects into container\" << std::endl;\nstd::vector< Dummy > vec;\nvec.reserve(2);\nvec.emplace_back( \"one\" );\nvec.emplace_back( \"two\" );\n\nstd::cout << \"\\n-> Run a loop\" << std::endl;\nfor( Dummy& x: vec ) {\n std::cout << \"* \" << x.getName() << std::endl;\n}","metadata":{"trusted":true},"execution_count":31,"outputs":[{"name":"stdout","text":"-> Put objects into container\nObject 'one' was created\nObject 'two' was created\n\n-> Run a loop\n* one\n* two\n","output_type":"stream"}],"id":"28b2e3fb"},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[],"id":"c4afab4e-a800-4176-a26f-15d11ffd3964"}]}
\ No newline at end of file
{"metadata":{"orig_nbformat":4,"kernelspec":{"display_name":"C++14","language":"C++14","name":"xcpp14"},"language_info":{"codemirror_mode":"text/x-c++src","file_extension":".cpp","mimetype":"text/x-c++src","name":"c++","version":"14"}},"nbformat_minor":5,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Standard Template Library (STL)\n","metadata":{},"id":"0921615f"},{"cell_type":"markdown","source":"#### Executive Summary\n* Since its inclusion in 1994 the STL forms an integral part of the **Standard C++ Library**.\n* The STL is composed of three major components:\n 1. Definition of several **container types** for data storage\n 1. Access functions for those containers, especially so called **iterators**\n 1. **Algorithms** for working with the containers.\n* The following table lists the header files to include for different aspects of the STL\n \n| STL containers | Iterators, Functors, Algorithms |\n| :------------------ | :------------------------------ |\n| **```<deque>```** | **```<iterator>```** |\n| **```<list>```** | **```<algorithm>```** |\n| **```<map>```** | **```<functional>```** |\n| **```<queue>```** | |\n| **```<set>```** | |\n| **```<stack>```** | |\n| **```<vector>```** | |\n\n* As its name suggests the STL makes heavy use of *generic programming* based on **templates**.","metadata":{},"id":"eacfe313"},{"cell_type":"markdown","source":"#### Vector\n* Probably the STL container most often used is **```std::vector```**.\n* It provides an array data-structure that\n 1. can grow and shrink dynamically at runtime\n 2. provides classical index-based access\n 3. allows dynamic bounds checking","metadata":{},"id":"e3d3641e"},{"cell_type":"code","source":"#include <vector>\n#include <iostream>\n#include <iomanip> // required for setw() below\n\nint main() {\n\n const int n = 5;\n \n std::vector< int > vec; // need to tell compiler what we will\n // store inside vec: < int > = template argument\n \n for( int k = 1; k <= n; ++k ) {\n vec.push_back( k*k ); // append new entries at the end\n }\n\n for( int k = 1; k <= n; ++k ) {\n std::cout << \"Square of \" << k << \" is \" << std::setw(2)\n << vec[k-1] << std::endl; // this container allows standard 0-based subscript access\n }\n}","metadata":{},"execution_count":1,"outputs":[],"id":"213d8360"},{"cell_type":"code","source":"main();","metadata":{},"execution_count":2,"outputs":[{"name":"stdout","text":"Square of 1 is 1\nSquare of 2 is 4\nSquare of 3 is 9\nSquare of 4 is 16\nSquare of 5 is 25\n","output_type":"stream"}],"id":"bd290a8a"},{"cell_type":"markdown","source":"Of course we can also store data of other types in a vector.","metadata":{},"id":"aa2f2d9d"},{"cell_type":"code","source":"#include <string>\n\nint main() {\n\n std::vector< double > dVec;\n dVec.push_back( 22.0 );\n dVec.push_back( -1.0 );\n \n std::vector< std::string > sVec;\n sVec.push_back( \"Helena\");\n sVec.push_back( \"Odysseus\" );\n}","metadata":{},"execution_count":3,"outputs":[],"id":"0bb0723b"},{"cell_type":"markdown","source":"So let's try an example with a **home-made class**. We make the con/destructor/s of our class a little talkative, to better understand what's happening.","metadata":{},"id":"aca19e34"},{"cell_type":"code","source":"class Dummy {\n\npublic:\n\n Dummy() = delete;\n \n Dummy( const std::string& name ) : name_(name) {\n std::cout << \"Object '\" << name_ << \"' was created\" << std::endl;\n }\n \n Dummy( const Dummy& other ) {\n name_ = other.getName() + \"_copy\";\n std::cout << \"Object '\" << name_ << \"' was created\" << std::endl;\n }\n\n ~Dummy() {\n std::cout << \"Object '\" << name_ << \"' was destroyed\" << std::endl;\n }\n\n const std::string& getName() const {\n return name_;\n }\n\nprivate:\n \n std::string name_;\n\n};","metadata":{},"execution_count":4,"outputs":[],"id":"e5b2a349"},{"cell_type":"code","source":"int main() {\n\n std::cout << \"-> Creating single objects\" << std::endl;\n Dummy obj1( \"one\" );\n Dummy obj2( \"two\" );\n\n std::cout << \"\\n-> Putting objects in container\" << std::endl;\n std::vector< Dummy > vec;\n vec.push_back( obj1 );\n vec.push_back( obj2 );\n\n std::cout << \"\\n-> Auto clean-up at program end\" << std::endl;\n}","metadata":{},"execution_count":5,"outputs":[],"id":"bd331ee4"},{"cell_type":"code","source":"main();","metadata":{},"execution_count":6,"outputs":[{"name":"stdout","text":"-> Creating single objects\nObject 'one' was created\nObject 'two' was created\n\n-> Putting objects in container\nObject 'one_copy' was created\nObject 'two_copy' was created\nObject 'one_copy_copy' was created\nObject 'one_copy' was destroyed\n\n-> Auto clean-up at program end\nObject 'one_copy_copy' was destroyed\nObject 'two_copy' was destroyed\nObject 'two' was destroyed\nObject 'one' was destroyed\n","output_type":"stream"}],"id":"a04654d2"},{"cell_type":"markdown","source":"**Observations:**\n1. Apparently ```std::vector``` stores **copies** of the objects we append.\n1. But why is a second copy of **one**, i.e. **one_copy_copy** created?","metadata":{},"id":"e9db8ff6"},{"cell_type":"code","source":" // Suggestions anyone?\n\n\n","metadata":{},"execution_count":7,"outputs":[],"id":"5d4ba36f"},{"cell_type":"markdown","source":"This has to do with the fact that an ```std::vector``` can dynamically grow and shrink. Consequently it has two different important properties\n\n* size = number of elements currently stored inside the vector\n* capacity = currently available slots to store entries inside the vector\n\nThe capacity of the vector is managed dynamically. We can see this in the following experiment:","metadata":{},"id":"e36f2bb9"},{"cell_type":"code","source":"#include <vector>\n#include <iostream>\n#include <iomanip>\n\nint main() {\n\n std::vector< int > vec;\n\n std::cout << \"vec: length = \" << std::setw(2) << vec.size()\n << \", capacity = \" << std::setw(2) << vec.capacity()\n << std::endl;\n\n for( int k = 1; k <= 33; k++ ) {\n vec.push_back( k );\n std::cout << \"vec: length = \" << std::setw(2) << vec.size()\n << \", capacity = \" << std::setw(2) << vec.capacity()\n << std::endl;\n }\n}\n\nmain();","metadata":{},"execution_count":8,"outputs":[{"name":"stdout","text":"vec: length = 0, capacity = 0\nvec: length = 1, capacity = 1\nvec: length = 2, capacity = 2\nvec: length = 3, capacity = 4\nvec: length = 4, capacity = 4\nvec: length = 5, capacity = 8\nvec: length = 6, capacity = 8\nvec: length = 7, capacity = 8\nvec: length = 8, capacity = 8\nvec: length = 9, capacity = 16\nvec: length = 10, capacity = 16\nvec: length = 11, capacity = 16\nvec: length = 12, capacity = 16\nvec: length = 13, capacity = 16\nvec: length = 14, capacity = 16\nvec: length = 15, capacity = 16\nvec: length = 16, capacity = 16\nvec: length = 17, capacity = 32\nvec: length = 18, capacity = 32\nvec: length = 19, capacity = 32\nvec: length = 20, capacity = 32\nvec: length = 21, capacity = 32\nvec: length = 22, capacity = 32\nvec: length = 23, capacity = 32\nvec: length = 24, capacity = 32\nvec: length = 25, capacity = 32\nvec: length = 26, capacity = 32\nvec: length = 27, capacity = 32\nvec: length = 28, capacity = 32\nvec: length = 29, capacity = 32\nvec: length = 30, capacity = 32\nvec: length = 31, capacity = 32\nvec: length = 32, capacity = 32\nvec: length = 33, capacity = 64\n","output_type":"stream"}],"id":"261a8327"},{"cell_type":"markdown","source":"**What happens, when the capacity is exhausted and must be enlarged?**\n\n* ```std::vector<T>``` is basically a wrapper class around a dynamic array ``T* data`` \n *(we can actually access this via the data() member function, e.g. to interface with C or Fortran)* \n* When the capacity is exhausted, but we want to add another entry three steps need to happen\n 1. Dynamical allocation of a new internal data-array with a new larger capacity.\n 1. **Copying** of all exisisting entries from the old data-array to the new one. \n *[That's what gave us \"Object 'one_copy_copy' was created\" above]*\n 1. Destruction of objects in old data-array and its de-allocation. \n *[That's what gave us \"Object 'one_copy' was destroyed\" above]*\n* Afterwards the new entry can be appended.\n\n**Performance issue** \nThe memory management, but especially the copying constitutes a significant overhead, especially for large arrays.\nWhen we already know what the maximal size of our vector will be, we can avoid this, by **reserving** a large enough\ncapacity.","metadata":{},"id":"b9d88e30"},{"cell_type":"code","source":"#include <vector>\n#include <iostream>\n#include <iomanip>\n\nint main() {\n\n std::vector< int > vec;\n\n // make the vector allocate a data-array of sufficient length\n vec.reserve( 100 );\n \n std::cout << \"vec: length = \" << std::setw(2) << vec.size()\n << \", capacity = \" << std::setw(2) << vec.capacity()\n << std::endl;\n\n for( int k = 1; k <= 33; k++ ) {\n vec.push_back( k );\n std::cout << \"vec: length = \" << std::setw(2) << vec.size()\n << \", capacity = \" << std::setw(2) << vec.capacity()\n << std::endl;\n }\n}\n\nmain();","metadata":{},"execution_count":9,"outputs":[{"name":"stdout","text":"vec: length = 0, capacity = 100\nvec: length = 1, capacity = 100\nvec: length = 2, capacity = 100\nvec: length = 3, capacity = 100\nvec: length = 4, capacity = 100\nvec: length = 5, capacity = 100\nvec: length = 6, capacity = 100\nvec: length = 7, capacity = 100\nvec: length = 8, capacity = 100\nvec: length = 9, capacity = 100\nvec: length = 10, capacity = 100\nvec: length = 11, capacity = 100\nvec: length = 12, capacity = 100\nvec: length = 13, capacity = 100\nvec: length = 14, capacity = 100\nvec: length = 15, capacity = 100\nvec: length = 16, capacity = 100\nvec: length = 17, capacity = 100\nvec: length = 18, capacity = 100\nvec: length = 19, capacity = 100\nvec: length = 20, capacity = 100\nvec: length = 21, capacity = 100\nvec: length = 22, capacity = 100\nvec: length = 23, capacity = 100\nvec: length = 24, capacity = 100\nvec: length = 25, capacity = 100\nvec: length = 26, capacity = 100\nvec: length = 27, capacity = 100\nvec: length = 28, capacity = 100\nvec: length = 29, capacity = 100\nvec: length = 30, capacity = 100\nvec: length = 31, capacity = 100\nvec: length = 32, capacity = 100\nvec: length = 33, capacity = 100\n","output_type":"stream"}],"id":"664eec8e"},{"cell_type":"markdown","source":"***\nThis will also work for our original example with the class Dummy:","metadata":{},"id":"2352d31e"},{"cell_type":"code","source":"int main() {\n\n std::cout << \"-> Creating single objects\" << std::endl;\n Dummy obj1( \"one\" );\n Dummy obj2( \"two\" );\n\n std::cout << \"\\n-> Putting objects in container\" << std::endl;\n std::vector< Dummy > vec;\n vec.reserve( 2 );\n vec.push_back( obj1 );\n vec.push_back( obj2 );\n\n std::cout << \"\\n-> Auto clean-up at program end\" << std::endl;\n}\n\nmain();","metadata":{},"execution_count":10,"outputs":[{"name":"stdout","text":"-> Creating single objects\nObject 'one' was created\nObject 'two' was created\n\n-> Putting objects in container\nObject 'one_copy' was created\nObject 'two_copy' was created\n\n-> Auto clean-up at program end\nObject 'one_copy' was destroyed\nObject 'two_copy' was destroyed\nObject 'two' was destroyed\nObject 'one' was destroyed\n","output_type":"stream"}],"id":"a5dffa46-65b8-477b-bbd8-a0ad0801d1fb"},{"cell_type":"markdown","source":"**But:** Wouldn't it be nicer/easier to have the reservation be part of the instantiation? Let's check:","metadata":{},"id":"41bf0d83"},{"cell_type":"code","source":"std::vector< Dummy > vec( 2 );","metadata":{},"execution_count":11,"outputs":[{"name":"stderr","text":"In file included from input_line_5:1:\nIn file included from /srv/conda/envs/notebook/include/xeus/xinterpreter.hpp:15:\nIn file included from /srv/conda/envs/notebook/bin/../lib/gcc/../../x86_64-conda-linux-gnu/include/c++/9.4.0/vector:65:\n/srv/conda/envs/notebook/bin/../lib/gcc/../../x86_64-conda-linux-gnu/include/c++/9.4.0/bits/stl_construct.h:75:38: error: call to deleted constructor of '__cling_N55::Dummy'\n { ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); }\n ^\n/srv/conda/envs/notebook/bin/../lib/gcc/../../x86_64-conda-linux-gnu/include/c++/9.4.0/bits/stl_uninitialized.h:545:8: note: in instantiation of function template specialization 'std::_Construct<__cling_N55::Dummy>' requested here\n std::_Construct(std::__addressof(*__cur));\n ^\n/srv/conda/envs/notebook/bin/../lib/gcc/../../x86_64-conda-linux-gnu/include/c++/9.4.0/bits/stl_uninitialized.h:601:2: note: in instantiation of function template specialization 'std::__uninitialized_default_n_1<false>::__uninit_default_n<__cling_N55::Dummy *, unsigned long>' requested here\n __uninit_default_n(__first, __n);\n ^\n/srv/conda/envs/notebook/bin/../lib/gcc/../../x86_64-conda-linux-gnu/include/c++/9.4.0/bits/stl_uninitialized.h:663:19: note: in instantiation of function template specialization 'std::__uninitialized_default_n<__cling_N55::Dummy *, unsigned long>' requested here\n { return std::__uninitialized_default_n(__first, __n); }\n ^\n/srv/conda/envs/notebook/bin/../lib/gcc/../../x86_64-conda-linux-gnu/include/c++/9.4.0/bits/stl_vector.h:1603:9: note: in instantiation of function template specialization 'std::__uninitialized_default_n_a<__cling_N55::Dummy *, unsigned long, __cling_N55::Dummy>' requested here\n std::__uninitialized_default_n_a(this->_M_impl._M_start, __n,\n ^\n/srv/conda/envs/notebook/bin/../lib/gcc/../../x86_64-conda-linux-gnu/include/c++/9.4.0/bits/stl_vector.h:509:9: note: in instantiation of member function 'std::vector<__cling_N55::Dummy, std::allocator<__cling_N55::Dummy> >::_M_default_initialize' requested here\n { _M_default_initialize(__n); }\n ^\ninput_line_21:2:23: note: in instantiation of member function 'std::vector<__cling_N55::Dummy, std::allocator<__cling_N55::Dummy> >::vector' requested here\n std::vector< Dummy > vec( 2 );\n ^\ninput_line_12:3:5: note: 'Dummy' has been explicitly marked deleted here\n Dummy() = delete;\n ^\n","output_type":"stream"},{"ename":"Interpreter Error","evalue":"","traceback":["Interpreter Error: "],"output_type":"error"}],"id":"351ace20"},{"cell_type":"markdown","source":"Problem is that this version of the ```std::vector<T>``` constructor will try to fill/initialise the vector with objects of type ```T``` using their default constructor.","metadata":{},"id":"ec2de34b"},{"cell_type":"markdown","source":"Let us take a look at other ways to construct an std::vector:","metadata":{},"id":"d1cdc134"},{"cell_type":"code","source":"int main() {\n\n std::cout << \"-> Creating single objects\" << std::endl;\n Dummy obj1( \"one\" );\n Dummy obj2( \"two\" );\n\n std::cout << \"\\n-> Putting objects in container\" << std::endl;\n std::vector< Dummy > vec;\n\n // Will fail, as we have no default constructor\n // std::vector< Dummy > vec2( 2 );\n\n vec.push_back( obj1 );\n // std::vector< Dummy > vec2( vec ); // -> construction by copying\n // std::vector< Dummy > vec2 = vec; // -> construction by copying (but move semantics?)\n // std::vector< Dummy > vec2( 5, obj1 ); // -> vector with 5 copies of object one\n // std::vector< Dummy > vec2( {obj1, obj2} ); // -> using initialiser list (but is a temporary generated here?)\n // std::vector< Dummy > vec2{obj1, obj2}; // -> clearer version with initialiser list\n\n vec.emplace_back( \"three\" ); // <- what will this do?\n\n std::cout << \"\\n-> Auto clean-up at program end\" << std::endl;\n\n}","metadata":{},"execution_count":12,"outputs":[],"id":"b3dfe338"},{"cell_type":"code","source":"main();","metadata":{},"execution_count":13,"outputs":[{"name":"stdout","text":"-> Creating single objects\nObject 'one' was created\nObject 'two' was created\n\n-> Putting objects in container\nObject 'one_copy' was created\nObject 'three' was created\nObject 'one_copy_copy' was created\nObject 'one_copy' was destroyed\n\n-> Auto clean-up at program end\nObject 'one_copy_copy' was destroyed\nObject 'three' was destroyed\nObject 'two' was destroyed\nObject 'one' was destroyed\n","output_type":"stream"}],"id":"ad928c13"},{"cell_type":"markdown","source":"* ```emplace()``` allows to **generate** a new entry at a specified place inside the vector, passing the given arguments to the entry's constructor.\n* ```emplace_back()``` does the same, but **appends at the end**.","metadata":{},"id":"eebc1269"},{"cell_type":"markdown","source":"***\nWe have already seen that we have read/write access to entries of our vector with the **```[ ]```** operator. Another alternative is the **```at()```** method.\n\nThe difference is that **```at()```** will throw an exception, when we commit an out-of-bounds access error.","metadata":{},"id":"dc64b2c9"},{"cell_type":"code","source":"%%file exception.cpp\n\n#include <iostream>\n#include <vector>\n\nint main() {\n\n std::vector< double > vec( 10, 2.0 );\n\n#ifdef OUT_OF_BOUNDS\n for( int k = 0; k <= 10; ++k ) {\n std::cout << \"vec[ \" << k << \" ] = \" << vec[k] << std::endl;\n }\n std::cout << \"Too bad we reached this line :-(\" << std::endl;\n#else\n for( int k = 0; k <= 10; ++k ) {\n std::cout << \"vec[ \" << k << \" ] = \" << vec.at( k ) << std::endl;\n }\n#endif\n\n}","metadata":{},"execution_count":14,"outputs":[{"name":"stdout","text":"Overwriting exception.cpp\n","output_type":"stream"}],"id":"7e537a6b"},{"cell_type":"code","source":"!g++ -Wall -DOUT_OF_BOUNDS exception.cpp","metadata":{},"execution_count":15,"outputs":[],"id":"0513b187"},{"cell_type":"code","source":"!./a.out","metadata":{},"execution_count":16,"outputs":[{"name":"stdout","text":"vec[ 0 ] = 2\nvec[ 1 ] = 2\nvec[ 2 ] = 2\nvec[ 3 ] = 2\nvec[ 4 ] = 2\nvec[ 5 ] = 2\nvec[ 6 ] = 2\nvec[ 7 ] = 2\nvec[ 8 ] = 2\nvec[ 9 ] = 2\nvec[ 10 ] = 0\nToo bad we reached this line :-(\n","output_type":"stream"}],"id":"af593caa"},{"cell_type":"code","source":"!g++ -Wall exception.cpp","metadata":{},"execution_count":17,"outputs":[],"id":"131dd4f5"},{"cell_type":"code","source":"!./a.out","metadata":{},"execution_count":18,"outputs":[{"name":"stdout","text":"vec[ 0 ] = 2\nvec[ 1 ] = 2\nvec[ 2 ] = 2\nvec[ 3 ] = 2\nvec[ 4 ] = 2\nvec[ 5 ] = 2\nvec[ 6 ] = 2\nvec[ 7 ] = 2\nvec[ 8 ] = 2\nvec[ 9 ] = 2\nterminate called after throwing an instance of 'std::out_of_range'\n what(): vector::_M_range_check: __n (which is 10) >= this->size() (which is 10)\nAborted\n","output_type":"stream"}],"id":"e6ce23bd"},{"cell_type":"markdown","source":"### Iterators and range-based access","metadata":{},"id":"8e0987ec"},{"cell_type":"markdown","source":"Our **std::vector** belongs to the class of sequence containers, i.e. the ordering of entries $e_k$ is important\n\n$$ \\Big( e_1, e_2, \\ldots, e_n \\Big)$$\n\nand the same value $v$ can show up at multiple positions ($e_i = v = e_j$ with $i\\neq j$).\n\nAnother type of sequence container is the **linked list**. The **std::list** implements a doubly-linked list, i.e. each entry/element is composed of three parts\n\n$$ \\text{entry} = \\Big( \\text{payload}, \\text{prev}, \\text{next} \\Big)$$\n\nwhere\n * payload = value of the entry\n * prev = information on where to find the predecessor of this entry\n * next = information on where to find the successor of this entry\n \nContrary to a vector, a list does not offer index-based access to its elements. Instead we can do list-traversal.\n\nNow consider the following piece of code:","metadata":{},"id":"80b33721"},{"cell_type":"code","source":"#include <iostream>\n#include <vector>\n\nint main() {\n using Container = std::vector< int >;\n Container box;\n\n const int n = 5;\n \n // fill the box with odd numbers\n for( int k = 0; k < n; k++ ) {\n box.push_back( 2*k+1 );\n }\n \n // print its contents\n for( int k = 0; k < n; k++ ) {\n std::cout << \"box(\" << k << \") = \" << box[k] << std::endl;\n }\n}\n\nmain();","metadata":{},"execution_count":19,"outputs":[{"name":"stdout","text":"box(0) = 1\nbox(1) = 3\nbox(2) = 5\nbox(3) = 7\nbox(4) = 9\n","output_type":"stream"}],"id":"eee3b34a"},{"cell_type":"markdown","source":"For what we are doing in the program the type of underlying container does not really matter. \nHow about replacing vector with list for a change?","metadata":{},"id":"33c83653"},{"cell_type":"code","source":"#include <iostream>\n#include <list> // okay, need to adapt the include\n\nint main() {\n using Container = std::list< int >; // need to alter this statement\n Container box;\n\n const int n = 5;\n \n // fill the box with odd numbers\n for( int k = 0; k < n; k++ ) {\n box.push_back( 2*k+1 );\n }\n \n // print its contents\n for( int k = 0; k < n; k++ ) {\n std::cout << \"box(\" << k << \") = \" << box[k] << std::endl;\n }\n}\n\nmain();","metadata":{},"execution_count":20,"outputs":[{"name":"stderr","text":"input_line_27:13:50: error: type 'Container' (aka 'list<int>') does not provide a subscript operator\n std::cout << \"box(\" << k << \") = \" << box[k] << std::endl;\n ~~~^~\n","output_type":"stream"},{"ename":"Interpreter Error","evalue":"","traceback":["Interpreter Error: "],"output_type":"error"}],"id":"d2806a4f"},{"cell_type":"markdown","source":"***\nNow that is, where **iterators** come into play. They abstract away the details of the underlying container.","metadata":{},"id":"40d19e9a"},{"cell_type":"code","source":"#include <iostream>\n#include <vector>\n\nint main() {\n using Container = std::vector< int >;\n Container box;\n\n const int n = 5;\n \n // fill the box with odd numbers\n for( int k = 0; k < n; k++ ) {\n box.push_back( 2*k+1 );\n }\n \n // use an iterator-based loop\n int k = 0;\n for( Container::iterator it = box.begin(); it != box.end(); it++ ) {\n std::cout << \"box(\" << k << \") = \" << *it << std::endl;\n k++;\n }\n}","metadata":{},"execution_count":21,"outputs":[],"id":"4a6ba619"},{"cell_type":"code","source":"main();","metadata":{},"execution_count":22,"outputs":[{"name":"stdout","text":"box(0) = 1\nbox(1) = 3\nbox(2) = 5\nbox(3) = 7\nbox(4) = 9\n","output_type":"stream"}],"id":"907c51a5"},{"cell_type":"markdown","source":"**Remarks:**\n* The iterator is no pointer, but 'feels' like one, as it overloads the dereferencing operator ```*```.\n* ```box.begin()``` points to the first entry in the container.\n* ```box.end()``` points **after** the last entry in the container. Thus, dereferencing it would be illegal.\n* Do not test for ```it < box.end()```, but for (in)equality only.","metadata":{},"id":"5b9766a5"},{"cell_type":"markdown","source":"Since our loop is using an iterator now, we can with two small modifications switch from a **std::vector** to a **std::list**.","metadata":{},"id":"ba14f11e"},{"cell_type":"code","source":"#include <iostream>\n#include <list> // mod #1\n\nint main() {\n using Container = std::list< int >; // mod #2\n Container box;\n\n const int n = 5;\n \n // fill the box with odd numbers\n for( int k = 0; k < n; k++ ) {\n box.push_back( 2*k+1 );\n }\n \n // use an iterator-based loop\n int k = 0;\n for( Container::iterator it = box.begin(); it != box.end(); it++ ) {\n std::cout << \"box(\" << k << \") = \" << *it << std::endl;\n k++;\n }\n}","metadata":{},"execution_count":23,"outputs":[],"id":"9f8daf26"},{"cell_type":"code","source":"main();","metadata":{},"execution_count":24,"outputs":[{"name":"stdout","text":"box(0) = 1\nbox(1) = 3\nbox(2) = 5\nbox(3) = 7\nbox(4) = 9\n","output_type":"stream"}],"id":"53211059"},{"cell_type":"markdown","source":"#### 'Reverse mode' \nUp above we have used a forward iterator. However, we can also ask for a reverse iterator to go through the container from end to start (if the container supports it).","metadata":{},"id":"44f9f4e2"},{"cell_type":"code","source":"#include <iostream>\n\nstd::vector< short > vec;\n\nvec.push_back( 1 );\nvec.push_back( 2 );\nvec.push_back( 3 );\n\nfor( std::vector< short >::reverse_iterator it = vec.rbegin(); it != vec.rend(); it++ ) {\n std::cout << *it << std::endl;\n}","metadata":{},"execution_count":25,"outputs":[{"name":"stdout","text":"3\n2\n1\n","output_type":"stream"}],"id":"158950d1"},{"cell_type":"markdown","source":"#### 'Const mode'\nWhen we do not intend to change the element in the loop body, we can also employ a ```const_iterator```.","metadata":{},"id":"96bed53d"},{"cell_type":"code","source":"#include <iostream>\nstd::list< double > lc;\nusing cIter = std::list< double >::const_iterator;\nlc.push_back( 4.0 );\nlc.push_back( 5.0 );\nlc.push_back( 6.0 );\nfor( cIter it = lc.begin(); it != lc.end(); it++ ) {\n std::cout << *it << std::endl;\n}","metadata":{},"execution_count":26,"outputs":[{"name":"stdout","text":"4\n5\n6\n","output_type":"stream"}],"id":"44a9bf28"},{"cell_type":"markdown","source":"#### 'Free Functions'\nC++ introduced free-function forms of ```begin()``` and ```end()``` which we can use alternatively to the member functions above:","metadata":{},"id":"da22b078"},{"cell_type":"code","source":"for( cIter it = begin( lc ), e = end( lc ); it != e; it++ ) {\n std::cout << *it << std::endl;\n}","metadata":{},"execution_count":27,"outputs":[{"name":"stdout","text":"4\n5\n6\n","output_type":"stream"}],"id":"ceb3de09"},{"cell_type":"markdown","source":"Why did we introduce ```e```? Is it necessary?","metadata":{},"id":"14632b71"},{"cell_type":"markdown","source":"### Range-based access\nC++ nowadays also supports an even simpler way to encode a loop over a container:","metadata":{},"id":"c731df99"},{"cell_type":"code","source":"#include <iostream>\n#include <list>\n\nint main() {\n using Container = std::list< int >;\n Container box;\n\n const int n = 5;\n \n // fill the box with odd numbers\n for( int k = 0; k < n; k++ ) {\n box.push_back( 2*k+1 );\n }\n \n // change that to even, using a range-based loop\n for( int& entry: box ) {\n entry -= 1;\n }\n \n // print using a range-based loop (in this case we can do w/o a ref)\n int k = 0;\n for( int value: box ) {\n std::cout << \"box(\" << k << \") = \" << value << std::endl;\n k++;\n }\n}","metadata":{},"execution_count":28,"outputs":[],"id":"2497a826"},{"cell_type":"code","source":"main();","metadata":{},"execution_count":29,"outputs":[{"name":"stdout","text":"box(0) = 0\nbox(1) = 2\nbox(2) = 4\nbox(3) = 6\nbox(4) = 8\n","output_type":"stream"}],"id":"a8918a0e"},{"cell_type":"markdown","source":"Note that the last loop version involved a copy operation!","metadata":{},"id":"f4d8fd5e"},{"cell_type":"code","source":"std::cout << \"-> Put objects into container\" << std::endl;\nstd::vector< Dummy > vec;\nvec.reserve(2);\nvec.emplace_back( \"one\" );\nvec.emplace_back( \"two\" );\n\nstd::cout << \"\\n-> Run a loop\" << std::endl;\nfor( Dummy x: vec ) {\n std::cout << \"* \" << x.getName() << std::endl;\n}","metadata":{},"execution_count":30,"outputs":[{"name":"stdout","text":"-> Put objects into container\nObject 'one' was created\nObject 'two' was created\n\n-> Run a loop\nObject 'one_copy' was created\n* one_copy\nObject 'one_copy' was destroyed\nObject 'two_copy' was created\n* two_copy\nObject 'two_copy' was destroyed\n","output_type":"stream"}],"id":"d1c64b51"},{"cell_type":"markdown","source":"Now let's try the same, but use a reference","metadata":{},"id":"eaf4c60a-ce19-410a-a81e-163d292de58d"},{"cell_type":"code","source":"std::cout << \"-> Put objects into container\" << std::endl;\nstd::vector< Dummy > vec;\nvec.reserve(2);\nvec.emplace_back( \"one\" );\nvec.emplace_back( \"two\" );\n\nstd::cout << \"\\n-> Run a loop\" << std::endl;\nfor( Dummy& x: vec ) {\n std::cout << \"* \" << x.getName() << std::endl;\n}","metadata":{},"execution_count":31,"outputs":[{"name":"stdout","text":"-> Put objects into container\nObject 'one' was created\nObject 'two' was created\n\n-> Run a loop\n* one\n* two\n","output_type":"stream"}],"id":"28b2e3fb"},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[],"id":"c4afab4e-a800-4176-a26f-15d11ffd3964"}]}
\ No newline at end of file
%% Cell type:markdown id:bac1d62f tags:
# STL - Part 2 : More Containers & Algorithms
%% Cell type:markdown id:efdc5616 tags:
#### Sets
%% Cell type:code id:c2848a05 tags:
``` C++14
#include <iostream>
#include <set>
std::set< int > s = {1, 7, 4, 8, 2}; // create a set container and fill it
s.insert(5); // insert another element
s.insert(4);
// check how often an element is inside the set
for ( int i = 0; i < 8; ++i ) {
std::cout << i << " appears " << s.count( i ) << " times\n";
}
```
%% Output
0 appears 0 times
1 appears 1 times
2 appears 1 times
3 appears 0 times
4 appears 1 times
5 appears 1 times
6 appears 0 times
7 appears 1 times
%% Cell type:markdown id:44573775 tags:
**Remarks:**
- Although we inserted **4** twice, it is only stored once in the set data structure. Just like a mathematical set a ```std::set``` can contain a specific object only once.
- Hence, ```count()``` will always return either 0 or 1.
- This shows that ```std::set``` is no sequence container.
- Instead it stores its elements in a **sorted** fashion. This guarantees logarithmic complexity for element access.
%% Cell type:markdown id:a415d236 tags:
Of course we can store various kinds of objects in a set ...
%% Cell type:code id:2213e98e tags:
``` C++14
struct myPair {
myPair( int fir, int sec ) : fir_( fir ), sec_( sec ) {};
int fir_;
int sec_;
};
std::set< myPair > pairs;
std::set< myPair > pairs;
```
%% Cell type:markdown id:725387bc tags:
... but there is a caveat. Check what happens, when we try to insert an object of type ```myPair``` into the set:
%% Cell type:code id:51fd5597 tags:
``` C++14
pairs.insert( myPair(1,2) );
```
%% Output
In file included from input_line_5:1:
In file included from /srv/conda/envs/notebook/include/xeus/xinterpreter.hpp:13:
In file included from /srv/conda/envs/notebook/bin/../lib/gcc/../../x86_64-conda-linux-gnu/include/c++/9.4.0/functional:49:
/srv/conda/envs/notebook/bin/../lib/gcc/../../x86_64-conda-linux-gnu/include/c++/9.4.0/bits/stl_function.h:386:20: error: invalid operands to binary expression ('const __cling_N54::myPair' and 'const __cling_N54::myPair')
{ return __x < __y; }
~~~ ^ ~~~
/srv/conda/envs/notebook/bin/../lib/gcc/../../x86_64-conda-linux-gnu/include/c++/9.4.0/bits/stl_tree.h:2100:13: note: in instantiation of member function 'std::less<__cling_N54::myPair>::operator()' requested here
__comp = _M_impl._M_key_compare(__k, _S_key(__x));
^
/srv/conda/envs/notebook/bin/../lib/gcc/../../x86_64-conda-linux-gnu/include/c++/9.4.0/bits/stl_tree.h:2153:4: note: in instantiation of member function 'std::_Rb_tree<__cling_N54::myPair, __cling_N54::myPair, std::_Identity<__cling_N54::myPair>, std::less<__cling_N54::myPair>, std::allocator<__cling_N54::myPair> >::_M_get_insert_unique_pos' requested here
= _M_get_insert_unique_pos(_KeyOfValue()(__v));
^
/srv/conda/envs/notebook/bin/../lib/gcc/../../x86_64-conda-linux-gnu/include/c++/9.4.0/bits/stl_set.h:521:9: note: in instantiation of function template specialization 'std::_Rb_tree<__cling_N54::myPair, __cling_N54::myPair, std::_Identity<__cling_N54::myPair>, std::less<__cling_N54::myPair>, std::allocator<__cling_N54::myPair> >::_M_insert_unique<__cling_N54::myPair>' requested here
_M_t._M_insert_unique(std::move(__x));
^
input_line_12:2:8: note: in instantiation of member function 'std::set<__cling_N54::myPair, std::less<__cling_N54::myPair>, std::allocator<__cling_N54::myPair> >::insert' requested here
pairs.insert( myPair(1,2) );
^
In file included from input_line_5:1:
In file included from /srv/conda/envs/notebook/include/xeus/xinterpreter.hpp:17:
In file included from /srv/conda/envs/notebook/include/xeus/xcomm.hpp:15:
In file included from /srv/conda/envs/notebook/bin/../lib/gcc/../../x86_64-conda-linux-gnu/include/c++/9.4.0/map:60:
/srv/conda/envs/notebook/bin/../lib/gcc/../../x86_64-conda-linux-gnu/include/c++/9.4.0/bits/stl_tree.h:777:2: error: static_assert failed "comparison object must be invocable with two arguments of key type"
static_assert(__is_invocable<_Compare&, const _Key&, const _Key&>{},
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/srv/conda/envs/notebook/bin/../lib/gcc/../../x86_64-conda-linux-gnu/include/c++/9.4.0/bits/stl_tree.h:2100:41: note: in instantiation of member function 'std::_Rb_tree<__cling_N54::myPair, __cling_N54::myPair, std::_Identity<__cling_N54::myPair>, std::less<__cling_N54::myPair>, std::allocator<__cling_N54::myPair> >::_S_key' requested here
__comp = _M_impl._M_key_compare(__k, _S_key(__x));
^
/srv/conda/envs/notebook/bin/../lib/gcc/../../x86_64-conda-linux-gnu/include/c++/9.4.0/bits/stl_tree.h:2153:4: note: in instantiation of member function 'std::_Rb_tree<__cling_N54::myPair, __cling_N54::myPair, std::_Identity<__cling_N54::myPair>, std::less<__cling_N54::myPair>, std::allocator<__cling_N54::myPair> >::_M_get_insert_unique_pos' requested here
= _M_get_insert_unique_pos(_KeyOfValue()(__v));
^
/srv/conda/envs/notebook/bin/../lib/gcc/../../x86_64-conda-linux-gnu/include/c++/9.4.0/bits/stl_set.h:521:9: note: in instantiation of function template specialization 'std::_Rb_tree<__cling_N54::myPair, __cling_N54::myPair, std::_Identity<__cling_N54::myPair>, std::less<__cling_N54::myPair>, std::allocator<__cling_N54::myPair> >::_M_insert_unique<__cling_N54::myPair>' requested here
_M_t._M_insert_unique(std::move(__x));
^
input_line_12:2:8: note: in instantiation of member function 'std::set<__cling_N54::myPair, std::less<__cling_N54::myPair>, std::allocator<__cling_N54::myPair> >::insert' requested here
pairs.insert( myPair(1,2) );
^
Interpreter Error:
%% Cell type:markdown id:2ee8f731 tags:
**What went wrong?**
As the set stores its elements in a **sorted** fashion it needs some way to **order** them.
By default that standard ```<``` relation is used.
By default the standard ```<``` relation is used.
That does not work for our ```pair``` data type.
That does not work for our ```myPair``` data type.
We need to define our own comparison operator for the class.
There are plenty ways of doing that. Below we will use a free function for it.
%% Cell type:code id:e8c29594 tags:
``` C++14
%%file demo.cpp
#include <iostream>
#include <set>
struct myPair {
myPair( int fir, int sec ) : fir_( fir ), sec_( sec ) {};
int fir_;
int sec_;
};
// our comparison function will return true, if the sum of the squares
// of the values in left is smaller than that in right
// of the values in left is smaller than that in right (problem with tie-breaking?)
bool cmp( const myPair& left, const myPair& right ) {
int val1 = left.fir_ * left.fir_ + left.sec_ * left.sec_;
int val2 = right.fir_ * right.fir_ + right.sec_ * right.sec_;
return val1 < val2;
}
int main() {
// create two objects
myPair p1( 1, 2 );
myPair p2( 3, 4 );
// compare them
std::cout << "p1 < p2 is " << cmp( p1, p2 ) << std::endl;
std::cout << "p2 < p1 is " << cmp( p2, p1 ) << std::endl;
// a free function in C/C++ is represented by the address of its machine code in memory;
// thus, the type of argument is a function pointer;
// we need to pass that info as a second template argument;
// decltype returns the type of an expression
std::set< myPair, decltype( cmp )* > pairs ( cmp );
std::set< myPair, decltype( cmp )* > pairs( cmp );
// alternatively we could do it ourselves; note that the (*) is important!
// but that way is not very C++-ish
// std::set< myPair, bool (*)( const myPair&, const myPair& ) > pairs( cmp );
// now insertion will work
pairs.insert( p1 );
pairs.insert( p2 );
std::cout << "insertion successful" << std::endl;
}
```
%% Output
Overwriting demo.cpp
Writing demo.cpp
%% Cell type:code id:0a0207dd tags:
``` C++14
!g++ demo.cpp
```
%% Cell type:code id:68e1df4b tags:
``` C++14
!./a.out
```
%% Output
p1 < p2 is 1
p2 < p1 is 0
insertion successful
p1 < p2 is 1
p2 < p1 is 0
insertion successful
%% Cell type:markdown id:f8b3109b tags:
**Note:**
**Note:**
The STL offers a variant **```std::unordered_set```** that stores its elements in an unsorted fashion. However, that would not solve our problem. Instead of a **comparison** we then would need to implement a **hashing function**.
%% Cell type:markdown id:59ab93ca tags:
***
The way we added the new element in the example, i.e. via
```pairs.insert( myPair(1,2) );```
again induced a copy operation. As with ```vector``` and ```list``` we could use ```emplace()``` instead.
For demonstration purposes we make the constructor of ```myPair``` verbose.
%% Cell type:code id:bdda6c33 tags:
``` C++14
%%file demo.cpp
#include <iostream>
#include <set>
struct myPair {
myPair( int fir, int sec ) : fir_( fir ), sec_( sec ) {
std::cout << "(" << fir_ << "," << sec_ << ") constructed" << std::endl;
};
int fir_;
int sec_;
};
// our comparison function: will return true, if the square of the values
// in left is smaller than that in right
bool cmp( const myPair& left, const myPair& right ) {
int val1 = left.fir_ * left.fir_ + left.sec_ * left.sec_;
int val2 = right.fir_ * right.fir_ + right.sec_ * right.sec_;
return val1 < val2;
}
int main() {
std::set< myPair, decltype( cmp )* > pairs( cmp );
pairs.emplace( 1, 2 );
pairs.emplace( 3, 4 );
pairs.emplace( 1, 2 );
}
```
%% Output
Overwriting demo.cpp
%% Cell type:code id:8099d1a4 tags:
``` C++14
!g++ demo.cpp
```
%% Cell type:code id:5d491b27 tags:
``` C++14
!./a.out
```
%% Output
(1,2) constructed
(3,4) constructed
(1,2) constructed
%% Cell type:markdown id:8e256275 tags:
#### Associative Containers
Quoting Wikipedia:
> In computer science, an **associative array**, **map**, **symbol table**, or **dictionary** is an abstract data type composed of a collection of **(key, value) pairs**, such that each possible key appears at most once in the collection.
>
> Operations associated with this data type allow to:
>
>- add a pair to the collection;
>- remove a pair from the collection;
>- modify an existing pair;
>- lookup a value associated with a particular key.
The STL also offers associative containers. An example is ```std::map```.
%% Cell type:markdown id:fb3ef7bb tags:
Return to our example with the traffic light. Assume that we want to print the currect state of a specific traffic light. How to do that?
Well, using an enumeration for the state we can use the classical switch-case-construct:
%% Cell type:code id:59839aea tags:
``` C++14
%%file ampel.cpp
#include <iostream>
typedef enum{ GREEN, YELLOW, RED, YELLOW_RED } trafficLight;
void ampel( trafficLight tf ) {
switch( tf ) {
case GREEN:
std::cout << "Lights are green" << std::endl;
break;
case YELLOW:
std::cout << "Lights are yellow" << std::endl;
break;
case RED:
std::cout << "Lights are red" << std::endl;
break;
case YELLOW_RED:
std::cout << "Lights are yellow-red" << std::endl;
break;
}
}
int main() {
trafficLight tf = GREEN;
ampel( tf );
}
```
%% Output
Writing ampel.cpp
%% Cell type:code id:76c8886d tags:
``` C++14
!g++ ampel.cpp
```
%% Cell type:code id:731e7c94 tags:
``` C++14
!./a.out
```
%% Output
Lights are green
%% Cell type:markdown id:08b68b2b tags:
Another (neater) way is to associate the state with a corresponding string
%% Cell type:code id:8fe484bf tags:
``` C++14
%%file ampel.cpp
#include <iostream>
#include <map>
typedef enum{ GREEN, YELLOW, RED, YELLOW_RED } trafficLight;
int main() {
std::map< trafficLight, std::string > tf2str =
{ { GREEN, "green" }, { YELLOW, "yellow" }, { RED, "red" },
{ YELLOW_RED, "yellow-red" } };
trafficLight tf = GREEN;
std::cout << "Lights are " << tf2str[ tf ] << std::endl;
}
```
%% Output
Overwriting ampel.cpp
%% Cell type:code id:74251bd8 tags:
``` C++14
!g++ ampel.cpp
```
%% Cell type:code id:5b919ca3 tags:
``` C++14
!./a.out
```
%% Output
Lights are green
%% Cell type:markdown id:b1718cdd tags:
Another example, demonstrating additional features of ```std::map``` (converted from Gottschling, *Forschung mit modernem C++*)
%% Cell type:code id:52f631d4 tags:
``` C++14
%%file constants.cpp
#include <iostream>
#include <string>
#include <map>
#include <cmath>
int main() {
// create a map and fill it with some (key,value) pairs
std::map< std::string, double > constants = { {"e", std::exp(1.0)},
{"pi", 4.0*std::atan(1.0) }, {"h", 6.6e-34} };
// subscript operator is overloaded to allow access via a key
std::cout << "Value of Planck constant is " << constants[ "h" ] << '\n';
constants[ "c" ] = 299792458;
// Hmmm, what happens here? No key "k" exists so far!
std::cout << "Value of Coulomb constant is " << constants[ "k" ] << '\n';
// find() allows to check for existance of a key; returns an iterator
// to the pair, if it is found
std::cout << "Value of pi is " << constants.find( "pi" )->second << '\n';
// if not it returns end
auto it_phi = constants.find( "phi" );
if ( it_phi != constants.end() ) {
std::cout << "The golden ratio is " << it_phi->second << '\n';
}
// can use at(), if we know the pair to exists, returns value for key
// will throw an "out_of_range" exception, if we were wrong
std::cout << "Value of Euler constant is " << constants.at( "e" ) << "\n\n";
// range-based loop
for ( auto& c: constants ) {
std::cout << "Value of " << c.first << " is " << c.second << '\n';
}
}
```
%% Output
Overwriting constants.cpp
%% Cell type:code id:aaba61ba tags:
``` C++14
!g++ constants.cpp
```
%% Cell type:code id:3f9362c9 tags:
``` C++14
!./a.out
```
%% Output
Value of Planck constant is 6.6e-34
Value of Coulomb constant is 0
Value of pi is 3.14159
Value of Euler constant is 2.71828
Value of c is 2.99792e+08
Value of e is 2.71828
Value of h is 6.6e-34
Value of k is 0
Value of pi is 3.14159
%% Cell type:markdown id:a7e8490c tags:
**Note:**
**Note:**
As with ```set``` there is also a variant of ```map``` that uses hashing instead of ordered storage and is called ```unordered_map```.
%% Cell type:markdown id:2032e7f2 tags:
#### Algorithm
The STL supports also various algorithms that work on the entries in a container. The advantage again is that these save us implementation and will (potentially) work for various kinds of containers.
We will briefly look at two examples.
%% Cell type:markdown id:4c239bfa tags:
**Example 1:** Remove double entries from a sequence
%% Cell type:code id:aed454ce tags:
``` C++14
%%file uniq.cpp
#include <algorithm>
#include <vector>
#include <iostream>
using Container = std::vector<int>;
void print( const Container& box, const std::string& note ) {
std::cout << "( ";
for( int w: box ) std::cout << w << ' ';
std::cout << ") " << note << std::endl;
}
int main() {
Container box{ 3, 5, 2, 4, 1, 2, 1 };
print( box, "<- unsorted" );
sort( box.begin(), box.end() );
print( box, "<- sorted" );
Container::iterator last = unique( begin(box), end(box) );
print( box, "<- happened in-place" );
box.resize( distance( box.begin(), last ) );
print( box, " <- truncated to new length" );
}
```
%% Output
Writing uniq.cpp
%% Cell type:code id:57c92377 tags:
``` C++14
!g++ uniq.cpp
```
%% Cell type:code id:b190c1c6 tags:
``` C++14
!./a.out
```
%% Output
( 3 5 2 4 1 2 1 ) <- unsorted
( 1 1 2 2 3 4 5 ) <- sorted
( 1 2 3 4 5 4 5 ) <- happened in-place
( 1 2 3 4 5 ) <- truncated to new length
%% Cell type:markdown id:e9140c15 tags:
**Note:**
Replacing ```vector``` by ```list``` via
using Container = std::vector<int>;
using Container = std::list<int>;
will not work in this example. ```std::sort()``` is implemented to work with a **random access iterator**, which list does not provide. Instead it has its own ```list::sort()```
and ```list::unique()``` member functions.
%% Cell type:markdown id:7856aa32 tags:
***
**Example 2:** Performing reductions on an array
The STL also provides some algorithms for performing numeric operations. For these we need to include ```<numeric>```.
%% Cell type:code id:bed09ecd tags:
``` C++14
%%file accumulate.cpp
#include <numeric>
#include <vector>
#include <iostream>
int alternate( int a, int b ) {
static bool odd = false; // static preserves value of local variable
if( odd ) {
odd = false;
return a-b;
}
else {
odd = true;
return a+b;
}
}
int main() {
std::vector<int> v{1, 2, 3, 4, 5};
int gs = std::accumulate( v.begin(), v.end(), 0 );
std::cout << "sum is " << gs << std::endl;
int check = std::accumulate( v.begin(), v.end(), -15 );
if( check == 0 ) std::cout << "checks out" << std::endl;
int prod = std::accumulate( v.begin(), v.end(), 1, std::multiplies<int>() );
std::cout << "5! = " << prod << std::endl;
int alt = std::accumulate( v.begin(), v.end(), 0, alternate );
std::cout << "Alternating sum gives " << alt << std::endl;
// 0 + 1 - 2 + 3 - 4 + 5
}
```
%% Output
Writing accumulate.cpp
%% Cell type:code id:16a6d2e8 tags:
``` C++14
!g++ accumulate.cpp
```
%% Cell type:code id:015eb80f tags:
``` C++14
!./a.out
```
%% Output
sum is 15
checks out
5! = 120
Alternating sum gives 3
%% Cell type:code id:1efec163 tags:
``` C++14
```
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment