From f78eb54166a5ee667418fd1ae98818a93e0fffb3 Mon Sep 17 00:00:00 2001 From: Marcus Mohr <marcus.mohr@lmu.de> Date: Tue, 30 Nov 2021 12:30:14 +0100 Subject: [PATCH] Adds details on std::map and example for using STL algorithms --- notebooks/07_STL_part2.ipynb | 489 +++++++++++++++++++++++++++++------ 1 file changed, 404 insertions(+), 85 deletions(-) diff --git a/notebooks/07_STL_part2.ipynb b/notebooks/07_STL_part2.ipynb index 8ab29aa..4d6b127 100644 --- a/notebooks/07_STL_part2.ipynb +++ b/notebooks/07_STL_part2.ipynb @@ -18,25 +18,10 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "c2848a05", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0 appears 0 times\n", - "1 appears 1 times\n", - "2 appears 1 times\n", - "3 appears 0 times\n", - "4 appears 1 times\n", - "5 appears 1 times\n", - "6 appears 0 times\n", - "7 appears 1 times\n" - ] - } - ], + "outputs": [], "source": [ "#include <iostream>\n", "#include <set>\n", @@ -58,7 +43,7 @@ "metadata": {}, "source": [ "**Remarks:**\n", - "- Although we inserted **4** twice it is only stored once in the set datastructure. Just like a mathematical set a ```std::set``` can contain a specific object only once.\n", + "- 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.\n", "- Hence, ```count()``` will always return either 0 or 1.\n", "- This shows that ```std::set``` is no sequence container.\n", "- Instead it stores its elements in a **sorted** fashion. This guarantees logarithmic complexity for element access." @@ -74,7 +59,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "2213e98e", "metadata": {}, "outputs": [], @@ -97,9 +82,11 @@ ] }, { - "cell_type": "markdown", - "id": "e62cf3d6", + "cell_type": "code", + "execution_count": null, + "id": "a84d9620", "metadata": {}, + "outputs": [], "source": [ "pairs.insert( myPair(1,2) );" ] @@ -109,32 +96,24 @@ "id": "2ee8f731", "metadata": {}, "source": [ - "What went wrong?\n", + "**What went wrong?**\n", "\n", - "As the set stores its elements in an **sorted** fashion it needs some way to **order** them.\n", - "By default a ```<``` relation is used.\n", + "As the set stores its elements in a **sorted** fashion it needs some way to **order** them.\n", + "By default that standard ```<``` relation is used.\n", "\n", - "That does not work for our ```pair``` datatype.\n", + "That does not work for our ```pair``` data type.\n", "\n", - "Need to define a comparison operator for the class.\n", + "We need to define our own comparison operator for the class.\n", "\n", "There are plenty ways of doing that. Below we will use a free function for it." ] }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "id": "e8c29594", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Overwriting demo.cpp\n" - ] - } - ], + "outputs": [], "source": [ "%%file demo.cpp\n", "\n", @@ -147,8 +126,8 @@ " int sec_;\n", "};\n", "\n", - "// our comparison function: will return true, if the square of the values\n", - "// in left is smaller than that in right\n", + "// our comparison function will return true, if the sum of the squares\n", + "// of the values in left is smaller than that in right\n", "bool cmp( const myPair& left, const myPair& right ) {\n", " int val1 = left.fir_ * left.fir_ + left.sec_ * left.sec_;\n", " int val2 = right.fir_ * right.fir_ + right.sec_ * right.sec_;\n", @@ -166,7 +145,8 @@ " std::cout << \"p2 < p1 is \" << cmp( p2, p1 ) << std::endl;\n", "\n", " // a free function in C/C++ is represented by the address of its machine code in memory;\n", - " // we can pass that info as a second template argument;\n", + " // thus, the type of argument is a function pointer;\n", + " // we need to pass that info as a second template argument;\n", " // decltype returns the type of an expression\n", " std::set< myPair, decltype( cmp )* > pairs ( cmp );\n", "\n", @@ -174,13 +154,13 @@ " pairs.insert( p1 );\n", " pairs.insert( p2 );\n", "\n", - " std::cout << \"done with set demo\" << std::endl;\n", + " std::cout << \"insertion successful\" << std::endl;\n", "}" ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "id": "0a0207dd", "metadata": {}, "outputs": [], @@ -190,20 +170,10 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "id": "68e1df4b", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "p1 < p2 is 1\n", - "p2 < p1 is 0\n", - "done with set demo\n" - ] - } - ], + "outputs": [], "source": [ "!./a.out" ] @@ -227,23 +197,17 @@ "\n", "```pairs.insert( myPair(1,2) );```\n", "\n", - "again induced a copy operation. As with ```vector``` and ```list``` we could use ```emplace()``` instead." + "again induced a copy operation. As with ```vector``` and ```list``` we could use ```emplace()``` instead.\n", + "\n", + "For demonstration purposes we make the constructor of ```myPair``` verbose." ] }, { "cell_type": "code", - "execution_count": 37, + "execution_count": null, "id": "bdda6c33", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Overwriting demo.cpp\n" - ] - } - ], + "outputs": [], "source": [ "%%file demo.cpp\n", "\n", @@ -279,7 +243,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": null, "id": "8099d1a4", "metadata": {}, "outputs": [], @@ -289,20 +253,10 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": null, "id": "5d491b27", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(1,2) constructed\n", - "(3,4) constructed\n", - "(1,2) constructed\n" - ] - } - ], + "outputs": [], "source": [ "!./a.out" ] @@ -314,9 +268,9 @@ "source": [ "#### Associative Containers\n", "\n", - "> Quoting Wikipedia:\n", - ">\n", - "> 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. Not to be confused with Associative Processors\n", + "Quoting Wikipedia:\n", + "\n", + "> 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.\n", ">\n", "> Operations associated with this data type allow to:\n", ">\n", @@ -325,22 +279,387 @@ ">- modify an existing pair;\n", ">- lookup a value associated with a particular key.\n", "\n", - " \n", - " " + "The STL also offers associative containers. An example is ```std::map```." + ] + }, + { + "cell_type": "markdown", + "id": "a942d3bd", + "metadata": {}, + "source": [ + "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?\n", + "\n", + "Well, using an enumeration for the state we can use the classical switch-case-construct:" ] }, { "cell_type": "code", "execution_count": null, - "id": "f6269079", + "id": "59839aea", "metadata": {}, "outputs": [], - "source": [] + "source": [ + "%%file ampel.cpp\n", + "\n", + "#include <iostream>\n", + "\n", + "typedef enum{ GREEN, YELLOW, RED, YELLOW_RED } trafficLight;\n", + "\n", + "void ampel( trafficLight tf ) {\n", + "\n", + " switch( tf ) {\n", + " case GREEN:\n", + " std::cout << \"Lights are green\" << std::endl;\n", + " break;\n", + " case YELLOW:\n", + " std::cout << \"Lights are yellow\" << std::endl;\n", + " break;\n", + " case RED:\n", + " std::cout << \"Lights are red\" << std::endl;\n", + " break;\n", + " case YELLOW_RED:\n", + " std::cout << \"Lights are yellow-red\" << std::endl;\n", + " break;\n", + " }\n", + "\n", + "}\n", + "\n", + "int main() {\n", + " trafficLight tf = GREEN;\n", + " ampel( tf );\n", + "}" + ] }, { "cell_type": "code", "execution_count": null, - "id": "59839aea", + "id": "5847cc0d", + "metadata": {}, + "outputs": [], + "source": [ + "!g++ ampel.cpp" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6a1163c9", + "metadata": {}, + "outputs": [], + "source": [ + "!./a.out" + ] + }, + { + "cell_type": "markdown", + "id": "e5b29324", + "metadata": {}, + "source": [ + "Another (neater) way is to associate the state with a corresponding string" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e020ca91", + "metadata": {}, + "outputs": [], + "source": [ + "%%file ampel.cpp\n", + "\n", + "#include <iostream>\n", + "#include <map>\n", + "\n", + "typedef enum{ GREEN, YELLOW, RED, YELLOW_RED } trafficLight;\n", + "\n", + "int main() {\n", + "\n", + " std::map< trafficLight, std::string > tf2str =\n", + " { { GREEN, \"green\" }, { YELLOW, \"yellow\" }, { RED, \"red\" },\n", + " { YELLOW_RED, \"yellow-red\" } };\n", + "\n", + " trafficLight tf = GREEN;\n", + "\n", + " std::cout << \"Lights are \" << tf2str[ tf ] << std::endl;\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "03890f8b", + "metadata": {}, + "outputs": [], + "source": [ + "!g++ ampel.cpp" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bfd20e03", + "metadata": {}, + "outputs": [], + "source": [ + "!./a.out" + ] + }, + { + "cell_type": "markdown", + "id": "2919cfda", + "metadata": {}, + "source": [ + "Another example, demonstrating additional features of ```std::map``` (converted from Gottschling, *Forschung mit modernem C++*)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e46017ba", + "metadata": {}, + "outputs": [], + "source": [ + "%%file constants.cpp\n", + "\n", + "#include <iostream>\n", + "#include <string>\n", + "#include <map>\n", + "#include <cmath>\n", + "\n", + "int main() {\n", + "\n", + " // create a map and fill it with some (key,value) pairs\n", + " std::map< std::string, double > constants = { {\"e\", std::exp(1.0)},\n", + " {\"pi\", 4.0*std::atan(1.0) }, {\"h\", 6.6e-34} };\n", + "\n", + " // subscript operator is overloaded to allow access via a key\n", + " std::cout << \"Value of Planck constant is \" << constants[ \"h\" ] << '\\n';\n", + " constants[ \"c\" ] = 299792458;\n", + "\n", + " // Hmmm, what happens here? No key \"k\" exists so far!\n", + " std::cout << \"Value of Coulomb constant is \" << constants[ \"k\" ] << '\\n';\n", + "\n", + " // find() allows to check for existance of a key; returns an iterator\n", + " // to the pair, if it is found\n", + " std::cout << \"Value of pi is \" << constants.find( \"pi\" )->second << '\\n';\n", + "\n", + " // if not it returns end\n", + " auto it_phi = constants.find( \"phi\" );\n", + " if ( it_phi != constants.end() ) {\n", + " std::cout << \"The golden ratio is \" << it_phi->second << '\\n';\n", + " }\n", + "\n", + " // can use at(), if we know the pair to exists, returns value for key\n", + " // will throw an \"out_of_range\" exception, if we were wrong\n", + " std::cout << \"Value of Euler constant is \" << constants.at( \"e\" ) << \"\\n\\n\";\n", + "\n", + " // range-based loop\n", + " for ( auto& c: constants ) {\n", + " std::cout << \"Value of \" << c.first << \" is \" << c.second << '\\n';\n", + " }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c6102380", + "metadata": {}, + "outputs": [], + "source": [ + "!g++ constants.cpp" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2d71c79e", + "metadata": {}, + "outputs": [], + "source": [ + "!./a.out" + ] + }, + { + "cell_type": "markdown", + "id": "26214da5", + "metadata": {}, + "source": [ + "**Note:** \n", + "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": "afd6c8a0", + "metadata": {}, + "source": [ + "#### Algorithm\n", + "\n", + "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.\n", + "\n", + "We will briefly look at two examples." + ] + }, + { + "cell_type": "markdown", + "id": "fe55e848", + "metadata": {}, + "source": [ + "**Example 1:** Remove double entries from a sequence" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a307aade", + "metadata": {}, + "outputs": [], + "source": [ + "%%file uniq.cpp\n", + "\n", + "#include <algorithm>\n", + "#include <vector>\n", + "#include <iostream>\n", + "\n", + "using Container = std::vector<int>;\n", + "\n", + "void print( const Container& box, const std::string& note ) {\n", + " std::cout << \"( \";\n", + " for( int w: box ) std::cout << w << ' ';\n", + " std::cout << \") \" << note << std::endl;\n", + "}\n", + " \n", + "int main() {\n", + "\n", + " Container box{ 3, 5, 2, 4, 1, 2, 1 };\n", + " print( box, \"<- unsorted\" );\n", + "\n", + " sort( box.begin(), box.end() );\n", + " print( box, \"<- sorted\" );\n", + "\n", + " Container::iterator last = unique( begin(box), end(box) );\n", + " print( box, \"<- happened in-place\" );\n", + "\n", + " box.resize( distance( box.begin(), last ) );\n", + " print( box, \" <- truncated to new length\" );\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f2e2c4d5", + "metadata": {}, + "outputs": [], + "source": [ + "!g++ uniq.cpp" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b90a9b8a", + "metadata": {}, + "outputs": [], + "source": [ + "!./a.out" + ] + }, + { + "cell_type": "markdown", + "id": "f52ee84c", + "metadata": {}, + "source": [ + "**Note:**\n", + "\n", + "Replacing ```vector``` by ```list``` via\n", + "\n", + " using Container = std::vector<int>;\n", + "\n", + "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()```\n", + "and ```list::unique()``` member functions." + ] + }, + { + "cell_type": "markdown", + "id": "e435d1ec", + "metadata": {}, + "source": [ + "***\n", + "**Example 2:** Performing reductions on an array\n", + "\n", + "The STL also provides some algorithms for performing numeric operations. For these we need to include ```<numeric>```." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ecfa4c0a", + "metadata": {}, + "outputs": [], + "source": [ + "%%file accumulate.cpp\n", + "\n", + "#include <numeric>\n", + "#include <vector>\n", + "#include <iostream>\n", + "\n", + "int alternate( int a, int b ) {\n", + " static bool odd = false; // static preserves value of local variable\n", + " if( odd ) {\n", + " odd = false;\n", + " return a-b;\n", + " }\n", + " else {\n", + " odd = true;\n", + " return a+b;\n", + " }\n", + "}\n", + "\n", + "int main() {\n", + "\n", + " std::vector<int> v{1, 2, 3, 4, 5};\n", + " \n", + " int gs = std::accumulate( v.begin(), v.end(), 0 );\n", + " std::cout << \"sum is \" << gs << std::endl;\n", + "\n", + " int check = std::accumulate( v.begin(), v.end(), -15 );\n", + " if( check == 0 ) std::cout << \"checks out\" << std::endl;\n", + " \n", + " int prod = std::accumulate( v.begin(), v.end(), 1, std::multiplies<int>() );\n", + " std::cout << \"5! = \" << prod << std::endl;\n", + "\n", + " int alt = std::accumulate( v.begin(), v.end(), 0, alternate );\n", + " std::cout << \"Alternating sum gives \" << alt << std::endl;\n", + " // 0 + 1 - 2 + 3 - 4 + 5\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c9c3cb86", + "metadata": {}, + "outputs": [], + "source": [ + "!g++ accumulate.cpp" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "499d6af6", + "metadata": {}, + "outputs": [], + "source": [ + "!./a.out" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dc2715fd", "metadata": {}, "outputs": [], "source": [] -- GitLab