{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "42a19770",
   "metadata": {},
   "source": [
    "### 0. Doing simple things: how do I output something in Python?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "0bf32ee0",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Welcome to the Genetics and Genomics class!\n"
     ]
    }
   ],
   "source": [
    "print('Welcome to the Genetics and Genomics class!')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "c9882908",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Welcome to the practical Python session!'"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "'Welcome to the Genetics and Genomics class!'\n",
    "'Welcome to the practical Python session!'  # Only the last line is visualized"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e64d0c89",
   "metadata": {},
   "source": [
    "You may notice that only the last line of the cell above is visualized. To print both lines you can use the following syntax:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "c09ebe43",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Welcome to the Genetics and Genomics class!\n",
      "Welcome to the practical Python session!\n"
     ]
    }
   ],
   "source": [
    "print('Welcome to the Genetics and Genomics class!')\n",
    "print('Welcome to the practical Python session!')  # Both lines are visualized\n",
    "# print('Something I do not want to print')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9fedcc92",
   "metadata": {},
   "source": [
    "**Side note:** *The hash symbol **#** is used to indicate comments or non-executable parts of the script that you don't want do remove from the code.*"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "61df48be",
   "metadata": {},
   "source": [
    "Congrats, you just printed several strings! As in many other languages, strings in Python are indicated with either single or double quotation. In most of the cases, single quotes are enough unless the string contains a single character **'**. For example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "04e574a5",
   "metadata": {},
   "outputs": [
    {
     "ename": "SyntaxError",
     "evalue": "invalid syntax (<ipython-input-4-baa0c323ab26>, line 2)",
     "output_type": "error",
     "traceback": [
      "\u001b[0;36m  File \u001b[0;32m\"<ipython-input-4-baa0c323ab26>\"\u001b[0;36m, line \u001b[0;32m2\u001b[0m\n\u001b[0;31m    print('I'm familiar with Python')\u001b[0m\n\u001b[0m             ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid syntax\n"
     ]
    }
   ],
   "source": [
    "## single quotes will not work in this example\n",
    "print('I'm familiar with Python')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6dc71961",
   "metadata": {},
   "source": [
    "The special backslash character **\\** can be used in the single quote setting to avoid errors:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "cddd439e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "I'm familiar with Python\n"
     ]
    }
   ],
   "source": [
    "print('I\\'m familiar with Python') ## single quotes *will* work in this example"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "75d56cfc",
   "metadata": {},
   "source": [
    "And finally, double quotes:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "c0235f25",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "I'm familiar with Python\n"
     ]
    }
   ],
   "source": [
    "print(\"I'm familiar with Python\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d2f8a9b9",
   "metadata": {},
   "source": [
    "The backslash character might be useful in cases when quotation inside the string is needed, for example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "16e820d8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The concept of \"special characters\" is useful. Backslash \\ is a special character.\n"
     ]
    }
   ],
   "source": [
    "print('The concept of \\\"special characters\\\" is useful. Backslash \\\\ is a special character.')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "511fea4c",
   "metadata": {},
   "source": [
    "To print a multiline string, use three single or three double quotes:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "ba5b4641",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Welcome to the Genetics and Genomics class!\n",
      "Welcome to the practical Python session!\n"
     ]
    }
   ],
   "source": [
    "print('''Welcome to the Genetics and Genomics class!\n",
    "Welcome to the practical Python session!''')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "723a73c3",
   "metadata": {},
   "source": [
    "Alternatively:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "9ff43df7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Welcome to the Genetics and Genomics class!\n",
      "Welcome to the practical Python session!\n"
     ]
    }
   ],
   "source": [
    "print(\"\"\"Welcome to the Genetics and Genomics class!\n",
    "Welcome to the practical Python session!\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "79112c9c",
   "metadata": {},
   "source": [
    "And finally:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "877b2e8d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Welcome to the Genetics and Genomics class!\n",
      "Welcome to the practical Python session!\n"
     ]
    }
   ],
   "source": [
    "print('Welcome to the Genetics and Genomics class!\\nWelcome to the practical Python session!')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ded359c9",
   "metadata": {},
   "source": [
    "In the example above, the **\\n** character indicates the new line. Another frequently used character is **\\t** that indicates tab. For example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "626d1f32",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Welcome to the Genetics and Genomics class!\n",
      "\tWelcome to the practical Python session!\n"
     ]
    }
   ],
   "source": [
    "print('Welcome to the Genetics and Genomics class!\\n\\tWelcome to the practical Python session!')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5b200fac",
   "metadata": {},
   "source": [
    "### 1. Variables in Python"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b8b8ac95",
   "metadata": {},
   "source": [
    "#### 1.1. Creating variables"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "02d41979",
   "metadata": {},
   "source": [
    "Creating variables in Python is simple, the only thing you need to do is to choose a variable name and assign a value or object to it. The variable can store different data types which we are going to discuss in the next section. As compared to other programing languages, Python doesn't require data type specification when a variable is defined.\n",
    "\n",
    "**Side note**: *It is impotant to use proper variable names and maintain the same style throughout your code. Variable names in Python should start with either a letter or an underscore! Also keep in mind that letter cases are important, so `my_variable` and `My_variable` would represent two different variables*. To read more on variable names, check out the following links: https://www.w3schools.com/python/python_variables_names.asp and https://realpython.com/python-variables/\n",
    "\n",
    "For example, we can use the string from the code above, assign it to the variable named `my_string` and print this variable:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "75326683",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Welcome to the Genetics and Genomics class!\n"
     ]
    }
   ],
   "source": [
    "my_string = 'Welcome to the Genetics and Genomics class!'\n",
    "print(my_string)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fa470683",
   "metadata": {},
   "source": [
    "To output the variable content, you may omit the `print` function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "b3eeb7bc",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Welcome to the Genetics and Genomics class!'"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "my_string"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2dfa4d45",
   "metadata": {},
   "source": [
    "One can combine custom strings and variables in the `print()` function: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "f003bec8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The variable my_string contains the following sentence: \" Welcome to the Genetics and Genomics class! \"\n"
     ]
    }
   ],
   "source": [
    "print('The variable my_string contains the following sentence: \\\"', my_string, '\\\"')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "65432c56",
   "metadata": {},
   "source": [
    "#### 1.2. Assigning data to multiple variables"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "50e28853",
   "metadata": {},
   "source": [
    "If you want to create several variables, you may use the following syntax: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "e6dff507",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Bart is giving lectures, Vincent is giving practicals\n"
     ]
    }
   ],
   "source": [
    "teacher_1, teacher_2 = 'Bart', 'Vincent'\n",
    "print(teacher_1, 'is giving lectures,', teacher_2, 'is giving practicals')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "851b5706",
   "metadata": {},
   "source": [
    "To create variables with the same values, use the following syntax:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "d614c794",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "In this course we can use either Python or Python\n"
     ]
    }
   ],
   "source": [
    "option_1 = option_2 = 'Python'\n",
    "print('In this course we can use either', option_1, 'or', option_2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7ebd4090",
   "metadata": {},
   "source": [
    "#### 1.3. Changing the variable"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cf4e56fd",
   "metadata": {},
   "source": [
    "The variable content can be changed by reassigning it to another value/object, for example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "b39a6cf8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Before: my_string = Welcome to the Genetics and Genomics class!\n",
      "After: my_string = Hello world!\n"
     ]
    }
   ],
   "source": [
    "print('Before: my_string =', my_string)\n",
    "my_string = 'Hello world!'\n",
    "print('After: my_string =', my_string)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "92ecfad6",
   "metadata": {},
   "source": [
    "**Side note:** *If you run the cell above for the second time, both output lines will be the same because the variable has been changed!*"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f4008fb2",
   "metadata": {},
   "source": [
    "### 2. Data types and operations with them"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "614bedb9",
   "metadata": {},
   "source": [
    "Python uses classes to define data types. The most frequently used data types are: `str`, `int`, `float`, `range()`, `list()`, `set()`, `dict()`, `bool()`, `None` (and there are some more)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e94cb3e5",
   "metadata": {},
   "source": [
    "#### 2.1. Numeric types in Python"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "69f91d60",
   "metadata": {},
   "source": [
    "There are three numerical types in Python, which include integers (`int`), floating point numbers (real values, aka `float`) and complex numbers (`complex`). Here are some examples:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "dde60f35",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Integer number is  3 and its type is <class 'int'>\n",
      "Floating number is  1.5 and its type is <class 'float'>\n",
      "Complex number is  1j and its type is <class 'complex'>\n"
     ]
    }
   ],
   "source": [
    "int_number = 3\n",
    "float_number = 1.5\n",
    "complex_number = 1j\n",
    "\n",
    "print('Integer number is ', int_number, 'and its type is', type(int_number))\n",
    "print('Floating number is ', float_number, 'and its type is', type(float_number))\n",
    "print('Complex number is ', complex_number, 'and its type is', type(complex_number))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8c5276a5",
   "metadata": {},
   "source": [
    "You can perform standard arithmetical operations numerical types, including:\n",
    "* Addition +\n",
    "* Subtraction -\n",
    "* Multiplication *\n",
    "* Division /\n",
    "* Exponentiation **\n",
    "* Floor division //\n",
    "* Modulus %"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "717a0839",
   "metadata": {},
   "source": [
    "**Excercise 1.**\n",
    "\n",
    "Define the variables x and y, set their values to 2 and 3 respectively. Apply the following operations to x and y, print the output and its type: `x + y`, `x - y`, `x * y`, `x / y`, `x ** y`, `x // y`, `x % y`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "e48e2c81",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "x + y = 5 <class 'int'>\n",
      "x - y = -1 <class 'int'>\n",
      "x * y = 6 <class 'int'>\n",
      "x / y = 0.6666666666666666 <class 'float'>\n",
      "x ** y = 8 <class 'int'>\n",
      "x // y = 0 <class 'int'>\n",
      "x % y = 2 <class 'int'>\n"
     ]
    }
   ],
   "source": [
    "x, y = 2, 3\n",
    "print('x + y =', x + y, type(x + y))\n",
    "print('x - y =', x - y, type(x - y))\n",
    "print('x * y =', x * y, type(x * y))\n",
    "print('x / y =', x / y, type(x / y))\n",
    "print('x ** y =', x ** y, type(x ** y))\n",
    "print('x // y =', x // y, type(x // y))\n",
    "print('x % y =', x % y, type(x % y))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ef785016",
   "metadata": {},
   "source": [
    "**Side note:**  if needed, the variable types can be specified in the following way: `x = float(2)`, in this case, python will add a floating point to the number, i.e. `x = 2.0`. The process of specifying a variable type is called *casting*, and the variable type can be changes by using the *constructor functions*, such as `int()`, `float()`, etc."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eaae5d3d",
   "metadata": {},
   "source": [
    "**Exercise 2.**\n",
    "\n",
    "1. Use the same input values `x` and `y` as in the Exercise 1, but this time define the variable `y` as a float. Will output change? If so, how?\n",
    "2. Apply the constructor function `int()` to the output of the expression `x / y`. Did the result change as compared to the previous exercise? If so how?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "95aed943",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "x + y = 5.0 <class 'float'>\n",
      "x - y = -1.0 <class 'float'>\n",
      "x * y = 6.0 <class 'float'>\n",
      "x / y = 0.6666666666666666 <class 'float'>\n",
      "x ** y = 8.0 <class 'float'>\n",
      "x // y = 0.0 <class 'float'>\n",
      "x % y = 2.0 <class 'float'>\n",
      "--------------------\n",
      "x / y = 0 <class 'float'>\n"
     ]
    }
   ],
   "source": [
    "x, y = 2, float(3)\n",
    "print('x + y =', x + y, type(x + y))\n",
    "print('x - y =', x - y, type(x - y))\n",
    "print('x * y =', x * y, type(x * y))\n",
    "print('x / y =', x / y, type(x / y))\n",
    "print('x ** y =', x ** y, type(x ** y))\n",
    "print('x // y =', x // y, type(x // y))\n",
    "print('x % y =', x % y, type(x % y))\n",
    "print('-' * 20)\n",
    "print('x / y =', int(x / y), type(x / y))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dd6f7014",
   "metadata": {},
   "source": [
    "If there are a lot of digits after the decimal point, the `round()` function can be used to round the floating number to the desired number of decimals. The `round()` function takes as the first argument the number, and as the second argument the number of digits for the rounding precision:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "fa0e82fc",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.6666666666666666 0.667\n"
     ]
    }
   ],
   "source": [
    "print(x / y, round(x / y, 3))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "edae8210",
   "metadata": {},
   "source": [
    "**Important:** to check arguments of functions (e.g. the function named `my_function()`), use one of the options below:\n",
    "* `?my_function`\n",
    "* `help(my_function)`\n",
    "* Put the pointer inside the function and press `shift + tab`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "9612c3e8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on built-in function round in module builtins:\n",
      "\n",
      "round(number, ndigits=None)\n",
      "    Round a number to a given precision in decimal digits.\n",
      "    \n",
      "    The return value is an integer if ndigits is omitted or None.  Otherwise\n",
      "    the return value has the same type as the number.  ndigits may be negative.\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# For example:\n",
    "help(round)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "257e19cb",
   "metadata": {},
   "source": [
    "**Side note:** *Floating values can be sometimes represented in a scientific form, for example, `x = 5e13` which is equivalent to `x = 5 * (10 ** 13)`*"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ea9516de",
   "metadata": {},
   "source": [
    "#### 2.2 Booleans"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9f4de541",
   "metadata": {},
   "source": [
    "We can compare the numbers with the operators less (`<`), greater (`>`), equal (`==`), not equal (`!=`) and, of course, less or equal (`<=`) and greater or equal (`>=`). For example, let's check if the side note statement is correct!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "76e966b7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n",
      "<class 'bool'>\n"
     ]
    }
   ],
   "source": [
    "print(5 * (10 ** 13) == 5e13)  # Testing the statement\n",
    "print(type(5 * (10 ** 13) == 5e13))  # Printing the type of the output"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "da09f90a",
   "metadata": {},
   "source": [
    "As we can see, the output of the statement is `True`, that has the boolean type. Boolean values are either `True` or `False`. Note that integers `0` and `1` are equivalent to `False` and `True` respectively."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "13816633",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The statement 15 < 1 is False\n",
      "The statement 10 != 12 is True\n",
      "----------\n",
      "The statement 0 == False is True\n",
      "The statement 1 == True is True\n"
     ]
    }
   ],
   "source": [
    "print('The statement 15 < 1 is', 15 <= 1)\n",
    "print('The statement 10 != 12 is', 10 != 12)\n",
    "print('-' * 10)\n",
    "print('The statement 0 == False is', 0 == False)\n",
    "print('The statement 1 == True is', 0 == False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "54da7a51",
   "metadata": {},
   "source": [
    "#### 2.2.1 Logical operators `and`, `or`, `not`"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3521450c",
   "metadata": {},
   "source": [
    "The logical operators `and`, `or`, `not` can be used to test different statements to get a boolean answer, for example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "6870a372",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(False, False, True)"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 'and' returns True if both statements are True\n",
    "False and False, False and True, True and True"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "ff54de02",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(False, True, True)"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 'or' returns True if one of the statements is True\n",
    "False or False, False or True, True or True"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "36730987",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "not False is True\n",
      "not True is False\n"
     ]
    }
   ],
   "source": [
    "# 'not' is a negation operator, e.g.\n",
    "print('not False is', not False)\n",
    "print('not True is', not True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "84dcc540",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "False\n",
      "False\n",
      "True\n",
      "False\n"
     ]
    }
   ],
   "source": [
    "print((15 < 1) and (150 < 10))\n",
    "print((15 > 1) and (150 < 10))\n",
    "\n",
    "print((15 > 1) or (150 < 10))\n",
    "print(not ((15 > 1) or (150 < 10)))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22d818f1",
   "metadata": {},
   "source": [
    "#### 2.1. Strings"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bb277bf9",
   "metadata": {},
   "source": [
    "In the previous section you have already used the variables of the type string (`str` in Python). Let's see what we can do with them!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "64fa7750",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'str'>\n"
     ]
    }
   ],
   "source": [
    "my_name = 'Lola'\n",
    "print(type(my_name))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2b4c6d7c",
   "metadata": {},
   "source": [
    "One can use built-in methods to change the string, i.e. replace a substring (`.replace()`), make all letters lowercase (`.lower()`) or uppercase (`.upper()`), for example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "74f72f11",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Loic\n",
      "lola\n",
      "LOLA\n"
     ]
    }
   ],
   "source": [
    "print(my_name.replace('la', 'ic'))\n",
    "print(my_name.lower())\n",
    "print(my_name.upper())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7163624a",
   "metadata": {},
   "source": [
    "In Python3.9 additional two methods `.removeprefix()` and `.removesuffix()` exist for strings. If the substring to be matched is found in either prefix (beginning) or suffix (end) of the string, then it is removed and the truncated version of the string is returned. If there is no match, the output is equal to the initial string. For example,"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "3ab992bd",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "la\n",
      "Lo\n"
     ]
    }
   ],
   "source": [
    "print(my_name.removeprefix('Lo'))  # the prefix 'Lo' is removed\n",
    "print(my_name.removesuffix('la'))  # the suffix 'la' is removed"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9e804bbe",
   "metadata": {},
   "source": [
    "The above mentioned methods do not change the string inplace, so if you want to save the updated string, store it to a new variable, e.g."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "9518b616",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Loic\n"
     ]
    }
   ],
   "source": [
    "new_name = my_name.replace('la', 'ic')\n",
    "print(new_name)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "baf0b936",
   "metadata": {},
   "source": [
    "To check if a string contains a paricular a substring, use the `in` keyword as in the following example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "ecd4b79e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "'ic' in new_name"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bc99bb7e",
   "metadata": {},
   "source": [
    "**Side note:** *to get the list of keywords type `import keyword` and in the next line `print(keyword.kwlist)`. Remember that keywords cannot be used as variable names.*"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ab5c982d",
   "metadata": {},
   "source": [
    "To repeat the same string `n` number of times use the multiplication operator `*`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "71ef6303",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "LolaLolaLolaLolaLolaLolaLolaLolaLolaLola\n"
     ]
    }
   ],
   "source": [
    "n = 10\n",
    "print(my_name * n)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ee4178ad",
   "metadata": {},
   "source": [
    "You can concatenate strings by using the `+` operator as follows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "ed9bcf4d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Lola and Loic are friends\n"
     ]
    }
   ],
   "source": [
    "print(my_name + ' and ' + new_name + ' are friends')  # Note that concatenation happens without spaces!"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "33472b33",
   "metadata": {},
   "source": [
    "To get a string that would be joined with the same separator (it can be comma, semicolon, dot, underscore, etc.), use the `.join()` method:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "b312eaff",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Lola Loic Robert and Sarah are friends\n",
      "----------\n",
      "Lola_Loic_Robert_and_Sarah_are_friends\n"
     ]
    }
   ],
   "source": [
    "print(' '.join([my_name, new_name, 'Robert', 'and', 'Sarah', 'are', 'friends']))\n",
    "print('-' * 10)\n",
    "print('_'.join([my_name, new_name, 'Robert', 'and', 'Sarah', 'are', 'friends']))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "65feee9b",
   "metadata": {},
   "source": [
    "Another useful method for strings is `.split()` which allows splitting by a defined pattern:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "217dc0ad",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Lola_Loic_Robert_and_Sarah_are_friends\n"
     ]
    }
   ],
   "source": [
    "long_string = '_'.join([my_name, new_name, 'Robert', 'and', 'Sarah', 'are', 'friends'])\n",
    "print(long_string)  # this string contains underscore as a separator"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c35fed52",
   "metadata": {},
   "source": [
    "Let's use `.split()` to extract separate words from the string:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "4f50f198",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['Lola', 'Loic', 'Robert', 'and', 'Sarah', 'are', 'friends']\n"
     ]
    }
   ],
   "source": [
    "print(long_string.split('_'))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d8f6cf12",
   "metadata": {},
   "source": [
    "Strings in Python are represented as arrays of Unicode characters and therefore, single elements of a string can be accessed through the square brackets in the following way:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "id": "9df9064e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('L', 'o', 'l', 'a')"
      ]
     },
     "execution_count": 39,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "my_name[0], my_name[1], my_name[2], my_name[3]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "879dc150",
   "metadata": {},
   "source": [
    "To acces several elements of the string, use colon and specify the index range in the `[start_idx:end_idx]` format as shown below. Note that it is not necessary to specify start or end of the slice if start/end match the sequence start/end coordinate."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "id": "b9f2bb25",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The first half of the name is: Lo\n",
      "The statement my_name[:2] == my_name[0:2] is True\n",
      "----------\n",
      "The second half of the name is: la\n",
      "The statement my_name[2:4] == my_name[2:] is True\n"
     ]
    }
   ],
   "source": [
    "print('The first half of the name is:', my_name[:2])\n",
    "print('The statement my_name[:2] == my_name[0:2] is', my_name[:2] == my_name[0:2])\n",
    "\n",
    "print('-' * 10)\n",
    "print('The second half of the name is:', my_name[2:])\n",
    "print('The statement my_name[2:4] == my_name[2:] is', my_name[2:4] == my_name[2:])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "54c8523f",
   "metadata": {},
   "source": [
    "**! Important:** *Enumeration in Python starts from **0** ! That is why the first index of the \"L\" letter in the variable `my_name` is accessed with `[0]`. When slicing a string keep in mind that the last coordinate is **not included**. One can access elements from the end of the string/list by using negative indexers starting from -1. In this case, the last element of `my_name` will be accessed as `my_name[-1]`. To acces a slice from the end, use the following syntax:*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "id": "9398227e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The second half of the name is: la\n"
     ]
    }
   ],
   "source": [
    "print('The second half of the name is:', my_name[-2:])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5b8c4814",
   "metadata": {},
   "source": [
    "To know the length of the string one can use the `len()` function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "85b0e246",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The string \" Lola \" contains 4 characters.\n"
     ]
    }
   ],
   "source": [
    "print('The string \\\"', my_name, '\\\" contains', len(my_name), 'characters.')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d75b4ba1",
   "metadata": {},
   "source": [
    "You may have noticed in the cell above the new structure represented with square brackets and comma-separated variables (strings in this example). This stucture is called `list` and it is one of the data types allowing to store data in Python. To know more about lists, sets and dictionaries, please see the following subsections."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a3ed77c1",
   "metadata": {},
   "source": [
    "#### 2.2. Lists"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "59475537",
   "metadata": {},
   "source": [
    "Lists in Python are created with square brackets and may contain mixed data types, multiple occurrences of the same element. For example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "id": "615855ee",
   "metadata": {},
   "outputs": [],
   "source": [
    "my_list = ['flowers', 20., 'cat', 'dog', 2, True, 'flowers', ['a', 'b', 5]]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "id": "97c5ac52",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'list'> , length of the list is 8\n"
     ]
    }
   ],
   "source": [
    "print(type(my_list), ', length of the list is', len(my_list))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eadc956d",
   "metadata": {},
   "source": [
    "Elements in the list are indexed, so to access a single element of the list or a several elements, use the following options:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "id": "b9a2bf09",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The first element of my_list is flowers\n",
      "The second element of my_list is 20.0\n",
      "The last two elements of my_list are ['flowers', ['a', 'b', 5]]\n"
     ]
    }
   ],
   "source": [
    "print('The first element of my_list is', my_list[0])\n",
    "print('The second element of my_list is', my_list[1])\n",
    "print('The last two elements of my_list are', my_list[-2:])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "44967517",
   "metadata": {},
   "source": [
    "Single elements in lists can be changed by using indeces. For example, let's take the word \"cat\" (the second element in Python counting system) and replace it with the word \"owl\"."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "id": "91691f63",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['flowers', 20.0, 'owl', 'dog', 2, True, 'flowers', ['a', 'b', 5]]\n"
     ]
    }
   ],
   "source": [
    "my_list[2] = 'owl'\n",
    "print(my_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "24bcd561",
   "metadata": {},
   "source": [
    "Great, the replacement worked! Let's now try changing several sequential words \"owl\" and \"dog\" to words \"pigeon\" and \"parrot\"."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "id": "e3498fad",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['flowers', 20.0, 'pigeon', 'parrot', 2, True, 'flowers', ['a', 'b', 5]]\n"
     ]
    }
   ],
   "source": [
    "my_list[2:4] = ['pigeon', 'parrot']\n",
    "print(my_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "69a781ea",
   "metadata": {},
   "source": [
    "Lists and strings can be reversed with `[::-1]`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "id": "3192b4ab",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Initial list is: ['flowers', 20.0, 'pigeon', 'parrot', 2, True, 'flowers', ['a', 'b', 5]]\n",
      "Reversed list is: [['a', 'b', 5], 'flowers', True, 2, 'parrot', 'pigeon', 20.0, 'flowers']\n"
     ]
    }
   ],
   "source": [
    "print('Initial list is:', my_list)\n",
    "print('Reversed list is:', my_list[::-1])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "26c7e197",
   "metadata": {},
   "source": [
    "Lists in Python can be modified, namely, you can append, remove, replace, sort objects inside a list. The full list of methods is described [here](https://docs.python.org/3/tutorial/datastructures.html).\n",
    "\n",
    "Let's try out some methods:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "id": "f7695f16",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Before: ['flowers', 20.0, 'pigeon', 'parrot', 2, True, 'flowers', ['a', 'b', 5]]\n",
      "Length is 8\n",
      "----------\n",
      "After: ['flowers', 20.0, 'pigeon', 'parrot', 2, True, 'flowers', ['a', 'b', 5], 'Lola']\n",
      "Length is 9\n"
     ]
    }
   ],
   "source": [
    "print('Before:', my_list)\n",
    "print('Length is', len(my_list))\n",
    "my_list.append(my_name)\n",
    "\n",
    "# Check out the .extend() method which allows\n",
    "# adding several elements from another list to the list of interest\n",
    "print('-' * 10)\n",
    "print('After:', my_list)\n",
    "print('Length is', len(my_list))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "id": "17cd172a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "my_list after removing an element: ['flowers', 20.0, 'pigeon', 'parrot', 2, 'flowers', ['a', 'b', 5], 'Lola']\n",
      "Length is 8\n"
     ]
    }
   ],
   "source": [
    "my_list.remove(True)\n",
    "print('my_list after removing an element:', my_list)\n",
    "print('Length is', len(my_list))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "id": "f5af4933",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The word \"flowers\" appears 2 times in my_list\n"
     ]
    }
   ],
   "source": [
    "print('The word \\\"flowers\\\" appears', my_list.count('flowers'), 'times in my_list')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5c7cf6ce",
   "metadata": {},
   "source": [
    "**Side note 1:** *Note that there are two methods for sorting list: `my_list.sort()` orders elements inplace (changes the initial list), and `sorted(my_list)` returns a sorted copy of the list.*"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9bbcd644",
   "metadata": {},
   "source": [
    "**Side note 2:** *By using the `list()` constructor, one can create a list from another data type, for example, `np.array()`, which we will discuss later. Then, the constructor will be used in the following way: `list(np.array(['a', 'b', 'c']))`.*"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4238ba94",
   "metadata": {},
   "source": [
    "To check if a list contains a paricular element, use the `in` keyword as in the following example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "id": "483ea6f8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 52,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "'parrot' in my_list"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d6d2776d",
   "metadata": {},
   "source": [
    "Note that `my_list` contains a sublist. It's elements can be accessed by first extracting the list based on its index in `my_list`, and then extracting an element inside the sublist by its index starting from `0` and to the length of the sublist:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "id": "9866e337",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Lola\n",
      "L\n",
      "l\n",
      "Lo\n"
     ]
    }
   ],
   "source": [
    "print(my_list[7])  # get the sublist\n",
    "print(my_list[7][0])  # extract the first element of the sublist\n",
    "print(my_list[7][2])  # extract the last element of the sublist\n",
    "print(my_list[7][:2])  # extract the fisrt two elements of the sublist"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "04cce783",
   "metadata": {},
   "source": [
    "#### 2.3. Sets"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a334dad8",
   "metadata": {},
   "source": [
    "Sets in Python represent a non-repetitive collection of elements, so it is the set in a mathematical sense. Therefore, sets allow various set-like operations including built-in methods, such as `.intersection()`, `.union()`, `.difference()`. Similarly to lists, you can add/remove elements to/from a set, but sets cannot be sorted, set elements cannot be changed because sets are not indexed.\n",
    "\n",
    "**Side note:** *To create an unchangeable set, use the `frozenset()` method. To read more about it, check out the [documentation](https://docs.python.org/3/library/stdtypes.html#frozenset).*"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e006c164",
   "metadata": {},
   "source": [
    "You can create a set with curly braces by listing the elements inside:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "id": "40e4adff",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{True, 2, 'flowers', 20.0, 'pigeon', 'parrot'} , the data type is <class 'set'>\n"
     ]
    }
   ],
   "source": [
    "my_set = {'flowers', 20.0, 'pigeon', 'parrot', 2, True, 'flowers'}\n",
    "print(my_set, ', the data type is', type(my_set))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d371c307",
   "metadata": {},
   "source": [
    "Even though the element 'flowers' is repeated twice, the set definition with `{}` will automatically remove this repetition.\n",
    "\n",
    "Alternatively, sets can be created from lists, for example, by using the `set()` constructor:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "id": "65a63284",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{True, 2, 'dog', 'cat', 'flowers', 20.0} , set length is 6\n"
     ]
    }
   ],
   "source": [
    "my_list = ['flowers', 20., 'cat', 'dog', 2, True, 'flowers']\n",
    "my_set = set(my_list)\n",
    "print(my_set, ', set length is', len(my_set))\n",
    "\n",
    "# Sets can be converted to lists with the list() constructor: list(my_set)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8cc6d9d6",
   "metadata": {},
   "source": [
    "To add an element to the set, use the `.add()` method:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "id": "5e7d8516",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{True, 2, 'dog', 'genetics', 'cat', 'flowers', 20.0}\n"
     ]
    }
   ],
   "source": [
    "my_set.add('genetics')\n",
    "print(my_set)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c44216d1",
   "metadata": {},
   "source": [
    "Existing sets can be extended with the elements from another set (or list) with the `.update()` method:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "id": "f503bb87",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "my_set before: {True, 2, 'dog', 'genetics', 'cat', 'flowers', 20.0}\n",
      "\n",
      "my_set after the update: {True, 2, 'dog', 'a', 'genetics', 'c', 'cat', 'flowers', 20.0, 'b'}\n"
     ]
    }
   ],
   "source": [
    "print('my_set before:', my_set)\n",
    "\n",
    "new_set = {'a', 'b', 'c', 20, 'flowers'}\n",
    "my_set.update(new_set)\n",
    "print('\\nmy_set after the update:', my_set)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "88a1c9a5",
   "metadata": {},
   "source": [
    "To check if a set contains a paricular element, use the `in` keyword as in the following example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "id": "7f25aa60",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 58,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "'parrot' in my_set"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9c6976aa",
   "metadata": {},
   "source": [
    "Below you can find several examples of set operations, such as intersection, union, difference:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "id": "cdd7e7da",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Intersection of sets: {'flowers', 20}\n",
      "Union of sets: {True, 2, 'dog', 'a', 'c', 'cat', 'flowers', 20.0, 'b'}\n",
      "Difference of sets, option 1: {'cat', True, 2, 'dog'}\n",
      "Difference of sets, option 2: {'cat', True, 2, 'dog'}\n",
      "Symmetric difference of sets: {True, 2, 'c', 'a', 'dog', 'cat', 'b'}\n"
     ]
    }
   ],
   "source": [
    "my_set = set(my_list)\n",
    "new_set = {'a', 'b', 'c', 20, 'flowers'}\n",
    "print('Intersection of sets:', my_set.intersection(new_set))\n",
    "print('Union of sets:', my_set.union(new_set))\n",
    "print('Difference of sets, option 1:', my_set.difference(new_set))\n",
    "# Note that the output of new_set.difference(my_set) will be different!\n",
    "\n",
    "# Equivalently, the set difference can be obtained as follows:\n",
    "print('Difference of sets, option 2:', my_set - new_set)\n",
    "\n",
    "print('Symmetric difference of sets:', my_set.symmetric_difference(new_set))\n",
    "# Symmetric difference of sets can be obtained as (my_set - new_set).union(new_set - my_set)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4f21a0a9",
   "metadata": {},
   "source": [
    "To know more about set methods in Python, check out [this](https://docs.python.org/3/library/stdtypes.html#set) link."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "07fab233",
   "metadata": {},
   "source": [
    "#### 2.4. Dictionaries"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f7c6c994",
   "metadata": {},
   "source": [
    "The last, but not less useful data type is dictionary. Dictionaries are powerful since they allow to store different data types in the `{key: value}` format, which allows efficient hashing of elements. Dictionaries represent ordered (since Python3.6) structures with no repetitive keys, but editable elements. Note that keys can be represented only by immutable data types, check out [this](https://www.scaler.com/topics/mutable-data-typesg-in-python/) link to know more about mutable and immutable data types.\n",
    "\n",
    "Dictionaries are represented with curly brackets and the following syntax:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "id": "9fcf25b7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'name': 'Lola', 'age': 20, 'favourite_animal': 'dog', 'sibling_names': ['John', 'Sarah']}\n",
      "Type is <class 'dict'>\n"
     ]
    }
   ],
   "source": [
    "my_dict = {\n",
    "    'name': my_name,\n",
    "    'age': 20,\n",
    "    'favourite_animal': 'dog',\n",
    "    'sibling_names': ['John', 'Sarah']\n",
    "}\n",
    "print(my_dict)\n",
    "print('Type is', type(my_dict))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0c1e637a",
   "metadata": {},
   "source": [
    "As strings, lists and sets, dictionaries have a length.\n",
    "\n",
    "**Exercise 3.**\n",
    "\n",
    "1. What is the length of `my_dict`?\n",
    "2. What are the data types of dictionary values?"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2ac31cb9",
   "metadata": {},
   "source": [
    "Dictionary elements (or items, can be obtained with `.items()`) are keys and values, which can be retrieved from the dictionary with `.keys()` and `.values()` methods."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9aa85926",
   "metadata": {},
   "source": [
    "**Exercise 4.**\n",
    "    \n",
    "1. What are the keys of my_dict?\n",
    "2. What are the values of my_dict?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "id": "fdf7b2a5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "dict_items([('name', 'Lola'), ('age', 20), ('favourite_animal', 'dog'), ('sibling_names', ['John', 'Sarah'])])\n",
      "dict_keys(['name', 'age', 'favourite_animal', 'sibling_names'])\n",
      "dict_values(['Lola', 20, 'dog', ['John', 'Sarah']])\n"
     ]
    }
   ],
   "source": [
    "print(my_dict.items())\n",
    "print(my_dict.keys())\n",
    "print(my_dict.values())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2fab63e7",
   "metadata": {},
   "source": [
    "To get values for a particular key of the dictionary, use the square brackets as follows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "id": "8af1ede2",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "20"
      ]
     },
     "execution_count": 62,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "my_dict['age']"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4ca8f9db",
   "metadata": {},
   "source": [
    "Alternatively, one can use the built-in method `.get()` which will return the value for a key if it is present and None otherwise (unless the alternative is specified):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "id": "589299bd",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "20\n",
      "None\n",
      "no_value\n"
     ]
    }
   ],
   "source": [
    "print(my_dict.get('age'))\n",
    "print(my_dict.get('eye_color'))\n",
    "print(my_dict.get('eye_color', 'no_value'))  # check parameters of the method to see how it works"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "77392db7",
   "metadata": {},
   "source": [
    "To add a new item, or several items to the dictionary, one can use either square brackets, or the `.update()` method in the following way:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "id": "93e9132d",
   "metadata": {},
   "outputs": [],
   "source": [
    "my_dict['eye_color'] = 'green'\n",
    "# Or alternatively:\n",
    "my_dict.update({'eye_color': 'green'})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f1d86d7e",
   "metadata": {},
   "source": [
    "**Exercise 5.**\n",
    "    \n",
    "1. Add two more keys and respective values of your choice to `my_dict` using the `.update()` function. Print the final dictionary."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "id": "4f0cdf5a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# To add more than one item:\n",
    "my_dict.update({'eye_color': 'green', 'height': 165})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "id": "55b342b8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'name': 'Lola',\n",
       " 'age': 20,\n",
       " 'favourite_animal': 'dog',\n",
       " 'sibling_names': ['John', 'Sarah'],\n",
       " 'eye_color': 'green',\n",
       " 'height': 165}"
      ]
     },
     "execution_count": 66,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "my_dict"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3f0711f5",
   "metadata": {},
   "source": [
    "To get more information on dictionary methods, check out [this](https://www.w3schools.com/python/python_ref_dictionary.asp) link."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "12c90eaa",
   "metadata": {},
   "source": [
    "**Side note:** *Dictionaries can be converted to lists and vise versa:*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "id": "400f41c6",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[('name', 'Lola'),\n",
       " ('age', 20),\n",
       " ('favourite_animal', 'dog'),\n",
       " ('sibling_names', ['John', 'Sarah']),\n",
       " ('eye_color', 'green'),\n",
       " ('height', 165)]"
      ]
     },
     "execution_count": 67,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dict_to_list = list(my_dict.items())\n",
    "dict_to_list"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d4ffc931",
   "metadata": {},
   "source": [
    "*Note that in the example above, elements of the list are tuples, which are defined with round brackets (see next section **2.5.** to know more about tuples):*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "id": "c8714ef2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "('name', 'Lola') <class 'tuple'>\n"
     ]
    }
   ],
   "source": [
    "print(dict_to_list[0], type(dict_to_list[0]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5268d9b2",
   "metadata": {},
   "source": [
    "*Lists can be converted to dictionaries as well:*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "id": "8fe4b613",
   "metadata": {},
   "outputs": [],
   "source": [
    "new_list = [\n",
    "    ['name', 'Lola'], ['age', 20], ['eye_color', 'green'],\n",
    "    ['height', 165], ['favourite_animal', 'dog'],\n",
    "    ['sibling_names', ['John', 'Sarah']],\n",
    "]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 70,
   "id": "ccd2b194",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'name': 'Lola',\n",
       " 'age': 20,\n",
       " 'eye_color': 'green',\n",
       " 'height': 165,\n",
       " 'favourite_animal': 'dog',\n",
       " 'sibling_names': ['John', 'Sarah']}"
      ]
     },
     "execution_count": 70,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "list_to_dict = dict(new_list)\n",
    "list_to_dict"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8488bf67",
   "metadata": {},
   "source": [
    "#### 2.5. Tuples"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "54c83daa",
   "metadata": {},
   "source": [
    "Tuples represent another data type that is ordered, immutable and allows duplicates. As in other data types, tuples can contain any data type, tuples have length and tuple elements can be accessed with indexing. Tuples in Python are defined with round brackets `()`, and the `.tuple()` constructor allows to create and/or convert other data types to tuples: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "id": "38831c08",
   "metadata": {},
   "outputs": [],
   "source": [
    "my_tuple = ('Antoni', 'Bart', 'Jacques', 'Olga', 'Valeria', 'Vincent')\n",
    "my_tuple = tuple(['Antoni', 'Bart', 'Jacques', 'Olga', 'Valeria', 'Vincent']) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 72,
   "id": "5e758a20",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tuple"
      ]
     },
     "execution_count": 72,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "type(my_tuple)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 73,
   "id": "406e589a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('Antoni', ('Olga', 'Valeria'))"
      ]
     },
     "execution_count": 73,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "my_tuple[0], my_tuple[3:5]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "508efcbf",
   "metadata": {},
   "source": [
    "#### 2.6 None data type"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f4fa5bdd",
   "metadata": {},
   "source": [
    "None is a data type whith is defined with a keyword `None` and indicates null value, or absence of a value. Therefore, `None` cannot be interpreted as zero value, False, or an empty object (e.g. string, list, dict)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "id": "f6ac218a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'NoneType'>\n"
     ]
    }
   ],
   "source": [
    "x = None\n",
    "print(type(x))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 75,
   "id": "9ddc245b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(True, False)"
      ]
     },
     "execution_count": 75,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Let's check if x is indeed the None data type\n",
    "x is None, x is not None"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8312c860",
   "metadata": {},
   "source": [
    "### 3. Conditional statements (if, elif, else)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "910acaa0",
   "metadata": {},
   "source": [
    "In the subsection 2.2. we introduced boolean variables and discussed logical operators `and`, `or` and `not` in the subsection 2.2.1. Conditional statements in Python allow to test statements and define the code based on that."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 76,
   "id": "4b9b0070",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The sum of x and y is equal to z\n"
     ]
    }
   ],
   "source": [
    "x, y, z = 1, 3, 4\n",
    "if (x + y) == z:\n",
    "    # execute some code here\n",
    "    print('The sum of x and y is equal to z')\n",
    "else:\n",
    "    # execute sth else here\n",
    "    print(';(')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 77,
   "id": "7d971255",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      ";(\n"
     ]
    }
   ],
   "source": [
    "# Let's change the value of z\n",
    "x, y, z = 1, 3, 5\n",
    "if (x + y) == z:\n",
    "    # execute some code here\n",
    "    print('The sum of x and y is equal to z')\n",
    "else:\n",
    "    # execute sth else here\n",
    "    print(';(')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "877acf7f",
   "metadata": {},
   "source": [
    "**! Important:** *When using Python, one needs to be careful with indents and spacings! One extra space or absence of an indent may cause an error.*"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "de9e2ba3",
   "metadata": {},
   "source": [
    "In conditional statements, the `else` statement is not required. In this case, if the `if` statement is not satisfied, noting will happen:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 78,
   "id": "9c6892a1",
   "metadata": {},
   "outputs": [],
   "source": [
    "x, y, z = 1, 3, 5\n",
    "if (x + y) == z:\n",
    "    # execute some code here\n",
    "    print('The sum of x and y is equal to z')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "39c14c22",
   "metadata": {},
   "source": [
    "To test several statements, you can use the `elif` statement, which will tell the interpreter that when the `if` statement is not satisfied, try another contition, and if it works -- great! Otherwise, proceed either to the `else` statement, or execute the following lines of code (if any). Note that conditional statements in Python can be nested."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 79,
   "id": "6d224284",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The sum of x and y is less than z\n"
     ]
    }
   ],
   "source": [
    "x, y, z = 1, 3, 5\n",
    "if (x + y) == z:\n",
    "    # execute some code here\n",
    "    print('The sum of x and y is equal to z')\n",
    "elif (x + y) < z:\n",
    "    # execute some code here\n",
    "    print('The sum of x and y is less than z')\n",
    "else:\n",
    "    # execute some code here\n",
    "    print('None of the conditions is satisfied')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dd3dfc2b",
   "metadata": {},
   "source": [
    "Python allows for having several consecutive `if` and/or `elif` statements. Python will test each `if` statement but will stop testing at the `elif` statement that was satisfied.\n",
    "\n",
    "**Side note:** *Sometimes you might want to have an empty conditional statement. In this case, use the `pass` statement right after a contition. To read more about it, check out [this](https://www.w3schools.com/python/ref_keyword_pass.asp) link.*"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4dfd8b7e",
   "metadata": {},
   "source": [
    "### 4. `While` and `for` loops"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fba35f12",
   "metadata": {},
   "source": [
    "Let's go back to our example with `my_list` and suppose we don't know its content and we want to store only numerical values to another list. How would you approach this problem? Well, you can, of course, check the length of the list and print each element by accessing it via indexing with square brackets, test whether an element has a numerical type and then add those elements to a new list. However, such approach is not efficient. This is the moment where loops come in hand!"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ab260ed3",
   "metadata": {},
   "source": [
    "#### 4.1. Writing loops with `while`"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "283d003f",
   "metadata": {},
   "source": [
    "`While` is a conditional looping statement that allows you to perform an operation (or many operations) until the `while` statement is satisfied. Let's try solving the problem of finding all numerical values in `my_list` and storing them to a new list."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 80,
   "id": "052d7da7",
   "metadata": {},
   "outputs": [],
   "source": [
    "counter = 0  # Since enumeration in Python starts from 0, we set counter to 0 and change it in the loop\n",
    "numerical_list = []  # Create an empty list\n",
    "while counter < len(my_list):\n",
    "    list_element = my_list[counter]  # get the element of my_list with the counter as index\n",
    "    if type(list_element) == int or type(list_element) == float:  # Check data type of the element\n",
    "        numerical_list.append(list_element)  # Append a numerical value to the new list\n",
    "    # Increase the counter by one to access next element of the my_list\n",
    "    counter += 1  # This is equivalent to: counter = counter + 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 81,
   "id": "29b7df42",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[20.0, 2]\n"
     ]
    }
   ],
   "source": [
    "print(numerical_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0402d354",
   "metadata": {},
   "source": [
    "If you want to stop the while loop if a custom condition is satisfied, use the `break` statement as in the example below:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 82,
   "id": "b2979510",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "There is at least one numerical element in the list! Breaking...\n"
     ]
    }
   ],
   "source": [
    "counter = 0  # Since enumeration in Python starts from 0,\n",
    "# we set counter to 0 and iteratively increase it in the loop\n",
    "numerical_list = []  # Create an empty list\n",
    "while counter < len(my_list):  # While condition\n",
    "    list_element = my_list[counter]  # get the element of my_list with the counter as index\n",
    "    if type(list_element) == int or type(list_element) == float:  # Check data type of the element\n",
    "        numerical_list.append(list_element)  # Append a numerical value to the new list\n",
    "    # Increase the counter by one to access next element of the my_list\n",
    "    counter += 1  # This is equivalent to: counter = counter + 1\n",
    "    if counter == 1:\n",
    "        print('There is at least one numerical element in the list! Breaking...')\n",
    "        break"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b255e19f",
   "metadata": {},
   "source": [
    "#### 4.2. Writing loops with `for`"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2112111b",
   "metadata": {},
   "source": [
    "The `for` loop is useful when you need to iterate over a sequence of elements, which can be represented as a string, set, list, tuple or dictionary. Loops can be combined with conditional statements and various python functionalities. Please see the subsections below for more examples."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1df973f5",
   "metadata": {},
   "source": [
    "#### 4.2.1. Iterating over lists"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0e7aba06",
   "metadata": {},
   "source": [
    "As in the subsection above, we will use `my_list` to find numerical data types."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 83,
   "id": "45ec94b8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['flowers', 20.0, 'cat', 'dog', 2, True, 'flowers']\n"
     ]
    }
   ],
   "source": [
    "print(my_list)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 84,
   "id": "7cabf8f8",
   "metadata": {},
   "outputs": [],
   "source": [
    "numerical_list = []  # create an empty list\n",
    "for list_element in my_list:  # iterate over list elements\n",
    "    if type(list_element) == int or type(list_element) == float:  # check element type\n",
    "        numerical_list.append(list_element)  # append numerical value to list"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 85,
   "id": "a5e1accf",
   "metadata": {},
   "outputs": [],
   "source": [
    "numerical_list = []  # create an empty list\n",
    "for i in range(len(my_list)):  # iterate over list indices in range [0, 1, 2,... len(my_list))\n",
    "    list_element = my_list[i]  # get element from the list based on the index\n",
    "    if type(list_element) == int or type(list_element) == float:  # check element type\n",
    "        numerical_list.append(list_element)  # append numerical value to list"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 86,
   "id": "eac321eb",
   "metadata": {},
   "outputs": [],
   "source": [
    "numerical_list = []  # create an empty list\n",
    "for i, list_element in enumerate(my_list):  # iterate over list indices and elements with enumerate fucntion\n",
    "    if type(list_element) == int or type(list_element) == float:  # check element type\n",
    "        numerical_list.append(list_element)  # append numerical value to list"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "240664fd",
   "metadata": {},
   "source": [
    "All three examples above will generate the exact same output, which is:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 87,
   "id": "a792d080",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[20.0, 2]\n"
     ]
    }
   ],
   "source": [
    "print(numerical_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9316f9ec",
   "metadata": {},
   "source": [
    "In the example above we saw how to interupt looping with the `break` statement. `continue` is another useful statement that allows to jump over one of the elements if a conditional statement is true.\n",
    "\n",
    "Let's try it out by skipping the numerical values and printing all other elements while looping:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 88,
   "id": "35ac8b93",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "flowers\n",
      "cat\n",
      "dog\n",
      "True\n",
      "flowers\n",
      "[1, 'fish', True]\n"
     ]
    }
   ],
   "source": [
    "my_list = ['flowers', 20.0, 'cat', 'dog', 2, True, 'flowers', [1, 'fish', True]]\n",
    "for i, list_element in enumerate(my_list):  # iterate over list indices and elements with enumerate fucntion\n",
    "    if type(list_element) == int or type(list_element) == float:\n",
    "        continue  # skip the element and do nothing if the condition is satisfied\n",
    "    else:  # otherwise print the list element\n",
    "        print(list_element)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fffec2bf",
   "metadata": {},
   "source": [
    "`for` loops can be nested, for example, you may have noticed that we have a sublist inside of `my_list` which contains one numerical value. Let's get is out and save to the numerical list with other values:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 89,
   "id": "05d13203",
   "metadata": {},
   "outputs": [],
   "source": [
    "numerical_list = []  # create an empty list\n",
    "for i, list_element in enumerate(my_list):  # iterate over list indices and elements with enumerate fucntion\n",
    "    if type(list_element) == list:  # first, check if the element type is list\n",
    "        for sublist_element in list_element:  # if it is, iterate over sublist elements\n",
    "            if type(sublist_element) == int or type(sublist_element) == float:\n",
    "                # if the sublist element type is numerical, add it to the numerical_list:\n",
    "                numerical_list.append(sublist_element)\n",
    "    else:  # go here is the element type is not list\n",
    "        if type(list_element) == int or type(list_element) == float:\n",
    "            # if the element type is numerical, add it to the numerical_list:\n",
    "            numerical_list.append(list_element)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 90,
   "id": "c342a79d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[20.0, 2, 1]\n"
     ]
    }
   ],
   "source": [
    "print(numerical_list)\n",
    "# It worked!\n",
    "# We have added a numerical element from a sublist to all other numerical values!"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "689ed541",
   "metadata": {},
   "source": [
    "#### 4.2.2. List comprehensions"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6b055048",
   "metadata": {},
   "source": [
    "List comprehensions are a compact way of writing `for` loops. For example, the code from the subsection 4.2.1. can be rewritten as a one-liner:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 91,
   "id": "f3c5d669",
   "metadata": {},
   "outputs": [],
   "source": [
    "numerical_list = [list_element for list_element in my_list if type(list_element) == int or type(list_element) == float]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 92,
   "id": "3edaf99f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[20.0, 2]\n"
     ]
    }
   ],
   "source": [
    "print(numerical_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "64db4332",
   "metadata": {},
   "source": [
    "However, this representation is not esthetically nice and does not follow the PEP8 Python code styling guidelines. To know more about styling your code with [PEP8](https://peps.python.org/pep-0008/), check out the enclosed link.\n",
    "\n",
    "If we follow the guidelines, the one-liner will turn into the following structure, which is better structured and allows much easier to navigation through the code:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 93,
   "id": "49df84e4",
   "metadata": {},
   "outputs": [],
   "source": [
    "numerical_list = [\n",
    "    list_element\n",
    "    for list_element in my_list\n",
    "    if type(list_element) == int or type(list_element) == float\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6e6a1d5f",
   "metadata": {},
   "source": [
    "#### 4.2.3. Iterating over strings and sets"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "185a37d5",
   "metadata": {},
   "source": [
    "Iteration over strings and sets is similar to iteration over lists. Let's use the variables we have defined above and implement the "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 94,
   "id": "ea66bbaa",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Lola\n",
      "{True, 2, 'dog', 'cat', 'flowers', 20.0}\n"
     ]
    }
   ],
   "source": [
    "print(my_name)\n",
    "print(my_set)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 95,
   "id": "5a6602dc",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "L\n",
      "o\n",
      "l\n",
      "a\n"
     ]
    }
   ],
   "source": [
    "list_of_letters = []\n",
    "for letter in my_name:\n",
    "    print(letter)\n",
    "    list_of_letters.append(letter)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4d6b73a9",
   "metadata": {},
   "source": [
    "Or as a list comprehension:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 96,
   "id": "7d2c6084",
   "metadata": {},
   "outputs": [],
   "source": [
    "list_of_letters = [letter for letter in my_name]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 97,
   "id": "890cea76",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n",
      "2\n",
      "dog\n",
      "cat\n",
      "flowers\n",
      "20.0\n"
     ]
    }
   ],
   "source": [
    "set_elements_list = []\n",
    "for set_element in my_set:\n",
    "    print(set_element)\n",
    "    set_elements_list.append(set_element)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 98,
   "id": "8a1b5164",
   "metadata": {},
   "outputs": [],
   "source": [
    "set_elements_list = [set_element for set_element in my_set]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "37e87dcd",
   "metadata": {},
   "source": [
    "#### 4.2.4. Iterating over dictionaries"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 99,
   "id": "c89756f7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "name Lola\n",
      "age 20\n",
      "favourite_animal dog\n",
      "sibling_names ['John', 'Sarah']\n",
      "eye_color green\n",
      "height 165\n"
     ]
    }
   ],
   "source": [
    "for key in my_dict.keys():  # iterate over keys\n",
    "    print(key, my_dict[key])  # print dictionary key and the respective value"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 100,
   "id": "af501005",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Lola\n",
      "20\n",
      "dog\n",
      "['John', 'Sarah']\n",
      "green\n",
      "165\n"
     ]
    }
   ],
   "source": [
    "for value in my_dict.values():  # iterate over values\n",
    "    print(value)  # print dictionary value"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 101,
   "id": "89eff50f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "name Lola\n",
      "age 20\n",
      "favourite_animal dog\n",
      "sibling_names ['John', 'Sarah']\n",
      "eye_color green\n",
      "height 165\n"
     ]
    }
   ],
   "source": [
    "for key, value in my_dict.items():  # iterate over both keys and values\n",
    "    print(key, value)  # print dictionary key and value"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d5432a81",
   "metadata": {},
   "source": [
    "Let's create a dictionary from `my_dict` that would contain only string values:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 102,
   "id": "0c2b1ba4",
   "metadata": {},
   "outputs": [],
   "source": [
    "str_my_dict = {}  # initialize empty dictionary\n",
    "for key, value in my_dict.items():  # iterate over both keys and values\n",
    "    if type(value) == str:  # check the value type\n",
    "        str_my_dict[key] = value  # assign the value to its key in the *new* dictionary"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 103,
   "id": "e54536e2",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "dict_values(['Lola', 'dog', 'green'])"
      ]
     },
     "execution_count": 103,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "str_my_dict.values()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f6ecf11e",
   "metadata": {},
   "source": [
    "Let's now do the same thing but with dictionary comprehension:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 104,
   "id": "85774bc3",
   "metadata": {},
   "outputs": [],
   "source": [
    "str_my_dict = {\n",
    "    key: value\n",
    "    for key, value in my_dict.items()\n",
    "    if type(value) == str\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0ace5d5f",
   "metadata": {},
   "source": [
    "Another useful function is `zip()` which takes two or more iterable objects (e.g., lists) and joins them into tuple. Let's see how we can create a dictionary from two lists with the `zip()` function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 105,
   "id": "50885798",
   "metadata": {},
   "outputs": [],
   "source": [
    "list_with_keys = ['name', 'eye_color', 'favourite_animal']\n",
    "list_with_values = ['Lola', 'green', 'dog']\n",
    "new_dict = {}\n",
    "for key, value in zip(list_with_keys, list_with_values):\n",
    "    new_dict[key] = value"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 106,
   "id": "3c4c4657",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<zip object at 0x146dad44c4c0>\n"
     ]
    }
   ],
   "source": [
    "print(zip(list_with_keys, list_with_values))\n",
    "# by just printing the zip object, you won't see its content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 107,
   "id": "24353db5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[('name', 'Lola'), ('eye_color', 'green'), ('favourite_animal', 'dog')]\n"
     ]
    }
   ],
   "source": [
    "# To view the content, first convert the zip object into list\n",
    "print(list(zip(list_with_keys, list_with_values)))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8def4702",
   "metadata": {},
   "source": [
    "### 5. Creating custom functions and merging all functionality together!"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7e6ba439",
   "metadata": {},
   "source": [
    "Sometimes you will need to execute the same piece of code several times. Let's imagine that you have 10 different lists and you want to know if the largest list element is larger than the sum of all other elements in the list. An efficient way of approaching this task is to first, create a function with a keyword `def` that would take a list as input and return a boolean for the tested condition. Here is how to do it:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 108,
   "id": "07c54d46",
   "metadata": {},
   "outputs": [],
   "source": [
    "def is_larger(input_list):  # define function name and its arguments\n",
    "    # function body with the code\n",
    "    max_element = max(input_list)  # get maximum value in the list\n",
    "    sum_elements = sum([el for el in input_list if el != max_element])  # get the elements other than max\n",
    "    return max_element > sum_elements  # the output to return"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 109,
   "id": "4325a966",
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_max_and_sum(input_list):  # define function name and its arguments\n",
    "    # function body with the code\n",
    "    max_element = max(input_list)  # get maximum value in the list\n",
    "    sum_elements = sum([el for el in input_list if el != max_element])  # get the elements other than max\n",
    "    return max_element, sum_elements # you can output several values that are separated with a comma"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3c8db75f",
   "metadata": {},
   "source": [
    "Let's try out these functions! But first, we need to create some input data:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 110,
   "id": "1d2bda40",
   "metadata": {},
   "outputs": [],
   "source": [
    "lst_1 = [i for i in range(5)] + [20]\n",
    "lst_2 = [m / 10 for m in range(10)]\n",
    "lst_3 = [j for j in range(150, 800, 500)] + [k for k in range(200, 300, 50)]\n",
    "\n",
    "dict_with_lists = {\n",
    "    'lst_1': lst_1,\n",
    "    'lst_2': lst_2,\n",
    "    'lst_3': lst_3,\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 111,
   "id": "e10c1e2c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "For lst_1 , maximum value 20 is larger than the sum of other values.\n",
      "For lst_3 , maximum value 650 is larger than the sum of other values.\n"
     ]
    }
   ],
   "source": [
    "for lst_name, lst_i in dict_with_lists.items():\n",
    "    if is_larger(lst_i):\n",
    "        max_v, sum_v = get_max_and_sum(lst_i)\n",
    "        print('For', lst_name, ', maximum value', max_v, 'is larger than the sum of other values.')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "532dd142",
   "metadata": {},
   "source": [
    "#### Congrats! Now you know the basics of coding with Python!\n",
    "\n",
    "Of those intereseted, here is the list of useful resourses:\n",
    "\n",
    "0. The main [Python](https://www.python.org) page with the most recent documentation and [links](https://wiki.python.org/moin/BeginnersGuide) to learning platforms, excercises, cheat sheets and many other things for beginners.\n",
    "1. The [Rosalind](https://rosalind.info/problems/locations/) platform -- allows learning Python through Bioinformatics problem solving."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python-3.9.5",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
