{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "130cdd05-f376-42f8-a45f-f5643307c913",
   "metadata": {},
   "source": [
    "<center><H1>ACOL106: Data Structures</H1></center>\n",
    "<center><H2>(Recap: Python Module 1)</H2></center>\n",
    "This sequence of Jupyter notebooks is meant to help the students recap the material taught in ACOL100. We will revisit some of the key concepts of Python programming while focusing on those that will be important for Data Structures. This is mainly the first chapter of the prescribed textbook. Much of the material here is copied from the textbook. The advantage of the Jupyter notebook is that it gives you an opportunity to experiment with the code while you are trying to pick up the concepts. Such experimentation and engagement make it much easier to pick up the concepts.\n",
    "\n",
    "<H3>1. Python interpreter</H3> Python is an interpreted language, which means there is a software known as the \"Python interpreter\" that executes the programming instructions from the user interactively, one instruction at a time. A user can either interact with the Python interpreter instruction-by-instruction OR give a sequence of instructions to the interpreter to process together. The Jupyter notebook has a Python interpreter running in the background (<I>recall starting the jupyterlab program from the terminal to get this notebook started</I>), and you can interact with it using the code segments such as the one given below. Let us test this interpreter by asking it to execute the following simple instruction to print \"Hello World\". You can click on the code segment below and run it to see the result. Alternatively, if you are using this notebook in the HTML format, then you can copy the code in the code segments on this notebook, paste them as commands on a separate terminal on which you have the Python interpreter running, and see the results there."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "38698086-0421-4ba4-b797-895b31d52b60",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Hello World\n"
     ]
    }
   ],
   "source": [
    "print('Hello World')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1e67dd6e-518a-4cdd-b6d6-3282d5b76429",
   "metadata": {},
   "source": [
    "We can also give a sequence of commands such as given below: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "a4d9334e-95ca-4962-8614-b6b5353d143c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Hello World\n",
      "Hello Python\n",
      "Within iteration 0 of the loop\n",
      "Within iteration 1 of the loop\n",
      "Within iteration 2 of the loop\n",
      "Within iteration 3 of the loop\n",
      "Within iteration 4 of the loop\n"
     ]
    }
   ],
   "source": [
    "print('Hello World')\n",
    "print('Hello Python')\n",
    "for i in range(5):\n",
    "    print('Within iteration', i, 'of the loop')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0a1dfea7-7d06-421f-bfd9-6c0accf431c9",
   "metadata": {},
   "source": [
    "<FONT color=red>Questions:</FONT>\n",
    "<OL>\n",
    "    <LI> <I>There are programming languages (e.g., C) that are \"compiled\", where all the programming instructions are converted to machine code and then executed instead of a line-by-line conversion as in an interpreted language. What could be the advantages/disadvantages of an interpreted language over a compiled language.</I></LI>\n",
    "</OL>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8b4171d5-72f9-4d3c-955d-a66964bd73fe",
   "metadata": {},
   "source": [
    "<H3>2. Python objects</H3>\n",
    "Python is an <I>object-oriented</I> language, and <I>classes</I> form the basis of all data types. Even data types required for basic computations, such as integers, floating-point numbers, and strings, have built-in classes defined for them, and we need to know these to be able to do even basic data processing.\n",
    "<UL>\n",
    "    <LI>Class for integers: <b>int</b></LI>\n",
    "    <LI>Class for floating point numbers: <b>float</b></LI>\n",
    "    <LI>Class for strings: <b>str</b></LI>\n",
    "</UL>\n",
    "\n",
    "<h4>2.1 Assignment</h4>\n",
    "The most basic data processing instruction in any language is the assignment statement, where a value is \"assigned\" to an <i>identifier</i>. A simple example from the book is given below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "0e6aa16e-abf2-49f9-8af5-e362ea1ab898",
   "metadata": {},
   "outputs": [],
   "source": [
    "temperature = 98.6"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f0ae0ad5-57c7-4703-acb1-3da8df04df2d",
   "metadata": {},
   "source": [
    "Here <var>temperature</var> is an identifier. This identifier references (or gets associated with) a <b>float</b> object that has the value 98.6. The diagram below captures this notion.\n",
    "\n",
    "<img src=./Module-1/assignment.png style=\"width:250px;\">"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f6e367e7-e139-4289-8d84-bd8b0e94aac8",
   "metadata": {},
   "source": [
    "There are rules for what could be a valid identifier, and students should look this up. In short, most combinations of letters, numerals, and underscores that do not start with a numeral should be a valid identifier.\n",
    "\n",
    "An interesting property of the Python programming language is that we do not need to define the \"type\" of an identifier before assigning an object to it. In other languages, such as Java or C++, we must first define the type of an identifier before assignment. For example, a similar assignment statement in Java will be <mark><tt>float temperature = 98.6</tt></mark>. This is because Python is a <em>dynamically typed</em> language. This means that the same identifier can be used to reference a different type of object. For example, see the code below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "cdd89871-2df0-41f6-a953-6fc3c8e37092",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The temperature identifier is of type <class 'float'>\n",
      "The temperature identifier is of type <class 'str'>\n"
     ]
    }
   ],
   "source": [
    "temperature = 98.6\n",
    "print(\"The temperature identifier is of type\", type(temperature))\n",
    "temperature = 'Hello World'\n",
    "print(\"The temperature identifier is of type\", type(temperature))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2dfc2f88-17fd-4b10-abb7-e0d2c1f8534d",
   "metadata": {},
   "source": [
    "One can establish an <em>alias</em> by assigning a second identifier to an existing object using an assignment statement as given below:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "45cb8408-2516-49ba-993e-7a81dee7f30b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original 98.6\n"
     ]
    }
   ],
   "source": [
    "temperature = 98.6\n",
    "original = temperature\n",
    "print(\"Original\", original)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f82fea9f-80e6-4e22-a77c-e6da2e5d452c",
   "metadata": {},
   "source": [
    "The assignment statement <mark><tt>original = temperature</tt></mark> makes the identifier <mark><tt>original</tt></mark> reference the same object as referenced by <mark><tt>temperature</tt></mark>. The diagram below captures this notion.\n",
    "\n",
    "<img src=./Module-1/assignment-1.png style=\"width:300px;\">"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "02db691d-09b0-49e1-9082-fd79ce727583",
   "metadata": {},
   "source": [
    "There is always an identifer to the left of '=' of an assignment statement. To the right of '=', we could have an <em>expression</em> (which includes values such as 98.6 or another identifier). The object that the identifier on the left references depends on how the expression on the right gets evaluated. There are certain rules related to expression evaluation, that we will see soon. Let us see an example to get started:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "48b1dc7e-9794-4dcd-a81b-fe2115b7e9d6",
   "metadata": {},
   "outputs": [],
   "source": [
    "temperature = 98.6\n",
    "original = temperature\n",
    "temperature = temperature + 5"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22e37116-1006-4656-83cd-ac77095ca19f",
   "metadata": {},
   "source": [
    "What do you think is the result of the above sequence of instructions? Do identifiers <mark><tt>temperature</tt></mark> and <mark><tt>original</tt></mark> reference the same object storing value 103.6 at the end of these instructions? This crucially depends on how the expression <mark><tt>temperature + 5</tt></mark> is evaluated. It turns out, the value 98.6 associated with the object referenced by <mark><tt>temperature</tt></mark> is added to the value 5, and a new object is created to store the result 103.6. This new object is assigned to the identifier to the left of '=', which is <mark><tt>temperature</tt></mark>. So, after these lines <mark><tt>temperature</tt></mark> references a float object with value 103.6, whereas <tt>original</tt></mark> continues to reference the object with value 98.6. See diagram below. Add print statements in the code segment above to check for yourself. \n",
    "\n",
    "<img src=./Module-1/assignment-2.png style=\"height:80px;\">"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4273830f-c7b8-4e23-b3da-223eae9c449f",
   "metadata": {},
   "source": [
    "<h4>2.2 Creating and using objects</h4>\n",
    "Since objects and classes are at the core of programming in Python, it is important to have a familiarity with the key concepts to be able to start with basic programming. The full power of object-oriented programming is a separate topic, which we will recap soon. As you must have seen in the ACOL100 course, classes help to encapsulate data and define actions on the data so that a programmer who intends to use the code need not know the details of the inner workings within the class. An object is an instantiation of a class. For example, suppose there is a <mark><tt>Student</tt></mark> class that is designed to store grades and perform data processing on the grades of a student. We can instantiate an object for every student in the class. To instantiate an object of a class, we invoke the <b>constructor</b> of the class. For example, the <mark><tt>Student</tt></mark> class may have a constructor that takes as input the name of the student. Then, we can instantiate an object as <mark><tt>s1 = Student(\"Ragesh\")</tt></mark>. Python's <em>built-in</em> classes (i.e., classes that are pre-defined within Python) support what is known as a <b>literal</b> form for instantiating. The assignment statement we saw earlier: <mark><tt>temperature = 98.6</tt></mark> is an example of instantiating a <b>float</b> object using this literal form.\n",
    "\n",
    "<BR><BR>\n",
    "\n",
    "Classes have member functions that are known as <b>methods</b> that are invoked on a specific instance of the class using the dot operator ('.'). For example, the <mark><tt>Student</tt></mark> may have a method to calculate the CGPA of the student, which can be invoked using the statement <mark><tt>s1.calculateCGPA()</tt></mark>. Some methods can change the state of the corresponding object (on which the method is called), which are called <b>mutators</b>. Others may only provide access to the state and do not change the state. These are called <b>accessors</b>.\n",
    "\n",
    "<h4>2.3 Python's built-in classes</h4>\n",
    "To start doing basic programming, we must know the basic built-in classes of Python related to various types of data. One of the key concepts when dealing with Python objects is <b>mutability</b> of classes. A class is said to be <b>immutable</b> if the value of any object of the class cannot change after instantiation. For example, <mark><tt>float</tt></mark> is an immutable object. Classes where the value of the object is allowed to change after instantiation are known as <b>mutable</b> classes. We look at some of the basic built-in classes in Python below. \n",
    "\n",
    "<h5>The <mark><tt>bool</tt></mark> class:</h5> This is an immutable class for manipulating Boolean data. There are two literal forms: <mark><tt>True</tt></mark> and <mark><tt>False</tt></mark>. There is also a default constructor <mark><tt>bool()</tt></mark> that instantiates to <mark><tt>False</tt></mark>. There is also <mark><tt>bool(val)</tt></mark>. When <mark><tt>val</tt></mark> is a number, then <mark><tt>bool(val)</tt></mark> instantiates to <mark><tt>False</tt></mark> when <mark><tt>val</tt></mark> is 0, and <mark><tt>True</tt></mark>, otherwise. When <mark><tt>val</tt></mark> is a string, then <mark><tt>bool(val)</tt></mark> instantiates to <mark><tt>False</tt></mark> when <mark><tt>val</tt></mark> is an empty string, and <mark><tt>True</tt></mark>, otherwise. See some examples below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "63863398-190a-4b70-81fe-8ae308fb8c5c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "x, y, z, t:  True False False True\n"
     ]
    }
   ],
   "source": [
    "x = True\n",
    "y = False\n",
    "z = bool(\"\")\n",
    "t = bool(90)\n",
    "print(\"x, y, z, t: \", x, y, z, t)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "febaee02-7108-41e1-95f4-bfd342b7bab0",
   "metadata": {},
   "source": [
    "<h5> The <mark><tt>int</tt></mark> class</h5>\n",
    "This class is for manipulating integer values with arbitrary magnitude (<i>note that this is unlike in other languages, such as C, C++, where there are different data types for different precisions</i>). The literal forms are values, such as -1, 23, 456785. There is a constructor <mark><tt>int()</tt></mark> that instantiates to 0. There is also a constructor <mark><tt>int(val)</tt></mark>. When <mark><tt>val</tt></mark> is a float, then the instantiation is to the truncated value of <mark><tt>val</tt></mark>. For example, <mark><tt>int(3.14)</tt></mark> is instantiated to <mark><tt>3</tt></mark> and <mark><tt>int(-4.14)</tt></mark> is instantiated to <mark><tt>3</tt></mark>. When <mark><tt>val</tt></mark> is a string, then the instantiation is to the integer value of the string. See some examples below. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "edec7f5e-0944-457d-91d1-6f94f0e852f7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "x, y, z, t, r:  3 0 3 -4 14326\n"
     ]
    }
   ],
   "source": [
    "x = 3\n",
    "y = int()\n",
    "z = int(3.14)\n",
    "t = int(-4.14)\n",
    "r = int('14326')\n",
    "print(\"x, y, z, t, r: \", x, y, z, t, r)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "50e4222c-ef17-42ec-b6f8-3587df92043b",
   "metadata": {},
   "source": [
    "<h5> The <mark><tt>float</tt></mark> class</h5>\n",
    "This is to support operations on floating point numbers (<i>Python supports 64-bit precision adhering to the IEEE 754 standard</i>). Literal forms include simply the values, such as <mark><tt>98.6</tt></mark>. Another literal form is <mark><tt>6.022e23</tt></mark> that represents the number 6.022x10<sup>23</sup>. Other constructor examples include <mark><tt>float()</tt></mark> that instantiates to 0.0, <mark><tt>float(2)</tt></mark> that instantiates to 2.0, <mark><tt>float('3.14')</tt></mark> that instantiates to 3.14. See some examples below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "4dd3051b-0fc3-49bd-acd9-9bf2c026074b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "x, y, z, t, r:  2.0 6.022e+23 3.14 2.0 0.0\n"
     ]
    }
   ],
   "source": [
    "x = 2.0\n",
    "y = 6.022e23\n",
    "z = float('3.14')\n",
    "t = float(2)\n",
    "r = float()\n",
    "print(\"x, y, z, t, r: \", x, y, z, t, r)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "64eac07b-abd0-4000-b506-d37a8e5559af",
   "metadata": {},
   "source": [
    "<h5> The <mark><tt>list</tt></mark> class</h5>\n",
    "A list is a mutable class meant to store a sequence of references to arbitrary objects. They are <b>zero-indexed</b> and <b>array-based</b> sequences. The literal form involves using '[]' as delimiters. Some of the examples below should be sufficient to understand the options for constructing a list."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "a4415426-3955-4d01-8502-5e90b806534f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['red', 'green', 'blue'] [] [1, 3.14, 'red', True] [['red', 'green', 'blue'], [], [1, 3.14, 'red', True]]\n",
      "[]\n",
      "['T', 'E', 'S', 'T']\n",
      "[]\n",
      "['T', 'e', 's', 't', 'i', 'n', 'g', ' ', 'l', 'i', 's', 't', 's']\n"
     ]
    }
   ],
   "source": [
    "L1 = ['red', 'green', 'blue']\n",
    "L2 = [] #an empty list\n",
    "L3 = [1, 3.14, 'red', True]\n",
    "L4 = [L1, L2, L3]\n",
    "print(L1, L2, L3, L4)\n",
    "print(L4[1])\n",
    "L4[1] = ['T', 'E', 'S', 'T']\n",
    "print(L4[1])\n",
    "\n",
    "#The list() constructor instantiates to an empty list\n",
    "L5 = list() \n",
    "print(L5)\n",
    "\n",
    "#The list(val) constructor can be given any input of iterable type, such as string, list, dictionary, etc. \n",
    "L6 = list(\"Testing lists\")\n",
    "print(L6)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0e3f4623-d69b-4bf6-8e16-cb8654a57b9e",
   "metadata": {},
   "source": [
    "<h5> The <mark><tt>tuple</tt></mark> class</h5>\n",
    "This is an immutable version of lists. The literal form uses '()' as delimiters. The examples below should be sufficient to understand the constructions."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "ab3545b2-fbf6-467e-9377-446f6369329d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3\n",
      "((), (2,), (2, 3, 'red'))\n"
     ]
    }
   ],
   "source": [
    "T0 = () #an empty tuple\n",
    "T1 = (2,) #This is a special representation for a tuple with one element since (2) would also represent the number 2\n",
    "\n",
    "T2 = (2, 3, 'red')\n",
    "print(T2[1])\n",
    "# Try executing T2[1] = 4 to check that tuples are immutable\n",
    "\n",
    "# A tuple of tuples\n",
    "T4 = (T0, T1, T2)\n",
    "print(T4)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "715ceacf-e9e2-411b-8701-7ea6becb8f78",
   "metadata": {},
   "source": [
    "<h5> The <mark><tt>str</tt></mark> class</h5>\n",
    "Python’s <mark><tt>str</tt></mark> class is specifically designed to efficiently represent an immutable sequence of characters, based upon the Unicode international character set. The examples below should be self-explanatory: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "6abf9d4a-095f-43b7-9b64-894aaf46463c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "Hello\n",
      "Hello\n",
      "Don't worry\n",
      "Don't worry\n",
      "A newline\n",
      "example\n",
      "A multiline\n",
      "example using triple \"\n"
     ]
    }
   ],
   "source": [
    "S = '' #An empty string\n",
    "print(S)\n",
    "S = \"\" #Another empty string\n",
    "print(S)\n",
    "S = \"Hello\"\n",
    "print(S)\n",
    "S = 'Hello'\n",
    "print(S)\n",
    "S = \"Don't worry\"\n",
    "print(S)\n",
    "S = 'Don\\'t worry' # A backslash is used to differentiate the delimiters from characters within the string\n",
    "print(S)\n",
    "S = \"A newline\\nexample\"\n",
    "print(S)\n",
    "S = \"\"\"A multiline\n",
    "example using triple \\\"\"\"\"\n",
    "print(S)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6ddd1149-73d1-409c-abce-d85bc25a35e4",
   "metadata": {},
   "source": [
    "<h5> The <mark><tt>set</tt></mark> class</h5>\n",
    "Python’s <mark><tt>set</tt></mark> class represents the mathematical notion of a set, namely a collection of elements, without duplicates, and without an inherent order to those elements. Note that only instances of immutable types can be\n",
    "added to a Python set. Python uses curly braces { and } as delimiters for a set. Examples below are self-explanatory:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "id": "c8a154f9-c341-4a87-8dac-b28aabe3ad77",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "set()\n",
      "{1, 2, 3, 'red'}\n",
      "{'H', 'o', 'l', 'e'}\n",
      "{(2, 3), False, 2.13, (2,)}\n"
     ]
    }
   ],
   "source": [
    "S = set() #This instantiates to the empty set. Note that {} does not represent the empty set\n",
    "print(S)\n",
    "S = {1, 2, 3, 'red', 2, 1}\n",
    "print(S)\n",
    "\n",
    "# For an iterable parameter as input, the set is initialized with the elements of the input\n",
    "S = set(\"Hellooooooo\")\n",
    "print(S)\n",
    "\n",
    "# One can create more complex sets\n",
    "S1 = {(2, 3), False, 2.13, (2,)}\n",
    "print(S1)\n",
    "# On the other hand, one cannot create a set containing another set or a list, since sets and lists are mutable\n",
    "# Try creating a set such as S1 = {S, 1, 2} OR S1 = {[1, 2], 1, 2} to check."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "60dc45f2-39b0-4be5-9970-41b4e89390e2",
   "metadata": {},
   "source": [
    "<h5> The <mark><tt>dict</tt></mark> class</h5>\n",
    "Python’s dict class represents a dictionary, or mapping, from a set of distinct keys to associated values. For example, a dictionary might map from unique course ID numbers to larger records (such as the instructor’s name and prerequisite). Python implements a dict using an almost identical approach to that of a set, but with storage of the associated values. This also means that the dict class is mutable. The literal form includes the use of '{}' as delimiters. See examples below:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "id": "efd0bfdb-c675-47a8-8e21-bf7dde5e92ba",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{}\n",
      "{'ACOL100': 'Venkata', 'ACOL106': 'Ragesh'}\n",
      "{'ACOL100': ('Venkata', 'None'), 'ACOL106': ('Ragesh', 'ACOL100')}\n",
      "{'ACOL100': ('Venkata', 'None'), 'ACOL106': ('Ragesh', 'ACOL100')}\n"
     ]
    }
   ],
   "source": [
    "D1 = {} #An empty dictionary\n",
    "print(D1)\n",
    "\n",
    "D2 = {'ACOL100': 'Venkata', 'ACOL106': 'Ragesh'}\n",
    "print(D2)\n",
    "D2 = {'ACOL100': ('Venkata', 'None'), 'ACOL106': ('Ragesh', 'ACOL100')}\n",
    "print(D2)\n",
    "\n",
    "D3 = dict([('ACOL100', ('Venkata', 'None')), ('ACOL106', ('Ragesh', 'ACOL100'))])\n",
    "print(D3)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "29ff4893-fa15-4ee2-89cc-a451db6a8791",
   "metadata": {},
   "source": [
    "<h3>3. Expressions, operators, and precedence</h3>\n",
    "Existing values/objects can be combined into larger syntactic <b>expressions</b> using a variety of special symbols and keywords known as <b>operators</b>. The meaning of an operator depends on the type of its operands. For example, when <mark><tt>a</tt></mark> and <mark><tt>b</tt></mark> are numbers, the syntax <mark><tt>a+b</tt></mark> indicates addition, while if <mark><tt>a</tt></mark> and <mark><tt>b</tt></mark> are strings, the operator indicates concatenation. In this section, we describe Python’s operators in various contexts of the built-in types."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f8db3c7f-2af0-4b50-8df8-f32aae4108b7",
   "metadata": {},
   "source": [
    "<h4>The logical operators</h4>\n",
    "The following operators are supported on Boolean values: (i) Logical negation: <mark><tt>not</tt></mark>, (ii) Logical AND: <mark><tt>and</tt></mark>, (iii) Logical OR: <mark><tt>or</tt></mark>. See some examples below:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "id": "6a249ce9-7a18-48f0-8ac6-03c33c0c7542",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Hello 1\n",
      "Hello 2\n",
      "Hello 3\n"
     ]
    }
   ],
   "source": [
    "if(not False):\n",
    "    print(\"Hello 1\")\n",
    "a = True\n",
    "b = False\n",
    "if(a or (not b)):\n",
    "    print(\"Hello 2\")\n",
    "\n",
    "if(a or b):\n",
    "    print(\"Hello 3\")\n",
    "\n",
    "if(b and a):\n",
    "    print(\"Hello 4\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4029682d-3cc9-406a-97a9-aeb26e721b70",
   "metadata": {},
   "source": [
    "<h4>The equality operators</h4>\n",
    "Python supports the following operators to test two notions of equality: (i) Same identity: <mark><tt>is</tt></mark>, (ii) Different identity: <mark><tt>is not</tt></mark>, (iii) Equivalent: <mark><tt>==</tt></mark>, (iv) Not Equivalent: <mark><tt>!=</tt></mark>. The expression <mark><tt>a is b</tt></mark> evaluates to <mark><tt>True</tt></mark>, precisely when identifiers <mark><tt>a</tt></mark> and <mark><tt>b</tt></mark> are aliases for the same object. The expression <mark><tt>a == b</tt></mark> tests a more general notion of equivalence. If identifiers <mark><tt>a</tt></mark> and <mark><tt>b</tt></mark> refer to the same object, then <mark><tt>a == b</tt></mark> should also evaluate to <mark><tt>True</tt></mark>. Yet <mark><tt>a == b</tt></mark> also evaluates to <mark><tt>True</tt></mark> when the identifiers refer to different objects that happen to have values that are deemed equivalent. The precise notion of equivalence depends on the data type. For example, two strings are considered equivalent if they match character for character. Two sets are equivalent if they have the same contents, irrespective of order. In most programming situations, the equivalence tests <mark><tt>==</tt></mark> and <mark><tt>!=</tt></mark> are the appropriate operators; use of <mark><tt>is</tt></mark> and <mark><tt>is not</tt></mark> should be reserved for situations in which it is necessary to detect true aliasing. Here are a few examples to test our understanding. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 70,
   "id": "867bc0b8-a3e0-4edb-8e4a-8c3ead2c6465",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Hello 1\n",
      "Hello 2\n",
      "Hello 4\n"
     ]
    }
   ],
   "source": [
    "A = [1, 2, 3]\n",
    "B = A\n",
    "if(B is A):\n",
    "    print(\"Hello 1\")\n",
    "if(B == A):\n",
    "    print(\"Hello 2\")\n",
    "\n",
    "C = [1, 2, 3]\n",
    "D = [1, 2, 3]\n",
    "if(C is D):\n",
    "    print(\"Hello 3\")\n",
    "if(C == D):\n",
    "    print(\"Hello 4\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9d4ba81a-0f3d-4eef-905d-ff6b9feee82f",
   "metadata": {},
   "source": [
    "<h4>The comparison operators</h4>\n",
    "The  standard comparison operators <mark><tt><, <=, >, >=</tt></mark> are defined on numeric and string types. These operators have expected behaviour for numeric types, and are defined lexicographically and case-sensitively for strings. See a few examples below:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 75,
   "id": "b915ba30-3caf-4f22-b6ed-65bd2e593938",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n"
     ]
    }
   ],
   "source": [
    "print(\"Ragesh\" < \"ragesh\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f3348ede-a840-427d-8d92-35e77049b6c9",
   "metadata": {},
   "source": [
    "<h4>The arithmetic operators</h4>\n",
    "Python supports the following arithmetic operators on numeric types: (i) <mark><tt>+</tt></mark> addition, (ii)<mark><tt>−</tt></mark> subtraction, (iii) <mark><tt>*</tt></mark> multiplication, (iv) <mark><tt>/</tt></mark> true division, (v) <mark><tt>//</tt></mark> integer division, (vi) <mark><tt>%</tt></mark> the modulo operator. There are a few subtle points regarding these operations that students should pick up from the book. See a few examples below:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 80,
   "id": "e498e5c0-2de2-43b5-90d0-f35c7637107c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "6\n",
      "3\n",
      "-7\n",
      "-7\n",
      "2.0\n",
      "1.919999999999999\n"
     ]
    }
   ],
   "source": [
    "print(27//4)\n",
    "print(27%4)\n",
    "print(-27//4)\n",
    "print(27//-4)\n",
    "print(8.2//3.14)\n",
    "print(8.2%3.14)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ed3e3d92-885b-4585-87f6-b2c296c612b3",
   "metadata": {},
   "source": [
    "<h4>The bitwise operators</h4>\n",
    "Python provides the following bitwise operators for integers: (i) <mark><tt>∼</tt></mark> bitwise complement (prefix unary operator), (ii) <mark><tt>&</tt></mark> bitwise, (iii) <mark><tt>|</tt></mark> bitwise or, (iv) <mark><tt>ˆ</tt></mark> bitwise exclusive-or, (v) <mark><tt><<</tt></mark> shift bits left, filling in with zeros, (vi) <mark><tt>>></tt></mark> shift bits right, filling in with sign bit. See a few examples below that you can further experiment with:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 89,
   "id": "339f1060-6e72-4edc-bda2-dac55d84ef99",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3\n",
      "26\n",
      "8\n",
      "13\n",
      "9\n"
     ]
    }
   ],
   "source": [
    "# The bit representation of 13 is 1101. So the bit representation of 13>>2 should be 11 (i.e., 3 in decimal)\n",
    "print(13>>2) \n",
    "\n",
    "print(13<<1)\n",
    "print(12 & 9)\n",
    "print(12 | 9)\n",
    "print(12 ^ 5)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dae1c9d3-a2f1-4732-9d63-acf1baa0c66b",
   "metadata": {},
   "source": [
    "<h4>The sequence operators</h4>\n",
    "Each of Python’s built-in sequence types (str, tuple, and list) support the following operator syntaxes:\n",
    "<UL>\n",
    "    <LI> <mark><tt>s[j]</tt></mark>: element at index j</LI>\n",
    "    <LI> <mark><tt>s[start:stop]</tt></mark>: slice including indices [start,stop)</LI>\n",
    "    <LI> <mark><tt>s[start:stop:step]</tt></mark>: slice including indices start, start + step, start + 2 step, . . . , up to but not equalling or stop</LI>\n",
    "    <LI> <mark><tt>s + t</tt></mark>: concatenation of sequences</LI>\n",
    "    <LI> <mark><tt>k*s</tt></mark>: shorthand for s + s + s + ... (k times)</LI>\n",
    "    <LI> <mark><tt>val in s</tt></mark>: containment check</LI>\n",
    "    <LI> <mark><tt>val not in s</tt></mark>: non-containment check</LI>\n",
    "</UL>\n",
    "Python uses a <b>slicing</b> notation to describe subsequences of a sequence. Slices are described as half-open intervals, with a start index that is included, and a stop index that is excluded. For example, the syntax <mark><tt>data[3:8]</tt></mark> denotes a subsequence including the five indices: 3,4,5,6,7. An optional “step” value, possibly negative, can be indicated as a third parameter of the slice. If a start index or stop index is omitted in the slicing notation, it is presumed to designate the respective extreme of the original sequence.\n",
    "<BR><BR>\n",
    "\n",
    "Because lists are mutable, the syntax <mark><tt>s[j] = val</tt></mark> can be used to replace an element at a given index. Lists also support a syntax, <mark><tt>del s[j]</tt></mark>, that removes the designated element from the list. Slice notation can also be used to replace or delete a sublist.<BR><BR>\n",
    "\n",
    "The notation <mark><tt>val in s</tt></mark> can be used for any of the sequences to see if there is an element equivalent to <mark><tt>val</tt></mark> in the sequence. For strings, this syntax can be used to check for a single character or for a larger substring, as with \"amp\" in \"example\". All sequences define comparison operations based on lexicographic order, performing an element-by-element comparison until the first difference is found. For example, <mark><tt>[5, 6, 9] < [5, 7]</tt></mark> because of the entries at index 1. Therefore, the following operations are supported by sequence types:\n",
    "<UL>\n",
    "    <LI> <mark><tt>s == t</tt></mark>: equivalent (element by element)</LI>\n",
    "    <LI> <mark><tt>s != t</tt></mark>: not equivalent</LI>\n",
    "    <LI> <mark><tt>s < t</tt></mark>: lexicographically less than</LI>\n",
    "    <LI> <mark><tt>s <= t</tt></mark>: lexicographically less than or equal to</LI>\n",
    "    <LI> <mark><tt>s > t</tt></mark>: lexicographically greater than</LI>\n",
    "    <LI> <mark><tt>s >= t</tt></mark>: lexicographically greater than or equal to</LI>\n",
    "</UL>\n",
    "Let's experiment with allt he sequence operators below:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 106,
   "id": "08e84289-86b0-440a-9fdc-f06b6b52c9e9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "A\n",
      "['B', 'C', 'D', 'E']\n",
      "['B', 'D', 'F', 'H', 'J']\n",
      "['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R']\n",
      "['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N']\n",
      "['O', 'P', 'Q', 'R']\n",
      "['O', 'P', 'Q', 'R', 'O', 'P', 'Q', 'R']\n",
      "Hello 1\n",
      "['O', 'P', 'Q', 'R']\n",
      "['O', 'P', 'R']\n",
      "['O', 'Z', 'R']\n",
      "Hello 3\n",
      "Hello 4\n"
     ]
    }
   ],
   "source": [
    "L = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N']\n",
    "print(L[0])\n",
    "print(L[1:5])\n",
    "print(L[1:10:2])\n",
    "S = ['O', 'P', 'Q', 'R']\n",
    "print(L+S)\n",
    "print(L)\n",
    "print(S)\n",
    "print(2*S)\n",
    "if('G' in L):\n",
    "    print(\"Hello 1\")\n",
    "if('G' in S):\n",
    "    print(\"Hello 2\")\n",
    "\n",
    "# Delete an element of the list \n",
    "print(S)\n",
    "del S[2]\n",
    "print(S)\n",
    "S[1] = 'Z'\n",
    "print(S)\n",
    "\n",
    "if(L != S):\n",
    "    print(\"Hello 3\")\n",
    "    \n",
    "if(L < S):\n",
    "    print(\"Hello 4\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "23d7d4e4-6269-4279-b35b-c89708a21e8b",
   "metadata": {},
   "source": [
    "<h4>Operators for sets</h4>\n",
    "Sets support the following operators:\n",
    "<UL>\n",
    "    <LI> <mark><tt>key in s</tt></mark>: containment check</LI>\n",
    "    <LI> <mark><tt>key not in s</tt></mark>: non-containment check</LI>\n",
    "    <LI> <mark><tt>s1 == s2</tt></mark>: s1 is equivalent to s2</LI>\n",
    "    <LI> <mark><tt>s1 != s2</tt></mark>: s1 is not equivalent to s2</LI>\n",
    "    <LI> <mark><tt>s1 <= s2</tt></mark>: s1 is subset of s2</LI>\n",
    "    <LI> <mark><tt>s1 < s2</tt></mark>: s1 is proper subset of s2</LI>\n",
    "    <LI> <mark><tt>s1 >= s2</tt></mark>: s1 is superset of s2</LI>\n",
    "    <LI> <mark><tt>s1 > s2</tt></mark>: s1 is proper superset of s2</LI>\n",
    "    <LI> <mark><tt>s1 | s2</tt></mark>: the union of s1 and s2</LI>\n",
    "    <LI> <mark><tt>s1 & s2</tt></mark>: the intersection of s1 and s2</LI>\n",
    "    <LI> <mark><tt>s1− s2</tt></mark>: the set of elements in s1 but not s2</LI>\n",
    "    <LI> <mark><tt>s1 ˆ s2</tt></mark>: the set of elements in precisely one of s1 or s2</LI>\n",
    "</UL>\n",
    "Let's experiment with these operators below:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 111,
   "id": "c7d5defa-a199-4cc4-b8a3-ef960c95ca35",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Hello 1\n",
      "Hello 4\n",
      "{1, 2, 3, 4, 5}\n",
      "{10, 11, 12}\n",
      "{1, 2, 3, 4, 5, 10, 11, 12}\n"
     ]
    }
   ],
   "source": [
    "s1 = {1, 2, 3, 4, 5, 6, 7, 8, 9}\n",
    "x = 2\n",
    "if(x in s1):\n",
    "    print(\"Hello 1\")\n",
    "s2 = {6, 7, 8, 9, 10, 11, 12}\n",
    "if(s1 == s2):\n",
    "    print(\"Hello 2\")\n",
    "\n",
    "if(s1 <= s2):\n",
    "    print(\"Hello 3\")\n",
    "\n",
    "if(s1 <= s1|s2):\n",
    "    print(\"Hello 4\")\n",
    "\n",
    "print(s1 - s2)\n",
    "print(s2 - s1)\n",
    "print(s1 ^ s2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "61cebd27-1fdd-42fe-b6b0-63de18406a1e",
   "metadata": {},
   "source": [
    "<h4>Operators for dictionaries</h4>\n",
    "The supported operators are as follows:\n",
    "<UL>\n",
    "    <LI> <mark><tt>d[key]</tt></mark>: value associated with given key</LI>\n",
    "    <LI> <mark><tt>d[key] = value</tt></mark>: set (or reset) the value associated with given key</LI>\n",
    "    <LI> <mark><tt>del d[key]</tt></mark>: remove key and its associated value from dictionary</LI>\n",
    "    <LI> <mark><tt>key in d</tt></mark>: containment check</LI>\n",
    "    <LI> <mark><tt>key not in d</tt></mark>: non-containment check</LI>\n",
    "    <LI> <mark><tt>d1 == d2</tt></mark>: d1 is equivalent to d2</LI>\n",
    "    <LI> <mark><tt>d1 != d2</tt></mark>: d1 is not equivalent to d2</LI>\n",
    "</UL>\n",
    "Let's experiment:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 119,
   "id": "c4460e53-b3bb-4a8d-b7c1-068581c70967",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "('yellow', 'liquid')\n",
      "('blue', 'solid')\n",
      "('green', 'solid')\n",
      "Hello 2\n"
     ]
    }
   ],
   "source": [
    "d = {'sample1': ('green', 'solid'), 'sample2': ('yellow', 'liquid'), 'sample3': ('red', 'gas')}\n",
    "print(d['sample2'])\n",
    "d['sample3'] = ('blue', 'solid')\n",
    "print(d['sample3'])\n",
    "\n",
    "s = 'sample1'\n",
    "if(s in d):\n",
    "    print(d[s])\n",
    "\n",
    "d1 = {'sample1': ('green', 'solid'), 'sample2': ('yellow', 'liquid'), 'sample3': ('red', 'gas')}\n",
    "if(d == d1):\n",
    "    print(\"Hello 1\")\n",
    "\n",
    "d2 = {'sample1': ('green', 'solid'), 'sample2': ('yellow', 'liquid'), 'sample3': ('blue', 'solid')}\n",
    "if(d == d2):\n",
    "    print(\"Hello 2\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7c2f7c44-1bc3-4db5-97c6-80e66030ef03",
   "metadata": {},
   "source": [
    "<h4>Extended assignment operator</h4>\n",
    "Students who have been exposed to programming languages such as Java, C++ must be used to assignment statements such as <mark><tt>count+=1</tt></mark> for incrementing the value of variable count by 1. This is the short way to write <mark><tt>count = count + 1</tt></mark>. However, in Python <mark><tt>count = count + 1</tt></mark> has a different interpretation. Here, <mark><tt>count + 1</tt></mark> creates a new object with value one more than the value in the object referenced by <mark><tt>count</tt></mark>. This new object is assigned the identifier <mark><tt>count</tt></mark>. The reason is that the <mark><tt>int</tt></mark> class is <b>immutable</b>, so its value cannot be changed by 1. In cases where the class is mutable, <mark><tt>+=</tt></mark> has a different effect as exhibited by the example below. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 121,
   "id": "daf2eb36-dddb-4619-9ce4-b1c91abbf792",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1, 2, 3, 4, 5]\n",
      "[1, 2, 3, 4, 5, 6, 7]\n"
     ]
    }
   ],
   "source": [
    "alpha = [1, 2, 3]\n",
    "beta = alpha # an alias for alpha\n",
    "beta += [4, 5] # extends the original list with two more elements\n",
    "beta = beta + [6, 7]  # reassigns beta to a new list [1, 2, 3, 4, 5, 6, 7]\n",
    "print(alpha) # will be [1, 2, 3, 4, 5]\n",
    "print(beta)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "42e80de1-a54a-4b6e-b7e8-62d738750ab8",
   "metadata": {},
   "source": [
    "<h3>Compound Expressions and Operator Precedence</h3>\n",
    "Programming languages must have clear rules for the order in which compound expressions, such as <mark><tt>5 + 2*3</tt></mark>, are evaluated. The formal order of precedence for operators in Python is given in the Table below. Operators in a category with higher precedence will be evaluated before those with lower precedence, unless the expression is otherwise parenthesized. Therefore, we see that Python gives precedence to multiplication over addition, and therefore evaluates the expression <mark><tt>5 + 2*3</tt></mark> as <mark><tt>5 + (2*3)</tt></mark>, with value 11, but the parenthesized expression <mark><tt>(5 + 2) * 3</tt></mark> evaluates to value 21. Operators within a category are typically evaluated from left-to-right, thus <mark><tt>5 − 2 + 3</tt></mark> has a value of 6. Exceptions to this rule include that unary operators and exponentiation are evaluated from right to left.<BR><BR>\n",
    "\n",
    "Python allows a <b>chained assignment</b>, such as <mark><tt>x = y = 0</tt></mark>, to assign multiple identifiers to the rightmost value. Python also allows the chaining of comparison operators. For example, the expression <mark><tt>1 <= x + y <= 10</tt></mark> is evaluated as the compound <mark><tt>(1 <= x + y) and (x + y <= 10)</tt></mark>, but without computing the intermediate value x + y twice.\n",
    "\n",
    "<img src=./Module-1/precedence.png style=\"height:400px;\">\n",
    "\n",
    "Some experiments are given below. You should run more experiments."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 129,
   "id": "a5505695-d471-429f-b893-a80eb68d5aca",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n",
      "8\n"
     ]
    }
   ],
   "source": [
    "print(2**-10+1 < 3+5*6/100+20)\n",
    "print(2 * 3 + 5 & 10 + 2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1f94d752-1c98-42cd-8a59-297326f7cb39",
   "metadata": {},
   "source": [
    "<h3>4. Module Endnote</h3>\n",
    "In this module, we have learnt basic elements of Python programming, including instantiating and working with Python's built-in classes. We have also learnt the use of operators and expressions to process a bunch of objects. Is this sufficient to start writing interesting Python programs? Yes, we can write some very basic programs using expressions and operators to do \"<i>calculator-like</i>\" computations. However, it would be worth waiting to learn a few more concepts, such as <em>conditionals</em> and <em>loops</em>, to exploit the full power of programming. This is known as <b>control flow</b>, and we start the discussion in the next module. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "453fcde0-0295-464d-9036-d5fffbb2614b",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
