{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "5b200fac",
   "metadata": {},
   "source": [
    "### 1. Variables in Python"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "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": "f4008fb2",
   "metadata": {},
   "source": [
    "### 2. Data types and operations"
   ]
  },
  {
   "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": 2,
   "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"
     ]
    }
   ],
   "source": [
    "int_number = 3\n",
    "float_number = 1.5\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))"
   ]
  },
  {
   "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": "code",
   "execution_count": 3,
   "id": "fa0e82fc",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.6666666666666666 0.667\n"
     ]
    }
   ],
   "source": [
    "x = 2\n",
    "y = 3\n",
    "print(x / y, round(x / y, 3))  # round() function allows rounding the float to n decimals, here n=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": 4,
   "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": "ea9516de",
   "metadata": {},
   "source": [
    "#### 2.2 Booleans"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9f4de541",
   "metadata": {},
   "source": [
    "Boolean values are either `True` or `False`. Note that integers `0` and `1` are equivalent to `False` and `True` respectively.\n",
    "\n",
    "For instance, we can compare the numbers with the operators less (`<`), greater (`>`), equal (`==`), not equal (`!=`) and, of course, less or equal (`<=`) and greater or equal (`>=`). This will yield a boolean output:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "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', 1 == True)"
   ]
  },
  {
   "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": 6,
   "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": 7,
   "id": "64fa7750",
   "metadata": {},
   "outputs": [],
   "source": [
    "my_name = 'Lola'"
   ]
  },
  {
   "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": 8,
   "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": "code",
   "execution_count": 9,
   "id": "a85705d4",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Let's save the transformed name into a new variable\n",
    "new_name = my_name.replace('la', 'ic')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ee4178ad",
   "metadata": {},
   "source": [
    "You can concatenate strings by using the `+` operator as follows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "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')\n",
    "# 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. To split a string, use `.split()` function, which allows splitting by a defined pattern:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "b312eaff",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Lola_Loic_Robert_and_Sarah_are_friends\n",
      "['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)\n",
    "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. 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": 12,
   "id": "9df9064e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "L a\n",
      "The first half of the name is: Lo\n"
     ]
    }
   ],
   "source": [
    "print(my_name[0], my_name[3])\n",
    "print('The first half of the name is:', 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": "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": 13,
   "id": "615855ee",
   "metadata": {},
   "outputs": [],
   "source": [
    "my_list = ['flowers', 20., 'cat', 'dog', 2, True, 'flowers', ['a', 'b', 5]]"
   ]
  },
  {
   "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": 14,
   "id": "b9a2bf09",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The first element of my_list is flowers\n",
      "The last two elements of my_list are ['flowers', ['a', 'b', 5]]\n",
      "Reversed list is: [['a', 'b', 5], 'flowers', True, 2, 'dog', 'cat', 20.0, 'flowers']\n"
     ]
    }
   ],
   "source": [
    "print('The first element of my_list is', my_list[0])\n",
    "print('The last two elements of my_list are', my_list[-2:])\n",
    "print('Reversed list is:', my_list[::-1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "f7695f16",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['flowers', 20.0, 'cat', 'dog', 2, True, 'flowers', ['a', 'b', 5], 'Lola']\n",
      "New length = 9\n"
     ]
    }
   ],
   "source": [
    "my_list.append(my_name)\n",
    "print(my_list)\n",
    "print('New length =', len(my_list))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d6d2776d",
   "metadata": {},
   "source": [
    "Note that `my_list` contains a sublist. Its 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": 16,
   "id": "9866e337",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['a', 'b', 5]\n",
      "a\n",
      "['a', 'b']\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 fisrt two elements of the sublist"
   ]
  },
  {
   "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": "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": 17,
   "id": "40e4adff",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'flowers', True, 2, 'pigeon', 20.0, 'parrot'}\n"
     ]
    }
   ],
   "source": [
    "my_set = {'flowers', 20.0, 'pigeon', 'parrot', 2, True, 'flowers'}\n",
    "print(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": "markdown",
   "id": "9c6976aa",
   "metadata": {},
   "source": [
    "Below you can find several examples of set operations, such as intersection, union, difference:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "50e40893",
   "metadata": {},
   "outputs": [],
   "source": [
    "my_list = ['flowers', 20.0, 'cat', 'dog', 2, True, 'flowers']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "cdd7e7da",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Intersection of sets: {'flowers', 20}\n",
      "Union of sets: {'flowers', True, 2, 'dog', 'b', 'c', 20.0, 'cat', 'a'}\n",
      "Difference of sets, option 1: {True, 2, 'cat', 'dog'}\n",
      "Difference of sets, option 2: {True, 2, 'cat', 'dog'}\n",
      "Symmetric difference of sets: {True, 2, 'dog', 'b', 'c', 'cat', 'a'}\n"
     ]
    }
   ],
   "source": [
    "my_set = set(my_list)\n",
    "new_set = {'a', 'b', 'c', 20, 'flowers'}\n",
    "\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",
    "\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": 20,
   "id": "9fcf25b7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'name': 'Lola', 'age': 20, 'favourite_animal': 'dog', 'sibling_names': ['John', 'Sarah']}\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)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "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())  # get all dictionary items (keys and values)\n",
    "print(my_dict.keys())  # get only dictionary keys\n",
    "print(my_dict.values())  #get only dictionary 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": 22,
   "id": "8af1ede2",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(20, 20)"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "my_dict['age'], my_dict.get('age')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "74e5b5f3",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(None, 'no info')"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "my_dict.get('eye_color'), my_dict.get('eye_color', 'no info')"
   ]
  },
  {
   "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": "8312c860",
   "metadata": {},
   "source": [
    "### 3. Conditional statements (if, elif, else)"
   ]
  },
  {
   "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": 24,
   "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": "4dfd8b7e",
   "metadata": {},
   "source": [
    "### 4. `While` and `for` loops"
   ]
  },
  {
   "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": 25,
   "id": "c63ea7dc",
   "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": 26,
   "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": 27,
   "id": "29b7df42",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[20.0, 2]\n"
     ]
    }
   ],
   "source": [
    "print(numerical_list)"
   ]
  },
  {
   "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": 28,
   "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": 29,
   "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": 30,
   "id": "c342a79d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[20.0, 2]\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": "37e87dcd",
   "metadata": {},
   "source": [
    "#### 4.2.4. Iterating over dictionaries"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "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"
     ]
    }
   ],
   "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": 32,
   "id": "af501005",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Lola\n",
      "20\n",
      "dog\n",
      "['John', 'Sarah']\n"
     ]
    }
   ],
   "source": [
    "for value in my_dict.values():  # iterate over values\n",
    "    print(value)  # print dictionary value"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "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"
     ]
    }
   ],
   "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": "f85a2003",
   "metadata": {},
   "source": [
    "One of the useful functions 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": 34,
   "id": "6870638d",
   "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": 35,
   "id": "0cba6751",
   "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"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "07c54d46",
   "metadata": {},
   "outputs": [],
   "source": [
    "def sum_values_in_list(input_list):  # define function name and its arguments\n",
    "    sum_ = 0\n",
    "    for element in input_list:\n",
    "        sum_ += element\n",
    "    return sum_  # the output to return"
   ]
  },
  {
   "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": 37,
   "id": "1d2bda40",
   "metadata": {},
   "outputs": [],
   "source": [
    "lst_1 = [i for i in range(5)]\n",
    "lst_2 = [m / 10 for m in range(10)]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "e10c1e2c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Sum of values in the first list = 10\n",
      "Sum of values in the second list = 4.5\n"
     ]
    }
   ],
   "source": [
    "print('Sum of values in the first list =', sum_values_in_list(lst_1))\n",
    "print('Sum of values in the second list =', sum_values_in_list(lst_2))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5249581e",
   "metadata": {},
   "source": [
    "Great, let's continue to the next notebook!"
   ]
  }
 ],
 "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
}
