diff --git a/Part One/1-1-introduction-to-python-programming.ipynb b/Part One/1-1-introduction-to-python-programming.ipynb index fa9424e..8568188 100644 --- a/Part One/1-1-introduction-to-python-programming.ipynb +++ b/Part One/1-1-introduction-to-python-programming.ipynb @@ -1 +1 @@ -{"cells":[{"cell_type":"markdown","metadata":{},"source":["**Learning Python -- The Programming Language for Artificial Intelligence and Data Science**\n","\n","**Lecture 1: Introduction to Python Programming**\n","\n","**By Allen Y. Yang, PhD**\n","\n","(c) Copyright Intelligent Racing Inc., 2021-2024. All rights reserved. Materials may NOT be distributed or used for any commercial purposes.\n","\n","If you just start reading this website, you have found the coding site for the introductory Python programming course developed by Dr. Allen Y. Yang. At the time of creating this course, Dr. Yang is in the faculty of the Department of EECS at the University of California, Berkeley. You may find his academic website at: https://people.eecs.berkeley.edu/~yang/\n"," \n","Beyond a research career at Berkeley, Dr. Yang is immensely interested about innovation and entrepreneurship, partially thanks to the geolocation of Berkeley close to Silicon Valley, a famous place in the US and around the world for its technology innovation and many inspiring entrepreneurial stories. Intelligent Racing is just one of many such companies founded in Silicon Valley by Dr. Yang, with the singular purpose to deliver state-of-the-art science and technology learning curricula to students, who will become the next technology leader and successful entrepreneurs.\n","\n","In this course, we will be learning about the programming language of Python. If this is your very first time hearing about Python programming,\n","language, then you are in the right place: This course is for you!\n"," \n","In fact, you are not alone wondering: \n"," 1. How can a computer language help you to communicate with a computer?\n"," 2. What is Python?\n"," 3. Is Python a suitable language to learn for beginners?\n","\n","For the rest of the course in ten lectures, we will gradually learn about the in-and-out of Python programming that is rigorous and fun for beginners. You will learn about all the basic elements of Python to effectively use the language to start coding useful computer programs and solving intersting practical real-world problems. We hope you will find that Python is a quite user-friendly programming language whose use cases may only be limited by your imagination.\n","\n","Of course, being a super user-friendly programming language that is easy to use for humans does not mean that Python is good for every occasion. One can quickly point out several obvious examples: \n","\n","First, Python is not the fastest language to run on computers. There are more traditional languages that were designed to be very easy to be understood by the processors of the computer and hence can be executed faster than Python. \n","\n","Second, Python is not supported by all computing platforms. For example, it is still relatively difficult to code up and run Python programs on iOS or Android systems natively. To best develop applications that run on mobile platforms, one is recommended to continue their programming journey to pick up some other languages."]},{"cell_type":"markdown","metadata":{},"source":["# Keywords\n","\n","In this interactive lecture nodes, written in the style of Jupyter Notebook, we will impose a prelude section for each lecture called *Keywords*. This is the place we will help the learner to highlight some new technical jargons that will be introduced more formally in the lecture note. Our goal is that the learner can take advantage of this structure to quickly familarize themselves with the upcoming new concepts, and the section together with the later Summary section may also help the learner to better review the content of each lecture after taking the classes.\n","\n","* **Jupyter Notebook**: Jupyter Notebook is an open-source computer document format developed by Project Jupyter Foundation. The document can be hosted and interactively edited within a web browser similar to a webpage, and then the web browser may connect with several supported language kernels to interpret the code embedded within the document. A primary programming language used in Jupyter Notebook is Python.\n","* **Interpreted language**: Python is an interpreted language that can be understood and executed by a computer line by line. \n","* **Function**: a function in a programming language is a piece of stand-alone code that encaptulates a set of starting conditions set by its input arguments and a set of return results by its return arguments. \n","* **Data type**: Data when stored in computer memory must declare its data type, such as integer, floating point, text (as strings), etc.\n","* **Debug**: Debug is a programming jargon referring to the practice of finding programming errors when designing a computer code."]},{"cell_type":"markdown","metadata":{},"source":["# Running First Python Code in Command Line\n","After we have done a brief motivation about the Python language, we arrive at the best part of our coding exercise. Below, we are going to see, for the first time, how Python executes some basic computing commands. But before we do that, let us understand how a typical Python language command is executed by computer that is dictated by its language standards.\n","\n","The most important rule to remember for Python is that: *it is a high-level, interpreted language.* Let us further consider this statement below.\n","1. *Python is a high-level language*. This usually means two things: First, programming statements of a high-level language broadly adopt words in natural languages (mostly English) and math symbols that are fairly easy for humans to understand. Second, high-level statements are more descriptive about user's intent to solve a problem and more abstract about the execution of the statements by the computer, compared to low-level languages that typically are descriptive about computer processor's precise but sometimes rather tedious steps to execute the code.\n","2. *Python is an interpreted language*. It means the execution of language statements is more direct and freely, compared to compiled languages. A compiled language must go through a compiling process before a code can be executed. An interpreted language such as Python allows users to freely execute any one line of or a block of multiple lines of code without explicitly invoking a compiling process.\n","\n","Let us immediately run an example to have Python solving a simple problem for us. In this example, we want to know the result of an arithmetic problem: 3+2 = ?\n","\n","To get the result, this code site has prepared the following code block. On the left-hand side at the beginning of the code block, there is a triangle symbol that means asking Python to **RUN** this code. Please use your mouse to left click this triangle symbol and observe the result of your action. "]},{"cell_type":"code","execution_count":null,"metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","trusted":true},"outputs":[],"source":["3+2"]},{"cell_type":"markdown","metadata":{},"source":["Congratulations! You have just run your first Python code. The result of 5 can be seen in an *output* block, which is usually called **the terminal output** or **the console output**. It is so called because, before the invention of graphical user interface (GUI), all computer intput/output interaction with users was done on text-based terminal or console environment. Therefore, in many programming languages, text output by default always displays in the terminal environment. In fact, you may find the terminal application in most popular operating systems including Windows, Mac OSX, and Linux. \n","\n","We also see in this coding exercise, the Python statement includes three elements, namely, an integer number 3, an integer number 2, and an arithmetic operator +. Python adopts the same meanings of these math symbols and the proper order how an addition calculation should be denoted in arithmetic. Therefore, this is an example of a high-level programming.\n","\n","By clicking the **RUN** button, Python immediately executes the statement and results its computation result in the line immediately following the statement. This is thanks to the fact that Python is an interpreted language.\n","\n","Another important fact about running the above code is that the reader should be aware that we are in a special Python development environment. A development environment is itself a computer program, where the code of a program can be written and more importantly often be executed within the environment. The development environment implemented by Kaggle uses the development style of so-called Jupyter Notebook. This style of Python programming allows users to directly edit and run their code on web browsers, and the users can conveniently select to run any particular code block or run all the code blocks together on the same page. If you pay attention to the top of this Kaggle webpage, right under the menu bar, there is a double-triangle button that is marked as **RUN ALL**. This is the command to sequentially run all the code blocks on this page in one shot. You may try this function to see the result."]},{"cell_type":"markdown","metadata":{},"source":["Next, let us see Python interprets and executes a block of code together (by clicking the **RUN** button of the next code block):"]},{"cell_type":"code","execution_count":null,"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":true},"outputs":[],"source":["print(3+2.0)\n","print(17/3)"]},{"cell_type":"markdown","metadata":{},"source":["In the above code block, we see the use of **print()** function. As a special rule of Jupyter Notebook environment, if a code block contains only arithmetic equations, only the last equation will output its result on the browser. In the above code block, we want a block to contain tasks to evaluate three equations. We then use a Python keyword **print()** to ask the environment to print out each of the three results. \n","\n","A function in a high-level programming language takes in one or more input arguments, and then output one or more return values that are also called output. In Python, input arguments of a function such as **print()** are denoted using the pair of paratheses. For example, the input of the first print function is 3+2.0. After you **RUN** the code, its output result is 5.0. The input of the second print function is 17/3, and its output result is 5.666666666666667."]},{"cell_type":"markdown","metadata":{},"source":["You may also notice from the above code block that the Python calculation results from two seemingly very similar expressions, one of which is 3+2 and the other is 3+2.0, also return similar but different values. The result for 3+2 is 5, and for 3+2.0 is 5.0\n","\n","The difference lies in the way numeric numbers are represented in computer. For numbers 3, 2, and 5, they are called integers. But for 2.0 and 5.0, they are called floating numbers, or floats. Even when mathematically 5 and 5.0 are equal in magnitude, the use of fraction indicates one of them (i.e., 5) is an integer while the other that contains the fraction part is a float. We will discuss Python numeric type in more detail in the next lecture."]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["print(type(5))\n","print(type(5.0))"]},{"cell_type":"markdown","metadata":{},"source":["The type of a number or a function return value can be explicitly checked using another useful Python function, with the keyword **type()**. We observe after running the above code block, that we print the type of number 5 and the type of number 5.0, respectively. \n","\n","To correctly read the two statements, the results from nested functions are calculated in stages from right to the left. For example, the statement **print(type(5))** should be understood as firstly query the type of number 5 using the type() function, then the output of the type() function is fed into the second print() function as its input. The final result is the print out of the type of the integer 5.\n","\n","Finally, let us observe the information from the output of the two statements. The first result indicates number 5 is an 'int' type, which is a text string representing the integer type. The second result indicates number 5.0 is a 'float' type, a text string representing the float type. The two results also indicate that both the int and float types are classes. In fact, all Python numeric or other more complex value types are implemented as \"classes\". The use of class concept signals the third attributes of Python language (the first two being that it is high-level and interpreted):\n","\n","*Python is an object-oriented programming language.*\n","\n","In later lectures, we will go over the principles of object-oriented programming in details. For now, we will conclude the first lecture. "]},{"cell_type":"markdown","metadata":{},"source":["# Summary\n","\n","* Python is a high-level, interpreted programming language.\n","* Function print() outputs its input argument(s) as string output\n","* Python displays text output by print() in the terminal environment.\n","* In Jupyter Notebook or the terminal, Python will also evaluate and print out the value of the last numeric expression.\n","* Function type() outputs the type of the input argument.\n","* A class-type variable indicates the variable is created as a class type in object-oriented programming."]},{"cell_type":"markdown","metadata":{},"source":["# Exercises\n","\n","1. Create two integer variables: a = 10 and b = 5. Then use print() function to print out the division result of a / b. \n","\n","2. Please pay attention to the format of the above result, which indicates that it is not an integer. To verify that, continue with the above program, and print out the output of the function type(a/b). What can you say about this result?\n","\n","3. Print out the result of 10 factorial, that is, the product of all positive integers from 1 to 10.\n","\n","4. Can you list a few criteria that set apart between high-level programming languages such as Python and low-level programming languages used in early days of the computer industry?\n","\n","5. Debug: Please correct the code below so that it can be correctly run in Python"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-05-27T21:23:43.653811Z","iopub.status.busy":"2021-05-27T21:23:43.653419Z","iopub.status.idle":"2021-05-27T21:23:43.660137Z","shell.execute_reply":"2021-05-27T21:23:43.658547Z","shell.execute_reply.started":"2021-05-27T21:23:43.653772Z"},"trusted":true},"outputs":[],"source":["Print 3 + 2"]}],"metadata":{"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":30527,"isGpuEnabled":false,"isInternetEnabled":true,"language":"python","sourceType":"notebook"},"kernelspec":{"display_name":"Python 3","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.10.12"}},"nbformat":4,"nbformat_minor":4} +{"cells":[{"cell_type":"markdown","metadata":{},"source":["**Learning Python -- The Programming Language for Artificial Intelligence and Data Science**\n","\n","**Lecture 1: Introduction to Python Programming**\n","\n","**By Allen Y. Yang, PhD**\n","\n","(c) Copyright Intelligent Racing Inc., 2021-2024. All rights reserved. Materials may NOT be distributed or used for any commercial purposes.\n","\n","If you just start reading this website, you have found the coding site for the introductory Python programming course developed by Dr. Allen Y. Yang. At the time of creating this course, Dr. Yang is in the faculty of the Department of EECS at the University of California, Berkeley. You may find his academic website at: https://people.eecs.berkeley.edu/~yang/\n"," \n","Beyond a research career at Berkeley, Dr. Yang is immensely interested about innovation and entrepreneurship, partially thanks to the geolocation of Berkeley close to Silicon Valley, a famous place in the US and around the world for its technology innovation and many inspiring entrepreneurial stories. Intelligent Racing is just one of many such companies founded in Silicon Valley by Dr. Yang, with the singular purpose to deliver state-of-the-art science and technology learning curricula to students, who will become the next technology leader and successful entrepreneurs.\n","\n","In this course, we will be learning about the programming language of Python. If this is your very first time hearing about Python programming,\n","language, then you are in the right place: This course is for you!\n"," \n","In fact, you are not alone wondering: \n"," 1. How can a computer language help you to communicate with a computer?\n"," 2. What is Python?\n"," 3. Is Python a suitable language to learn for beginners?\n","\n","For the rest of the course in ten lectures, we will gradually learn about the in-and-out of Python programming that is rigorous and fun for beginners. You will learn about all the basic elements of Python to effectively use the language to start coding useful computer programs and solving intersting practical real-world problems. We hope you will find that Python is a quite user-friendly programming language whose use cases may only be limited by your imagination.\n","\n","Of course, being a super user-friendly programming language that is easy to use for humans does not mean that Python is good for every occasion. One can quickly point out several obvious examples: \n","\n","First, Python is not the fastest language to run on computers. There are more traditional languages that were designed to be very easy to be understood by the processors of the computer and hence can be executed faster than Python. \n","\n","Second, Python is not supported by all computing platforms. For example, it is still relatively difficult to code up and run Python programs on iOS or Android systems natively. To best develop applications that run on mobile platforms, one is recommended to continue their programming journey to pick up some other languages."]},{"cell_type":"markdown","metadata":{},"source":["# Keywords\n","\n","In this interactive lecture nodes, written in the style of Jupyter Notebook, we will impose a prelude section for each lecture called *Keywords*. This is the place we will help the learner to highlight some new technical jargons that will be introduced more formally in the lecture note. Our goal is that the learner can take advantage of this structure to quickly familarize themselves with the upcoming new concepts, and the section together with the later Summary section may also help the learner to better review the content of each lecture after taking the classes.\n","\n","* **Jupyter Notebook**: Jupyter Notebook is an open-source computer document format developed by Project Jupyter Foundation. The document can be hosted and interactively edited within a web browser similar to a webpage, and then the web browser may connect with several supported language kernels to interpret the code embedded within the document. A primary programming language used in Jupyter Notebook is Python.\n","* **Interpreted language**: Python is an interpreted language that can be understood and executed by a computer line by line. \n","* **Function**: a function in a programming language is a piece of stand-alone code that encaptulates a set of starting conditions set by its input arguments and a set of return results by its return arguments. \n","* **Data type**: Data when stored in computer memory must declare its data type, such as integer, floating point, text (as strings), etc.\n","* **Debug**: Debug is a programming jargon referring to the practice of finding programming errors when designing a computer code."]},{"cell_type":"markdown","metadata":{},"source":["# Running First Python Code in Command Line\n","After we have done a brief motivation about the Python language, we arrive at the best part of our coding exercise. Below, we are going to see, for the first time, how Python executes some basic computing commands. But before we do that, let us understand how a typical Python language command is executed by computer that is dictated by its language standards.\n","\n","The most important rule to remember for Python is that: *it is a high-level, interpreted language.* Let us further consider this statement below.\n","1. *Python is a high-level language*. This usually means two things: First, programming statements of a high-level language broadly adopt words in natural languages (mostly English) and math symbols that are fairly easy for humans to understand. Second, high-level statements are more descriptive about user's intent to solve a problem and more abstract about the execution of the statements by the computer, compared to low-level languages that typically are descriptive about computer processor's precise but sometimes rather tedious steps to execute the code.\n","2. *Python is an interpreted language*. It means the execution of language statements is more direct and freely, compared to compiled languages. A compiled language must go through a compiling process before a code can be executed. An interpreted language such as Python allows users to freely execute any one line of or a block of multiple lines of code without explicitly invoking a compiling process.\n","\n","Let us immediately run an example to have Python solving a simple problem for us. In this example, we want to know the result of an arithmetic problem: 3+2 = ?\n","\n","To get the result, this code site has prepared the following code block. On the left-hand side at the beginning of the code block, there is a triangle symbol that means asking Python to **RUN** this code. Please use your mouse to left click this triangle symbol and observe the result of your action. "]},{"cell_type":"code","execution_count":null,"metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","trusted":true},"outputs":[],"source":["3+2"]},{"cell_type":"markdown","metadata":{},"source":["Congratulations! You have just run your first Python code. The result of 5 can be seen in an *output* block, which is usually called **the terminal output** or **the console output**. It is so called because, before the invention of graphical user interface (GUI), all computer intput/output interaction with users was done on text-based terminal or console environment. Therefore, in many programming languages, text output by default always displays in the terminal environment. In fact, you may find the terminal application in most popular operating systems including Windows, Mac OSX, and Linux. \n","\n","We also see in this coding exercise, the Python statement includes three elements, namely, an integer number 3, an integer number 2, and an arithmetic operator +. Python adopts the same meanings of these math symbols and the proper order how an addition calculation should be denoted in arithmetic. Therefore, this is an example of a high-level programming.\n","\n","By clicking the **RUN** button, Python immediately executes the statement and results its computation result in the line immediately following the statement. This is thanks to the fact that Python is an interpreted language.\n","\n","Another important fact about running the above code is that the reader should be aware that we are in a special Python development environment. A development environment is itself a computer program, where the code of a program can be written and more importantly often be executed within the environment. The development environment implemented by Kaggle uses the development style of so-called Jupyter Notebook. This style of Python programming allows users to directly edit and run their code on web browsers, and the users can conveniently select to run any particular code block or run all the code blocks together on the same page. If you pay attention to the top of this Kaggle webpage, right under the menu bar, there is a double-triangle button that is marked as **RUN ALL**. This is the command to sequentially run all the code blocks on this page in one shot. You may try this function to see the result."]},{"cell_type":"markdown","metadata":{},"source":["Next, let us see Python interprets and executes a block of code together (by clicking the **RUN** button of the next code block):"]},{"cell_type":"code","execution_count":null,"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":true},"outputs":[],"source":["print(3+2.0)\n","print(17/3)"]},{"cell_type":"markdown","metadata":{},"source":["In the above code block, we see the use of **print()** function. As a special rule of Jupyter Notebook environment, if a code block contains only arithmetic equations, only the last equation will output its result on the browser. In the above code block, we want a block to contain tasks to evaluate three equations. We then use a Python keyword **print()** to ask the environment to print out each of the three results. \n","\n","A function in a high-level programming language takes in one or more input arguments, and then output one or more return values that are also called output. In Python, input arguments of a function such as **print()** are denoted using the pair of paratheses. For example, the input of the first print function is 3+2.0. After you **RUN** the code, its output result is 5.0. The input of the second print function is 17/3, and its output result is 5.666666666666667."]},{"cell_type":"markdown","metadata":{},"source":["You may also notice from the above code block that the Python calculation results from two seemingly very similar expressions, one of which is 3+2 and the other is 3+2.0, also return similar but different values. The result for 3+2 is 5, and for 3+2.0 is 5.0\n","\n","The difference lies in the way numeric numbers are represented in computer. For numbers 3, 2, and 5, they are called integers. But for 2.0 and 5.0, they are called floating numbers, or floats. Even when mathematically 5 and 5.0 are equal in magnitude, the use of fraction indicates one of them (i.e., 5) is an integer while the other that contains the fraction part is a float. We will discuss Python numeric type in more detail in the next lecture."]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["print(type(5))\n","print(type(5.0))"]},{"cell_type":"markdown","metadata":{},"source":["The type of a number or a function return value can be explicitly checked using another useful Python function, with the keyword **type()**. We observe after running the above code block, that we print the type of number 5 and the type of number 5.0, respectively. \n","\n","To correctly read the two statements, the results from nested functions are calculated in stages from right to the left. For example, the statement **print(type(5))** should be understood as firstly query the type of number 5 using the type() function, then the output of the type() function is fed into the second print() function as its input. The final result is the print out of the type of the integer 5.\n","\n","Finally, let us observe the information from the output of the two statements. The first result indicates number 5 is an 'int' type, which is a text string representing the integer type. The second result indicates number 5.0 is a 'float' type, a text string representing the float type. The two results also indicate that both the int and float types are classes. In fact, all Python numeric or other more complex value types are implemented as \"classes\". The use of class concept signals the third attributes of Python language (the first two being that it is high-level and interpreted):\n","\n","*Python is an object-oriented programming language.*\n","\n","In later lectures, we will go over the principles of object-oriented programming in details. For now, we will conclude the first lecture. "]},{"cell_type":"markdown","metadata":{},"source":["# Summary\n","\n","* Python is a high-level, interpreted programming language.\n","* Function print() outputs its input argument(s) as string output\n","* Python displays text output by print() in the terminal environment.\n","* In Jupyter Notebook or the terminal, Python will also evaluate and print out the value of the last numeric expression.\n","* Function type() outputs the type of the input argument.\n","* A class-type variable indicates the variable is created as a class type in object-oriented programming."]},{"cell_type":"markdown","metadata":{},"source":["# Exercises\n","\n","1. Create two integer variables: a = 10 and b = 5. Then use print() function to print out the division result of a / b. \n","\n","2. Please pay attention to the format of the above result, which indicates that it is not an integer. To verify that, continue with the above program, and print out the output of the function type(a/b). What can you say about this result?\n","\n","3. Print out the result of 10 factorial, that is, the product of all positive integers from 1 to 10.\n","\n","4. Can you list a few criteria that set apart between high-level programming languages such as Python and low-level programming languages used in early days of the computer industry?\n","\n","5. Debug: Please correct the code below so that it can be correctly run in Python"]},{"cell_type":"code","execution_count":5,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["2.0\n","3628800\n"]}],"source":["#Q1\n","a=10\n","b=5\n","print(a/b)\n","\n","#Q2\n","type(a/b)\n","#Q3\n","import math\n","print(math.factorial(10))\n","#Q4\n","#Open source\n","#Easier to understand and modify"]},{"cell_type":"code","execution_count":6,"metadata":{"execution":{"iopub.execute_input":"2021-05-27T21:23:43.653811Z","iopub.status.busy":"2021-05-27T21:23:43.653419Z","iopub.status.idle":"2021-05-27T21:23:43.660137Z","shell.execute_reply":"2021-05-27T21:23:43.658547Z","shell.execute_reply.started":"2021-05-27T21:23:43.653772Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["5\n"]}],"source":["print(3 + 2)"]}],"metadata":{"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":30527,"isGpuEnabled":false,"isInternetEnabled":true,"language":"python","sourceType":"notebook"},"kernelspec":{"display_name":"Python 3","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.18"}},"nbformat":4,"nbformat_minor":4} diff --git a/Part One/1-10-classes-and-object-oriented-programming-ii.ipynb b/Part One/1-10-classes-and-object-oriented-programming-ii.ipynb index 75deeee..7560ed5 100644 --- a/Part One/1-10-classes-and-object-oriented-programming-ii.ipynb +++ b/Part One/1-10-classes-and-object-oriented-programming-ii.ipynb @@ -1 +1 @@ -{"cells":[{"cell_type":"markdown","metadata":{},"source":["**Learning Python -- The Programming Language for Artificial Intelligence and Data Science**\n","\n","**Lecture 10: Classes and Object-Oriented Programming II**\n","\n","**By Allen Y. Yang, PhD**\n","\n","(c) Copyright Intelligent Racing Inc., 2021-2024. All rights reserved. Materials may NOT be distributed or used for any commercial purposes."]},{"cell_type":"markdown","metadata":{},"source":["# Keywords\n","\n","* Inheritance: Inheritance refers to the ability to create a modified class type based on an existing class type. The modified class is called a subclass of the existing class."]},{"cell_type":"markdown","metadata":{},"source":["# Inheritance\n","\n","In the last lecture, we discussed the important encapsulation property of classes in Python. In this lecture, we will cover the other equally important properties. The first unique property is inheritance. Let us see an example:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["from datetime import date \n","\n","class Person:\n"," ''' An example class to show Python Class definitions'''\n","\n"," retirement_age = 65 # Class attribute\n","\n"," def __init__(self, first_name, last_name, age = None):\n"," if type(first_name)!=str or type(last_name)!=str:\n"," raise TypeError('Person class initialized with the unsupported types.')\n","\n"," self.first_name = first_name\n"," self.last_name = last_name\n"," self.age = age\n","\n"," def set_age(self, age):\n"," if age<0:\n"," raise ValueError('age attribute in Person must be nonnegative.')\n","\n"," self.age = age\n","\n"," def years_until_retirement(selfish): # The use of selfish is a joke, it serves the same purpose as \"self\"\n"," until_retirement_year = Person.retirement_age - selfish.age\n"," if until_retirement_year<=0:\n"," print('This person has retired')\n"," else:\n"," print('This person has {0} years until retirement'.format(until_retirement_year))\n"," \n"," @classmethod\n"," def fromBirth(cls, first_name, last_name, birth_year):\n"," if type(birth_year)!=int:\n"," raise TypeERror('birth_year must be an int')\n"," \n"," if birth_year > date.today().year:\n"," raise ValueError('Given birth year is greater than current year.')\n"," \n"," return cls(first_name, last_name, date.today().year - birth_year)\n"," \n"," @staticmethod\n"," def isAdult(age):\n"," return age > 18\n"," \n","class Student(Person):\n"," student_record = dict()\n"," \n"," def __init__(self, first_name, last_name, class_year):\n"," super().__init__(first_name, last_name)\n","\n"," if type(class_year)!=int:\n"," raise TypeError('class_year shall be an integer')\n"," if class_year < 1900 or class_year>2999:\n"," raise ValueError('Class year value is not supported')\n","\n"," if class_year in Student.student_record:\n"," Student.student_record[class_year] += 1\n"," else:\n"," Student.student_record[class_year] = 1\n","\n"," self.student_ID = str(class_year) + \\\n"," format(Student.student_record[class_year],'04d')\n"," Student.student_record[self.student_ID] = (first_name, last_name)\n","\n"," def year_of_graduation(self):\n"," return (int(self.student_ID[0:4]))\n"," \n","x1 = Student('John', 'Smith', 2024)\n","x1.age = 20\n","print(x1.student_ID)\n","print(x1.isAdult(x1.age))\n","x2 = Student('Jane', 'Doe', 2024)\n","print(x2.student_record)"]},{"cell_type":"markdown","metadata":{},"source":["In this example, we first repeat the definition of the Person class from the last lecture. Since a person can take on many different roles in the society, it is necessary to further define their more specific properties. For example, if a person is a student, then they will need to be assigned a unique student ID in school. In OOP languages such as Python, this relationship can be represented by defining a new *subclass* of Person, called Student. Conversely, Person is called the *superclass* of Student:\n","\n"," class Student(Person):\n"," \n","This declaration defines Student class to inherit all the attributes and methods from Person class first, and then the subsequent code block after the colon mark either overwrite and modify the methods in the superclass, or further define new methods and attributes.\n","\n","First, Student class overwrites the initialization method of Person class, which is typically a common practice for a subclass. However, part of the aims of the initialization process overlaps with that in the superclass, such as assigning the first name and last name attributes. As a subclass, Student benefits by the fact that a subclass can call its superclass' methods using the keyword **super**. *super().__init__()* instructs Python to call the same function as defined in its superclass. We now see that inheritance passes down relevant methods from superclasses to subclasses.\n","\n","As Student class is a subclass of Person, it is also a different class. Therefore, its *__init__()* needs to be modified. In the example, we added two new attributes:\n","\n"," * student_ID: As its prefix *self* indicates, this is an instance attribute that records a student's unique ID. The student ID is of the string type.\n"," * student_record: This is a class attribute of the dictionary type. Its entries contain two types of data. The first type records under each class year how many students so far have enrolled. For example, (2024, 2) indicates the Class of 2024 has two students. Then the second type records under each unique student ID the first and last name of the student. \n"," \n","We want to make a note here that in the assignment of *student_ID* string, we demonstrated the use of the built-in function *format()*. This is a pretty powerful function that, as its name suggests, converts the first argument into a format specified by the parameter string as the second argument. As the variation of its use cases is too broad, we encourage the reader to refer to Python's online document:\n","\n"," https://docs.python.org/3/library/functions.html?highlight=format#format\n"," \n","In the example, though, the parameter string '04d' means converting to an integer format with at least 4 digits. In other words, integer 1 will be converted to a string '0001'. This format fits the typical ID number format in the real world.\n","\n","Finally, we see that the subclass Student further defines a unique method called *year_of_graduation()*. This method is only relevant to those people who are students, and clearly is not general enough to be associated with the superclass Person."]},{"cell_type":"markdown","metadata":{},"source":["# Abstract Methods\n","\n","Abstract methods are those that are declared but not implemented. Abstract methods are useful in creating superclasses where certain methods must be concretely defined in their subclasses. For example, all shapes may have a property of *area size*; however, a specific algorithm to calculate the area size can be implemented for specific shape classes such as squares, circles, etc. Let us examine the sample code below:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["import math\n","\n","class Shape:\n","\n"," def get_area(self):\n"," ''' virtual method to calculate area of a shape'''\n"," raise NotImplementedError\n","\n"," def is_abstract(self):\n"," ''' return True if a shape has been assigned to the class'''\n"," if self.__class__ == Shape:\n"," return True\n"," else:\n"," return False\n","\n","class Square(Shape):\n"," ''' A subclass of Shape, specifically for calculating square area'''\n","\n"," def __init__(self, width):\n"," self.width = width\n"," self.area = self.get_area()\n","\n"," def get_area(self):\n"," ''' Area of a square is its width times width'''\n"," return self.width*self.width\n"," \n","class Circle(Shape):\n"," ''' A subclass of Shape, specifically for calculating circle area'''\n","\n"," def __init__(self, radius):\n"," self.radius = radius\n"," self.area = self.get_area()\n","\n"," def get_area(self):\n"," ''' Area of a circle is pi times radius square'''\n"," return math.pi * self.radius ** 2.0\n","\n","x1 = Square(5)\n","print('Is class initialized: ',x1.is_abstract())\n","print(x1.get_area())\n","\n","x2 = Circle(5)\n","print('Is class initialized: ', x2.is_abstract())\n","print(x2.get_area())\n","\n","x0 = Shape()\n","print('Is class initialized: ', x0.is_abstract())\n","print(x0.get_area())"]},{"cell_type":"markdown","metadata":{},"source":["The sample code above includes several new techniques related to the use of classes. First, the code defines a base class *Shape* and an **abstract method** *get_area()*. In the base class, *get_area()* is not implemented. Then the Shape class is inherited by two subclasses: *Square* and *Circle*. At the subclass level, when the shape attribute is properly initialized such as the width for Square and the radius for Circle, then *get_area()* is implemented.\n","\n","The fact that in *Shape* class *get_area()* is abstract can be declared in several ways, depending on the programmer's preference:\n","\n"," * Using *raise NotImplementedError*: When a method is abstract, *raise NotImplementedError* will interrupt the program when the method is called.\n"," * Using pass: One can also choose to silently ignore the fact that an abstract method is called, by writing *pass* function as the function body. *pass* function as its name suggests is a special statement in Python, which informs Python that the operation will not generate any results. \n"," * Using *abstract base class* (ABC): There is a special class that can be declared as the superclass of other classes. The *abc* module can be imported, which also contains a decorator called *@abc.abstractmethod*. Using an abstract method decorator is a very powerful statement, in that Python will not allow a class instance to be created if the class contains abstract method decorators. \n"," \n","Finally, the sample code demonstrates another more user-friendly technique to verify during runtime whether a class has implemented its superclass' abstract methods. In Shape class, the code does implement a function called *is_abstract()*. The purpose of the function is to inform a user of the class or its subclasses whether the class contains abstract methods. Since the definition of *get_area()* uses the first approach above, assigning a class object will not return a runtime error. We see from the output result that calling get_area() from a Square object or Circle object will return False, while from a Shape object will return True.\n","\n","This result shows a quite interesting ability of OOP in Python, that a method defined in a superclass can correctly calculate its return even when called from its subclasses. This ability will be credited to the inheritance property of classes. Technically, this is implemented using a special variable self.\\_\\_class\\_\\_. As its name suggests, it returns a reference of the current class name of the object. Thanks to the inheritance property, all subclasses of the superclass Shape will run the same code in *is_abstract()*. But the reference value of self.\\_\\_class\\_\\_ will be only determined during the runtime. This is an effective way to change the behavior of inherited classes in their definitions, without knowing during runtime which variable eventually binds with the classes. \n"]},{"cell_type":"markdown","metadata":{},"source":["# Special Variables and Methods\n","\n","Above we have seen that *__class__* for a class object is a special variable. We have also seen the use of *__init__()* method that always exists but can be customized by the user. As it turns out, for a Python class, a list of special variables and special methods always get created without user intervention. In this part, we will learn the usage of several important special variables and special methods:\n","\n"," * __file__: Path to the current Python file\n"," * __class__: Reference of the current class name\n"," * __doc__: Docstring record\n"," * __init__(): Class constructor\n"," * __hash__(): Hash function\n"," \n"," \n","All special variables and special methods for Python classes start with two underscores and end with two underscores. However, for those readers where Python is not their first OOP language, one of the most controversial rules in Python is the fact that Python classes have no private or protected attributes or methods, in other words, all attributes and methods are public (meaning they are visible at any place where their objects are defined). In the past examples, we have seen that the code can query and change an attribute of a class quite at will, and Python does not attempt to prevent that under any circumstances.\n","\n","Nevertheless, Python users in the past have created some naming convention that serves to help reminding themselves and other users about the limited scope of some variables. Note that these rules as we will see below are merely recommendations, and they are not enforced by Python system.\n","\n"," * Single underscore _var: Intended for internal use only in class methods.\n"," * Double underscores __var: Intended exclusively for one class but not its subclasses.\n","\n","That being said, Python does apply a trick to double underscore variables. Specifically, Python will protect *__var* to be associated with a single class by **name mangling** the class name before *__var*. In other words, if *__var* is defined within a class, when referenced outside the class definition, its name will be changed to object._CLASSNAME__var. The name mangling will not completely prevent other code to use an internal class variable, but the other code does need to go through the extra trouble to gain access to such internal variables. "]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["class PrefixPostfix:\n"," def __init__(self): # class constructor\n"," self._internal = 0\n"," self.__double_underscore = 'name mangling'\n"," self.__prefix_postfix_double__ = 'no change'\n","\n"," def print_out(self):\n"," print('_internal: ', self._internal)\n"," print('__double_underscore: ', self.__double_underscore)\n"," print('__prefix_postfix_double__: ', self.__prefix_postfix_double__)\n","\n"," def __del__(self): # class destructor\n"," print('Goodbye!')\n","\n"," def __eq__(self, other):\n"," if type(self)!=type(other):\n"," raise TypeError('PrefixPostfix == must compare same type')\n"," return('Operator has been hijacked: ' + str(self._internal == other._internal))\n","\n","test = PrefixPostfix()\n","print('------ print outside class definition ------')\n","print(dir(test))\n","print(test._internal)\n","print(test.__prefix_postfix_double__)\n","print(test._PrefixPostfix__double_underscore)\n","print(test == test)\n","del(test)"]},{"cell_type":"markdown","metadata":{},"source":["In the above sample code, we define three instance variables using special naming convention: \\_internal, \\_\\_double_underscore, and \\_\\_prefix_postfix_double__. First, we shall see that whether the \\_internal and \\__prefix_postfix_double__ are used internally or outside the class definition, their variable names are the same.\n","\n","However, if we want to reference \\_\\_double_underscore outside the class, it must be referenced as \\_PrefixPostfix__double_underscore__. In fact, all the attributes in a class can be listed out by calling a built-in function *dir()*, as we have also shown above. We can verify from this list that the name \\_\\_double_underscore has been modified by the **name mangling** convention.\n","\n","Finally, the sample code demonstrates the use of two other special methods: \\_\\_del\\_\\_() and \\_\\_eq\\_\\_. \\_\\_del\\_\\_() is also known as the class destructor. As opposite to the class constructor \\_\\_init\\_\\_(), \\_\\_del\\_\\_() when the class object is being deleted either automatically by Python (when doing automatic garbage collection) or manually by calling *del()*.\n","\n","\\_\\_eq\\_\\_() on the other hand belongs to a list of comparison operators for classes. When the \"==\" operator is used to compare two class objects, in fact, the code block defined within \\_\\_eq\\_\\_() function is called. When we customize this operator, the code can have the function to return any arbitrary values. The complete list of comparison operators are listed below:\n","\n"," *__lt__(self, other): <\n"," *__le__(self, other): <=\n"," *__eq__(self, other): ==\n"," *__ne__(self, other) != \n"," *__gt__(self, other): >\n"," *__ge__(self, other): >=\n"]},{"cell_type":"markdown","metadata":{},"source":["# Exercises\n","\n","1. Using the superclass Shape in the lecture, define a subclass called Rectangle, then update the methods \\_\\_init\\_\\_() and get_area(). Tip: A Rectangle has two sides, called width and length.\n","\n","2. In the Person class in the lecture, please code a class method using the @classmethod decorator called set_retirement_age(). The function will modify the value of the class attribute retirement_age().\n","\n","3. In the Circle class in the lecture, please code a static method using the @staticmethod decorator called get_radius(area). The function will return the radius of a circle given its area size as the input argument.\n","\n","4. Please implement the special method \\_\\_lt__() for the Shape class. Specifically, the method defines the operator \"<\" to compare the area sizes of any two subclasses of Shape. Pay attention to the fact that although the special method is defined in the superclass Shape, it remains effective to compare area sizes of any subclasses, even when the two subclasses are of different type. For instance, please verify that the method can be used to compare the area sizes between a Square class object and a Circle class object.\n","\n","5. Many built-in Python classes support the special operator \"+\". For instance, for integers, a + b executes an addition operation; while for strings, s1 + s2 executes an concatenation operation. For a user-defined class, the implementation of the same operator \"+\" can be defined by coding the special method \\_\\_add__(). \n","\n"," In this exercise, please create a new \\_\\_add__() method in the Square class in the lecture. If two squares have the same width, then square_1 + square_2 would return a Rectangle class object (as defined in Exercise 1 above) with the same width but 2*width as its length. If two squares are of different width, \\_\\_add__() should return None."]},{"cell_type":"markdown","metadata":{},"source":["# Challenges\n","\n","1. Create a subclass of the BankAccount class in the previous lecture, called StockAccount. Adding to the superclass, the subclass should have at least one more attribute of stock_positions as a dictionary type. Each dictionary entry has the stock ticker name as the key, and the customer's size of the stock as the value. Please also design relevant methods to update the values in stock_positions, such as retrieving stock price, buying or selling a stock based on its stock price, and linking the buying and selling with the account's cash_position."]}],"metadata":{"kernelspec":{"display_name":"Python 3","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.6.4"}},"nbformat":4,"nbformat_minor":4} +{"cells":[{"cell_type":"markdown","metadata":{},"source":["**Learning Python -- The Programming Language for Artificial Intelligence and Data Science**\n","\n","**Lecture 10: Classes and Object-Oriented Programming II**\n","\n","**By Allen Y. Yang, PhD**\n","\n","(c) Copyright Intelligent Racing Inc., 2021-2024. All rights reserved. Materials may NOT be distributed or used for any commercial purposes."]},{"cell_type":"markdown","metadata":{},"source":["# Keywords\n","\n","* Inheritance: Inheritance refers to the ability to create a modified class type based on an existing class type. The modified class is called a subclass of the existing class."]},{"cell_type":"markdown","metadata":{},"source":["# Inheritance\n","\n","In the last lecture, we discussed the important encapsulation property of classes in Python. In this lecture, we will cover the other equally important properties. The first unique property is inheritance. Let us see an example:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["from datetime import date \n","\n","class Person:\n"," ''' An example class to show Python Class definitions'''\n","\n"," retirement_age = 65 # Class attribute\n","\n"," def __init__(self, first_name, last_name, age = None):\n"," if type(first_name)!=str or type(last_name)!=str:\n"," raise TypeError('Person class initialized with the unsupported types.')\n","\n"," self.first_name = first_name\n"," self.last_name = last_name\n"," self.age = age\n","\n"," def set_age(self, age):\n"," if age<0:\n"," raise ValueError('age attribute in Person must be nonnegative.')\n","\n"," self.age = age\n","\n"," def years_until_retirement(selfish): # The use of selfish is a joke, it serves the same purpose as \"self\"\n"," until_retirement_year = Person.retirement_age - selfish.age\n"," if until_retirement_year<=0:\n"," print('This person has retired')\n"," else:\n"," print('This person has {0} years until retirement'.format(until_retirement_year))\n"," \n"," @classmethod\n"," def fromBirth(cls, first_name, last_name, birth_year):\n"," if type(birth_year)!=int:\n"," raise TypeERror('birth_year must be an int')\n"," \n"," if birth_year > date.today().year:\n"," raise ValueError('Given birth year is greater than current year.')\n"," \n"," return cls(first_name, last_name, date.today().year - birth_year)\n"," \n"," @staticmethod\n"," def isAdult(age):\n"," return age > 18\n"," \n","class Student(Person):\n"," student_record = dict()\n"," \n"," def __init__(self, first_name, last_name, class_year):\n"," super().__init__(first_name, last_name)\n","\n"," if type(class_year)!=int:\n"," raise TypeError('class_year shall be an integer')\n"," if class_year < 1900 or class_year>2999:\n"," raise ValueError('Class year value is not supported')\n","\n"," if class_year in Student.student_record:\n"," Student.student_record[class_year] += 1\n"," else:\n"," Student.student_record[class_year] = 1\n","\n"," self.student_ID = str(class_year) + \\\n"," format(Student.student_record[class_year],'04d')\n"," Student.student_record[self.student_ID] = (first_name, last_name)\n","\n"," def year_of_graduation(self):\n"," return (int(self.student_ID[0:4]))\n"," \n","x1 = Student('John', 'Smith', 2024)\n","x1.age = 20\n","print(x1.student_ID)\n","print(x1.isAdult(x1.age))\n","x2 = Student('Jane', 'Doe', 2024)\n","print(x2.student_record)"]},{"cell_type":"markdown","metadata":{},"source":["In this example, we first repeat the definition of the Person class from the last lecture. Since a person can take on many different roles in the society, it is necessary to further define their more specific properties. For example, if a person is a student, then they will need to be assigned a unique student ID in school. In OOP languages such as Python, this relationship can be represented by defining a new *subclass* of Person, called Student. Conversely, Person is called the *superclass* of Student:\n","\n"," class Student(Person):\n"," \n","This declaration defines Student class to inherit all the attributes and methods from Person class first, and then the subsequent code block after the colon mark either overwrite and modify the methods in the superclass, or further define new methods and attributes.\n","\n","First, Student class overwrites the initialization method of Person class, which is typically a common practice for a subclass. However, part of the aims of the initialization process overlaps with that in the superclass, such as assigning the first name and last name attributes. As a subclass, Student benefits by the fact that a subclass can call its superclass' methods using the keyword **super**. *super().__init__()* instructs Python to call the same function as defined in its superclass. We now see that inheritance passes down relevant methods from superclasses to subclasses.\n","\n","As Student class is a subclass of Person, it is also a different class. Therefore, its *__init__()* needs to be modified. In the example, we added two new attributes:\n","\n"," * student_ID: As its prefix *self* indicates, this is an instance attribute that records a student's unique ID. The student ID is of the string type.\n"," * student_record: This is a class attribute of the dictionary type. Its entries contain two types of data. The first type records under each class year how many students so far have enrolled. For example, (2024, 2) indicates the Class of 2024 has two students. Then the second type records under each unique student ID the first and last name of the student. \n"," \n","We want to make a note here that in the assignment of *student_ID* string, we demonstrated the use of the built-in function *format()*. This is a pretty powerful function that, as its name suggests, converts the first argument into a format specified by the parameter string as the second argument. As the variation of its use cases is too broad, we encourage the reader to refer to Python's online document:\n","\n"," https://docs.python.org/3/library/functions.html?highlight=format#format\n"," \n","In the example, though, the parameter string '04d' means converting to an integer format with at least 4 digits. In other words, integer 1 will be converted to a string '0001'. This format fits the typical ID number format in the real world.\n","\n","Finally, we see that the subclass Student further defines a unique method called *year_of_graduation()*. This method is only relevant to those people who are students, and clearly is not general enough to be associated with the superclass Person."]},{"cell_type":"markdown","metadata":{},"source":["# Abstract Methods\n","\n","Abstract methods are those that are declared but not implemented. Abstract methods are useful in creating superclasses where certain methods must be concretely defined in their subclasses. For example, all shapes may have a property of *area size*; however, a specific algorithm to calculate the area size can be implemented for specific shape classes such as squares, circles, etc. Let us examine the sample code below:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["import math\n","\n","class Shape:\n","\n"," def get_area(self):\n"," ''' virtual method to calculate area of a shape'''\n"," raise NotImplementedError\n","\n"," def is_abstract(self):\n"," ''' return True if a shape has been assigned to the class'''\n"," if self.__class__ == Shape:\n"," return True\n"," else:\n"," return False\n","\n","class Square(Shape):\n"," ''' A subclass of Shape, specifically for calculating square area'''\n","\n"," def __init__(self, width):\n"," self.width = width\n"," self.area = self.get_area()\n","\n"," def get_area(self):\n"," ''' Area of a square is its width times width'''\n"," return self.width*self.width\n"," \n","class Circle(Shape):\n"," ''' A subclass of Shape, specifically for calculating circle area'''\n","\n"," def __init__(self, radius):\n"," self.radius = radius\n"," self.area = self.get_area()\n","\n"," def get_area(self):\n"," ''' Area of a circle is pi times radius square'''\n"," return math.pi * self.radius ** 2.0\n","\n","x1 = Square(5)\n","print('Is class initialized: ',x1.is_abstract())\n","print(x1.get_area())\n","\n","x2 = Circle(5)\n","print('Is class initialized: ', x2.is_abstract())\n","print(x2.get_area())\n","\n","x0 = Shape()\n","print('Is class initialized: ', x0.is_abstract())\n","print(x0.get_area())"]},{"cell_type":"markdown","metadata":{},"source":["The sample code above includes several new techniques related to the use of classes. First, the code defines a base class *Shape* and an **abstract method** *get_area()*. In the base class, *get_area()* is not implemented. Then the Shape class is inherited by two subclasses: *Square* and *Circle*. At the subclass level, when the shape attribute is properly initialized such as the width for Square and the radius for Circle, then *get_area()* is implemented.\n","\n","The fact that in *Shape* class *get_area()* is abstract can be declared in several ways, depending on the programmer's preference:\n","\n"," * Using *raise NotImplementedError*: When a method is abstract, *raise NotImplementedError* will interrupt the program when the method is called.\n"," * Using pass: One can also choose to silently ignore the fact that an abstract method is called, by writing *pass* function as the function body. *pass* function as its name suggests is a special statement in Python, which informs Python that the operation will not generate any results. \n"," * Using *abstract base class* (ABC): There is a special class that can be declared as the superclass of other classes. The *abc* module can be imported, which also contains a decorator called *@abc.abstractmethod*. Using an abstract method decorator is a very powerful statement, in that Python will not allow a class instance to be created if the class contains abstract method decorators. \n"," \n","Finally, the sample code demonstrates another more user-friendly technique to verify during runtime whether a class has implemented its superclass' abstract methods. In Shape class, the code does implement a function called *is_abstract()*. The purpose of the function is to inform a user of the class or its subclasses whether the class contains abstract methods. Since the definition of *get_area()* uses the first approach above, assigning a class object will not return a runtime error. We see from the output result that calling get_area() from a Square object or Circle object will return False, while from a Shape object will return True.\n","\n","This result shows a quite interesting ability of OOP in Python, that a method defined in a superclass can correctly calculate its return even when called from its subclasses. This ability will be credited to the inheritance property of classes. Technically, this is implemented using a special variable self.\\_\\_class\\_\\_. As its name suggests, it returns a reference of the current class name of the object. Thanks to the inheritance property, all subclasses of the superclass Shape will run the same code in *is_abstract()*. But the reference value of self.\\_\\_class\\_\\_ will be only determined during the runtime. This is an effective way to change the behavior of inherited classes in their definitions, without knowing during runtime which variable eventually binds with the classes. \n"]},{"cell_type":"markdown","metadata":{},"source":["# Special Variables and Methods\n","\n","Above we have seen that *__class__* for a class object is a special variable. We have also seen the use of *__init__()* method that always exists but can be customized by the user. As it turns out, for a Python class, a list of special variables and special methods always get created without user intervention. In this part, we will learn the usage of several important special variables and special methods:\n","\n"," * __file__: Path to the current Python file\n"," * __class__: Reference of the current class name\n"," * __doc__: Docstring record\n"," * __init__(): Class constructor\n"," * __hash__(): Hash function\n"," \n"," \n","All special variables and special methods for Python classes start with two underscores and end with two underscores. However, for those readers where Python is not their first OOP language, one of the most controversial rules in Python is the fact that Python classes have no private or protected attributes or methods, in other words, all attributes and methods are public (meaning they are visible at any place where their objects are defined). In the past examples, we have seen that the code can query and change an attribute of a class quite at will, and Python does not attempt to prevent that under any circumstances.\n","\n","Nevertheless, Python users in the past have created some naming convention that serves to help reminding themselves and other users about the limited scope of some variables. Note that these rules as we will see below are merely recommendations, and they are not enforced by Python system.\n","\n"," * Single underscore _var: Intended for internal use only in class methods.\n"," * Double underscores __var: Intended exclusively for one class but not its subclasses.\n","\n","That being said, Python does apply a trick to double underscore variables. Specifically, Python will protect *__var* to be associated with a single class by **name mangling** the class name before *__var*. In other words, if *__var* is defined within a class, when referenced outside the class definition, its name will be changed to object._CLASSNAME__var. The name mangling will not completely prevent other code to use an internal class variable, but the other code does need to go through the extra trouble to gain access to such internal variables. "]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["class PrefixPostfix:\n"," def __init__(self): # class constructor\n"," self._internal = 0\n"," self.__double_underscore = 'name mangling'\n"," self.__prefix_postfix_double__ = 'no change'\n","\n"," def print_out(self):\n"," print('_internal: ', self._internal)\n"," print('__double_underscore: ', self.__double_underscore)\n"," print('__prefix_postfix_double__: ', self.__prefix_postfix_double__)\n","\n"," def __del__(self): # class destructor\n"," print('Goodbye!')\n","\n"," def __eq__(self, other):\n"," if type(self)!=type(other):\n"," raise TypeError('PrefixPostfix == must compare same type')\n"," return('Operator has been hijacked: ' + str(self._internal == other._internal))\n","\n","test = PrefixPostfix()\n","print('------ print outside class definition ------')\n","print(dir(test))\n","print(test._internal)\n","print(test.__prefix_postfix_double__)\n","print(test._PrefixPostfix__double_underscore)\n","print(test == test)\n","del(test)"]},{"cell_type":"markdown","metadata":{},"source":["In the above sample code, we define three instance variables using special naming convention: \\_internal, \\_\\_double_underscore, and \\_\\_prefix_postfix_double__. First, we shall see that whether the \\_internal and \\__prefix_postfix_double__ are used internally or outside the class definition, their variable names are the same.\n","\n","However, if we want to reference \\_\\_double_underscore outside the class, it must be referenced as \\_PrefixPostfix__double_underscore__. In fact, all the attributes in a class can be listed out by calling a built-in function *dir()*, as we have also shown above. We can verify from this list that the name \\_\\_double_underscore has been modified by the **name mangling** convention.\n","\n","Finally, the sample code demonstrates the use of two other special methods: \\_\\_del\\_\\_() and \\_\\_eq\\_\\_. \\_\\_del\\_\\_() is also known as the class destructor. As opposite to the class constructor \\_\\_init\\_\\_(), \\_\\_del\\_\\_() when the class object is being deleted either automatically by Python (when doing automatic garbage collection) or manually by calling *del()*.\n","\n","\\_\\_eq\\_\\_() on the other hand belongs to a list of comparison operators for classes. When the \"==\" operator is used to compare two class objects, in fact, the code block defined within \\_\\_eq\\_\\_() function is called. When we customize this operator, the code can have the function to return any arbitrary values. The complete list of comparison operators are listed below:\n","\n"," *__lt__(self, other): <\n"," *__le__(self, other): <=\n"," *__eq__(self, other): ==\n"," *__ne__(self, other) != \n"," *__gt__(self, other): >\n"," *__ge__(self, other): >=\n"]},{"cell_type":"markdown","metadata":{},"source":["# Exercises\n","\n","1. Using the superclass Shape in the lecture, define a subclass called Rectangle, then update the methods \\_\\_init\\_\\_() and get_area(). Tip: A Rectangle has two sides, called width and length.\n","\n","2. In the Person class in the lecture, please code a class method using the @classmethod decorator called set_retirement_age(). The function will modify the value of the class attribute retirement_age().\n","\n","3. In the Circle class in the lecture, please code a static method using the @staticmethod decorator called get_radius(area). The function will return the radius of a circle given its area size as the input argument.\n","\n","4. Please implement the special method \\_\\_lt__() for the Shape class. Specifically, the method defines the operator \"<\" to compare the area sizes of any two subclasses of Shape. Pay attention to the fact that although the special method is defined in the superclass Shape, it remains effective to compare area sizes of any subclasses, even when the two subclasses are of different type. For instance, please verify that the method can be used to compare the area sizes between a Square class object and a Circle class object.\n","\n","5. Many built-in Python classes support the special operator \"+\". For instance, for integers, a + b executes an addition operation; while for strings, s1 + s2 executes an concatenation operation. For a user-defined class, the implementation of the same operator \"+\" can be defined by coding the special method \\_\\_add__(). \n","\n"," In this exercise, please create a new \\_\\_add__() method in the Square class in the lecture. If two squares have the same width, then square_1 + square_2 would return a Rectangle class object (as defined in Exercise 1 above) with the same width but 2*width as its length. If two squares are of different width, \\_\\_add__() should return None."]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["import math\n","\n","class Shape:\n","\n"," def get_area(self):\n"," ''' virtual method to calculate area of a shape'''\n"," raise NotImplementedError\n","\n"," def is_abstract(self):\n"," ''' return True if a shape has been assigned to the class'''\n"," if self.__class__ == Shape:\n"," return True\n"," else:\n"," return False\n","\n","class Square(Shape):\n"," ''' A subclass of Shape, specifically for calculating square area'''\n","\n"," def __init__(self, width):\n"," self.width = width\n"," self.area = self.get_area()\n","\n"," def get_area(self):\n"," ''' Area of a square is its width times width'''\n"," return self.width*self.width\n","class Rectangle(Shape):\n"," def __init__(self, width, height):\n"," self.width = width\n"," self.height = height\n"," self.area = self.get_area()\n"," def get_area(self):\n"," return self.width*self.height"]},{"cell_type":"code","execution_count":3,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["20240001\n","True\n","{2024: 2, '20240001': ('John', 'Smith'), '20240002': ('Jane', 'Doe')}\n"]}],"source":["from datetime import date \n","\n","class Person:\n"," ''' An example class to show Python Class definitions'''\n","\n"," retirement_age = 65 # Class attribute\n","\n"," def __init__(self, first_name, last_name, age = None):\n"," if type(first_name)!=str or type(last_name)!=str:\n"," raise TypeError('Person class initialized with the unsupported types.')\n","\n"," self.first_name = first_name\n"," self.last_name = last_name\n"," self.age = age\n","\n"," def set_age(self, age):\n"," if age<0:\n"," raise ValueError('age attribute in Person must be nonnegative.')\n","\n"," self.age = age\n","\n"," def years_until_retirement(selfish): # The use of selfish is a joke, it serves the same purpose as \"self\"\n"," until_retirement_year = Person.retirement_age - selfish.age\n"," if until_retirement_year<=0:\n"," print('This person has retired')\n"," else:\n"," print('This person has {0} years until retirement'.format(until_retirement_year))\n"," \n"," @classmethod\n"," def fromBirth(cls, first_name, last_name, birth_year):\n"," if type(birth_year)!=int:\n"," raise TypeERror('birth_year must be an int')\n"," \n"," if birth_year > date.today().year:\n"," raise ValueError('Given birth year is greater than current year.')\n"," \n"," return cls(first_name, last_name, date.today().year - birth_year)\n"," \n","\n"," @classmethod\n"," def set_retirement_age(cls,age):\n"," retirement_age = age\n","\n","\n"," @staticmethod\n"," def isAdult(age):\n"," return age > 18\n"," \n","class Student(Person):\n"," student_record = dict()\n"," \n"," def __init__(self, first_name, last_name, class_year):\n"," super().__init__(first_name, last_name)\n","\n"," if type(class_year)!=int:\n"," raise TypeError('class_year shall be an integer')\n"," if class_year < 1900 or class_year>2999:\n"," raise ValueError('Class year value is not supported')\n","\n"," if class_year in Student.student_record:\n"," Student.student_record[class_year] += 1\n"," else:\n"," Student.student_record[class_year] = 1\n","\n"," self.student_ID = str(class_year) + \\\n"," format(Student.student_record[class_year],'04d')\n"," Student.student_record[self.student_ID] = (first_name, last_name)\n","\n"," def year_of_graduation(self):\n"," return (int(self.student_ID[0:4]))\n"," \n","x1 = Student('John', 'Smith', 2024)\n","x1.age = 20\n","print(x1.student_ID)\n","print(x1.isAdult(x1.age))\n","x2 = Student('Jane', 'Doe', 2024)\n","print(x2.student_record)\n"]},{"cell_type":"code","execution_count":5,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["Is class initialized: False\n","25\n","Is class initialized: False\n","78.53981633974483\n"]}],"source":["#3\n","import math\n","\n","class Shape:\n","\n"," def get_area(self):\n"," ''' virtual method to calculate area of a shape'''\n"," raise NotImplementedError\n","\n"," def is_abstract(self):\n"," ''' return True if a shape has been assigned to the class'''\n"," if self.__class__ == Shape:\n"," return True\n"," else:\n"," return False\n","\n","class Square(Shape):\n"," ''' A subclass of Shape, specifically for calculating square area'''\n","\n"," def __init__(self, width):\n"," self.width = width\n"," self.area = self.get_area()\n","\n"," def get_area(self):\n"," ''' Area of a square is its width times width'''\n"," return self.width*self.width\n"," \n","class Circle(Shape):\n"," ''' A subclass of Shape, specifically for calculating circle area'''\n","\n"," def __init__(self, radius):\n"," self.radius = radius\n"," self.area = self.get_area()\n","\n"," def get_area(self):\n"," ''' Area of a circle is pi times radius square'''\n"," return math.pi * self.radius ** 2.0\n"," \n"," @staticmethod\n"," def get_radius(self,area):\n"," radius = math.sqrt(area/math.pi)\n"," return radius\n","\n","x1 = Square(5)\n","print('Is class initialized: ',x1.is_abstract())\n","print(x1.get_area())\n","\n","x2 = Circle(5)\n","print('Is class initialized: ', x2.is_abstract())\n","print(x2.get_area())\n","\n"]},{"cell_type":"code","execution_count":8,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["Is class initialized: False\n","25\n","Is class initialized: False\n","78.53981633974483\n"]}],"source":["import math\n","\n","class Shape:\n","\n"," def get_area(self):\n"," ''' virtual method to calculate area of a shape'''\n"," raise NotImplementedError\n","\n"," def is_abstract(self):\n"," ''' return True if a shape has been assigned to the class'''\n"," if self.__class__ == Shape:\n"," return True\n"," else:\n"," return False\n"," def __lt__(self,other) -> bool:\n"," return self.get_area < other.get_area\n","\n","class Square(Shape):\n"," ''' A subclass of Shape, specifically for calculating square area'''\n","\n"," def __init__(self, width):\n"," self.width = width\n"," self.area = self.get_area()\n","\n"," def get_area(self):\n"," ''' Area of a square is its width times width'''\n"," return self.width*self.width\n"," \n","class Circle(Shape):\n"," ''' A subclass of Shape, specifically for calculating circle area'''\n","\n"," def __init__(self, radius):\n"," self.radius = radius\n"," self.area = self.get_area()\n","\n"," def get_area(self):\n"," ''' Area of a circle is pi times radius square'''\n"," return math.pi * self.radius ** 2.0\n","\n","x1 = Square(5)\n","print('Is class initialized: ',x1.is_abstract())\n","print(x1.get_area())\n","\n","x2 = Circle(5)\n","print('Is class initialized: ', x2.is_abstract())\n","print(x2.get_area())\n","\n","\n","\n","\n"]},{"cell_type":"code","execution_count":10,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["Is class initialized: False\n","25\n","Is class initialized: False\n","25\n","<__main__.Rectangle object at 0x10ca49160>\n"]}],"source":["#Q5\n","import math\n","\n","class Shape:\n","\n"," def get_area(self):\n"," ''' virtual method to calculate area of a shape'''\n"," raise NotImplementedError\n","\n"," def is_abstract(self):\n"," ''' return True if a shape has been assigned to the class'''\n"," if self.__class__ == Shape:\n"," return True\n"," else:\n"," return False\n","\n","class Square(Shape):\n"," ''' A subclass of Shape, specifically for calculating square area'''\n","\n"," def __init__(self, width):\n"," self.width = width\n"," self.area = self.get_area()\n","\n"," def get_area(self):\n"," ''' Area of a square is its width times width'''\n"," return self.width*self.width\n"," def __ADD__(self,other):\n"," if self.width == other.width:\n"," return Rectangle(self.width, self.width*2)\n","class Circle(Shape):\n"," ''' A subclass of Shape, specifically for calculating circle area'''\n","\n"," def __init__(self, radius):\n"," self.radius = radius\n"," self.area = self.get_area()\n","\n"," def get_area(self):\n"," ''' Area of a circle is pi times radius square'''\n"," return math.pi * self.radius ** 2.0\n"," \n"," @staticmethod\n"," def get_radius(self,area):\n"," radius = math.sqrt(area/math.pi)\n"," return radius\n","class Rectangle(Shape):\n"," def __init__(self, width, height):\n"," self.width = width\n"," self.height = height\n"," self.area = self.get_area()\n"," def get_area(self):\n"," return self.width*self.height\n","\n","saqure = Square(5)\n","print('Is class initialized: ',x1.is_abstract())\n","print(saqure.get_area())\n","\n","\n","squar = Square(5)\n","print('Is class initialized: ',x1.is_abstract())\n","print(squar.get_area())\n","\n","\n","print(Square.__ADD__(saqure, squar))"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":[]},{"cell_type":"markdown","metadata":{},"source":["# Challenges\n","\n","1. Create a subclass of the BankAccount class in the previous lecture, called StockAccount. Adding to the superclass, the subclass should have at least one more attribute of stock_positions as a dictionary type. Each dictionary entry has the stock ticker name as the key, and the customer's size of the stock as the value. Please also design relevant methods to update the values in stock_positions, such as retrieving stock price, buying or selling a stock based on its stock price, and linking the buying and selling with the account's cash_position."]},{"cell_type":"code","execution_count":11,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["authenticated\n","NEW BALANCE: 10000\n"]}],"source":["class BankAccount:\n","\n"," customer_ID = \"\"\n"," cash_position=0\n"," amnt = 0\n"," @classmethod\n"," def GetCustomerID(cls,identification):\n"," id = input('Enter ID')\n"," if id == identification:\n"," customer_ID = str(identification)\n"," print(\"authenticated\")\n"," @classmethod\n"," def TNSCTN(cls):\n"," wthdORdpst = input(\"withdraw or deposit?\")\n"," if wthdORdpst == \"withdraw\":\n"," amnt=int(input(\"how much withdraw?\"))\n"," if amnt - cls.cash_position <=0:\n"," cls.cash_position-=amnt\n"," amnt = 0\n"," print(\"NEW BALANCE:\",cls.cash_position)\n"," elif wthdORdpst ==\"deposit\":\n"," amnt=int(input(\"how much deposit?\"))\n"," if amnt >=0:\n"," cls.cash_position+=amnt\n"," amnt = 0\n"," print(\"NEW BALANCE:\",cls.cash_position)\n","class StockAccount(BankAccount):\n"," pass\n","\n","\n","BankAccount.GetCustomerID(\"glizzygobbler23\")\n","BankAccount.TNSCTN()\n","\n"," "]}],"metadata":{"kernelspec":{"display_name":"Python 3","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.18"}},"nbformat":4,"nbformat_minor":4} diff --git a/Part One/1-11-binary-representation.ipynb b/Part One/1-11-binary-representation.ipynb index 9afe9e8..05ce6d1 100644 --- a/Part One/1-11-binary-representation.ipynb +++ b/Part One/1-11-binary-representation.ipynb @@ -1 +1 @@ -{"cells":[{"cell_type":"markdown","metadata":{},"source":["**Learning Python -- The Programming Language for Artificial Intelligence and Data Science**\n","\n","**Lecture 11: Binary Representation**\n","\n","**By Allen Y. Yang, PhD**\n","\n","(c) Copyright Intelligent Racing Inc., 2021-2024. All rights reserved. Materials may NOT be distrubted or used for any commercial purposes."]},{"cell_type":"markdown","metadata":{},"source":["From this lecture, we will start the Level-2 course on learning modern AI theories and applications using Python. In the first level course of this series, we have learned the basic programming skill on Python. In our journey so far, we have encountered several basic Python variable types, including integers, floats, boolean, string, lists, tuples, dictionaries, and sets. I will assume in this course, you have fluently mastered the basic skills of coding Python programs using these variables. You are encouraged to refresh your memory about these topics from the lecture notes and videos of the first course.\n","\n","In this first lecture of the second course, we will discuss how these basic variable types are stored in computer memory.\n","\n","# Integers\n","\n","In Python since version 3, an *int* variable is capable of storing arbitrarily large integer values in magnitude. This affects how the integer values are stored in the computer memory.\n","\n","To understand the fundamental data structures of Python integer variables, we first need to understand that modern computers store data and programs only in binary form with two distict values of 0 and 1. Since the modern processors commonly known as central processing units (CPU) all use digital transistors as the basic building block, the transistors can conveniently using two discrete voltage values, namely, low and high, to represent two binary digits 0 and 1. In the computer literature, one digit of 0 or 1 value is called one bit; 8 bits together is called a byte. For example, a modern computer and its operating system is called a 64-bit system because its processor in one step can calculate fundamental operators such as addition or multiplication of one to two numbers represented by 64-bits.\n","\n","In this lecture, we will not dive deeper to practice arithmetic calculations in binary format. Below, we use the basic 8-bit binary representation to just show some examples:\n","\n"," * integer 0 is represented by eight 0's: (00000000).\n"," * integer 1 is represented by: (00000001). A one at the lowest digit represents the same value one in base-10 system.\n"," * integer 2 is represented by: (00000010). Notice that each binary digit only holds two possible values, when value 2 is larger than 1, its binary representation will be forced to use one digit higher to represent (00000001) + (00000001) = (00000010).\n"," * integer 3 is represented by: (00000011). The representation shows the fact that 3 in base-10 representation is equal to (00000010) + (00000001) = (00000011).\n"," \n","In Python beyond version 3, an integer variable is allowed to use an unlimited number of bytes to store integers, as long as the computer has the space to allocate valid memory addresses to store the bytes. As a result, this implementation supports storing very large integers in *int* variables.\n","\n","To verify the fact, we use a useful Python function called *sys.getsizeof()*. In the sample code below, we see that the smallest number of bytes (again, 1 byte is 8 bits) to store any integer value is 28. The reader may wonder why the smallest number of bytes is not one byte or any other number much smaller than 28. The reason is that Python is an OOP language, as such, any variable type is in fact a *class* data structure. Therefore, there are fixed overhead data structure to define the properties of a class object in addition to storing just its data value. \n","\n","As we increase the integer value to a fairly large value, such as *i3* in the sample code, then Python would need to increate the number of bytes in the memory."]},{"cell_type":"code","execution_count":1,"metadata":{"execution":{"iopub.execute_input":"2024-03-15T07:13:47.431898Z","iopub.status.busy":"2024-03-15T07:13:47.431494Z","iopub.status.idle":"2024-03-15T07:13:47.438784Z","shell.execute_reply":"2024-03-15T07:13:47.437755Z","shell.execute_reply.started":"2024-03-15T07:13:47.431816Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["Byte count: 28 28\n","Byte count: 36\n"]}],"source":["import sys\n","\n","i1 = 2022\n","i2 = 1\n","print('Byte count: ', sys.getsizeof(i1), sys.getsizeof(i2))\n","\n","i3 = 314159265358979323846\n","print('Byte count: ', sys.getsizeof(i3))"]},{"cell_type":"markdown","metadata":{},"source":["Finally, let us talk about storing positive integers and negative integers in Python. If our readers are familiar with C or C++ language, they would know that an integer can be declared as signed integer or unsigned integer. A signed integer as its name suggests differentiates an integer value to be either positive or negative, while an unsigned integer does not represent negative numbers. \n","\n","To illutrate how the sign of a number affects its data representation in the memory, let us again borrow the 8-bit binary representation as an example. If we represent only positive integers (or more precies, nonnegative integers) using 8 bits, then the smallest integer is 0 or (00000000) and the largest integer is 255 or (11111111). More specifically,\n","\n"," 255 = 1 + 2 + 4 + 8 + 16 + 32 + 64 + 128 = (00000001) + (00000010) + (00000100) + (00001000) + (00010000) + (00100000) + (01000000) + (10000000)\n"," \n","Now let us consider using 8 bits to represent both positive and negative integers, then we need to take over at least one bit to represent the + and - sign. Therefore, there are only 7 bits left to store the magnitude of the integer value. As a result, the range of the signed integers in 8-bit representation is between +127 and -128.\n","\n","The above is the situation if a programming language supports both signed and unsigned integer type. Nevertheless, in Python, there is no unsigned integer type. Since integers can be arbitrarily large only constrained by the available computer memory, there is very little reason to create unsigned integer type by avoiding storing the sign digit. \n","\n","In the sample code below, we use the same function *sys.getsizeof()* to calculate the byte number when storing *i1* and *i3* with the negative sign. We can see that the final byte counts are the same as the previous example."]},{"cell_type":"code","execution_count":2,"metadata":{"execution":{"iopub.execute_input":"2024-03-15T07:13:55.937203Z","iopub.status.busy":"2024-03-15T07:13:55.936848Z","iopub.status.idle":"2024-03-15T07:13:55.943040Z","shell.execute_reply":"2024-03-15T07:13:55.942114Z","shell.execute_reply.started":"2024-03-15T07:13:55.937168Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["Byte count: 28\n","Byte count: 36\n"]}],"source":["import sys\n","\n","i1 = -2022\n","print('Byte count: ', sys.getsizeof(i1))\n","\n","i3 = -314159265358979323846\n","print('Byte count: ', sys.getsizeof(i3))"]},{"cell_type":"markdown","metadata":{},"source":["# Bitwise Operations\n","\n","The fact that numeric values are stored in computer in binary format leads to certain **bitwise operations** applied in binary representation but not in the conventional decimal arithmetic. Below, we go over some of these examples:\n","\n"," * Shift left: x<>y represents shifting the binary digits in x to the right by y steps. One can verify that shifting an integer to the right by one bit is equivalent to divide by 2.\n"," * bitwise and: x & y first requires both x and y to be represented in binary format by the same number of digits. Then the resulting number assigns 1 in one digit if only the same digit from both numbers in x and y are also 1.\n"," * bitwise or: x | y also requires both x and y to be represented in binary format by the same number of digits. Then the resulting number assigns 1 in one digit if either the same digit in x or y is 1."]},{"cell_type":"code","execution_count":3,"metadata":{"execution":{"iopub.execute_input":"2024-03-15T07:14:09.325582Z","iopub.status.busy":"2024-03-15T07:14:09.325269Z","iopub.status.idle":"2024-03-15T07:14:09.332691Z","shell.execute_reply":"2024-03-15T07:14:09.331690Z","shell.execute_reply.started":"2024-03-15T07:14:09.325550Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["00000100 << 1 = 8\n","00000111 >> 1 = 3\n","00000100 & 00000101 = 4\n","00000101 | 00000110 = 7\n"]}],"source":["print('{0:08b}'.format(4),'<< 1 = ', 4<<1)\n","print('{0:08b}'.format(7), '>> 1 =', 7>>1)\n","print('{0:08b}'.format(4), '&', '{0:08b}'.format(5), '=', 4&5)\n","print('{0:08b}'.format(5), '|', '{0:08b}'.format(6), '=', 5|6)"]},{"cell_type":"markdown","metadata":{},"source":["In the above sample code, we showed some examples of bitwise operations. Note that we also utilized the *format()* function to display any integer number in binary representation. The format *{0:08b}* is understood as follows: \n"," * the first \"0\" indicates taking the first argument of the *format()* method and put its string at the location; \n"," * the colon will specify a string format to display the *format()* argument; \n"," * \"08\" means maintaining minimal 8 digits; \n"," * and \"b\" means in binary format."]},{"cell_type":"markdown","metadata":{},"source":["# Floating-Point Numbers\n","\n","Although Python enables storing integers with arbitrary numbers of digits, more traditional systems typically only support numeric representation using fixed bits. Another limitation of integers is obviously that they cannot represent real numbers that contain both the integer part and the fraction part. To more effectively represent large numbers with fractions with only fixed numbers of bits, modern computers rely on floating-point numbers.\n","\n","Typically, Python represents a floating-point number, or *float* in short, using 64-bit storage in the memory. Due to the same reason that float is a class object, its overall byte count for one float variable will be a little larger than 64 bits or 8 bytes. Let us see some examples to compare with the situation for integers:"]},{"cell_type":"code","execution_count":4,"metadata":{"execution":{"iopub.execute_input":"2024-03-15T07:14:54.456441Z","iopub.status.busy":"2024-03-15T07:14:54.456115Z","iopub.status.idle":"2024-03-15T07:14:54.463554Z","shell.execute_reply":"2024-03-15T07:14:54.462420Z","shell.execute_reply.started":"2024-03-15T07:14:54.456405Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["Byte count: 24 24\n","Byte count: 24\n","3.1415926535897933e+20 314159265358979334144.000000\n"]}],"source":["import sys\n","\n","f1 = 2022.0\n","f2 = 1.0\n","print('Byte count: ', sys.getsizeof(f1), sys.getsizeof(f2))\n","\n","f3 = 314159265358979323846.0\n","print('Byte count: ', sys.getsizeof(f3))\n","print(f3, '{0:20f}'.format(f3))"]},{"cell_type":"markdown","metadata":{},"source":["In the sample code, we use the same values to create three variables but in float type. First, we see that the byte counts for all three variables are 24, meaning the float type in Python uses the same amount of memory to store floating-point numbers.\n","\n","Second, in base-10 decimal representation, a float is represented by a single nonzero digit plus other digits in the fraction part and then followed by a base-10 exponent part: 3.1415926535897933e+20. In base-2 representation in the memory, it is similarly represented by a single nonzero digit (the only nonzero binary digit is 1) plus other binary digits in the fraction part followed by a base-2 exponent part. \n","\n","In both representations, having the memory storage limited to 64 bits means any float can only have limited accuracy, meaning it cannot store the fraction part with unlimited precise digits. We see in the final output of the example above, that in base-10 representation, Python float has 15 valid digits in the fraction part; in base-2 representation, the number of valid digits in the fraction part is 52.\n","\n","The 64-bit floating-point representation in Python actually follows an industry standard called IEEE Standard 754. Using this standard guarantees those floating points stored in a data block by Python can be correctly loaded for some other programs and other languages to use. So in the IEEE Standard 754, using the floating point representation, some special numeric values are also defined:\n","\n"," * 0.0: In float, zero actually is a special value because normal float arithmetic will never result in a zero value. This is due to the fact that the integer part of a float is always nonzero. Except for the symbolic zero in float, where all 64 bits are forced to be set to zero.\n"," * infinity and -infinity: Infinity will be treated to be greater than any finite float number, defined by float('inf'); Negative infinity will be treated to be less than any finite float number, defined by float('-inf').\n"," \n","Let us see some examples below:"]},{"cell_type":"code","execution_count":5,"metadata":{"execution":{"iopub.execute_input":"2024-03-15T07:15:02.124028Z","iopub.status.busy":"2024-03-15T07:15:02.123702Z","iopub.status.idle":"2024-03-15T07:15:02.129659Z","shell.execute_reply":"2024-03-15T07:15:02.128538Z","shell.execute_reply.started":"2024-03-15T07:15:02.123996Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["False\n","True True\n"]}],"source":["a = (0.3 * 3) + 0.1\n","b = 1.0\n","print(a == b)\n","print(a float('-inf'))"]},{"cell_type":"markdown","metadata":{},"source":["We can see from the first example, that when two float variables *a* and *b* are created, in principle, the condition that *a==b* should hold true. However, in their float representation in the memory, they are not exactly equal, which may come as a surprise. To deal with the limited precision problem if two floating points need to be compared, it is a better practice to compare the difference within a given range. Let us see the alternative approach below"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["import math\n","a = (0.3 * 3) + 0.1\n","b = 1.0\n","print(math.isclose(a,b))"]},{"cell_type":"markdown","metadata":{},"source":["In this new approach, an imported function *math.isclose()* compares two float numbers within a tolerance value. The default value is 1e-09, but the user can further modify this tolerance by providing a third argument as the customized tolerance value."]},{"cell_type":"markdown","metadata":{},"source":["# Strings\n","\n","Similar to the *int* and *float* type, string type or *str* in short is a class type, which contains some suppplementary information about the string data, such as length, character kind, encoding type, and hash value, etc. Therefore, although storing basic English alphabets only take one byte, but the *str* class will take more bytes to store in the memory. In the sample code below, we see that the byte count for an empty string is 49, for one character is 50, and for four characters is 53. "]},{"cell_type":"code","execution_count":3,"metadata":{"execution":{"iopub.execute_input":"2024-03-15T08:25:27.795795Z","iopub.status.busy":"2024-03-15T08:25:27.795309Z","iopub.status.idle":"2024-03-15T08:25:27.804084Z","shell.execute_reply":"2024-03-15T08:25:27.802451Z","shell.execute_reply.started":"2024-03-15T08:25:27.795753Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["\n","Byte counts: 49 50 53\n"]}],"source":["import sys\n","\n","s0 = \"\"\n","s1 = \"2\"\n","s2 = \"2020\"\n","\n","print(type(s1))\n","print('Byte counts: ', sys.getsizeof(s0), sys.getsizeof(s1), sys.getsizeof(s2))"]},{"cell_type":"markdown","metadata":{},"source":["Note that the string class uses the **Unicode** standard to encode characters into bytes. Although if the characters are basic English alphabets, the coding length for one character is one byte, but other kinds of characters may take longer spaces. Let us see the comparison below:"]},{"cell_type":"code","execution_count":4,"metadata":{"execution":{"iopub.execute_input":"2024-03-15T08:25:30.281226Z","iopub.status.busy":"2024-03-15T08:25:30.280603Z","iopub.status.idle":"2024-03-15T08:25:30.289116Z","shell.execute_reply":"2024-03-15T08:25:30.287688Z","shell.execute_reply.started":"2024-03-15T08:25:30.281183Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["\n","Byte counts: 76 78 82\n"]}],"source":["s1 = \"二\"\n","s2 = \"二0\"\n","s3 = \"二0二0\"\n","\n","print(type(s1))\n","print('Byte counts: ', sys.getsizeof(s1), sys.getsizeof(s2), sys.getsizeof(s3))"]},{"cell_type":"markdown","metadata":{},"source":["We see from the above example that when a string contains special characters or characters from other languages than English, it will be necessary to increase the byte count to encode each character. For a Chinese character \"二“ that means the number two, the byte count is 2. However, when we add another basic character \"0\" in *s2*, we can see that the increase in byte count is also 2. In *s3*, the increase in byte count is 3*2 = 6. This means a Python string always use the same byte count to encode every character. The encoding strategy makes calculating the memory address of any character to be straightforward, namely, the memory address for str[offset] is str[0] + byte_count * offset, since the byte_count is uniform for every character, may it be 1, 2, or 3, etc.\n","\n","In the level-1 course when we introduced Python's hashing library, we introduced another encoding standard called ASCII or UTF-8. ASCII stands for *American Standard Cod for Information Interchange*, and it was first introduced to encode English text and standard symbols in 1963. Since early computers did not care about encoding characters from other international language, the original ASCII code only has 8 bits or 1 byte. This is the same length when Unicode format encodes ASCII characters. However, when more special characters and international languages are added in text documents, ASCII is expanded to take up more bytes, which leads to the UTF-8 standard also supported by Python.\n","\n","We can see in the example below, that the same strings from the above example can be encoded by UTF-8 instead of the default Unicode. However, UTF-8 encoding uses variable byte counts. For example, from *b1* to *b2*, only one byte is added to the memory size because \"0\" is encoded in ASCII using one byte. From *b2* to *b3*, 3 bytes are added to encode the Chinese character \"二“. The tradeoff using the UTF-8 standard is that it returns a shorter byte array than the Unicode standard, but since each character is variable byte length, calculating the memory address of a character in the middle of a string would need to traverse and count the accumulated byte counts of every character before it, which significantly increases the time complexity."]},{"cell_type":"code","execution_count":5,"metadata":{"execution":{"iopub.execute_input":"2024-03-15T08:25:33.395478Z","iopub.status.busy":"2024-03-15T08:25:33.394606Z","iopub.status.idle":"2024-03-15T08:25:33.405296Z","shell.execute_reply":"2024-03-15T08:25:33.402989Z","shell.execute_reply.started":"2024-03-15T08:25:33.395426Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["\n","Byte counts: 36 37 40 41\n"]}],"source":["b1 = \"二\".encode('utf-8')\n","b2 = \"二0\".encode('utf-8')\n","b3 = \"二0二\".encode('utf-8')\n","b4 = \"二0二0\".encode('utf-8')\n","\n","print(type(b1))\n","print('Byte counts: ', sys.getsizeof(b1), sys.getsizeof(b2), sys.getsizeof(b3), sys.getsizeof(b4))"]},{"cell_type":"markdown","metadata":{},"source":["To convert a *bytes* type to the standard *str* type, one can use another function in reverse of the *.encode()* method, called *.decode()*. By default, it will assume the encoding of the source data is *'utf-8'*. Therefore, this argument can be ignored if the default is the correct assumption."]},{"cell_type":"code","execution_count":8,"metadata":{"execution":{"iopub.execute_input":"2024-03-15T08:41:42.571260Z","iopub.status.busy":"2024-03-15T08:41:42.570572Z","iopub.status.idle":"2024-03-15T08:41:42.581495Z","shell.execute_reply":"2024-03-15T08:41:42.580202Z","shell.execute_reply.started":"2024-03-15T08:41:42.571213Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["\n","二 二0\n"]}],"source":["b1 = \"二\".encode('utf-8')\n","b2 = \"二0\".encode('utf-8')\n","s1 = b1.decode('utf-8')\n","s2 = b2.decode()\n","\n","print(type(s1))\n","print(s1, s2)"]},{"cell_type":"markdown","metadata":{},"source":["Finally, if the characters in a string is limited to only ASCII code, we can use the prefix *b* in front of the quotation marks to obtain an ASCII coding. However, ASCII coding cannot encode special characters, such as characters from other international languages. We will see another sample code below. Notice that encoding character \"二\" using ASCII will receive a SyntexError."]},{"cell_type":"code","execution_count":7,"metadata":{"execution":{"iopub.execute_input":"2024-03-15T08:25:45.806985Z","iopub.status.busy":"2024-03-15T08:25:45.806531Z","iopub.status.idle":"2024-03-15T08:25:45.815331Z","shell.execute_reply":"2024-03-15T08:25:45.814107Z","shell.execute_reply.started":"2024-03-15T08:25:45.806943Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["\n","Byte counts: 34 37 37\n"]}],"source":["s1 = b\"2\"\n","s2 = b\"2020\"\n","s3 = \"2020\".encode('ascii')\n","\n","print(type(s1))\n","print('Byte counts: ', sys.getsizeof(s1), sys.getsizeof(s2), sys.getsizeof(s3))"]},{"cell_type":"code","execution_count":10,"metadata":{"execution":{"iopub.execute_input":"2024-03-15T07:17:44.947771Z","iopub.status.busy":"2024-03-15T07:17:44.947412Z","iopub.status.idle":"2024-03-15T07:17:44.955014Z","shell.execute_reply":"2024-03-15T07:17:44.953408Z","shell.execute_reply.started":"2024-03-15T07:17:44.947736Z"},"trusted":true},"outputs":[{"ename":"SyntaxError","evalue":"bytes can only contain ASCII literal characters. (, line 1)","output_type":"error","traceback":["\u001b[0;36m File \u001b[0;32m\"\"\u001b[0;36m, line \u001b[0;32m1\u001b[0m\n\u001b[0;31m s4 = b\"二0二0\"\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m bytes can only contain ASCII literal characters.\n"]}],"source":["s4 = b\"二0二0\""]},{"cell_type":"markdown","metadata":{},"source":["# Lists\n","\n","Lists are a versatile variable type where the elements in one list can have different types. Let us use the *sys.getsizeof()* function to examine some illustrative examples:"]},{"cell_type":"code","execution_count":11,"metadata":{"execution":{"iopub.execute_input":"2024-03-15T07:18:06.974774Z","iopub.status.busy":"2024-03-15T07:18:06.974441Z","iopub.status.idle":"2024-03-15T07:18:06.982029Z","shell.execute_reply":"2024-03-15T07:18:06.980078Z","shell.execute_reply.started":"2024-03-15T07:18:06.974744Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["Byte counts: 72 80 88 96\n"]}],"source":["import sys\n","\n","l0 = [] # an empty list\n","l1 = [\"二\"] # a list that contains a Chinese character of two\n","l2 = [\"二\", 0] # a list that contains a Chinese character of two and an integer zero\n","l3 = [\"二\", 0, [\"二\", 0]] # a list that further contains another list as one element\n","\n","print('Byte counts: ', sys.getsizeof(l0), sys.getsizeof(l1), sys.getsizeof(l2), sys.getsizeof(l3))"]},{"cell_type":"markdown","metadata":{},"source":["From the sample code, we see that the byte count for an empty list variable is 72 bytes. Again, this is due to the fact that list is a class type that comes with associated class methods and attributes, so even the empty list will take up a size of memory space. \n","\n","We then notice that when we add one by one more elements into the list, the increase in byte counts seems to be constant 8 bytes, regardless of the content of the element. For example, from *l2* to *l3*, even when we add an entire sublist to the end of *l2*, the increase byte count is still 8. More surprisingly, we can recall from the above string examples that the byte count for a list [\"二\", 0] is 88. It seems counter-intuitive that adding a list element [\"二\", 0] only increases byte count by 8 from 88 to 96.\n","\n","To resolve this dilemma, we have to understand how the list elements are stored and referenced in Python. The hint is to recall that list is a mutable type, meaning, modifying the element values in a list does not change its memory address and ID. This means what is included in the list's byte counts are only references to separate element objects of their own memory addresses. Since storing memory references only costs constant space of 8 bytes (i.e., 64 bits in a 64-bit OS), the *sys.getsizeof()* function can only count the bytes for the memory allocation of these reference pointers.\n","\n","If one wants to calculate precisely the total memory size of the whole list data, we should use the following approach:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["import sys\n","l2 = [\"二\", 0]\n","\n","byte_count = sys.getsizeof(l2) + sys.getsizeof(l2[0]) + sys.getsizeof(l2[1])\n","print(byte_count)"]},{"cell_type":"markdown","metadata":{},"source":["Next, since all the element references are organized as an ordered sequence, we consider the time complexity to add or remove elements from this ordered sequence."]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["trial_count = 1000\n","\n","import time\n","\n","# Test remove the last elements\n","test_list = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ')*1000000\n","tic = time.time()\n","for i in range(trial_count):\n"," test_list.pop(-1)\n","elapsed_time = time.time() - tic\n","print('Total time removing last elements: ', elapsed_time)\n","\n","# Test remove the first elements\n","test_list = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ')*1000000\n","tic = time.time()\n","for i in range(trial_count):\n"," test_list.pop(0)\n","elapsed_time = time.time() - tic\n","print('Total time removing first elements: ',elapsed_time)"]},{"cell_type":"markdown","metadata":{},"source":["When we run the above sample code, we will see the dramatic difference in the time complexity, where the first operation to remove the last element of a list repeating 1,000 times only costs less than 0.001 second, but the second operation to remove the first element of a list repeating 1,000 times costs more than 26 seconds on Kaggle's online Jupyter Notebook. This dramatic difference in cost of time and therefore computer resources is caused by the fact that the list type simply stores its element references as an ordered sequence. Consider the following two situations:\n","\n","* When the last element of a sequence is removed, the length of the sequence is simply reduced by one, and there is no other operations necessary to maintain values for the rest of the elements in the list.\n","* When the first element of a sequence is removed, to maintain the order of the sequence except removing the first element, Python actually iteratively moves the (i+1)-th element reference to overwrite the i-th element reference. As a result, when our test list is long such as in the above example, this operation of shifting all elements to the left by one actually is very expensive even in modern computer's standard. \n"," \n","The above complexity analysis about the *pop()* operation also applies to the *insert()* operation. In conclusion, when we use the list-type variables, we shall try to encourage poping and inserting more from the end of the list, and avoid the same operations from the beginning or other random intermediate locations."]},{"cell_type":"markdown","metadata":{},"source":["# Exercises\n","\n"]},{"cell_type":"markdown","metadata":{},"source":["1. Please write a Python code to evaluate the number of bytes used to encode an emoji character. The starting assignment statement is provided below:"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["s = \"😊\" # Smiling face with smiling eyes emoji"]},{"cell_type":"markdown","metadata":{},"source":["2. Explain the difference between the hash results of the two examples that are fairly similar in values:"]},{"cell_type":"code","execution_count":13,"metadata":{"execution":{"iopub.execute_input":"2024-03-15T08:57:50.745974Z","iopub.status.busy":"2024-03-15T08:57:50.745229Z","iopub.status.idle":"2024-03-15T08:57:50.754228Z","shell.execute_reply":"2024-03-15T08:57:50.752831Z","shell.execute_reply.started":"2024-03-15T08:57:50.745928Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["True\n","False\n"]}],"source":["s1 = '2'; b1 = s1.encode('utf-8')\n","print(s1.__hash__()==b1.__hash__())\n","\n","s2 = \"二0\"\n","b2 = s2.encode('utf-8')\n","print(s2.__hash__()==b2.__hash__())"]},{"cell_type":"markdown","metadata":{},"source":["3. Debug the following code"]},{"cell_type":"code","execution_count":14,"metadata":{"execution":{"iopub.execute_input":"2024-03-15T09:10:51.999623Z","iopub.status.busy":"2024-03-15T09:10:51.998813Z","iopub.status.idle":"2024-03-15T09:10:52.028879Z","shell.execute_reply":"2024-03-15T09:10:52.026719Z","shell.execute_reply.started":"2024-03-15T09:10:51.999574Z"},"trusted":true},"outputs":[{"ename":"TypeError","evalue":"a bytes-like object is required, not 'str'","output_type":"error","traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)","\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0ms\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m\"test string\"\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mencode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'utf-8'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0ma\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mb\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0ms\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msplit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m' '\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mb\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;31mTypeError\u001b[0m: a bytes-like object is required, not 'str'"]}],"source":["s = \"test string\".encode('utf-8')\n","a, b = s.split(' ')\n","\n","print(a, b)"]},{"cell_type":"markdown","metadata":{},"source":["# Challenges\n","\n","1. In Python, there is a dynamically linked list type called *deque* from the module name *collections*. A deque type variable can pop its elements from the right using *.pop()* method, and from the left using *.popleft()* method.\n","\n","Please modify the sample code above to pop a deque variable with the same one million copies of the 26 alphabet letters from the left and right, one by one. Compare the time complexity of the operations and discuss the result."]}],"metadata":{"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":30055,"isGpuEnabled":false,"isInternetEnabled":true,"language":"python","sourceType":"notebook"},"kernelspec":{"display_name":"Python 3","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.7.9"}},"nbformat":4,"nbformat_minor":4} +{"cells":[{"cell_type":"markdown","metadata":{},"source":["**Learning Python -- The Programming Language for Artificial Intelligence and Data Science**\n","\n","**Lecture 11: Binary Representation**\n","\n","**By Allen Y. Yang, PhD**\n","\n","(c) Copyright Intelligent Racing Inc., 2021-2024. All rights reserved. Materials may NOT be distrubted or used for any commercial purposes."]},{"cell_type":"markdown","metadata":{},"source":["From this lecture, we will start the Level-2 course on learning modern AI theories and applications using Python. In the first level course of this series, we have learned the basic programming skill on Python. In our journey so far, we have encountered several basic Python variable types, including integers, floats, boolean, string, lists, tuples, dictionaries, and sets. I will assume in this course, you have fluently mastered the basic skills of coding Python programs using these variables. You are encouraged to refresh your memory about these topics from the lecture notes and videos of the first course.\n","\n","In this first lecture of the second course, we will discuss how these basic variable types are stored in computer memory.\n","\n","# Integers\n","\n","In Python since version 3, an *int* variable is capable of storing arbitrarily large integer values in magnitude. This affects how the integer values are stored in the computer memory.\n","\n","To understand the fundamental data structures of Python integer variables, we first need to understand that modern computers store data and programs only in binary form with two distict values of 0 and 1. Since the modern processors commonly known as central processing units (CPU) all use digital transistors as the basic building block, the transistors can conveniently using two discrete voltage values, namely, low and high, to represent two binary digits 0 and 1. In the computer literature, one digit of 0 or 1 value is called one bit; 8 bits together is called a byte. For example, a modern computer and its operating system is called a 64-bit system because its processor in one step can calculate fundamental operators such as addition or multiplication of one to two numbers represented by 64-bits.\n","\n","In this lecture, we will not dive deeper to practice arithmetic calculations in binary format. Below, we use the basic 8-bit binary representation to just show some examples:\n","\n"," * integer 0 is represented by eight 0's: (00000000).\n"," * integer 1 is represented by: (00000001). A one at the lowest digit represents the same value one in base-10 system.\n"," * integer 2 is represented by: (00000010). Notice that each binary digit only holds two possible values, when value 2 is larger than 1, its binary representation will be forced to use one digit higher to represent (00000001) + (00000001) = (00000010).\n"," * integer 3 is represented by: (00000011). The representation shows the fact that 3 in base-10 representation is equal to (00000010) + (00000001) = (00000011).\n"," \n","In Python beyond version 3, an integer variable is allowed to use an unlimited number of bytes to store integers, as long as the computer has the space to allocate valid memory addresses to store the bytes. As a result, this implementation supports storing very large integers in *int* variables.\n","\n","To verify the fact, we use a useful Python function called *sys.getsizeof()*. In the sample code below, we see that the smallest number of bytes (again, 1 byte is 8 bits) to store any integer value is 28. The reader may wonder why the smallest number of bytes is not one byte or any other number much smaller than 28. The reason is that Python is an OOP language, as such, any variable type is in fact a *class* data structure. Therefore, there are fixed overhead data structure to define the properties of a class object in addition to storing just its data value. \n","\n","As we increase the integer value to a fairly large value, such as *i3* in the sample code, then Python would need to increate the number of bytes in the memory."]},{"cell_type":"code","execution_count":1,"metadata":{"execution":{"iopub.execute_input":"2024-03-15T07:13:47.431898Z","iopub.status.busy":"2024-03-15T07:13:47.431494Z","iopub.status.idle":"2024-03-15T07:13:47.438784Z","shell.execute_reply":"2024-03-15T07:13:47.437755Z","shell.execute_reply.started":"2024-03-15T07:13:47.431816Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["Byte count: 28 28\n","Byte count: 36\n"]}],"source":["import sys\n","\n","i1 = 2022\n","i2 = 1\n","print('Byte count: ', sys.getsizeof(i1), sys.getsizeof(i2))\n","\n","i3 = 314159265358979323846\n","print('Byte count: ', sys.getsizeof(i3))"]},{"cell_type":"markdown","metadata":{},"source":["Finally, let us talk about storing positive integers and negative integers in Python. If our readers are familiar with C or C++ language, they would know that an integer can be declared as signed integer or unsigned integer. A signed integer as its name suggests differentiates an integer value to be either positive or negative, while an unsigned integer does not represent negative numbers. \n","\n","To illutrate how the sign of a number affects its data representation in the memory, let us again borrow the 8-bit binary representation as an example. If we represent only positive integers (or more precies, nonnegative integers) using 8 bits, then the smallest integer is 0 or (00000000) and the largest integer is 255 or (11111111). More specifically,\n","\n"," 255 = 1 + 2 + 4 + 8 + 16 + 32 + 64 + 128 = (00000001) + (00000010) + (00000100) + (00001000) + (00010000) + (00100000) + (01000000) + (10000000)\n"," \n","Now let us consider using 8 bits to represent both positive and negative integers, then we need to take over at least one bit to represent the + and - sign. Therefore, there are only 7 bits left to store the magnitude of the integer value. As a result, the range of the signed integers in 8-bit representation is between +127 and -128.\n","\n","The above is the situation if a programming language supports both signed and unsigned integer type. Nevertheless, in Python, there is no unsigned integer type. Since integers can be arbitrarily large only constrained by the available computer memory, there is very little reason to create unsigned integer type by avoiding storing the sign digit. \n","\n","In the sample code below, we use the same function *sys.getsizeof()* to calculate the byte number when storing *i1* and *i3* with the negative sign. We can see that the final byte counts are the same as the previous example."]},{"cell_type":"code","execution_count":2,"metadata":{"execution":{"iopub.execute_input":"2024-03-15T07:13:55.937203Z","iopub.status.busy":"2024-03-15T07:13:55.936848Z","iopub.status.idle":"2024-03-15T07:13:55.943040Z","shell.execute_reply":"2024-03-15T07:13:55.942114Z","shell.execute_reply.started":"2024-03-15T07:13:55.937168Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["Byte count: 28\n","Byte count: 36\n"]}],"source":["import sys\n","\n","i1 = -2022\n","print('Byte count: ', sys.getsizeof(i1))\n","\n","i3 = -314159265358979323846\n","print('Byte count: ', sys.getsizeof(i3))"]},{"cell_type":"markdown","metadata":{},"source":["# Bitwise Operations\n","\n","The fact that numeric values are stored in computer in binary format leads to certain **bitwise operations** applied in binary representation but not in the conventional decimal arithmetic. Below, we go over some of these examples:\n","\n"," * Shift left: x<>y represents shifting the binary digits in x to the right by y steps. One can verify that shifting an integer to the right by one bit is equivalent to divide by 2.\n"," * bitwise and: x & y first requires both x and y to be represented in binary format by the same number of digits. Then the resulting number assigns 1 in one digit if only the same digit from both numbers in x and y are also 1.\n"," * bitwise or: x | y also requires both x and y to be represented in binary format by the same number of digits. Then the resulting number assigns 1 in one digit if either the same digit in x or y is 1."]},{"cell_type":"code","execution_count":3,"metadata":{"execution":{"iopub.execute_input":"2024-03-15T07:14:09.325582Z","iopub.status.busy":"2024-03-15T07:14:09.325269Z","iopub.status.idle":"2024-03-15T07:14:09.332691Z","shell.execute_reply":"2024-03-15T07:14:09.331690Z","shell.execute_reply.started":"2024-03-15T07:14:09.325550Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["00000100 << 1 = 8\n","00000111 >> 1 = 3\n","00000100 & 00000101 = 4\n","00000101 | 00000110 = 7\n"]}],"source":["print('{0:08b}'.format(4),'<< 1 = ', 4<<1)\n","print('{0:08b}'.format(7), '>> 1 =', 7>>1)\n","print('{0:08b}'.format(4), '&', '{0:08b}'.format(5), '=', 4&5)\n","print('{0:08b}'.format(5), '|', '{0:08b}'.format(6), '=', 5|6)"]},{"cell_type":"markdown","metadata":{},"source":["In the above sample code, we showed some examples of bitwise operations. Note that we also utilized the *format()* function to display any integer number in binary representation. The format *{0:08b}* is understood as follows: \n"," * the first \"0\" indicates taking the first argument of the *format()* method and put its string at the location; \n"," * the colon will specify a string format to display the *format()* argument; \n"," * \"08\" means maintaining minimal 8 digits; \n"," * and \"b\" means in binary format."]},{"cell_type":"markdown","metadata":{},"source":["# Floating-Point Numbers\n","\n","Although Python enables storing integers with arbitrary numbers of digits, more traditional systems typically only support numeric representation using fixed bits. Another limitation of integers is obviously that they cannot represent real numbers that contain both the integer part and the fraction part. To more effectively represent large numbers with fractions with only fixed numbers of bits, modern computers rely on floating-point numbers.\n","\n","Typically, Python represents a floating-point number, or *float* in short, using 64-bit storage in the memory. Due to the same reason that float is a class object, its overall byte count for one float variable will be a little larger than 64 bits or 8 bytes. Let us see some examples to compare with the situation for integers:"]},{"cell_type":"code","execution_count":4,"metadata":{"execution":{"iopub.execute_input":"2024-03-15T07:14:54.456441Z","iopub.status.busy":"2024-03-15T07:14:54.456115Z","iopub.status.idle":"2024-03-15T07:14:54.463554Z","shell.execute_reply":"2024-03-15T07:14:54.462420Z","shell.execute_reply.started":"2024-03-15T07:14:54.456405Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["Byte count: 24 24\n","Byte count: 24\n","3.1415926535897933e+20 314159265358979334144.000000\n"]}],"source":["import sys\n","\n","f1 = 2022.0\n","f2 = 1.0\n","print('Byte count: ', sys.getsizeof(f1), sys.getsizeof(f2))\n","\n","f3 = 314159265358979323846.0\n","print('Byte count: ', sys.getsizeof(f3))\n","print(f3, '{0:20f}'.format(f3))"]},{"cell_type":"markdown","metadata":{},"source":["In the sample code, we use the same values to create three variables but in float type. First, we see that the byte counts for all three variables are 24, meaning the float type in Python uses the same amount of memory to store floating-point numbers.\n","\n","Second, in base-10 decimal representation, a float is represented by a single nonzero digit plus other digits in the fraction part and then followed by a base-10 exponent part: 3.1415926535897933e+20. In base-2 representation in the memory, it is similarly represented by a single nonzero digit (the only nonzero binary digit is 1) plus other binary digits in the fraction part followed by a base-2 exponent part. \n","\n","In both representations, having the memory storage limited to 64 bits means any float can only have limited accuracy, meaning it cannot store the fraction part with unlimited precise digits. We see in the final output of the example above, that in base-10 representation, Python float has 15 valid digits in the fraction part; in base-2 representation, the number of valid digits in the fraction part is 52.\n","\n","The 64-bit floating-point representation in Python actually follows an industry standard called IEEE Standard 754. Using this standard guarantees those floating points stored in a data block by Python can be correctly loaded for some other programs and other languages to use. So in the IEEE Standard 754, using the floating point representation, some special numeric values are also defined:\n","\n"," * 0.0: In float, zero actually is a special value because normal float arithmetic will never result in a zero value. This is due to the fact that the integer part of a float is always nonzero. Except for the symbolic zero in float, where all 64 bits are forced to be set to zero.\n"," * infinity and -infinity: Infinity will be treated to be greater than any finite float number, defined by float('inf'); Negative infinity will be treated to be less than any finite float number, defined by float('-inf').\n"," \n","Let us see some examples below:"]},{"cell_type":"code","execution_count":5,"metadata":{"execution":{"iopub.execute_input":"2024-03-15T07:15:02.124028Z","iopub.status.busy":"2024-03-15T07:15:02.123702Z","iopub.status.idle":"2024-03-15T07:15:02.129659Z","shell.execute_reply":"2024-03-15T07:15:02.128538Z","shell.execute_reply.started":"2024-03-15T07:15:02.123996Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["False\n","True True\n"]}],"source":["a = (0.3 * 3) + 0.1\n","b = 1.0\n","print(a == b)\n","print(a float('-inf'))"]},{"cell_type":"markdown","metadata":{},"source":["We can see from the first example, that when two float variables *a* and *b* are created, in principle, the condition that *a==b* should hold true. However, in their float representation in the memory, they are not exactly equal, which may come as a surprise. To deal with the limited precision problem if two floating points need to be compared, it is a better practice to compare the difference within a given range. Let us see the alternative approach below"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["import math\n","a = (0.3 * 3) + 0.1\n","b = 1.0\n","print(math.isclose(a,b))"]},{"cell_type":"markdown","metadata":{},"source":["In this new approach, an imported function *math.isclose()* compares two float numbers within a tolerance value. The default value is 1e-09, but the user can further modify this tolerance by providing a third argument as the customized tolerance value."]},{"cell_type":"markdown","metadata":{},"source":["# Strings\n","\n","Similar to the *int* and *float* type, string type or *str* in short is a class type, which contains some suppplementary information about the string data, such as length, character kind, encoding type, and hash value, etc. Therefore, although storing basic English alphabets only take one byte, but the *str* class will take more bytes to store in the memory. In the sample code below, we see that the byte count for an empty string is 49, for one character is 50, and for four characters is 53. "]},{"cell_type":"code","execution_count":3,"metadata":{"execution":{"iopub.execute_input":"2024-03-15T08:25:27.795795Z","iopub.status.busy":"2024-03-15T08:25:27.795309Z","iopub.status.idle":"2024-03-15T08:25:27.804084Z","shell.execute_reply":"2024-03-15T08:25:27.802451Z","shell.execute_reply.started":"2024-03-15T08:25:27.795753Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["\n","Byte counts: 49 50 53\n"]}],"source":["import sys\n","\n","s0 = \"\"\n","s1 = \"2\"\n","s2 = \"2020\"\n","\n","print(type(s1))\n","print('Byte counts: ', sys.getsizeof(s0), sys.getsizeof(s1), sys.getsizeof(s2))"]},{"cell_type":"markdown","metadata":{},"source":["Note that the string class uses the **Unicode** standard to encode characters into bytes. Although if the characters are basic English alphabets, the coding length for one character is one byte, but other kinds of characters may take longer spaces. Let us see the comparison below:"]},{"cell_type":"code","execution_count":4,"metadata":{"execution":{"iopub.execute_input":"2024-03-15T08:25:30.281226Z","iopub.status.busy":"2024-03-15T08:25:30.280603Z","iopub.status.idle":"2024-03-15T08:25:30.289116Z","shell.execute_reply":"2024-03-15T08:25:30.287688Z","shell.execute_reply.started":"2024-03-15T08:25:30.281183Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["\n","Byte counts: 76 78 82\n"]}],"source":["s1 = \"二\"\n","s2 = \"二0\"\n","s3 = \"二0二0\"\n","\n","print(type(s1))\n","print('Byte counts: ', sys.getsizeof(s1), sys.getsizeof(s2), sys.getsizeof(s3))"]},{"cell_type":"markdown","metadata":{},"source":["We see from the above example that when a string contains special characters or characters from other languages than English, it will be necessary to increase the byte count to encode each character. For a Chinese character \"二“ that means the number two, the byte count is 2. However, when we add another basic character \"0\" in *s2*, we can see that the increase in byte count is also 2. In *s3*, the increase in byte count is 3*2 = 6. This means a Python string always use the same byte count to encode every character. The encoding strategy makes calculating the memory address of any character to be straightforward, namely, the memory address for str[offset] is str[0] + byte_count * offset, since the byte_count is uniform for every character, may it be 1, 2, or 3, etc.\n","\n","In the level-1 course when we introduced Python's hashing library, we introduced another encoding standard called ASCII or UTF-8. ASCII stands for *American Standard Cod for Information Interchange*, and it was first introduced to encode English text and standard symbols in 1963. Since early computers did not care about encoding characters from other international language, the original ASCII code only has 8 bits or 1 byte. This is the same length when Unicode format encodes ASCII characters. However, when more special characters and international languages are added in text documents, ASCII is expanded to take up more bytes, which leads to the UTF-8 standard also supported by Python.\n","\n","We can see in the example below, that the same strings from the above example can be encoded by UTF-8 instead of the default Unicode. However, UTF-8 encoding uses variable byte counts. For example, from *b1* to *b2*, only one byte is added to the memory size because \"0\" is encoded in ASCII using one byte. From *b2* to *b3*, 3 bytes are added to encode the Chinese character \"二“. The tradeoff using the UTF-8 standard is that it returns a shorter byte array than the Unicode standard, but since each character is variable byte length, calculating the memory address of a character in the middle of a string would need to traverse and count the accumulated byte counts of every character before it, which significantly increases the time complexity."]},{"cell_type":"code","execution_count":5,"metadata":{"execution":{"iopub.execute_input":"2024-03-15T08:25:33.395478Z","iopub.status.busy":"2024-03-15T08:25:33.394606Z","iopub.status.idle":"2024-03-15T08:25:33.405296Z","shell.execute_reply":"2024-03-15T08:25:33.402989Z","shell.execute_reply.started":"2024-03-15T08:25:33.395426Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["\n","Byte counts: 36 37 40 41\n"]}],"source":["b1 = \"二\".encode('utf-8')\n","b2 = \"二0\".encode('utf-8')\n","b3 = \"二0二\".encode('utf-8')\n","b4 = \"二0二0\".encode('utf-8')\n","\n","print(type(b1))\n","print('Byte counts: ', sys.getsizeof(b1), sys.getsizeof(b2), sys.getsizeof(b3), sys.getsizeof(b4))"]},{"cell_type":"markdown","metadata":{},"source":["To convert a *bytes* type to the standard *str* type, one can use another function in reverse of the *.encode()* method, called *.decode()*. By default, it will assume the encoding of the source data is *'utf-8'*. Therefore, this argument can be ignored if the default is the correct assumption."]},{"cell_type":"code","execution_count":8,"metadata":{"execution":{"iopub.execute_input":"2024-03-15T08:41:42.571260Z","iopub.status.busy":"2024-03-15T08:41:42.570572Z","iopub.status.idle":"2024-03-15T08:41:42.581495Z","shell.execute_reply":"2024-03-15T08:41:42.580202Z","shell.execute_reply.started":"2024-03-15T08:41:42.571213Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["\n","二 二0\n"]}],"source":["b1 = \"二\".encode('utf-8')\n","b2 = \"二0\".encode('utf-8')\n","s1 = b1.decode('utf-8')\n","s2 = b2.decode()\n","\n","print(type(s1))\n","print(s1, s2)"]},{"cell_type":"markdown","metadata":{},"source":["Finally, if the characters in a string is limited to only ASCII code, we can use the prefix *b* in front of the quotation marks to obtain an ASCII coding. However, ASCII coding cannot encode special characters, such as characters from other international languages. We will see another sample code below. Notice that encoding character \"二\" using ASCII will receive a SyntexError."]},{"cell_type":"code","execution_count":7,"metadata":{"execution":{"iopub.execute_input":"2024-03-15T08:25:45.806985Z","iopub.status.busy":"2024-03-15T08:25:45.806531Z","iopub.status.idle":"2024-03-15T08:25:45.815331Z","shell.execute_reply":"2024-03-15T08:25:45.814107Z","shell.execute_reply.started":"2024-03-15T08:25:45.806943Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["\n","Byte counts: 34 37 37\n"]}],"source":["s1 = b\"2\"\n","s2 = b\"2020\"\n","s3 = \"2020\".encode('ascii')\n","\n","print(type(s1))\n","print('Byte counts: ', sys.getsizeof(s1), sys.getsizeof(s2), sys.getsizeof(s3))"]},{"cell_type":"code","execution_count":10,"metadata":{"execution":{"iopub.execute_input":"2024-03-15T07:17:44.947771Z","iopub.status.busy":"2024-03-15T07:17:44.947412Z","iopub.status.idle":"2024-03-15T07:17:44.955014Z","shell.execute_reply":"2024-03-15T07:17:44.953408Z","shell.execute_reply.started":"2024-03-15T07:17:44.947736Z"},"trusted":true},"outputs":[{"ename":"SyntaxError","evalue":"bytes can only contain ASCII literal characters. (, line 1)","output_type":"error","traceback":["\u001b[0;36m File \u001b[0;32m\"\"\u001b[0;36m, line \u001b[0;32m1\u001b[0m\n\u001b[0;31m s4 = b\"二0二0\"\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m bytes can only contain ASCII literal characters.\n"]}],"source":["s4 = b\"二0二0\""]},{"cell_type":"markdown","metadata":{},"source":["# Lists\n","\n","Lists are a versatile variable type where the elements in one list can have different types. Let us use the *sys.getsizeof()* function to examine some illustrative examples:"]},{"cell_type":"code","execution_count":11,"metadata":{"execution":{"iopub.execute_input":"2024-03-15T07:18:06.974774Z","iopub.status.busy":"2024-03-15T07:18:06.974441Z","iopub.status.idle":"2024-03-15T07:18:06.982029Z","shell.execute_reply":"2024-03-15T07:18:06.980078Z","shell.execute_reply.started":"2024-03-15T07:18:06.974744Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["Byte counts: 72 80 88 96\n"]}],"source":["import sys\n","\n","l0 = [] # an empty list\n","l1 = [\"二\"] # a list that contains a Chinese character of two\n","l2 = [\"二\", 0] # a list that contains a Chinese character of two and an integer zero\n","l3 = [\"二\", 0, [\"二\", 0]] # a list that further contains another list as one element\n","\n","print('Byte counts: ', sys.getsizeof(l0), sys.getsizeof(l1), sys.getsizeof(l2), sys.getsizeof(l3))"]},{"cell_type":"markdown","metadata":{},"source":["From the sample code, we see that the byte count for an empty list variable is 72 bytes. Again, this is due to the fact that list is a class type that comes with associated class methods and attributes, so even the empty list will take up a size of memory space. \n","\n","We then notice that when we add one by one more elements into the list, the increase in byte counts seems to be constant 8 bytes, regardless of the content of the element. For example, from *l2* to *l3*, even when we add an entire sublist to the end of *l2*, the increase byte count is still 8. More surprisingly, we can recall from the above string examples that the byte count for a list [\"二\", 0] is 88. It seems counter-intuitive that adding a list element [\"二\", 0] only increases byte count by 8 from 88 to 96.\n","\n","To resolve this dilemma, we have to understand how the list elements are stored and referenced in Python. The hint is to recall that list is a mutable type, meaning, modifying the element values in a list does not change its memory address and ID. This means what is included in the list's byte counts are only references to separate element objects of their own memory addresses. Since storing memory references only costs constant space of 8 bytes (i.e., 64 bits in a 64-bit OS), the *sys.getsizeof()* function can only count the bytes for the memory allocation of these reference pointers.\n","\n","If one wants to calculate precisely the total memory size of the whole list data, we should use the following approach:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["import sys\n","l2 = [\"二\", 0]\n","\n","byte_count = sys.getsizeof(l2) + sys.getsizeof(l2[0]) + sys.getsizeof(l2[1])\n","print(byte_count)"]},{"cell_type":"markdown","metadata":{},"source":["Next, since all the element references are organized as an ordered sequence, we consider the time complexity to add or remove elements from this ordered sequence."]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["trial_count = 1000\n","\n","import time\n","\n","# Test remove the last elements\n","test_list = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ')*1000000\n","tic = time.time()\n","for i in range(trial_count):\n"," test_list.pop(-1)\n","elapsed_time = time.time() - tic\n","print('Total time removing last elements: ', elapsed_time)\n","\n","# Test remove the first elements\n","test_list = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ')*1000000\n","tic = time.time()\n","for i in range(trial_count):\n"," test_list.pop(0)\n","elapsed_time = time.time() - tic\n","print('Total time removing first elements: ',elapsed_time)"]},{"cell_type":"markdown","metadata":{},"source":["When we run the above sample code, we will see the dramatic difference in the time complexity, where the first operation to remove the last element of a list repeating 1,000 times only costs less than 0.001 second, but the second operation to remove the first element of a list repeating 1,000 times costs more than 26 seconds on Kaggle's online Jupyter Notebook. This dramatic difference in cost of time and therefore computer resources is caused by the fact that the list type simply stores its element references as an ordered sequence. Consider the following two situations:\n","\n","* When the last element of a sequence is removed, the length of the sequence is simply reduced by one, and there is no other operations necessary to maintain values for the rest of the elements in the list.\n","* When the first element of a sequence is removed, to maintain the order of the sequence except removing the first element, Python actually iteratively moves the (i+1)-th element reference to overwrite the i-th element reference. As a result, when our test list is long such as in the above example, this operation of shifting all elements to the left by one actually is very expensive even in modern computer's standard. \n"," \n","The above complexity analysis about the *pop()* operation also applies to the *insert()* operation. In conclusion, when we use the list-type variables, we shall try to encourage poping and inserting more from the end of the list, and avoid the same operations from the beginning or other random intermediate locations."]},{"cell_type":"markdown","metadata":{},"source":["# Exercises\n","\n"]},{"cell_type":"markdown","metadata":{},"source":["1. Please write a Python code to evaluate the number of bytes used to encode an emoji character. The starting assignment statement is provided below:"]},{"cell_type":"code","execution_count":2,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["Byte count: 80\n"]}],"source":["import sys\n","s = \"😊\" # Smiling face with smiling eyes emoji\n","print('Byte count: ', sys.getsizeof(s))"]},{"cell_type":"markdown","metadata":{},"source":["2. Explain the difference between the hash results of the two examples that are fairly similar in values:"]},{"cell_type":"code","execution_count":9,"metadata":{"execution":{"iopub.execute_input":"2024-03-15T08:57:50.745974Z","iopub.status.busy":"2024-03-15T08:57:50.745229Z","iopub.status.idle":"2024-03-15T08:57:50.754228Z","shell.execute_reply":"2024-03-15T08:57:50.752831Z","shell.execute_reply.started":"2024-03-15T08:57:50.745928Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["True\n","False\n","Byte count: 76\n","Byte count: 50\n","Byte count: 36\n","Byte count: 34\n"]}],"source":["s1 = '2'; b1 = s1.encode('utf-8')\n","print(s1.__hash__()==b1.__hash__())\n","\n","s2 = \"二\"\n","b2 = s2.encode('utf-8')\n","print(s2.__hash__()==b2.__hash__())\n","print('Byte count: ', sys.getsizeof(s2))\n","print('Byte count: ', sys.getsizeof(s1))\n","print('Byte count: ', sys.getsizeof(b2))\n","print('Byte count: ', sys.getsizeof(b1))\n","# >^'.'^<"]},{"cell_type":"markdown","metadata":{},"source":["3. Debug the following code"]},{"cell_type":"code","execution_count":14,"metadata":{"execution":{"iopub.execute_input":"2024-03-15T09:10:51.999623Z","iopub.status.busy":"2024-03-15T09:10:51.998813Z","iopub.status.idle":"2024-03-15T09:10:52.028879Z","shell.execute_reply":"2024-03-15T09:10:52.026719Z","shell.execute_reply.started":"2024-03-15T09:10:51.999574Z"},"trusted":true},"outputs":[{"ename":"TypeError","evalue":"a bytes-like object is required, not 'str'","output_type":"error","traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)","\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0ms\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m\"test string\"\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mencode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'utf-8'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0ma\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mb\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0ms\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msplit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m' '\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mb\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;31mTypeError\u001b[0m: a bytes-like object is required, not 'str'"]}],"source":["s = \"test string\".encode('utf-8')\n","a, b = s.split(' ')\n","\n","print(a, b)"]},{"cell_type":"markdown","metadata":{},"source":["# Challenges\n","\n","1. In Python, there is a dynamically linked list type called *deque* from the module name *collections*. A deque type variable can pop its elements from the right using *.pop()* method, and from the left using *.popleft()* method.\n","\n","Please modify the sample code above to pop a deque variable with the same one million copies of the 26 alphabet letters from the left and right, one by one. Compare the time complexity of the operations and discuss the result."]}],"metadata":{"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":30055,"isGpuEnabled":false,"isInternetEnabled":true,"language":"python","sourceType":"notebook"},"kernelspec":{"display_name":"Python 3","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.18"}},"nbformat":4,"nbformat_minor":4} diff --git a/Part One/1-2-python-numeric-types.ipynb b/Part One/1-2-python-numeric-types.ipynb index 7dce00e..e121aeb 100644 --- a/Part One/1-2-python-numeric-types.ipynb +++ b/Part One/1-2-python-numeric-types.ipynb @@ -1 +1 @@ -{"cells":[{"cell_type":"markdown","metadata":{},"source":["**Learning Python -- The Programming Language for Artificial Intelligence and Data Science**\n","\n","**Lecture 2: Python Numeric Variable Types**\n","\n","By Allen Y. Yang, PhD\n","\n","(c) Copyright Intelligent Racing Inc., 2021-2024. All rights reserved. Materials may NOT be distributed or used for any commercial purposes."]},{"cell_type":"markdown","metadata":{},"source":["# Keywords\n","\n","* **Variable**: a variable defines a word using a combination of letters, numbers, and some symbols, which serves the purpose to reference a computer memory address where some data value is stored.\n","* **int**: The keyword of the integer type data in Python.\n","* **float**: The keyword of the floating-point type data in Python.\n","* **bool**: The keyword of the Boolean type data that takes only two possible values: True or False.\n","* **Operator**: An operator is a special function with a reserved symbol, such as + or /. The list of input arguments of an operator is pre-defined by the language, usually includes the values immediately before or/and after the symbol.\n","* **Module**: A Python module is a set of code that is stored in a stand-alone program file, ending with a suffix string \".py\" called a filename extension. Storing Python functions in a module further allows programmers to logically group a set of relevant functions in one file and then later can be imported into other Python code."]},{"cell_type":"markdown","metadata":{},"source":["# Integers in Python\n","\n","Integers are a basic numeric type. What is somewhat special and may be surprising to many beginners is that Python standards specify that it is required that an integer in Python may represent a value of arbitrary size, namely, any integer number between - infinity to + infinity.\n","\n","The next code block shows some examples:"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":[]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2024-02-23T15:12:17.772127Z","iopub.status.busy":"2024-02-23T15:12:17.771412Z","iopub.status.idle":"2024-02-23T15:12:17.816813Z","shell.execute_reply":"2024-02-23T15:12:17.815519Z","shell.execute_reply.started":"2024-02-23T15:12:17.772086Z"},"trusted":true},"outputs":[],"source":["Int = 10\n","\n","i2 = int(False) # type casting from boolean to int\n","print(i2)\n","\n","int = 10 # What could be potential risk in this command\n","\n","result = Int + int\n","\n","print(result)"]},{"cell_type":"code","execution_count":null,"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","execution":{"iopub.execute_input":"2024-02-07T05:09:11.325003Z","iopub.status.busy":"2024-02-07T05:09:11.324588Z","iopub.status.idle":"2024-02-07T05:09:11.333990Z","shell.execute_reply":"2024-02-07T05:09:11.332592Z","shell.execute_reply.started":"2024-02-07T05:09:11.324971Z"},"trusted":true},"outputs":[],"source":["print(5-2)\n","print(5*2)\n","print(5**2) # 5^2\n","print(17/3) # Return is a float number\n","print(17//3) # Floor division with integers will return integers\n","print(17%3) # 17 = (17//3) * 3 + 17 % 3"]},{"cell_type":"markdown","metadata":{},"source":["In this example, arithmetic operators \"-\" and \"*\" take the same meaning as in math. In the previous lecture, we also showed the \"+\" operator.\n","\n","\"**\" operator represents \"to the power of\". Hence, 5 to the power of 2 is 25.\n","\n","\"//\" represents floor division, meaning the operation will first perform the regular float division followed by the floor() function to return only the integer part. For example, the floor() return of the float division \"17/3\" is 5, where \"/\" represents the regular floating-point division, which we will show more examples later in the lecture. As another example, the floor() return of the float division \"-17/3\" is -6.\n","\n","\"%\" represents modulo operation. In the above example, we can see that 17 = 17 // 3 + 17 % 3"]},{"cell_type":"markdown","metadata":{},"source":["When we have two integers, their values can also be compared in the traditional sense. Let us look at examples below:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2024-02-07T05:09:15.138377Z","iopub.status.busy":"2024-02-07T05:09:15.137991Z","iopub.status.idle":"2024-02-07T05:09:15.144375Z","shell.execute_reply":"2024-02-07T05:09:15.143535Z","shell.execute_reply.started":"2024-02-07T05:09:15.138347Z"},"trusted":true},"outputs":[],"source":["print(3 > 2)\n","print(2 >= -1)\n","print(4//2 == 2)\n","print(4/2 == 2)\n","print(4/2 != 2)"]},{"cell_type":"markdown","metadata":{},"source":["From the above code block, comparison operators between two numbers (one can also view a comparison operator as a function with two input arguments) returns another value type that takes only two values: True or False. This type in Python and many other lauguages is called **Boolean**. A boolean type value is the return output from a comparison operator if executed properly.\n","\n","The following comparison operators in Python directly match to those same operators in arithmetic: <, <=, >, >=. \"is equal to\" comparison is denoted as \"==\", while \"is not equal to\" is denoated as \"!=\"\n","\n","Now let us dissect the five returns. The first two comparison results are trivial as just toy examples. The third comparison essentially tests whether (4//2) = 2 is equal to 2, which leads to return value True. However, we further observe in the fourth comparison that (4/2) = 2.0 (in here we simply recall that \"/\" in Python represents floating-point division, so the returning result is always a float, namely, with fraction portion even in cases that the fractional part is zero). So Python evaluation is telling us that 2.0 == 2. This verifies that comparison operators only compare their value magnitudes, but not their types or any other things. From the value magnitude, 2.0 and 2 are equal (but they are not equivalent, as we will soon see below)."]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["print(True == 1)\n","print(False == 0)\n","print(type(True))"]},{"cell_type":"markdown","metadata":{},"source":["The above two statements further review how boolean values are stored in computer. Specifically, True is stored as the same value as the integer value 1; while False is stored as the same value as the integer value 0. However, again, two numbers being equal in value does not mean that the two numbers are equivalent. In the third statement, if we print out the type of a boolean value, its type is shown as a Python class with the name 'bool', which is a text string referring to the boolean type. Clearly, the type of integer and the type of boolean are not equivalent or identical."]},{"cell_type":"markdown","metadata":{},"source":["# Variables in Python\n","\n","In computer, programs and data are stored in its memory space. When a program needs to use the data, such as an integer value of 3 after the calculation of 5-2, the program needs a convenient way to reference the location of the data 3 stored in the memory. \n","\n","One direct way to reference memory data is to declare its memory address, which will be a number starting from zero referencing the first **byte** of the memory, and so on and so forth. Byte is the fundamental memory unit when referencing and retrieving data in all modern computer systems. One byte contains a sequence of 8 bits, and 1 bit represents a single number of either 0 or 1. \n","\n","However, directly referencing memory addresses is very cumbersome due to at least two reasons: 1. Memory addresses are difficult to use in coding programs. An analogy is that it would be a lot easier to find people's phone number from today's smartphone systems using their names. 2. When addressing the memory, the memory address cannot be the only information. There is a whole list of other attributes Python requires to know to be able to properly retrieve the data from memory. One of such attributes is the value type as we mentioned before. An analogy that continues to borrow the phone number example would be associating a phone number not only with its user name but also the user's company, address, and other information. \n","\n","The concept of **variable** is created to solve the issues when a program references memory addresses to retrieve data. Let's see some examples below: "]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["a = 5-2\n","b = 5*a\n","print(a>b)\n","print(type(a))"]},{"cell_type":"markdown","metadata":{},"source":["In this simple example, we define a variable with a word using a single letter *a*. Variable *a* then is a reference of the calculation result: 5-2. In the second statement, the value of a is used to calculate the expression 5\\*a, which generates an integer result 15 and it is again stored in the memory and conveniently referenced by a variable *b*.\n","\n","We see in the third statement that the values of *a* and *b* are again retrieved in the comparison expression. We can also query the type of the memory value where the variable references, as shown in the fourth statement."]},{"cell_type":"markdown","metadata":{},"source":["# Floating-Point Numbers in Python\n","\n","A floating-point number is a rational number representation that contains an integer part and a fractional part. A rational number is defined by the division result of two integers. For example, let us look at the code block below:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-16T07:36:46.08102Z","iopub.status.busy":"2021-06-16T07:36:46.08062Z","iopub.status.idle":"2021-06-16T07:36:46.087165Z","shell.execute_reply":"2021-06-16T07:36:46.086205Z","shell.execute_reply.started":"2021-06-16T07:36:46.080984Z"},"trusted":true},"outputs":[],"source":["a = 1/80\n","print(a)\n","print(a == 1.25*10**-2)\n","print(type(a))"]},{"cell_type":"markdown","metadata":{},"source":["A variable *a* is created to reference the division of two integers 1 and 80. The result is equal to 0.0125, where the integer part is 0, and the fractional part is .0125.\n","\n","The name \"floating point\" is so called because the same rational number can be represented by placing its decimal point at different places and then multiplied by a base-10 exponent. For example, \\\\(0.0125 = 0.125\\times 10^{-1} = 1.25 \\times 10^{-2} \\\\), etc. All three representations define the same rational number. The fact is verified in the third statement (recall \"**\" means to the power of).\n","\n","In Python, the type of a floating point number is denoted as 'float'. It is stored as a class structure as the same as the 'int' type, which we will discuss in much later part of the course. Because of this notation, floating-point numbers are often simply called floats.\n","\n","In addition to the typical arithmetic operators that continue to apply to floating-point calculation, such as +, -, \\*, \\\\, let us see some more examples below. The examples show that the operations of modulo, integer division, and powers are valid also to floats."]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-16T07:35:41.072674Z","iopub.status.busy":"2021-06-16T07:35:41.072328Z","iopub.status.idle":"2021-06-16T07:35:41.07775Z","shell.execute_reply":"2021-06-16T07:35:41.076862Z","shell.execute_reply.started":"2021-06-16T07:35:41.072645Z"},"trusted":true},"outputs":[],"source":["print(3.14 % 2)\n","print(3.14 // 2)\n","print(3.14 ** 2 == 3.14*3.14)"]},{"cell_type":"markdown","metadata":{},"source":["In Python and many other languages, type conversion operations are defined to convert one type of variable to another type.\n","\n","In the above, we have discussed separately two types: int and float. Let us next discuss how to define type conversion between the two types. First, from int to float is trivial. It can be done in two ways:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-16T07:38:23.151285Z","iopub.status.busy":"2021-06-16T07:38:23.150915Z","iopub.status.idle":"2021-06-16T07:38:23.156427Z","shell.execute_reply":"2021-06-16T07:38:23.155336Z","shell.execute_reply.started":"2021-06-16T07:38:23.151255Z"},"trusted":true},"outputs":[],"source":["a = 3\n","print(a*1.0)\n","print(float(a))"]},{"cell_type":"markdown","metadata":{},"source":["We see in the above code block, an integer variable a is first defined to be equal to 3. Multiplying a by a float 1.0 will result in a float type return, but the magnitude of the result in floating-point remains the same.\n","\n","The second way is to use a built-in type conversion function *float()*. As the name suggests, the function takes in an input argument. As long as this argument can be understood to be represented by a float value, the function will output a float result of that value, as shown above. The *float()* function is also called a **casting** operation.\n","\n","The other direction to convert floats to ints is more complicated. The reason is that if a float contains nontrivial digits after the decimal point, then such a convertion from float to int will change the value of the input number. After the conversion, some information will be lost. Therefore, the conversion can be performed in a variety of ways, as we shall demonstrate below:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-16T07:33:47.583356Z","iopub.status.busy":"2021-06-16T07:33:47.582977Z","iopub.status.idle":"2021-06-16T07:33:47.592585Z","shell.execute_reply":"2021-06-16T07:33:47.590309Z","shell.execute_reply.started":"2021-06-16T07:33:47.583322Z"},"trusted":true},"outputs":[],"source":["import math\n","\n","print(int(4.6))\n","print(int(-4.1))\n","print(round(4.6))\n","print(round(-4.1))\n","print(math.floor(4.6))\n","print(math.floor(-4.6))\n","print(math.ceil(4.1))\n","print(math.ceil(-4.1))"]},{"cell_type":"markdown","metadata":{},"source":["In the above code block, we see four ways a float value can be converted to int. The most important fact to remember is that, if the float value has nonzero numbers after the decimal point, then the conversion will lose information. For example, in the first case, converting 4.6 using the function *int()* returns 4, which directly takes the integer part and discard the fractional part. So *int()* is another so-called **casting function**.\n","\n","The second function is *round()*. It rounds up a float to its closest int number. For example, in the code block, we see that 4.6 rounds up to 5, and -4.1 rounds up to -4.\n","\n","The last two functions are retrieving the floor and ceiling integer values from floats, respectively. When we read the code, we notice that if we directly call the two functions *floor()*, *ceil()*, Python will return error:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["print(floor(4.6))"]},{"cell_type":"markdown","metadata":{},"source":["The difference between using *floor()* and *math.floor()* highlights the ways Python organizes its available functions into separate modules. By default, Python at launch time will only load a set of so-called built-in functions. These build-in functions include *print()*, *int()*, *float()*, and *type()* that we have learned. We will learn a lot more as the course progresses.\n","\n","However, there are vast amounts of other functions that are not part of the built-in functions. These functions are organized into modules for two main reasons: \n","1. Placing more diverse functions into individual modules helps to enhance reusability of these functions, and each module can be organized in manageable size. Otherwise, consider the counter-example if all Python functions are stored in a single file, then this file would be impossibly large to store and load.\n","2. The action to **import** modules allows users to define and release their own modules. In fact, the real power of Python in the AI era has been that a lot of very powerful new modules have been open sourced by different developers. We benefit from their contributions by importing their modules in our code.\n","\n","So in summary, calling *floor()* and *ceil()* functions requires the code to explicitly declare to import the *math* module. Then, *math.floor()* returns the greatest integer that is smaller than the input argument; *math.ceil()* returns the least integer that is greater than the input argument."]},{"cell_type":"markdown","metadata":{},"source":["# Random Generation of Numbers\n","\n","Finally, let us review a following Python code that uses randomly generated numbers and operators to test your math abilities. Please refer to the lecture video for more details."]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2023-07-24T21:28:01.119113Z","iopub.status.busy":"2023-07-24T21:28:01.118728Z","iopub.status.idle":"2023-07-24T21:28:26.196251Z","shell.execute_reply":"2023-07-24T21:28:26.195237Z","shell.execute_reply.started":"2023-07-24T21:28:01.119081Z"},"trusted":true},"outputs":[],"source":["# import two Python modules\n","import math # includes additional math functions\n","from random import randint # includes functions for generating random numbers\n","\n","# Define constants\n","OPERATOR_ROUND = 1\n","OPERATOR_INT = 2\n","OPERATOR_FLOOR = 3\n","OPERATOR_CEIL = 4\n","\n","random_operator = randint(1,4) # Select an operator, equiv to random.randint\n","random_A = randint(-10,10) # Select first value\n","random_B = randint(1,10) # Select second value\n","if random_operator == OPERATOR_ROUND: # If selected operator is round()\n"," result = round(random_A/random_B)\n"," operator_string = \"round\"\n","elif random_operator == OPERATOR_INT: # If selected operator is int()\n"," result = int(random_A/random_B)\n"," operator_string = \"int\"\n","elif random_operator == OPERATOR_FLOOR: # If selected operator is floor()\n"," result = math.floor(random_A/random_B)\n"," operator_string = \"floor\"\n","else: # If selected operator is ceil()\n"," result = math.ceil(random_A/random_B)\n"," operator_string = \"ceil\"\n","\n","# Prepare question string\n","question_string = ( \"Question: \" + operator_string + \"(\" + str(random_A)\n"," + \"/\" + str(random_B) + \") = ? \")\n","\n","user_result = input(question_string) # Wait for user input\n","user_result = int(user_result) # Convert string to int\n","if user_result == result: # The answer is correct, add one score\n"," print(\"Correct!\")\n","else: # The answer is wrong, add one score\n"," print(\"Incorrect!\")"]},{"cell_type":"markdown","metadata":{},"source":["In the above code, we see the good practice of variable naming convention, that constants, namely, variables that are not supposed to change their values, are named using all-cap words connected by underlines \"_\", and normal variables are named using small-cap words also connected by underlines.\n","\n","In the beginning, the code uses two formats to import modules into the Python code, one is the *math* module, the other is the *randint()* function from the *random* module. Using \n","\n"," from random import randint\n"," \n","allows subsequent code to directly call the *randint()* function. Alternatively, if the code merely does\n","\n"," import random\n"," \n","then subsequently the code should use *random.randint()* to call the same function. The *randint()* function has two arguments, and will return a random integer in between the two number arguments, with the given numbers also included in the selection range.\n","\n","Then the code not only uses *randint()* to select a numerator and a denominator, but also to select an operator, where 1 represents *round()*, 2 represents *int()*, 3 represents *floor()*, and 4 represents *ceil()*. The *if -- elif --else* statements are conditional flow control statements that we will study in details in later courses. In here, its function is to select a function to convert the fraction calculation *random_A/random_B* based on the value of *random_operator*. \n","\n","Finally, a *user_result* records a user inputed number that estimates the same calculation by the user. The last *if -- else* statement compares user's input and computer result, and uses *print()* to report either the user's answer is correct or incorrect."]},{"cell_type":"markdown","metadata":{},"source":["# Summary\n","\n","* Python may represent integer numbers, or *int*, precisely up to arbitrary size, only limited by the available computer storage size.\n","* The following operations between two int's will return an int: +, -, *, //, %, **\n","* Boolean type values are stored as 1 (True) or 0 (False)\n","* Comparison operations, if legitimate between compatible variable types, return boolean values: ==, <, >, <=, >=, !=\n","* Python represents noninteger real numbers as using floating-point format, or *float*.\n","* The following operations between a float and another float or int will return a float: +, -, *, //, %, **.\n","* The float division always returns a float: /.\n","* A float can be converted to an int type, and may lose accuracy in the process: int(), round(), math.floor(), math.ceil().\n","* An int can also be converted to a float type: float()\n","* random.randin(left, right) function generates a random integer within the range [left, right]."]},{"cell_type":"markdown","metadata":{},"source":["# Exercises\n","\n","1. Discuss the difference between the results of two operations: 1. a = 10/5; 2. b = 10//5. Print out the boolean result that compares a == b.\n","\n","2. Please write a code block to implement the following operation: Obtain an integer value from user input, then separate the input value as the sum of two parts. First part is the result by integer division // of five (5), the second part is the result by modulo % of five (5). For example, if the input integer is 17, the output should be a string: 17 = 3 * 5 + 2.\n","\n","3. The math constant PI is defined in Python in the math module as math.pi. Please write a code block. In the code, first import the math module, then calculate a circle's area with its radius equal to 2. Assign the result to a variable called area, and then print out its final value.\n","\n","4. Continue with the above code block, with the value of the area of a circle given, please use math.pi constant and math.sqrt() function to calculate the radius of the circle. Please assign the result to a variable called radius, and then print out its final result.\n","\n","5. Debug:"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["print (3 plus 2)\n","print (sqrt(9))"]},{"cell_type":"markdown","metadata":{},"source":["# Challenges\n","\n","1. Write a code block, in which the human user use function input() to input a number between 0 and 9, and then the computer use random.randint() function to also generate a number between 0 and 9. If the user's number is greater, print the string \"You Win\"; if the user's number is less, print \"You Lose\"; otherwise, print \"Draw\". Hint: Do not forget to type cast the user's input in str type to int type using the function int().\n","\n","2. Evaluate the code below, and then find a possible explanation why the return result is either True or False. Hint: this is not a \"bug\"."]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2024-02-07T05:06:17.645820Z","iopub.status.busy":"2024-02-07T05:06:17.644929Z","iopub.status.idle":"2024-02-07T05:06:17.700851Z","shell.execute_reply":"2024-02-07T05:06:17.699691Z","shell.execute_reply.started":"2024-02-07T05:06:17.645779Z"},"trusted":true},"outputs":[],"source":["0.2 + 0.2 + 0.2 == 0.6"]}],"metadata":{"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":30527,"isGpuEnabled":false,"isInternetEnabled":true,"language":"python","sourceType":"notebook"},"kernelspec":{"display_name":"Python 3","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.10.12"}},"nbformat":4,"nbformat_minor":4} +{"cells":[{"cell_type":"markdown","metadata":{},"source":["**Learning Python -- The Programming Language for Artificial Intelligence and Data Science**\n","\n","**Lecture 2: Python Numeric Variable Types**\n","\n","By Allen Y. Yang, PhD\n","\n","(c) Copyright Intelligent Racing Inc., 2021-2024. All rights reserved. Materials may NOT be distributed or used for any commercial purposes."]},{"cell_type":"markdown","metadata":{},"source":["# Keywords\n","\n","* **Variable**: a variable defines a word using a combination of letters, numbers, and some symbols, which serves the purpose to reference a computer memory address where some data value is stored.\n","* **int**: The keyword of the integer type data in Python.\n","* **float**: The keyword of the floating-point type data in Python.\n","* **bool**: The keyword of the Boolean type data that takes only two possible values: True or False.\n","* **Operator**: An operator is a special function with a reserved symbol, such as + or /. The list of input arguments of an operator is pre-defined by the language, usually includes the values immediately before or/and after the symbol.\n","* **Module**: A Python module is a set of code that is stored in a stand-alone program file, ending with a suffix string \".py\" called a filename extension. Storing Python functions in a module further allows programmers to logically group a set of relevant functions in one file and then later can be imported into other Python code."]},{"cell_type":"markdown","metadata":{},"source":["# Integers in Python\n","\n","Integers are a basic numeric type. What is somewhat special and may be surprising to many beginners is that Python standards specify that it is required that an integer in Python may represent a value of arbitrary size, namely, any integer number between - infinity to + infinity.\n","\n","The next code block shows some examples:"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":[]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2024-02-23T15:12:17.772127Z","iopub.status.busy":"2024-02-23T15:12:17.771412Z","iopub.status.idle":"2024-02-23T15:12:17.816813Z","shell.execute_reply":"2024-02-23T15:12:17.815519Z","shell.execute_reply.started":"2024-02-23T15:12:17.772086Z"},"trusted":true},"outputs":[],"source":["Int = 10\n","\n","i2 = int(False) # type casting from boolean to int\n","print(i2)\n","\n","int = 10 # What could be potential risk in this command\n","\n","result = Int + int\n","\n","print(result)"]},{"cell_type":"code","execution_count":null,"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","execution":{"iopub.execute_input":"2024-02-07T05:09:11.325003Z","iopub.status.busy":"2024-02-07T05:09:11.324588Z","iopub.status.idle":"2024-02-07T05:09:11.333990Z","shell.execute_reply":"2024-02-07T05:09:11.332592Z","shell.execute_reply.started":"2024-02-07T05:09:11.324971Z"},"trusted":true},"outputs":[],"source":["print(5-2)\n","print(5*2)\n","print(5**2) # 5^2\n","print(17/3) # Return is a float number\n","print(17//3) # Floor division with integers will return integers\n","print(17%3) # 17 = (17//3) * 3 + 17 % 3"]},{"cell_type":"markdown","metadata":{},"source":["In this example, arithmetic operators \"-\" and \"*\" take the same meaning as in math. In the previous lecture, we also showed the \"+\" operator.\n","\n","\"**\" operator represents \"to the power of\". Hence, 5 to the power of 2 is 25.\n","\n","\"//\" represents floor division, meaning the operation will first perform the regular float division followed by the floor() function to return only the integer part. For example, the floor() return of the float division \"17/3\" is 5, where \"/\" represents the regular floating-point division, which we will show more examples later in the lecture. As another example, the floor() return of the float division \"-17/3\" is -6.\n","\n","\"%\" represents modulo operation. In the above example, we can see that 17 = 17 // 3 + 17 % 3"]},{"cell_type":"markdown","metadata":{},"source":["When we have two integers, their values can also be compared in the traditional sense. Let us look at examples below:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2024-02-07T05:09:15.138377Z","iopub.status.busy":"2024-02-07T05:09:15.137991Z","iopub.status.idle":"2024-02-07T05:09:15.144375Z","shell.execute_reply":"2024-02-07T05:09:15.143535Z","shell.execute_reply.started":"2024-02-07T05:09:15.138347Z"},"trusted":true},"outputs":[],"source":["print(3 > 2)\n","print(2 >= -1)\n","print(4//2 == 2)\n","print(4/2 == 2)\n","print(4/2 != 2)"]},{"cell_type":"markdown","metadata":{},"source":["From the above code block, comparison operators between two numbers (one can also view a comparison operator as a function with two input arguments) returns another value type that takes only two values: True or False. This type in Python and many other lauguages is called **Boolean**. A boolean type value is the return output from a comparison operator if executed properly.\n","\n","The following comparison operators in Python directly match to those same operators in arithmetic: <, <=, >, >=. \"is equal to\" comparison is denoted as \"==\", while \"is not equal to\" is denoated as \"!=\"\n","\n","Now let us dissect the five returns. The first two comparison results are trivial as just toy examples. The third comparison essentially tests whether (4//2) = 2 is equal to 2, which leads to return value True. However, we further observe in the fourth comparison that (4/2) = 2.0 (in here we simply recall that \"/\" in Python represents floating-point division, so the returning result is always a float, namely, with fraction portion even in cases that the fractional part is zero). So Python evaluation is telling us that 2.0 == 2. This verifies that comparison operators only compare their value magnitudes, but not their types or any other things. From the value magnitude, 2.0 and 2 are equal (but they are not equivalent, as we will soon see below)."]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["print(True == 1)\n","print(False == 0)\n","print(type(True))"]},{"cell_type":"markdown","metadata":{},"source":["The above two statements further review how boolean values are stored in computer. Specifically, True is stored as the same value as the integer value 1; while False is stored as the same value as the integer value 0. However, again, two numbers being equal in value does not mean that the two numbers are equivalent. In the third statement, if we print out the type of a boolean value, its type is shown as a Python class with the name 'bool', which is a text string referring to the boolean type. Clearly, the type of integer and the type of boolean are not equivalent or identical."]},{"cell_type":"markdown","metadata":{},"source":["# Variables in Python\n","\n","In computer, programs and data are stored in its memory space. When a program needs to use the data, such as an integer value of 3 after the calculation of 5-2, the program needs a convenient way to reference the location of the data 3 stored in the memory. \n","\n","One direct way to reference memory data is to declare its memory address, which will be a number starting from zero referencing the first **byte** of the memory, and so on and so forth. Byte is the fundamental memory unit when referencing and retrieving data in all modern computer systems. One byte contains a sequence of 8 bits, and 1 bit represents a single number of either 0 or 1. \n","\n","However, directly referencing memory addresses is very cumbersome due to at least two reasons: 1. Memory addresses are difficult to use in coding programs. An analogy is that it would be a lot easier to find people's phone number from today's smartphone systems using their names. 2. When addressing the memory, the memory address cannot be the only information. There is a whole list of other attributes Python requires to know to be able to properly retrieve the data from memory. One of such attributes is the value type as we mentioned before. An analogy that continues to borrow the phone number example would be associating a phone number not only with its user name but also the user's company, address, and other information. \n","\n","The concept of **variable** is created to solve the issues when a program references memory addresses to retrieve data. Let's see some examples below: "]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["a = 5-2\n","b = 5*a\n","print(a>b)\n","print(type(a))"]},{"cell_type":"markdown","metadata":{},"source":["In this simple example, we define a variable with a word using a single letter *a*. Variable *a* then is a reference of the calculation result: 5-2. In the second statement, the value of a is used to calculate the expression 5\\*a, which generates an integer result 15 and it is again stored in the memory and conveniently referenced by a variable *b*.\n","\n","We see in the third statement that the values of *a* and *b* are again retrieved in the comparison expression. We can also query the type of the memory value where the variable references, as shown in the fourth statement."]},{"cell_type":"markdown","metadata":{},"source":["# Floating-Point Numbers in Python\n","\n","A floating-point number is a rational number representation that contains an integer part and a fractional part. A rational number is defined by the division result of two integers. For example, let us look at the code block below:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-16T07:36:46.08102Z","iopub.status.busy":"2021-06-16T07:36:46.08062Z","iopub.status.idle":"2021-06-16T07:36:46.087165Z","shell.execute_reply":"2021-06-16T07:36:46.086205Z","shell.execute_reply.started":"2021-06-16T07:36:46.080984Z"},"trusted":true},"outputs":[],"source":["a = 1/80\n","print(a)\n","print(a == 1.25*10**-2)\n","print(type(a))"]},{"cell_type":"markdown","metadata":{},"source":["A variable *a* is created to reference the division of two integers 1 and 80. The result is equal to 0.0125, where the integer part is 0, and the fractional part is .0125.\n","\n","The name \"floating point\" is so called because the same rational number can be represented by placing its decimal point at different places and then multiplied by a base-10 exponent. For example, \\\\(0.0125 = 0.125\\times 10^{-1} = 1.25 \\times 10^{-2} \\\\), etc. All three representations define the same rational number. The fact is verified in the third statement (recall \"**\" means to the power of).\n","\n","In Python, the type of a floating point number is denoted as 'float'. It is stored as a class structure as the same as the 'int' type, which we will discuss in much later part of the course. Because of this notation, floating-point numbers are often simply called floats.\n","\n","In addition to the typical arithmetic operators that continue to apply to floating-point calculation, such as +, -, \\*, \\\\, let us see some more examples below. The examples show that the operations of modulo, integer division, and powers are valid also to floats."]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-16T07:35:41.072674Z","iopub.status.busy":"2021-06-16T07:35:41.072328Z","iopub.status.idle":"2021-06-16T07:35:41.07775Z","shell.execute_reply":"2021-06-16T07:35:41.076862Z","shell.execute_reply.started":"2021-06-16T07:35:41.072645Z"},"trusted":true},"outputs":[],"source":["print(3.14 % 2)\n","print(3.14 // 2)\n","print(3.14 ** 2 == 3.14*3.14)"]},{"cell_type":"markdown","metadata":{},"source":["In Python and many other languages, type conversion operations are defined to convert one type of variable to another type.\n","\n","In the above, we have discussed separately two types: int and float. Let us next discuss how to define type conversion between the two types. First, from int to float is trivial. It can be done in two ways:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-16T07:38:23.151285Z","iopub.status.busy":"2021-06-16T07:38:23.150915Z","iopub.status.idle":"2021-06-16T07:38:23.156427Z","shell.execute_reply":"2021-06-16T07:38:23.155336Z","shell.execute_reply.started":"2021-06-16T07:38:23.151255Z"},"trusted":true},"outputs":[],"source":["a = 3\n","print(a*1.0)\n","print(float(a))"]},{"cell_type":"markdown","metadata":{},"source":["We see in the above code block, an integer variable a is first defined to be equal to 3. Multiplying a by a float 1.0 will result in a float type return, but the magnitude of the result in floating-point remains the same.\n","\n","The second way is to use a built-in type conversion function *float()*. As the name suggests, the function takes in an input argument. As long as this argument can be understood to be represented by a float value, the function will output a float result of that value, as shown above. The *float()* function is also called a **casting** operation.\n","\n","The other direction to convert floats to ints is more complicated. The reason is that if a float contains nontrivial digits after the decimal point, then such a convertion from float to int will change the value of the input number. After the conversion, some information will be lost. Therefore, the conversion can be performed in a variety of ways, as we shall demonstrate below:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-16T07:33:47.583356Z","iopub.status.busy":"2021-06-16T07:33:47.582977Z","iopub.status.idle":"2021-06-16T07:33:47.592585Z","shell.execute_reply":"2021-06-16T07:33:47.590309Z","shell.execute_reply.started":"2021-06-16T07:33:47.583322Z"},"trusted":true},"outputs":[],"source":["import math\n","\n","print(int(4.6))\n","print(int(-4.1))\n","print(round(4.6))\n","print(round(-4.1))\n","print(math.floor(4.6))\n","print(math.floor(-4.6))\n","print(math.ceil(4.1))\n","print(math.ceil(-4.1))"]},{"cell_type":"markdown","metadata":{},"source":["In the above code block, we see four ways a float value can be converted to int. The most important fact to remember is that, if the float value has nonzero numbers after the decimal point, then the conversion will lose information. For example, in the first case, converting 4.6 using the function *int()* returns 4, which directly takes the integer part and discard the fractional part. So *int()* is another so-called **casting function**.\n","\n","The second function is *round()*. It rounds up a float to its closest int number. For example, in the code block, we see that 4.6 rounds up to 5, and -4.1 rounds up to -4.\n","\n","The last two functions are retrieving the floor and ceiling integer values from floats, respectively. When we read the code, we notice that if we directly call the two functions *floor()*, *ceil()*, Python will return error:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["print(floor(4.6))"]},{"cell_type":"markdown","metadata":{},"source":["The difference between using *floor()* and *math.floor()* highlights the ways Python organizes its available functions into separate modules. By default, Python at launch time will only load a set of so-called built-in functions. These build-in functions include *print()*, *int()*, *float()*, and *type()* that we have learned. We will learn a lot more as the course progresses.\n","\n","However, there are vast amounts of other functions that are not part of the built-in functions. These functions are organized into modules for two main reasons: \n","1. Placing more diverse functions into individual modules helps to enhance reusability of these functions, and each module can be organized in manageable size. Otherwise, consider the counter-example if all Python functions are stored in a single file, then this file would be impossibly large to store and load.\n","2. The action to **import** modules allows users to define and release their own modules. In fact, the real power of Python in the AI era has been that a lot of very powerful new modules have been open sourced by different developers. We benefit from their contributions by importing their modules in our code.\n","\n","So in summary, calling *floor()* and *ceil()* functions requires the code to explicitly declare to import the *math* module. Then, *math.floor()* returns the greatest integer that is smaller than the input argument; *math.ceil()* returns the least integer that is greater than the input argument."]},{"cell_type":"markdown","metadata":{},"source":["# Random Generation of Numbers\n","\n","Finally, let us review a following Python code that uses randomly generated numbers and operators to test your math abilities. Please refer to the lecture video for more details."]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2023-07-24T21:28:01.119113Z","iopub.status.busy":"2023-07-24T21:28:01.118728Z","iopub.status.idle":"2023-07-24T21:28:26.196251Z","shell.execute_reply":"2023-07-24T21:28:26.195237Z","shell.execute_reply.started":"2023-07-24T21:28:01.119081Z"},"trusted":true},"outputs":[],"source":["# import two Python modules\n","import math # includes additional math functions\n","from random import randint # includes functions for generating random numbers\n","\n","# Define constants\n","OPERATOR_ROUND = 1\n","OPERATOR_INT = 2\n","OPERATOR_FLOOR = 3\n","OPERATOR_CEIL = 4\n","\n","random_operator = randint(1,4) # Select an operator, equiv to random.randint\n","random_A = randint(-10,10) # Select first value\n","random_B = randint(1,10) # Select second value\n","if random_operator == OPERATOR_ROUND: # If selected operator is round()\n"," result = round(random_A/random_B)\n"," operator_string = \"round\"\n","elif random_operator == OPERATOR_INT: # If selected operator is int()\n"," result = int(random_A/random_B)\n"," operator_string = \"int\"\n","elif random_operator == OPERATOR_FLOOR: # If selected operator is floor()\n"," result = math.floor(random_A/random_B)\n"," operator_string = \"floor\"\n","else: # If selected operator is ceil()\n"," result = math.ceil(random_A/random_B)\n"," operator_string = \"ceil\"\n","\n","# Prepare question string\n","question_string = ( \"Question: \" + operator_string + \"(\" + str(random_A)\n"," + \"/\" + str(random_B) + \") = ? \")\n","\n","user_result = input(question_string) # Wait for user input\n","user_result = int(user_result) # Convert string to int\n","if user_result == result: # The answer is correct, add one score\n"," print(\"Correct!\")\n","else: # The answer is wrong, add one score\n"," print(\"Incorrect!\")"]},{"cell_type":"markdown","metadata":{},"source":["In the above code, we see the good practice of variable naming convention, that constants, namely, variables that are not supposed to change their values, are named using all-cap words connected by underlines \"_\", and normal variables are named using small-cap words also connected by underlines.\n","\n","In the beginning, the code uses two formats to import modules into the Python code, one is the *math* module, the other is the *randint()* function from the *random* module. Using \n","\n"," from random import randint\n"," \n","allows subsequent code to directly call the *randint()* function. Alternatively, if the code merely does\n","\n"," import random\n"," \n","then subsequently the code should use *random.randint()* to call the same function. The *randint()* function has two arguments, and will return a random integer in between the two number arguments, with the given numbers also included in the selection range.\n","\n","Then the code not only uses *randint()* to select a numerator and a denominator, but also to select an operator, where 1 represents *round()*, 2 represents *int()*, 3 represents *floor()*, and 4 represents *ceil()*. The *if -- elif --else* statements are conditional flow control statements that we will study in details in later courses. In here, its function is to select a function to convert the fraction calculation *random_A/random_B* based on the value of *random_operator*. \n","\n","Finally, a *user_result* records a user inputed number that estimates the same calculation by the user. The last *if -- else* statement compares user's input and computer result, and uses *print()* to report either the user's answer is correct or incorrect."]},{"cell_type":"markdown","metadata":{},"source":["# Summary\n","\n","* Python may represent integer numbers, or *int*, precisely up to arbitrary size, only limited by the available computer storage size.\n","* The following operations between two int's will return an int: +, -, *, //, %, **\n","* Boolean type values are stored as 1 (True) or 0 (False)\n","* Comparison operations, if legitimate between compatible variable types, return boolean values: ==, <, >, <=, >=, !=\n","* Python represents noninteger real numbers as using floating-point format, or *float*.\n","* The following operations between a float and another float or int will return a float: +, -, *, //, %, **.\n","* The float division always returns a float: /.\n","* A float can be converted to an int type, and may lose accuracy in the process: int(), round(), math.floor(), math.ceil().\n","* An int can also be converted to a float type: float()\n","* random.randin(left, right) function generates a random integer within the range [left, right]."]},{"cell_type":"markdown","metadata":{},"source":["# Exercises\n","\n","1. Discuss the difference between the results of two operations: 1. a = 10/5; 2. b = 10//5. Print out the boolean result that compares a == b.\n","\n","2. Please write a code block to implement the following operation: Obtain an integer value from user input, then separate the input value as the sum of two parts. First part is the result by integer division // of five (5), the second part is the result by modulo % of five (5). For example, if the input integer is 17, the output should be a string: 17 = 3 * 5 + 2.\n","\n","3. The math constant PI is defined in Python in the math module as math.pi. Please write a code block. In the code, first import the math module, then calculate a circle's area with its radius equal to 2. Assign the result to a variable called area, and then print out its final value.\n","\n","4. Continue with the above code block, with the value of the area of a circle given, please use math.pi constant and math.sqrt() function to calculate the radius of the circle. Please assign the result to a variable called radius, and then print out its final result.\n","\n","5. Debug:"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["print (3 plus 2)\n","print (sqrt(9))"]},{"cell_type":"markdown","metadata":{},"source":["# Challenges\n","\n","1. Write a code block, in which the human user use function input() to input a number between 0 and 9, and then the computer use random.randint() function to also generate a number between 0 and 9. If the user's number is greater, print the string \"You Win\"; if the user's number is less, print \"You Lose\"; otherwise, print \"Draw\". Hint: Do not forget to type cast the user's input in str type to int type using the function int().\n","\n","2. Evaluate the code below, and then find a possible explanation why the return result is either True or False. Hint: this is not a \"bug\"."]},{"cell_type":"code","execution_count":4,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["You win\n"]}],"source":["from random import randint\n","player = int(input(\"num\"))\n","if player > randint(0,9):\n"," print(\"You win\")\n","elif player < randint(0,9):\n"," print(\"You lose\")\n","else:\n"," print('Draw')\n"]},{"cell_type":"code","execution_count":2,"metadata":{"execution":{"iopub.execute_input":"2024-02-07T05:06:17.645820Z","iopub.status.busy":"2024-02-07T05:06:17.644929Z","iopub.status.idle":"2024-02-07T05:06:17.700851Z","shell.execute_reply":"2024-02-07T05:06:17.699691Z","shell.execute_reply.started":"2024-02-07T05:06:17.645779Z"},"trusted":true},"outputs":[{"data":{"text/plain":["False"]},"execution_count":2,"metadata":{},"output_type":"execute_result"}],"source":["0.2 + 0.2 + 0.2 == 0.6\n","#Accuracy up to machine epsilon prevents this from being equal"]}],"metadata":{"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":30527,"isGpuEnabled":false,"isInternetEnabled":true,"language":"python","sourceType":"notebook"},"kernelspec":{"display_name":"Python 3","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.18"}},"nbformat":4,"nbformat_minor":4} diff --git a/Part One/1-3-strings-and-text-input-output.ipynb b/Part One/1-3-strings-and-text-input-output.ipynb index e0e2e97..58882ae 100644 --- a/Part One/1-3-strings-and-text-input-output.ipynb +++ b/Part One/1-3-strings-and-text-input-output.ipynb @@ -1 +1 @@ -{"cells":[{"cell_type":"markdown","metadata":{},"source":["**Learning Python -- The Programming Language for Artificial Intelligence and Data Science**\n","\n","**Lecture 3: Strings and Text Input/Output**\n","\n","**By Allen Y. Yang, PhD**\n","\n","**(c) Copyright Intelligent Racing Inc., 2021-2024. All rights reserved. Materials may NOT be distributed or used for any commercial purposes.**\n"]},{"cell_type":"markdown","metadata":{},"source":["# Keywords\n","\n","* **str**: The keyword for the string data type in Python.\n","* **Indexing**: Indexing refers to finding the address and subsequently the stored value within a sequential data type such as a string of characters. Python starts the first index in a sequential data type as zero.\n","* **Slicing**: Extract a subset of sequential data by their indices.\n","* **Type casting**: Converting the representation of stored data value from one type to another. Casting may change the data accuracy and utility as determined by the implementation of the casting algorithm."]},{"cell_type":"markdown","metadata":{},"source":["# Defining Strings\n","\n","In the previous lecture, we have seen Python can output information about computation results using print(). Before the invention of graphical user interface (GUI), the computer interface was predominently using text. In Windows, Mac OSX, or Linux systems, if you search keyword \"terminal\", these systems include a terminal application that allows users to manage the computer using the legacy mode, called the console or terminal interface.\n","\n","As the No.1 programming language in Data Science, handling data encoded in text format is very important for Python. In this lecture, we will discuss basic operations how to use text as a Python program's input or output, collectively known as I/O functions."]},{"cell_type":"code","execution_count":1,"metadata":{"execution":{"iopub.execute_input":"2021-06-17T06:43:19.985761Z","iopub.status.busy":"2021-06-17T06:43:19.985114Z","iopub.status.idle":"2021-06-17T06:43:19.995229Z","shell.execute_reply":"2021-06-17T06:43:19.994480Z","shell.execute_reply.started":"2021-06-17T06:43:19.985724Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["\n","Hello World!\n","True\n","Hello\n","World!\n","True\n"]}],"source":["a_string = \"Hello World!\"\n","print(type(a_string))\n","\n","b_string = 'Hello World!'\n","print(a_string)\n","print(a_string == b_string)\n","\n","c_string = '''Hello\n","World!'''\n","print(c_string)\n","d_string = \"\"\"Hello\n","World!\"\"\"\n","print(c_string==d_string)"]},{"cell_type":"markdown","metadata":{},"source":["First, we see how a text can be defined in Python. The type of data that are responsible for storing text is called **string**. In the first example above, we define a string variable *a_string* to be the text \"Hello World!\". Then we check its type, and it is a variable class of the name 'str', short for string type.\n","\n","We can also print a string simply by using the same *print()* function.\n","\n","For the a_string variable, its text data are quoted using a pair of double quotation marks. What is contained in between the double quotation marks defines the string. However, For the next b_string variable, its text data are quoted using a pair of single quotation marks. In Python, creating string variables using a pair of either single or double quotation marks is equivalent.\n","\n","Next, we see the \"==\" operator previously was used to compare the equality of int or float values. In the string case, it can also be used to compare if two strings are identical. In the above example, a_string and b_string reference the same text, so the comparison result is True.\n","\n","Finally, Python has an additional text format, which is quite unique to itself. If we use a pair of triple quotation marks, again regardless if they are single or double quotations, it defines a string that contains multiple lines. Please note that using the previous single or double quotation marks, a string text must not have more than one line."]},{"cell_type":"markdown","metadata":{},"source":["By now,you may have wondered that the special characters Python takes over to denote strings, namely, the single and double quotation marks, themselve can be a part of a valid text. How can we include these special characters as data but not as Python symbols? Let us see the following examples first:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["e_string = \"World's best coffee\"\n","f_string = 'World\"s best coffee'\n","print(e_string == f_string)\n","\n","g_string = 'World\\'s best coffee'\n","print(g_string)\n","print(e_string == g_string)"]},{"cell_type":"markdown","metadata":{},"source":["The above code block demonstrates that, if a string is created using a pair of double quotation marks, then Python will treat single quotation marks as regular text data in the string. Vice versa, if a string is created using single quotation marks, then double quotation marks in the string will be treated as regular text.\n","\n","The second half of the code block demonstrates yet another way to declare that Python should treat special characters as regular text data, that is to use another special character specifically created for this purpose, namely, the backslash mark \"\\\". In the definition of g_string, the sequence \\\\' is treated as just a single quotation mark in the text.\n","\n","The special character \\\\ not only can declare regular quotation marks, it can also be used to define other special characters. Let's see a few more examples below:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["h_string = \"This character \\\\ is special\"\n","print(h_string)\n","\n","i_string = \"Hello World!\\b\" \n","print(i_string)\n","\n","j_string = \"Line 1 \\nLine 2\"\n","print(j_string)"]},{"cell_type":"markdown","metadata":{},"source":["In the above three examples, a double backslash sequence *\\\\* is treated in text as a single backslash character. The sequence *\\b* removes one character that immediately precedes it. The sequence *\\n* represents a return character to display what is after it in a new line."]},{"cell_type":"markdown","metadata":{},"source":["# Addressing String Elements: Indexing and Slicing\n","\n","String data are stored in computer memory as a character array, which means an ordered sequence of characters. Python can address and retrieve individual characters in a string using the notation of square brackets. This operation is also known as **indexing**. Some examples are shown below:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["a = \"Hello World!\"\n","print(a[0])\n","print(a[1:2])\n","print(a[0:6:2])\n","print(a[-1])\n","print(a[-5:-2])"]},{"cell_type":"markdown","metadata":{},"source":["Let us dissect the above examples. First, we can assume a string variable such as *a* in the example not only represents the entire string, but when in addressing string elements, it also indicates the memory address of the first element. Then any additional offsets from the beginning of the string are described within a pair of square brackets.\n","1. *a[0]* retrieves from the first string character plus zero offset, which represents the first character \"H\".\n","2. *a[1:2]* uses a colon to indicate retrieving a segment of the string characters. The left argument is called **begin**, and the right argument is called **end**. The following rule is somewhat special to Python, if the reader is familiar with other languages, that the string segment retrieved by [begin: end] will include the character at a[begin] position (if valid) but will exclude the character at a[end] position. For the print out of the second example is starting from a[1] = \"e\" but will exclude a[2] = \"l\".\n","3. If the brackets contain three numbers separated by two colons, then the third number is called the **step size**. Hence, the retrieved characters starting at a[0], stopping before a[6], and with step size 2 are a[0]=\"H\", a[2]=\"l\", and a[4]=\"o\".\n","4. The string character addresses can also be counted backwards from the last character. In Python, the last character of *a* is denoted as *a[-1]*.\n","5. Similarly, the same rule to retrieve a segment of a string applies when the offsets are given in negative numbers. In this example, the segment starts from a[-5]=\"o\" and stops before a[-2] = \"d\".\n","\n","Finally, the operations that use the format *string[begin:end:step]* are called **slicing**."]},{"cell_type":"markdown","metadata":{},"source":["# String Functions\n","\n","Next, we see some examples on Python functions that take strings as input arguments:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["first = \"John\"\n","last = \"Smith\"\n","space = \" \"\n","new_string = first + space + last\n","print(new_string)\n","\n","print(len(first))\n","print(ord(space))\n","print(chr(ord(space)) == space)"]},{"cell_type":"markdown","metadata":{},"source":["In the above code block, three strings are first created. Then the \"+\" operator applies to string type variables to concatenate the three strings into one new string.\n","\n","The function *len()* returns the length of the string, namely, the number of characters in the string.\n","\n","The next two statements convert a string character into an integer number, and then from the integer number back to a string character. Specifically, in computer memory, string characters are also stored as numbers. The correspondence between characters and numbers is called a codebook. The function *ord()* returns the code of characters based on a particular codebook called Unicode. \n","\n","Then the *chr()* function goes the opposite way, namely, generating a character based on a Unicode number input. In the example, we see that the Unicode for the space character is 32.\n","\n","There are more useful functions that are relevant to string type variables. We will introduce them later in other examples in the course."]},{"cell_type":"markdown","metadata":{},"source":["# Algorithm: Reverse a String\n","\n","Consider the problem that given an input string, we ask to solve for a output string whose characters are in exact reverse order of the input string. This can be done using the slicing operator."]},{"cell_type":"code","execution_count":1,"metadata":{"execution":{"iopub.execute_input":"2021-06-17T09:01:52.234913Z","iopub.status.busy":"2021-06-17T09:01:52.234382Z","iopub.status.idle":"2021-06-17T09:01:52.242242Z","shell.execute_reply":"2021-06-17T09:01:52.241209Z","shell.execute_reply.started":"2021-06-17T09:01:52.234879Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["nohty\n","nohtyP\n"]}],"source":["input_string = \"Python\"\n","\n","partial_string = input_string[-1:0:-1]\n","print(partial_string) # Not exactly correct\n","\n","sliced_string = input_string[::-1]\n","print(sliced_string)"]},{"cell_type":"markdown","metadata":{},"source":["We can see from the above code block that, firstly, slicing operation that retrieves the string characters backwards from the last one to the first one with step size -1 does not exactly achieve the goal. Looking at the slicing code, there are two key points to remember:\n","1. The last character is referenced using the negative offset value of -1. If we count the offset positively, this value should be *len(input_string)-1*. Because the string character offset starts from zero, so the length of the string actually points to an offset that is one character beyond the range of the string.\n","2. However, since Python dictates that the slicing will stop before the ending offset, so using the smallest non-negative value zero will not be able to retrieve the first character \"P\" in reverse order. Note we also cannot use values smaller than zero, because as we pointed out above, -1 actually refers to the last character of the string.\n","\n","So the proper way to reverse a string using slicing operation is to keep the -1 step size, but ignore the exact values for *begin* and *end*. In the second algorithm, neither *begin* nor *end* indexes is provided. In such a case, Python will automatically retrieve the longest possible string result. Hence, the algorithm uses step size -1 to traverse the input string in reverse order, and the result shows the intended output."]},{"cell_type":"markdown","metadata":{},"source":["# Using String in Text I/O\n","\n","We have used the function *print()* to output text strings. It is worth noting that *print()* is a rather special type of functions in Python in that it is capable of receiving no argument, one argument, or many arguments. Let us first see an example below:"]},{"cell_type":"code","execution_count":1,"metadata":{"execution":{"iopub.execute_input":"2021-06-17T10:03:33.989074Z","iopub.status.busy":"2021-06-17T10:03:33.988752Z","iopub.status.idle":"2021-06-17T10:03:33.997021Z","shell.execute_reply":"2021-06-17T10:03:33.995835Z","shell.execute_reply.started":"2021-06-17T10:03:33.989045Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["------\n","\n","------\n","John Smith\n","------\n","John Smith 2021 True\n"]}],"source":["print('------')\n","print()\n","print('------')\n","first = \"John\"\n","last = \"Smith\"\n","print(first, last)\n","print('------')\n","print(first, last, 2021, True)"]},{"cell_type":"markdown","metadata":{},"source":["We see in this code block, three scenarios are tested and printed out separated by dash lines. In the first case, *print()* without any argument will output an empty line in the text console. In the second case, *print()* can print out multiple input arguments sequentially, each separated by a space character automatically. In the third case, we see that the multiple input arguments can even by of different types. Our example includes the string type, int type, and boolean type.\n","\n","Next, let us talk about text input. From the console mode, Python can receive a user's text input, conveniently using the statement:\n","\n","*input_string = input()*\n","\n","The return result is of the string type. In coding user and computer interaction, it is strongly recommended that the program provides sufficient text cues before the *input()* function to explain what the program expects the user to input."]},{"cell_type":"markdown","metadata":{},"source":["# Example: Test Your Math \n","\n","Below is our last example today. The program was presented in the last lecture as a math challenge. In this lecture, we will discuss more coding details in Python."]},{"cell_type":"code","execution_count":4,"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","execution":{"iopub.execute_input":"2021-06-17T10:15:22.809893Z","iopub.status.busy":"2021-06-17T10:15:22.809579Z","iopub.status.idle":"2021-06-17T10:15:28.249210Z","shell.execute_reply":"2021-06-17T10:15:28.248104Z","shell.execute_reply.started":"2021-06-17T10:15:22.809863Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["Question: floor(9/7) = ? 1\n"]},{"name":"stdout","output_type":"stream","text":["Correct!\n"]}],"source":["# import two Python modules\n","import math # includes additional math functions\n","import random # includes functions for generating random numbers\n","\n","# Define constants\n","OPERATOR_FLOOR = 1\n","OPERATOR_CEIL = 2\n","\n","random_operator = random.randint(1,2) # Select an operator, equiv to random.randint\n","random_A = random.randint(-10,10) # Select first value\n","random_B = random.randint(1,10) # Select second value. Note denominator cannot be zero\n","if random_operator == OPERATOR_FLOOR: # If selected operator is floor()\n"," result = math.floor(random_A/random_B)\n"," operator_string = \"floor\"\n","else: # If selected operator is ceil()\n"," result = math.ceil(random_A/random_B)\n"," operator_string = \"ceil\"\n","\n","# Prepare question string\n","# question_string = ( \"Question: \" + operator_string + \"(\" + str(random_A)\n","# + \"/\" + str(random_B) + \") = ? \")\n","question_string = \"Question: {0}({1}/{2}) = ?\".format(operator_string, str(random_A), str(random_B))\n","\n","user_result = input(question_string) # Wait for user input\n","user_result = int(user_result) # Convert string to int\n","if user_result == result: # The answer is correct, add one score\n"," print(\"Correct!\")\n","else: # The answer is wrong, add one score\n"," print(\"Incorrect!\")"]},{"cell_type":"markdown","metadata":{},"source":["Let us go over this complete Python program from the beginning. First, we see for the first time in this course, the use of comments in Python code. A comment space is denoted by two ways: 1. Using a pair of triple quotation marks directly in the source code (instead of within the print statement). A pair of triple quotation marks will claim everything in between them as comments, which may cover multiple lines. 2. In comparison, using the hash symbol (or being called the pound sign in the US) \"#\" designates immediately after it till the remainder of the same line as comments. When encountering comments, Python will simply ignore them.\n","\n","The purpose of including comments is to increase the readability for the benefits of both the author and equally importantly other readers. If the author of a program did not leave sufficient comments about the logic of the code and the purpose of individual variables, it would be difficult for other readers to second-guess the author's logic and it compromises the reusability of the code.\n","\n","Therefore, in modern software engineering, practitioners should pay equal attention to both the logic of their code and explaining it using sufficient comments. It is quite normal in commercial-quality source code that professional developers could reserve 1/3 to 1/2 space of their code for commenting and documentation purposes. In this course, we strongly recommend our students to start cultivate this practice.\n","\n","In lines 2 and 3, the code imports two modules: math and random. Importing math is to be able to use the *floor()* and *ceil()* functions. Importing random serves another purpose, that we will use a randomized integer generator called *randint()* to generate random arithmetic challenges so that every time the program is executed the challenge can be different.\n","\n","In lines 9 to 11, three random integer numbers are generated. *random_operator* assumes the value of 1 or 2 as the output from the function *random.randint(1, 2)*; *random_A* is the random numerator between the values -10 and 10; *random_B* is the random denominator between the values 1 and 10.\n","\n","In lines 12 to 17, we see for the first time the use of flow control statements: *if -- else --*. We will discuss flow control statements later in this course. In here, it can be simply understood as, if random_operator is equal to the constant representing floor operator, the correct result will be using *math.floor()* to convert the fraction random_A/random_B, otherwise, the correct result will be using *math.ceil()*\n","\n","Then in line 20 and 22, two different ways are demonstrated to format a string *question_string*. The first way uses \"+\" to concatenate the operator string and the division expression. The second way in line 22 uses curly brackets and *.format()* string method to substitute in a string location with one of format() function's input arguments.\n","\n","In line 24, this question is printed out and cue the user to answer in text using function *input()*. Since the *input()* return is always of string type, the code then uses *int()* to type cast the string variable into an int variable. Note, the reader can run this code and test what if the input text is not a valid integer. In such cases, Python will return an error."]},{"cell_type":"markdown","metadata":{},"source":["# Summary\n","\n","* Single quotation marks and double quotation marks can be used to create a text string in one line, so long as the quotation marks must be in a pair of the same type.\n","* A pair of triple quotation marks can create a text string that contains multiple lines.\n","* Use of backslash \\ with the quotation marks inside a text string denotes the marks as regular characters rather than special characters.\n","* Some other backslash-defined characters include: \\\\, \\b, \\n\n","* A substring can be defined by **slicing** operation, defined by a pair of square brackets: string[begin: stop: step]. The substring will not take the *stop* position character.\n","* Negative values in *begin*, *stop*, or *step* indicate counting the positions from the end of the string backward.\n","* Functions that act on strings: len(), ord(), chr().\n","* input() function returns a user string input from the terminal."]},{"cell_type":"markdown","metadata":{},"source":["# Exercises\n","\n","1. Please create a string variable called *phrase*, and assign the value \"Hello World\". Then please use slicing method to separate the first word \"Hello\" to a variable called *subphrase_1*, and the second word \"World\" to another variable called *subphrase_2*. Note that please do not include the space in between the two words in either subphrases.\n","\n","2. Continue with the above program. Please write the code to remove the space character \" \" from the *phrase* variable, and print out the resulting value in the variable.\n","\n","3. Create three string variables that describe yourself: first_name, last_name, date_of_birth. Then concatenate the three strings into a new string called ID_string, using the \"+\" operator.\n","\n","4. Continue with the above program. Please extract from the string ID_string the following characters: the first letter of first_name, the first letter of last_name, and the first letter of date_of_birth. Assign the result into a new string variable called short_ID_string.\n","\n","5. Debug:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-05-27T23:14:15.415976Z","iopub.status.busy":"2021-05-27T23:14:15.415568Z","iopub.status.idle":"2021-05-27T23:14:18.400162Z","shell.execute_reply":"2021-05-27T23:14:18.399353Z","shell.execute_reply.started":"2021-05-27T23:14:15.415939Z"},"trusted":true},"outputs":[],"source":["a = input(\"Please input the first adden: \"); b = input(\"Please input the second adden: \")\n","print (\"The sum is \", a + b)"]},{"cell_type":"markdown","metadata":{},"source":["6. Debug:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-05-28T06:08:27.579981Z","iopub.status.busy":"2021-05-28T06:08:27.579446Z","iopub.status.idle":"2021-05-28T06:08:27.586183Z","shell.execute_reply":"2021-05-28T06:08:27.584784Z","shell.execute_reply.started":"2021-05-28T06:08:27.579933Z"},"trusted":true},"outputs":[],"source":["wrong_string = 'Mike's story'"]},{"cell_type":"markdown","metadata":{},"source":["# Challenges\n","\n","1. Based on the float value in math.pi, use the string type cast function str() to convert the float value into a string variable, called pi_string. Then keep the string type, move the decimal point character \".\" to the right by one position, and assign the result again back to pi_string. Finally, print out the resulting string. Hint: The result should look like \"31.41592653589793\""]}],"metadata":{"kaggle":{"accelerator":"none","dataSources":[],"isGpuEnabled":false,"isInternetEnabled":true,"language":"python","sourceType":"notebook"},"kernelspec":{"display_name":"Python 3","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.7.6"}},"nbformat":4,"nbformat_minor":4} +{"cells":[{"cell_type":"markdown","metadata":{},"source":["**Learning Python -- The Programming Language for Artificial Intelligence and Data Science**\n","\n","**Lecture 3: Strings and Text Input/Output**\n","\n","**By Allen Y. Yang, PhD**\n","\n","**(c) Copyright Intelligent Racing Inc., 2021-2024. All rights reserved. Materials may NOT be distributed or used for any commercial purposes.**\n"]},{"cell_type":"markdown","metadata":{},"source":["# Keywords\n","\n","* **str**: The keyword for the string data type in Python.\n","* **Indexing**: Indexing refers to finding the address and subsequently the stored value within a sequential data type such as a string of characters. Python starts the first index in a sequential data type as zero.\n","* **Slicing**: Extract a subset of sequential data by their indices.\n","* **Type casting**: Converting the representation of stored data value from one type to another. Casting may change the data accuracy and utility as determined by the implementation of the casting algorithm."]},{"cell_type":"markdown","metadata":{},"source":["# Defining Strings\n","\n","In the previous lecture, we have seen Python can output information about computation results using print(). Before the invention of graphical user interface (GUI), the computer interface was predominently using text. In Windows, Mac OSX, or Linux systems, if you search keyword \"terminal\", these systems include a terminal application that allows users to manage the computer using the legacy mode, called the console or terminal interface.\n","\n","As the No.1 programming language in Data Science, handling data encoded in text format is very important for Python. In this lecture, we will discuss basic operations how to use text as a Python program's input or output, collectively known as I/O functions."]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-17T06:43:19.985761Z","iopub.status.busy":"2021-06-17T06:43:19.985114Z","iopub.status.idle":"2021-06-17T06:43:19.995229Z","shell.execute_reply":"2021-06-17T06:43:19.994480Z","shell.execute_reply.started":"2021-06-17T06:43:19.985724Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["\n","Hello World!\n","True\n","Hello\n","World!\n","True\n"]}],"source":["a_string = \"Hello World!\"\n","print(type(a_string))\n","\n","b_string = 'Hello World!'\n","print(a_string)\n","print(a_string == b_string)\n","\n","c_string = '''Hello\n","World!'''\n","print(c_string)\n","d_string = \"\"\"Hello\n","World!\"\"\"\n","print(c_string==d_string)"]},{"cell_type":"markdown","metadata":{},"source":["First, we see how a text can be defined in Python. The type of data that are responsible for storing text is called **string**. In the first example above, we define a string variable *a_string* to be the text \"Hello World!\". Then we check its type, and it is a variable class of the name 'str', short for string type.\n","\n","We can also print a string simply by using the same *print()* function.\n","\n","For the a_string variable, its text data are quoted using a pair of double quotation marks. What is contained in between the double quotation marks defines the string. However, For the next b_string variable, its text data are quoted using a pair of single quotation marks. In Python, creating string variables using a pair of either single or double quotation marks is equivalent.\n","\n","Next, we see the \"==\" operator previously was used to compare the equality of int or float values. In the string case, it can also be used to compare if two strings are identical. In the above example, a_string and b_string reference the same text, so the comparison result is True.\n","\n","Finally, Python has an additional text format, which is quite unique to itself. If we use a pair of triple quotation marks, again regardless if they are single or double quotations, it defines a string that contains multiple lines. Please note that using the previous single or double quotation marks, a string text must not have more than one line."]},{"cell_type":"markdown","metadata":{},"source":["By now,you may have wondered that the special characters Python takes over to denote strings, namely, the single and double quotation marks, themselve can be a part of a valid text. How can we include these special characters as data but not as Python symbols? Let us see the following examples first:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["e_string = \"World's best coffee\"\n","f_string = 'World\"s best coffee'\n","print(e_string == f_string)\n","\n","g_string = 'World\\'s best coffee'\n","print(g_string)\n","print(e_string == g_string)"]},{"cell_type":"markdown","metadata":{},"source":["The above code block demonstrates that, if a string is created using a pair of double quotation marks, then Python will treat single quotation marks as regular text data in the string. Vice versa, if a string is created using single quotation marks, then double quotation marks in the string will be treated as regular text.\n","\n","The second half of the code block demonstrates yet another way to declare that Python should treat special characters as regular text data, that is to use another special character specifically created for this purpose, namely, the backslash mark \"\\\". In the definition of g_string, the sequence \\\\' is treated as just a single quotation mark in the text.\n","\n","The special character \\\\ not only can declare regular quotation marks, it can also be used to define other special characters. Let's see a few more examples below:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["h_string = \"This character \\\\ is special\"\n","print(h_string)\n","\n","i_string = \"Hello World!\\b\" \n","print(i_string)\n","\n","j_string = \"Line 1 \\nLine 2\"\n","print(j_string)"]},{"cell_type":"markdown","metadata":{},"source":["In the above three examples, a double backslash sequence *\\\\* is treated in text as a single backslash character. The sequence *\\b* removes one character that immediately precedes it. The sequence *\\n* represents a return character to display what is after it in a new line."]},{"cell_type":"markdown","metadata":{},"source":["# Addressing String Elements: Indexing and Slicing\n","\n","String data are stored in computer memory as a character array, which means an ordered sequence of characters. Python can address and retrieve individual characters in a string using the notation of square brackets. This operation is also known as **indexing**. Some examples are shown below:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["a = \"Hello World!\"\n","print(a[0])\n","print(a[1:2])\n","print(a[0:6:2])\n","print(a[-1])\n","print(a[-5:-2])"]},{"cell_type":"markdown","metadata":{},"source":["Let us dissect the above examples. First, we can assume a string variable such as *a* in the example not only represents the entire string, but when in addressing string elements, it also indicates the memory address of the first element. Then any additional offsets from the beginning of the string are described within a pair of square brackets.\n","1. *a[0]* retrieves from the first string character plus zero offset, which represents the first character \"H\".\n","2. *a[1:2]* uses a colon to indicate retrieving a segment of the string characters. The left argument is called **begin**, and the right argument is called **end**. The following rule is somewhat special to Python, if the reader is familiar with other languages, that the string segment retrieved by [begin: end] will include the character at a[begin] position (if valid) but will exclude the character at a[end] position. For the print out of the second example is starting from a[1] = \"e\" but will exclude a[2] = \"l\".\n","3. If the brackets contain three numbers separated by two colons, then the third number is called the **step size**. Hence, the retrieved characters starting at a[0], stopping before a[6], and with step size 2 are a[0]=\"H\", a[2]=\"l\", and a[4]=\"o\".\n","4. The string character addresses can also be counted backwards from the last character. In Python, the last character of *a* is denoted as *a[-1]*.\n","5. Similarly, the same rule to retrieve a segment of a string applies when the offsets are given in negative numbers. In this example, the segment starts from a[-5]=\"o\" and stops before a[-2] = \"d\".\n","\n","Finally, the operations that use the format *string[begin:end:step]* are called **slicing**."]},{"cell_type":"markdown","metadata":{},"source":["# String Functions\n","\n","Next, we see some examples on Python functions that take strings as input arguments:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["first = \"John\"\n","last = \"Smith\"\n","space = \" \"\n","new_string = first + space + last\n","print(new_string)\n","\n","print(len(first))\n","print(ord(space))\n","print(chr(ord(space)) == space)"]},{"cell_type":"markdown","metadata":{},"source":["In the above code block, three strings are first created. Then the \"+\" operator applies to string type variables to concatenate the three strings into one new string.\n","\n","The function *len()* returns the length of the string, namely, the number of characters in the string.\n","\n","The next two statements convert a string character into an integer number, and then from the integer number back to a string character. Specifically, in computer memory, string characters are also stored as numbers. The correspondence between characters and numbers is called a codebook. The function *ord()* returns the code of characters based on a particular codebook called Unicode. \n","\n","Then the *chr()* function goes the opposite way, namely, generating a character based on a Unicode number input. In the example, we see that the Unicode for the space character is 32.\n","\n","There are more useful functions that are relevant to string type variables. We will introduce them later in other examples in the course."]},{"cell_type":"markdown","metadata":{},"source":["# Algorithm: Reverse a String\n","\n","Consider the problem that given an input string, we ask to solve for a output string whose characters are in exact reverse order of the input string. This can be done using the slicing operator."]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-17T09:01:52.234913Z","iopub.status.busy":"2021-06-17T09:01:52.234382Z","iopub.status.idle":"2021-06-17T09:01:52.242242Z","shell.execute_reply":"2021-06-17T09:01:52.241209Z","shell.execute_reply.started":"2021-06-17T09:01:52.234879Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["nohty\n","nohtyP\n"]}],"source":["input_string = \"Python\"\n","\n","partial_string = input_string[-1:0:-1]\n","print(partial_string) # Not exactly correct\n","\n","sliced_string = input_string[::-1]\n","print(sliced_string)"]},{"cell_type":"markdown","metadata":{},"source":["We can see from the above code block that, firstly, slicing operation that retrieves the string characters backwards from the last one to the first one with step size -1 does not exactly achieve the goal. Looking at the slicing code, there are two key points to remember:\n","1. The last character is referenced using the negative offset value of -1. If we count the offset positively, this value should be *len(input_string)-1*. Because the string character offset starts from zero, so the length of the string actually points to an offset that is one character beyond the range of the string.\n","2. However, since Python dictates that the slicing will stop before the ending offset, so using the smallest non-negative value zero will not be able to retrieve the first character \"P\" in reverse order. Note we also cannot use values smaller than zero, because as we pointed out above, -1 actually refers to the last character of the string.\n","\n","So the proper way to reverse a string using slicing operation is to keep the -1 step size, but ignore the exact values for *begin* and *end*. In the second algorithm, neither *begin* nor *end* indexes is provided. In such a case, Python will automatically retrieve the longest possible string result. Hence, the algorithm uses step size -1 to traverse the input string in reverse order, and the result shows the intended output."]},{"cell_type":"markdown","metadata":{},"source":["# Using String in Text I/O\n","\n","We have used the function *print()* to output text strings. It is worth noting that *print()* is a rather special type of functions in Python in that it is capable of receiving no argument, one argument, or many arguments. Let us first see an example below:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-17T10:03:33.989074Z","iopub.status.busy":"2021-06-17T10:03:33.988752Z","iopub.status.idle":"2021-06-17T10:03:33.997021Z","shell.execute_reply":"2021-06-17T10:03:33.995835Z","shell.execute_reply.started":"2021-06-17T10:03:33.989045Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["------\n","\n","------\n","John Smith\n","------\n","John Smith 2021 True\n"]}],"source":["print('------')\n","print()\n","print('------')\n","first = \"John\"\n","last = \"Smith\"\n","print(first, last)\n","print('------')\n","print(first, last, 2021, True)"]},{"cell_type":"markdown","metadata":{},"source":["We see in this code block, three scenarios are tested and printed out separated by dash lines. In the first case, *print()* without any argument will output an empty line in the text console. In the second case, *print()* can print out multiple input arguments sequentially, each separated by a space character automatically. In the third case, we see that the multiple input arguments can even by of different types. Our example includes the string type, int type, and boolean type.\n","\n","Next, let us talk about text input. From the console mode, Python can receive a user's text input, conveniently using the statement:\n","\n","*input_string = input()*\n","\n","The return result is of the string type. In coding user and computer interaction, it is strongly recommended that the program provides sufficient text cues before the *input()* function to explain what the program expects the user to input."]},{"cell_type":"markdown","metadata":{},"source":["# Example: Test Your Math \n","\n","Below is our last example today. The program was presented in the last lecture as a math challenge. In this lecture, we will discuss more coding details in Python."]},{"cell_type":"code","execution_count":null,"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","execution":{"iopub.execute_input":"2021-06-17T10:15:22.809893Z","iopub.status.busy":"2021-06-17T10:15:22.809579Z","iopub.status.idle":"2021-06-17T10:15:28.249210Z","shell.execute_reply":"2021-06-17T10:15:28.248104Z","shell.execute_reply.started":"2021-06-17T10:15:22.809863Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["Question: floor(9/7) = ? 1\n"]},{"name":"stdout","output_type":"stream","text":["Correct!\n"]}],"source":["# import two Python modules\n","import math # includes additional math functions\n","import random # includes functions for generating random numbers\n","\n","# Define constants\n","OPERATOR_FLOOR = 1\n","OPERATOR_CEIL = 2\n","\n","random_operator = random.randint(1,2) # Select an operator, equiv to random.randint\n","random_A = random.randint(-10,10) # Select first value\n","random_B = random.randint(1,10) # Select second value. Note denominator cannot be zero\n","if random_operator == OPERATOR_FLOOR: # If selected operator is floor()\n"," result = math.floor(random_A/random_B)\n"," operator_string = \"floor\"\n","else: # If selected operator is ceil()\n"," result = math.ceil(random_A/random_B)\n"," operator_string = \"ceil\"\n","\n","# Prepare question string\n","# question_string = ( \"Question: \" + operator_string + \"(\" + str(random_A)\n","# + \"/\" + str(random_B) + \") = ? \")\n","question_string = \"Question: {0}({1}/{2}) = ?\".format(operator_string, str(random_A), str(random_B))\n","\n","user_result = input(question_string) # Wait for user input\n","user_result = int(user_result) # Convert string to int\n","if user_result == result: # The answer is correct, add one score\n"," print(\"Correct!\")\n","else: # The answer is wrong, add one score\n"," print(\"Incorrect!\")"]},{"cell_type":"markdown","metadata":{},"source":["Let us go over this complete Python program from the beginning. First, we see for the first time in this course, the use of comments in Python code. A comment space is denoted by two ways: 1. Using a pair of triple quotation marks directly in the source code (instead of within the print statement). A pair of triple quotation marks will claim everything in between them as comments, which may cover multiple lines. 2. In comparison, using the hash symbol (or being called the pound sign in the US) \"#\" designates immediately after it till the remainder of the same line as comments. When encountering comments, Python will simply ignore them.\n","\n","The purpose of including comments is to increase the readability for the benefits of both the author and equally importantly other readers. If the author of a program did not leave sufficient comments about the logic of the code and the purpose of individual variables, it would be difficult for other readers to second-guess the author's logic and it compromises the reusability of the code.\n","\n","Therefore, in modern software engineering, practitioners should pay equal attention to both the logic of their code and explaining it using sufficient comments. It is quite normal in commercial-quality source code that professional developers could reserve 1/3 to 1/2 space of their code for commenting and documentation purposes. In this course, we strongly recommend our students to start cultivate this practice.\n","\n","In lines 2 and 3, the code imports two modules: math and random. Importing math is to be able to use the *floor()* and *ceil()* functions. Importing random serves another purpose, that we will use a randomized integer generator called *randint()* to generate random arithmetic challenges so that every time the program is executed the challenge can be different.\n","\n","In lines 9 to 11, three random integer numbers are generated. *random_operator* assumes the value of 1 or 2 as the output from the function *random.randint(1, 2)*; *random_A* is the random numerator between the values -10 and 10; *random_B* is the random denominator between the values 1 and 10.\n","\n","In lines 12 to 17, we see for the first time the use of flow control statements: *if -- else --*. We will discuss flow control statements later in this course. In here, it can be simply understood as, if random_operator is equal to the constant representing floor operator, the correct result will be using *math.floor()* to convert the fraction random_A/random_B, otherwise, the correct result will be using *math.ceil()*\n","\n","Then in line 20 and 22, two different ways are demonstrated to format a string *question_string*. The first way uses \"+\" to concatenate the operator string and the division expression. The second way in line 22 uses curly brackets and *.format()* string method to substitute in a string location with one of format() function's input arguments.\n","\n","In line 24, this question is printed out and cue the user to answer in text using function *input()*. Since the *input()* return is always of string type, the code then uses *int()* to type cast the string variable into an int variable. Note, the reader can run this code and test what if the input text is not a valid integer. In such cases, Python will return an error."]},{"cell_type":"markdown","metadata":{},"source":["# Summary\n","\n","* Single quotation marks and double quotation marks can be used to create a text string in one line, so long as the quotation marks must be in a pair of the same type.\n","* A pair of triple quotation marks can create a text string that contains multiple lines.\n","* Use of backslash \\ with the quotation marks inside a text string denotes the marks as regular characters rather than special characters.\n","* Some other backslash-defined characters include: \\\\, \\b, \\n\n","* A substring can be defined by **slicing** operation, defined by a pair of square brackets: string[begin: stop: step]. The substring will not take the *stop* position character.\n","* Negative values in *begin*, *stop*, or *step* indicate counting the positions from the end of the string backward.\n","* Functions that act on strings: len(), ord(), chr().\n","* input() function returns a user string input from the terminal."]},{"cell_type":"markdown","metadata":{},"source":["# Exercises\n","\n","1. Please create a string variable called *phrase*, and assign the value \"Hello World\". Then please use slicing method to separate the first word \"Hello\" to a variable called *subphrase_1*, and the second word \"World\" to another variable called *subphrase_2*. Note that please do not include the space in between the two words in either subphrases.\n","\n","2. Continue with the above program. Please write the code to remove the space character \" \" from the *phrase* variable, and print out the resulting value in the variable.\n","\n","3. Create three string variables that describe yourself: first_name, last_name, date_of_birth. Then concatenate the three strings into a new string called ID_string, using the \"+\" operator.\n","\n","4. Continue with the above program. Please extract from the string ID_string the following characters: the first letter of first_name, the first letter of last_name, and the first letter of date_of_birth. Assign the result into a new string variable called short_ID_string.\n","\n","5. Debug:"]},{"cell_type":"code","execution_count":62,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["Hello\n","World\n","['H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd']\n"]}],"source":["\n","#Q1\n","phrase = \"Hello World\"\n","L = list(phrase)\n","subphrase_1 = phrase[0:5]\n","subphrase_2= phrase[6:11]\n","print(subphrase_1)\n","print(subphrase_2)\n","#Q2\n","L.remove(\" \")\n","print(L)"]},{"cell_type":"code","execution_count":67,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["Ian Petersen 3/23/2008\n","IP3\n"]}],"source":["\"\"\"Q3\"\"\"\n","first_name = \"Ian\"\n","#list[first_name]\n","#flen = len(first_name)\n","last_name = \"Petersen\"\n","date_of_birth = \"3/23/2008\"\n","ID_string = (first_name + \" \" + last_name + \" \" + date_of_birth)\n","print(ID_string)\n","\"\"\"Q4\"\"\"\n","short_ID_string = (ID_string[0] + ID_string[len(list(first_name))+1] + ID_string[len(list(last_name))+len(list(first_name))+2]) # add two to account for the spaces in the word\n","print(short_ID_string)"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-05-27T23:14:15.415976Z","iopub.status.busy":"2021-05-27T23:14:15.415568Z","iopub.status.idle":"2021-05-27T23:14:18.400162Z","shell.execute_reply":"2021-05-27T23:14:18.399353Z","shell.execute_reply.started":"2021-05-27T23:14:15.415939Z"},"trusted":true},"outputs":[],"source":["#Q5 DEBUG\n","a = int(input(\"Please input the first adden: \")); b = int(input(\"Please input the second adden: \"))\n","print (\"The sum is \", a + b)\n"]},{"cell_type":"markdown","metadata":{},"source":["6. Debug:"]},{"cell_type":"code","execution_count":23,"metadata":{"execution":{"iopub.execute_input":"2021-05-28T06:08:27.579981Z","iopub.status.busy":"2021-05-28T06:08:27.579446Z","iopub.status.idle":"2021-05-28T06:08:27.586183Z","shell.execute_reply":"2021-05-28T06:08:27.584784Z","shell.execute_reply.started":"2021-05-28T06:08:27.579933Z"},"trusted":true},"outputs":[],"source":["wrong_string = \"Mike's story\""]},{"cell_type":"markdown","metadata":{},"source":["# Challenges\n","\n","1. Based on the float value in math.pi, use the string type cast function str() to convert the float value into a string variable, called pi_string. Then keep the string type, move the decimal point character \".\" to the right by one position, and assign the result again back to pi_string. Finally, print out the resulting string. Hint: The result should look like \"31.41592653589793\""]},{"cell_type":"code","execution_count":22,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["3.141592653589793\n",".\n","31.4\n"]}],"source":["import math\n","pi_string= str(math.pi)\n","print(pi_string)\n","decimove = pi_string[1:2]\n","print(decimove)\n","integer = pi_string[0:1] + pi_string[2:3]\n","pi_string = integer + decimove + pi_string[3]\n","print(pi_string)\n"]}],"metadata":{"kaggle":{"accelerator":"none","dataSources":[],"isGpuEnabled":false,"isInternetEnabled":true,"language":"python","sourceType":"notebook"},"kernelspec":{"display_name":"Python 3","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.18"}},"nbformat":4,"nbformat_minor":4} diff --git a/Part One/1-4-lists.ipynb b/Part One/1-4-lists.ipynb index 6ab1048..06d71fe 100644 --- a/Part One/1-4-lists.ipynb +++ b/Part One/1-4-lists.ipynb @@ -1 +1 @@ -{"cells":[{"cell_type":"markdown","metadata":{},"source":["**Learning Python -- The Programming Language for Artificial Intelligence and Data Science**\n","\n","**Lecture 4: Lists**\n","\n","**By Allen Y. Yang, PhD**\n","\n","(c) Copyright Intelligent Racing Inc., 2021-2024. All rights reserved. Materials may NOT be distributed or used for any commercial purposes."]},{"cell_type":"markdown","metadata":{},"source":["# Keywords\n","\n","* **list**: The keyword for the list data type in Python.\n","* **Method**: In object-oriented programming languages such as Python, a method is a built-in function encaptulated together with the variable data.\n","* **Immutable data type**: A variable of an immutable data type cannot modify the stored data value at the referenced data address. Assigning a new value to the same variable leads to the variable pointing to a new memory address with the new value.\n","* **Mutable data type**: A variable of a mutable data type can modify the stored data value while keeping the same data address."]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2022-02-11T02:47:47.636854Z","iopub.status.busy":"2022-02-11T02:47:47.636363Z","iopub.status.idle":"2022-02-11T02:47:47.644028Z","shell.execute_reply":"2022-02-11T02:47:47.642681Z","shell.execute_reply.started":"2022-02-11T02:47:47.636815Z"},"trusted":true},"outputs":[],"source":["a = 4990\n","b = 4990\n","print(id(a)==id(b))"]},{"cell_type":"markdown","metadata":{},"source":["# Definition and Indexing\n","\n","As a string stores an array of characters, the *list* type in Python stores an ordered sequence of variables. Most notable is that fact that the variables in a list can be of different types. Let us see some examples below:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-17T21:28:08.085922Z","iopub.status.busy":"2021-06-17T21:28:08.085269Z","iopub.status.idle":"2021-06-17T21:28:08.094144Z","shell.execute_reply":"2021-06-17T21:28:08.092412Z","shell.execute_reply.started":"2021-06-17T21:28:08.085867Z"},"trusted":true},"outputs":[],"source":["l1 = []\n","l2 = ['a', 'b', 5]\n","l3 = [l1, l2, 5]\n","print(l3)"]},{"cell_type":"markdown","metadata":{},"source":["In the above code block, a list is defined by enumerating its elements contained within a pair of square brackets. A pair of empty brackets define an empty list (of length zero). The second list variable *l2* is formed from a list of mixed variables, namely, two string types and one integer type. \n","\n","More interestingly, a list may contain other lists as its elements. In the third example, *l3* contains first element as l1, second element as l2, and third element an integer.\n","\n","Next, we see how to address the elements in a list."]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-17T21:28:38.028192Z","iopub.status.busy":"2021-06-17T21:28:38.027807Z","iopub.status.idle":"2021-06-17T21:28:38.035014Z","shell.execute_reply":"2021-06-17T21:28:38.03375Z","shell.execute_reply.started":"2021-06-17T21:28:38.028155Z"},"trusted":true},"outputs":[],"source":["L = [['a', 'b'], 5, 'c']\n","print(L[0])\n","print(L[0][1])\n","print(L[-1])"]},{"cell_type":"markdown","metadata":{},"source":["Similar to the string type, elements in a list can be addressed by their indexes. In the first example above, the first element in L has offset zero, so *L[0]* retrieves the first element, which itself is a list.\n","\n","Since *L[0]* is a list, we can recursively address the elements in this list using the same square brackets. In the second example, *L[0][1]* references the second element of *L[0]*, which is 'b'.\n","\n","The third example shows that a list can also be indexed from the end to the beginning by negative index values. For example, the last element of *L* also can be addressed by index -1."]},{"cell_type":"code","execution_count":1,"metadata":{"execution":{"iopub.execute_input":"2024-02-25T04:17:29.969490Z","iopub.status.busy":"2024-02-25T04:17:29.968691Z","iopub.status.idle":"2024-02-25T04:17:30.267027Z","shell.execute_reply":"2024-02-25T04:17:30.265603Z","shell.execute_reply.started":"2024-02-25T04:17:29.969414Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["Slicing method: Elapsed time: 0.08 seconds\n","Built-in class method: Elapsed time: 0.01 seconds\n"]}],"source":["\"\"\"\n","In this sample code, we program an algorithm to reverse a list. Specifically, we will implement two methods and compare their time difference\n","The code shows a standard way to measure time to compare how fast an algorithm can run, using the time module\n","\"\"\"\n","\n","import time\n","\n","test_list = list('Python'*1000000) # Using \"*\" to duplicate a string 1,000,000 times\n","\n","# Method 1: Slicing operator\n","tic = time.time() # In practice, tic is a jargon referring the begin clock time\n","reverse_list = test_list[::-1]\n","toc = time.time() # toc is a jargon referring the end clock time in the unit of second\n","elapsed_time = toc - tic\n","print('Slicing method: Elapsed time: %.2f seconds' % elapsed_time )\n","\n","\n","# Method 2: Built-in object method\n","tic = time.time() \n","test_list.reverse() # a list type is a class in Python, which contains its own built-in functions\n","toc = time.time() \n","elapsed_time = toc - tic\n","print('Built-in class method: Elapsed time: {time:.2f} seconds'.format(time = elapsed_time) )"]},{"cell_type":"markdown","metadata":{},"source":["A good quality of an algorithm is measured by how fast it can run compared to other algorithms achieving the same results. In the above code block, we introduce a standard procedure to count the elapsed time, using the module function *time.time()*. The function returns a float value in the unit of seconds, which means the fraction part indicates sub-second accuracies. The function retrieves the current computer system clock, and reports a difference with the \"beginning of time\". In modern programming languages such as Python, the beginning of time with respect to modern computer systems is set to January 1, 1970, 00:00:00 (UTC). \n","\n","If we have designed an algorithm, when we retrieve *time.time()* before the execution as *tic*, and again after the execution as *toc*, then the difference is the elapsed time. This is a very useful measure of algorithm speed that the reader should learn to use fluently.\n","\n","Next let us talk about how the elapsed time is reported in a *print()* function. Since when we code the printed string format, it is not known to us about the value of elapsed_time. The exact value has to be formatted at runtime (meaning, when the code is actually running by the computer's processor). Python provides two basic ways to combine pre-defined constant string and dynamic argument together at runtime:\n","\n","1. The way inherited from older versions of Python is to use the \"%\" symbol to indicate an argument input to a string, which should be determined at runtime. One example is shown in line 15. In this example \".2f\" means displaying the string argument input as a floating point number with 2 digits after the decimal point.\n","2. The modern way recommended by Python 3 standards is to use a built-in function for any string type variables, as shown in line 23. The built-in function is called *format()*. Python will insert one or more arguments provided in *format()* function and convert them as part of the string as indicated by the reserved symbols {}. In this modern way, the displayed text still can be formatted by user specifications, such as \".2f\" in the example\n","\n","The new concept of owning built-in functions for different variable types is a benefit of so-called object-oriented programming (OOP). We will talk about OOP in the last part of the course, but previously we have noticed that all Python variable types are actually classes. A class in OOP languages contains data *and* methods that directly apply to the data (Note that in OOP, built-in functions in a class are often called **methods** to differentiate with regular functions that are not tightly associated with a class object). In other words, when a programmer creates a class, they not only have prepared a memory storage of the data, but also have prepared a list of functions that are properly coded to process the data. This is one of the benefits of using OOP.\n","\n","In the above sample code, we see that if the variable is a list, it also has a built-in method to perform exactly the task of reversing the list. Thanks to this built-in method, the operation can be coded in just one line built-in method call in line 20.\n","\n","Finally, the difference in algorithm design between the two used methods is that *list.reverse()* method is an \"in-place\" sorting algorithm, while slicing requires doubling the memory space to traverse from the list source. For more details, please be sure to watch the lecture video."]},{"cell_type":"markdown","metadata":{},"source":["# List Methods\n","\n","In Python and many object-oriented programming languages, a method refers to a built-in function encaptulated together with the variable data, when the variable is an object type. In Python, all variable types are objects. Therefore, each variable comes with its list of encaptulated methods. In this section, let us consider several some useful methods built in to the list type. \n","\n"," * clear(): Removes all list elements and causes the list to be empty.\n"," * insert(pos, element): Add an *element* at the *pos* position. If the position is greater than the current last position, then it will be added behind the current last element.\n"," * pop(pos): Remove and return the element at *pos* position. The argument can be empty, then the default position is the last one.\n"," * append(element): Add *element* from the end, and the length of the list is added by one.\n"," * extend(new_list): Add all elements from *new_list* to the end of the current list.\n"," * sort(): Sorts the list elements.\n"," * count(element): Counts the number of appearances of a specific element value."]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-17T21:39:31.975108Z","iopub.status.busy":"2021-06-17T21:39:31.974457Z","iopub.status.idle":"2021-06-17T21:39:31.985853Z","shell.execute_reply":"2021-06-17T21:39:31.984705Z","shell.execute_reply.started":"2021-06-17T21:39:31.975074Z"},"trusted":true},"outputs":[],"source":["source = list('notebook')\n","# Add an element at position-0\n","source.insert(0, 'a')\n","print(source)\n","\n","# pop position-0, add a list element\n","# Note here append creates a nested list\n","source.pop(0)\n","source.append(list('notebook'))\n","print(source)\n","\n","# pop last position then merge two lists\n","source.pop()\n","source.extend(list('notebook'))\n","print(source)\n","\n","# built-in sort method\n","source.sort()\n","print(source)\n","print(source.count('o'))"]},{"cell_type":"markdown","metadata":{},"source":["The following example demonstrates functions to convert a string to a list, and vice versa."]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-17T22:22:05.968026Z","iopub.status.busy":"2021-06-17T22:22:05.967639Z","iopub.status.idle":"2021-06-17T22:22:05.976324Z","shell.execute_reply":"2021-06-17T22:22:05.975486Z","shell.execute_reply.started":"2021-06-17T22:22:05.967995Z"},"trusted":true},"outputs":[],"source":["string = \"Python\"\n","L = list(string)\n","print(L)\n","\n","S1 = \"\".join(L)\n","S2 = \",\".join(L)\n","print(S1)\n","print(S2)\n","\n","list_string = str(L)\n","print(list_string)"]},{"cell_type":"markdown","metadata":{},"source":["# Mutable and Immutable Variable Types\n","\n","List type is also different from most of the previous variable types we have learned in the course in one important way. In Python, list type has an additional property to be mutable, while the variable types including int, float, bool, and string are immutable. In the rest of this lecture, let us familiarize ourselves about this distinction.\n","\n","In Python, we have said that each variable represents a class-type object stored in computer memory, and the object's class encapsulates both data and methods applied on the data. The use of class methods will be more clear when we formally introduce the OOP and classes. For the discussion about mutable versus immutable properties, we shall focus on the object's data.\n","\n","In particular, after an object is created and its memory allocated by Python, an **immutable** variable type will not permit the data to be updated later. For a **mutable** variable type, its data can be modified while the allocation of the object memory is preserved..\n","\n","To illustrate this distinction, let us introduce a Python function *id()*. The function takes a variable pointing to an object in the memory as its input, and return a unique reference to the said memory address. The Python standards guarantee for any Python implementation to provide unique ID numbers for unique objects in the memory. However, it is not guaranteed that the ID number is a valid memory address. The exact choice of the unique ID number is left for the language software to implement. \n","\n","In the following code block, we will examine the properties of mutability when different variables may represent the same or different class objects in Python:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-18T09:00:58.10331Z","iopub.status.busy":"2021-06-18T09:00:58.102752Z","iopub.status.idle":"2021-06-18T09:00:58.151269Z","shell.execute_reply":"2021-06-18T09:00:58.149588Z","shell.execute_reply.started":"2021-06-18T09:00:58.103183Z"},"trusted":true},"outputs":[],"source":["a = 10\n","print(id(a))\n","a = 32759680\n","print(id(a))\n","b = ['i','m','m','u','t','a','b','l','e']\n","print(b, id(b))\n","b.pop(0); b.pop(0)\n","print(b, id(b))\n","b = \"immutable\"\n","b[0] = \"a\""]},{"cell_type":"markdown","metadata":{},"source":["The above code block examines the mutability of three variable types when the code changes their value. First, *int* type is immutable. Therefore, updating variable *a*'s value leads to Python allocating two different memory locations for two integer values. Second, *list* type is mutable. Therefore, even when we pop the first two elements from the list ['i','m','m','u','t','a','b','l','e'] to ['m','u','t','a','b','l','e'], we can check that the variable *b*'s ID number remains the same. Finally, *string* is immutable. Therefore, any attempt to change a string's individual character in the array will lead to a runtime error."]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-18T09:09:27.53464Z","iopub.status.busy":"2021-06-18T09:09:27.534159Z","iopub.status.idle":"2021-06-18T09:09:27.557906Z","shell.execute_reply":"2021-06-18T09:09:27.555766Z","shell.execute_reply.started":"2021-06-18T09:09:27.534593Z"},"trusted":true},"outputs":[],"source":["# Two small integers of the same value\n","a = 10\n","b = 10\n","print('small int: ', id(a), id(b), id(a)==id(b))\n","\n","# Two large integers of the same value\n","a = 32759680\n","b = 32759680\n","print('large int: ', id(a), id(b), id(a)==id(b))\n","\n","# Two floats of the same value\n","a = 10.1\n","b = 10.1\n","print('float: ', id(a), id(b), id(a)==id(b))\n","\n","# Two short strings of the same value\n","a = 'immutable'\n","b = 'immutable'\n","print('short str: ', id(a), id(b), id(a)==id(b))\n","\n","# Two long strings of the same value\n","a = 'immutable'*1000\n","b = 'immutable'*1000\n","print('long str: ', id(a), id(b), id(a)==id(b))\n","\n","# Two lists of the same value\n","a = ['m', 'u', 't', 'a', 'b', 'l', 'e']\n","b = ['m', 'u', 't', 'a', 'b', 'l', 'e']\n","print('list: ', id(a), id(b), id(a)==id(b))\n","\n","# Forcing one variable to be equal to the other\n","b = a\n","print('identified lists: ', id(a), id(b), id(a)==id(b))\n","a.clear()\n","print(a, b)\n","\n","# Creating duplicate objects that have different IDs\n","# Then modifying one varible will not be reflected on the second\n","b = a.copy()\n","print('copied lists: ', id(a), id(b), id(a)==id(b))\n","a.insert(0, 'new')\n","print(a, b)"]},{"cell_type":"markdown","metadata":{},"source":["Now let us consider a more subtle question: Previously, when two variable values or types are different, two distinct objects will be created by Python. Hence, their IDs and memory addresses will be different. However, consider the following question: *When two variables represent the same type and same value, are they referencing the same object or different objects in the memory?*\n","\n","The answer to this is not that straightforward. Please carefully review the above updated code block:\n","1. When †wo variables are small immutable integers, Python is smart enough to point the two variables to the same object in the memory. Hence their IDs are identical.\n","2. However, this conclusion cannot be blindly extended to consider large integers and float numbers, because searching for existing integer or float objects costs time. So a software cannot do that for an arbitrary range of integers and float. So in the second and third examples, we see that for two variables of identical large integer value and float value, their IDs are not the same.\n","3. The same argument also applies to the immutable string type. In the two subsequent examples, short strings of the same sequence \"immutable\" are assigned the same ID. However, two strings of the same long sequences are assigned different IDs. \n","4. For the next variation, we consider two mutable list variables of the same value. Since mutable variables can be modified after creation, even if their initial values are assigned to be the same, Python will allocate different IDs and different memory addresses to store the variable values.\n","5. Nevertheless, for all the above situations, there is an approach to force Python to identify two variable names to reference the same object in the memory, that is to assign one variable to be equal to the other, as in the example *b = a*. In such a case, regardless of the mutability type, the two variables will return the same ID number. Furthermore, since the two variables are referencing the same object, it causes the situation that modifying the first mutable variable will be reflected also on the second variable.\n","6. Finally, for mutable variables, we can use a common built-in method *copy()* to tell Python to create two objects but copy the data from one object to the other. In such cases, the two variables will be initially of the same value just like the case above. However, the two objects have different memory addresses, later modifying one variable will not affect the object referenced by the second variable.\n","\n","The reader may wonder what ultimately let a Python programming software to determine when to identify two objects of the same value in the memory. We caution that except in the case of explicitly identifying two variables as in the example of *b = a*, practitioners should not assume that two objects of the same value are identical in memory. The choice of doing so is entirely for implementation optimization considerations, and it is up to the software designer to decide. In other words, if we want two variables to be identical, we should explicitly proclaim *b = a*. In other cases we should not imply in any way that two variables are the same object."]},{"cell_type":"markdown","metadata":{},"source":["# Summary\n","\n","* A list type variable stores an ordered sequence of variables, whose types can be different.\n","* A list element can be addressed by using a pair of square brackets: l[index]. Elements in a list of lists can be addressed recursively as: l[index0][index1]\n","* list is a class with built-in methods: clear(), insert(), pop(), append(), extend(), sort(), count().\n","* list is a mutable variable type. Earlier other variable types are immutable, including int, float, string, bool.\n","* id() function can be conveniently used to compare the assigned memory address of a variable. In particular, if two variables share the same ID, then they point to the same content in memory; if a variable after updating its value keeps the same ID, then it is a mutable variable type.\n","* list() casts an input argument to a list variable output."]},{"cell_type":"markdown","metadata":{},"source":["# Exercises\n","\n","1. Given a list variable: a = [2, 9, 1, \"2\", \"s\", 6], write a single line of code to sort the variable using the built-in sort() function. Explain the result.\n","\n","2. Write a code to type cast a string \"immutable\" into a list type variable, called input_list. Then remove the first two elements in input_list using the built-in method pop(). Finally, type cast the resulting input_list back to a string and print out.\n","\n","3. Continue with the above program. Now starting from a string \"mutable\", please type cast the variable to a list type, then add two new elements \"m\" and \"i\" one by one to the head of the list. Finally, type cast the resulting list back to a string and print out. Hint: The result should be \"immutable\".\n","\n","4. *sorted()* is a useful built-in function that can sort either a list or a string as input. Write a code to demonstrate of the output result of the function when the input argument is a list or a string.\n","\n","5. Debug"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["a = \"a test string\"\n","\n","a_list = list(a)\n","print(a_list.sort())\n","\n","# Please use the follow sorted() approach to again sort a string\n","# a_result = sorted(a)"]},{"cell_type":"markdown","metadata":{},"source":["6. Debug"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-07-20T18:46:57.546529Z","iopub.status.busy":"2021-07-20T18:46:57.546166Z","iopub.status.idle":"2021-07-20T18:46:57.552343Z","shell.execute_reply":"2021-07-20T18:46:57.551479Z","shell.execute_reply.started":"2021-07-20T18:46:57.546496Z"},"trusted":true},"outputs":[],"source":["# Please concatenate two strings from the elements of two lists\n","List = ['abcde']\n","List = List + list('fghij')\n","print(List)"]},{"cell_type":"markdown","metadata":{},"source":["# Challenges\n","\n","1. Create a list of lists with the variable name L. The first element of L is itself a list [1, 2], and the second element of L is another list ['a', 'b']. Then, swap the elements between L[0][1] and L[1][0]. Please note that a swap should be coded as a generic operation so as to not hard set the given values of L.\n","\n","2. Modify the above program: Use id() function to print out the ID of variable L before and after the swap operation. Observe that the ID numbers remain the same, and discuss the reason why.\n","\n","3. Further modify the above program: Use id() function to print out the ID values of elements L[0][1] and L[1][0] before and after the swap. Observe the change on the ID numbers and discuss the reason why."]}],"metadata":{"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":30646,"isGpuEnabled":false,"isInternetEnabled":true,"language":"python","sourceType":"notebook"},"kernelspec":{"display_name":"Python 3","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.10.13"}},"nbformat":4,"nbformat_minor":4} +{"cells":[{"cell_type":"markdown","metadata":{},"source":["**Learning Python -- The Programming Language for Artificial Intelligence and Data Science**\n","\n","**Lecture 4: Lists**\n","\n","**By Allen Y. Yang, PhD**\n","\n","(c) Copyright Intelligent Racing Inc., 2021-2024. All rights reserved. Materials may NOT be distributed or used for any commercial purposes."]},{"cell_type":"markdown","metadata":{},"source":["# Keywords\n","\n","* **list**: The keyword for the list data type in Python.\n","* **Method**: In object-oriented programming languages such as Python, a method is a built-in function encaptulated together with the variable data.\n","* **Immutable data type**: A variable of an immutable data type cannot modify the stored data value at the referenced data address. Assigning a new value to the same variable leads to the variable pointing to a new memory address with the new value.\n","* **Mutable data type**: A variable of a mutable data type can modify the stored data value while keeping the same data address."]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2022-02-11T02:47:47.636854Z","iopub.status.busy":"2022-02-11T02:47:47.636363Z","iopub.status.idle":"2022-02-11T02:47:47.644028Z","shell.execute_reply":"2022-02-11T02:47:47.642681Z","shell.execute_reply.started":"2022-02-11T02:47:47.636815Z"},"trusted":true},"outputs":[],"source":["a = 4990\n","b = 4990\n","print(id(a)==id(b))"]},{"cell_type":"markdown","metadata":{},"source":["# Definition and Indexing\n","\n","As a string stores an array of characters, the *list* type in Python stores an ordered sequence of variables. Most notable is that fact that the variables in a list can be of different types. Let us see some examples below:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-17T21:28:08.085922Z","iopub.status.busy":"2021-06-17T21:28:08.085269Z","iopub.status.idle":"2021-06-17T21:28:08.094144Z","shell.execute_reply":"2021-06-17T21:28:08.092412Z","shell.execute_reply.started":"2021-06-17T21:28:08.085867Z"},"trusted":true},"outputs":[],"source":["l1 = []\n","l2 = ['a', 'b', 5]\n","l3 = [l1, l2, 5]\n","print(l3)"]},{"cell_type":"markdown","metadata":{},"source":["In the above code block, a list is defined by enumerating its elements contained within a pair of square brackets. A pair of empty brackets define an empty list (of length zero). The second list variable *l2* is formed from a list of mixed variables, namely, two string types and one integer type. \n","\n","More interestingly, a list may contain other lists as its elements. In the third example, *l3* contains first element as l1, second element as l2, and third element an integer.\n","\n","Next, we see how to address the elements in a list."]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-17T21:28:38.028192Z","iopub.status.busy":"2021-06-17T21:28:38.027807Z","iopub.status.idle":"2021-06-17T21:28:38.035014Z","shell.execute_reply":"2021-06-17T21:28:38.03375Z","shell.execute_reply.started":"2021-06-17T21:28:38.028155Z"},"trusted":true},"outputs":[],"source":["L = [['a', 'b'], 5, 'c']\n","print(L[0])\n","print(L[0][1])\n","print(L[-1])"]},{"cell_type":"markdown","metadata":{},"source":["Similar to the string type, elements in a list can be addressed by their indexes. In the first example above, the first element in L has offset zero, so *L[0]* retrieves the first element, which itself is a list.\n","\n","Since *L[0]* is a list, we can recursively address the elements in this list using the same square brackets. In the second example, *L[0][1]* references the second element of *L[0]*, which is 'b'.\n","\n","The third example shows that a list can also be indexed from the end to the beginning by negative index values. For example, the last element of *L* also can be addressed by index -1."]},{"cell_type":"code","execution_count":1,"metadata":{"execution":{"iopub.execute_input":"2024-02-25T04:17:29.969490Z","iopub.status.busy":"2024-02-25T04:17:29.968691Z","iopub.status.idle":"2024-02-25T04:17:30.267027Z","shell.execute_reply":"2024-02-25T04:17:30.265603Z","shell.execute_reply.started":"2024-02-25T04:17:29.969414Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["Slicing method: Elapsed time: 0.08 seconds\n","Built-in class method: Elapsed time: 0.01 seconds\n"]}],"source":["\"\"\"\n","In this sample code, we program an algorithm to reverse a list. Specifically, we will implement two methods and compare their time difference\n","The code shows a standard way to measure time to compare how fast an algorithm can run, using the time module\n","\"\"\"\n","\n","import time\n","\n","test_list = list('Python'*1000000) # Using \"*\" to duplicate a string 1,000,000 times\n","\n","# Method 1: Slicing operator\n","tic = time.time() # In practice, tic is a jargon referring the begin clock time\n","reverse_list = test_list[::-1]\n","toc = time.time() # toc is a jargon referring the end clock time in the unit of second\n","elapsed_time = toc - tic\n","print('Slicing method: Elapsed time: %.2f seconds' % elapsed_time )\n","\n","\n","# Method 2: Built-in object method\n","tic = time.time() \n","test_list.reverse() # a list type is a class in Python, which contains its own built-in functions\n","toc = time.time() \n","elapsed_time = toc - tic\n","print('Built-in class method: Elapsed time: {time:.2f} seconds'.format(time = elapsed_time) )"]},{"cell_type":"markdown","metadata":{},"source":["A good quality of an algorithm is measured by how fast it can run compared to other algorithms achieving the same results. In the above code block, we introduce a standard procedure to count the elapsed time, using the module function *time.time()*. The function returns a float value in the unit of seconds, which means the fraction part indicates sub-second accuracies. The function retrieves the current computer system clock, and reports a difference with the \"beginning of time\". In modern programming languages such as Python, the beginning of time with respect to modern computer systems is set to January 1, 1970, 00:00:00 (UTC). \n","\n","If we have designed an algorithm, when we retrieve *time.time()* before the execution as *tic*, and again after the execution as *toc*, then the difference is the elapsed time. This is a very useful measure of algorithm speed that the reader should learn to use fluently.\n","\n","Next let us talk about how the elapsed time is reported in a *print()* function. Since when we code the printed string format, it is not known to us about the value of elapsed_time. The exact value has to be formatted at runtime (meaning, when the code is actually running by the computer's processor). Python provides two basic ways to combine pre-defined constant string and dynamic argument together at runtime:\n","\n","1. The way inherited from older versions of Python is to use the \"%\" symbol to indicate an argument input to a string, which should be determined at runtime. One example is shown in line 15. In this example \".2f\" means displaying the string argument input as a floating point number with 2 digits after the decimal point.\n","2. The modern way recommended by Python 3 standards is to use a built-in function for any string type variables, as shown in line 23. The built-in function is called *format()*. Python will insert one or more arguments provided in *format()* function and convert them as part of the string as indicated by the reserved symbols {}. In this modern way, the displayed text still can be formatted by user specifications, such as \".2f\" in the example\n","\n","The new concept of owning built-in functions for different variable types is a benefit of so-called object-oriented programming (OOP). We will talk about OOP in the last part of the course, but previously we have noticed that all Python variable types are actually classes. A class in OOP languages contains data *and* methods that directly apply to the data (Note that in OOP, built-in functions in a class are often called **methods** to differentiate with regular functions that are not tightly associated with a class object). In other words, when a programmer creates a class, they not only have prepared a memory storage of the data, but also have prepared a list of functions that are properly coded to process the data. This is one of the benefits of using OOP.\n","\n","In the above sample code, we see that if the variable is a list, it also has a built-in method to perform exactly the task of reversing the list. Thanks to this built-in method, the operation can be coded in just one line built-in method call in line 20.\n","\n","Finally, the difference in algorithm design between the two used methods is that *list.reverse()* method is an \"in-place\" sorting algorithm, while slicing requires doubling the memory space to traverse from the list source. For more details, please be sure to watch the lecture video."]},{"cell_type":"markdown","metadata":{},"source":["# List Methods\n","\n","In Python and many object-oriented programming languages, a method refers to a built-in function encaptulated together with the variable data, when the variable is an object type. In Python, all variable types are objects. Therefore, each variable comes with its list of encaptulated methods. In this section, let us consider several some useful methods built in to the list type. \n","\n"," * clear(): Removes all list elements and causes the list to be empty.\n"," * insert(pos, element): Add an *element* at the *pos* position. If the position is greater than the current last position, then it will be added behind the current last element.\n"," * pop(pos): Remove and return the element at *pos* position. The argument can be empty, then the default position is the last one.\n"," * append(element): Add *element* from the end, and the length of the list is added by one.\n"," * extend(new_list): Add all elements from *new_list* to the end of the current list.\n"," * sort(): Sorts the list elements.\n"," * count(element): Counts the number of appearances of a specific element value."]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-17T21:39:31.975108Z","iopub.status.busy":"2021-06-17T21:39:31.974457Z","iopub.status.idle":"2021-06-17T21:39:31.985853Z","shell.execute_reply":"2021-06-17T21:39:31.984705Z","shell.execute_reply.started":"2021-06-17T21:39:31.975074Z"},"trusted":true},"outputs":[],"source":["source = list('notebook')\n","# Add an element at position-0\n","source.insert(0, 'a')\n","print(source)\n","\n","# pop position-0, add a list element\n","# Note here append creates a nested list\n","source.pop(0)\n","source.append(list('notebook'))\n","print(source)\n","\n","# pop last position then merge two lists\n","source.pop()\n","source.extend(list('notebook'))\n","print(source)\n","\n","# built-in sort method\n","source.sort()\n","print(source)\n","print(source.count('o'))"]},{"cell_type":"markdown","metadata":{},"source":["The following example demonstrates functions to convert a string to a list, and vice versa."]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-17T22:22:05.968026Z","iopub.status.busy":"2021-06-17T22:22:05.967639Z","iopub.status.idle":"2021-06-17T22:22:05.976324Z","shell.execute_reply":"2021-06-17T22:22:05.975486Z","shell.execute_reply.started":"2021-06-17T22:22:05.967995Z"},"trusted":true},"outputs":[],"source":["string = \"Python\"\n","L = list(string)\n","print(L)\n","\n","S1 = \"\".join(L)\n","S2 = \",\".join(L)\n","print(S1)\n","print(S2)\n","\n","list_string = str(L)\n","print(list_string)"]},{"cell_type":"markdown","metadata":{},"source":["# Mutable and Immutable Variable Types\n","\n","List type is also different from most of the previous variable types we have learned in the course in one important way. In Python, list type has an additional property to be mutable, while the variable types including int, float, bool, and string are immutable. In the rest of this lecture, let us familiarize ourselves about this distinction.\n","\n","In Python, we have said that each variable represents a class-type object stored in computer memory, and the object's class encapsulates both data and methods applied on the data. The use of class methods will be more clear when we formally introduce the OOP and classes. For the discussion about mutable versus immutable properties, we shall focus on the object's data.\n","\n","In particular, after an object is created and its memory allocated by Python, an **immutable** variable type will not permit the data to be updated later. For a **mutable** variable type, its data can be modified while the allocation of the object memory is preserved..\n","\n","To illustrate this distinction, let us introduce a Python function *id()*. The function takes a variable pointing to an object in the memory as its input, and return a unique reference to the said memory address. The Python standards guarantee for any Python implementation to provide unique ID numbers for unique objects in the memory. However, it is not guaranteed that the ID number is a valid memory address. The exact choice of the unique ID number is left for the language software to implement. \n","\n","In the following code block, we will examine the properties of mutability when different variables may represent the same or different class objects in Python:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-18T09:00:58.10331Z","iopub.status.busy":"2021-06-18T09:00:58.102752Z","iopub.status.idle":"2021-06-18T09:00:58.151269Z","shell.execute_reply":"2021-06-18T09:00:58.149588Z","shell.execute_reply.started":"2021-06-18T09:00:58.103183Z"},"trusted":true},"outputs":[],"source":["a = 10\n","print(id(a))\n","a = 32759680\n","print(id(a))\n","b = ['i','m','m','u','t','a','b','l','e']\n","print(b, id(b))\n","b.pop(0); b.pop(0)\n","print(b, id(b))\n","b = \"immutable\"\n","b[0] = \"a\""]},{"cell_type":"markdown","metadata":{},"source":["The above code block examines the mutability of three variable types when the code changes their value. First, *int* type is immutable. Therefore, updating variable *a*'s value leads to Python allocating two different memory locations for two integer values. Second, *list* type is mutable. Therefore, even when we pop the first two elements from the list ['i','m','m','u','t','a','b','l','e'] to ['m','u','t','a','b','l','e'], we can check that the variable *b*'s ID number remains the same. Finally, *string* is immutable. Therefore, any attempt to change a string's individual character in the array will lead to a runtime error."]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-18T09:09:27.53464Z","iopub.status.busy":"2021-06-18T09:09:27.534159Z","iopub.status.idle":"2021-06-18T09:09:27.557906Z","shell.execute_reply":"2021-06-18T09:09:27.555766Z","shell.execute_reply.started":"2021-06-18T09:09:27.534593Z"},"trusted":true},"outputs":[],"source":["# Two small integers of the same value\n","a = 10\n","b = 10\n","print('small int: ', id(a), id(b), id(a)==id(b))\n","\n","# Two large integers of the same value\n","a = 32759680\n","b = 32759680\n","print('large int: ', id(a), id(b), id(a)==id(b))\n","\n","# Two floats of the same value\n","a = 10.1\n","b = 10.1\n","print('float: ', id(a), id(b), id(a)==id(b))\n","\n","# Two short strings of the same value\n","a = 'immutable'\n","b = 'immutable'\n","print('short str: ', id(a), id(b), id(a)==id(b))\n","\n","# Two long strings of the same value\n","a = 'immutable'*1000\n","b = 'immutable'*1000\n","print('long str: ', id(a), id(b), id(a)==id(b))\n","\n","# Two lists of the same value\n","a = ['m', 'u', 't', 'a', 'b', 'l', 'e']\n","b = ['m', 'u', 't', 'a', 'b', 'l', 'e']\n","print('list: ', id(a), id(b), id(a)==id(b))\n","\n","# Forcing one variable to be equal to the other\n","b = a\n","print('identified lists: ', id(a), id(b), id(a)==id(b))\n","a.clear()\n","print(a, b)\n","\n","# Creating duplicate objects that have different IDs\n","# Then modifying one varible will not be reflected on the second\n","b = a.copy()\n","print('copied lists: ', id(a), id(b), id(a)==id(b))\n","a.insert(0, 'new')\n","print(a, b)"]},{"cell_type":"markdown","metadata":{},"source":["Now let us consider a more subtle question: Previously, when two variable values or types are different, two distinct objects will be created by Python. Hence, their IDs and memory addresses will be different. However, consider the following question: *When two variables represent the same type and same value, are they referencing the same object or different objects in the memory?*\n","\n","The answer to this is not that straightforward. Please carefully review the above updated code block:\n","1. When †wo variables are small immutable integers, Python is smart enough to point the two variables to the same object in the memory. Hence their IDs are identical.\n","2. However, this conclusion cannot be blindly extended to consider large integers and float numbers, because searching for existing integer or float objects costs time. So a software cannot do that for an arbitrary range of integers and float. So in the second and third examples, we see that for two variables of identical large integer value and float value, their IDs are not the same.\n","3. The same argument also applies to the immutable string type. In the two subsequent examples, short strings of the same sequence \"immutable\" are assigned the same ID. However, two strings of the same long sequences are assigned different IDs. \n","4. For the next variation, we consider two mutable list variables of the same value. Since mutable variables can be modified after creation, even if their initial values are assigned to be the same, Python will allocate different IDs and different memory addresses to store the variable values.\n","5. Nevertheless, for all the above situations, there is an approach to force Python to identify two variable names to reference the same object in the memory, that is to assign one variable to be equal to the other, as in the example *b = a*. In such a case, regardless of the mutability type, the two variables will return the same ID number. Furthermore, since the two variables are referencing the same object, it causes the situation that modifying the first mutable variable will be reflected also on the second variable.\n","6. Finally, for mutable variables, we can use a common built-in method *copy()* to tell Python to create two objects but copy the data from one object to the other. In such cases, the two variables will be initially of the same value just like the case above. However, the two objects have different memory addresses, later modifying one variable will not affect the object referenced by the second variable.\n","\n","The reader may wonder what ultimately let a Python programming software to determine when to identify two objects of the same value in the memory. We caution that except in the case of explicitly identifying two variables as in the example of *b = a*, practitioners should not assume that two objects of the same value are identical in memory. The choice of doing so is entirely for implementation optimization considerations, and it is up to the software designer to decide. In other words, if we want two variables to be identical, we should explicitly proclaim *b = a*. In other cases we should not imply in any way that two variables are the same object."]},{"cell_type":"markdown","metadata":{},"source":["# Summary\n","\n","* A list type variable stores an ordered sequence of variables, whose types can be different.\n","* A list element can be addressed by using a pair of square brackets: l[index]. Elements in a list of lists can be addressed recursively as: l[index0][index1]\n","* list is a class with built-in methods: clear(), insert(), pop(), append(), extend(), sort(), count().\n","* list is a mutable variable type. Earlier other variable types are immutable, including int, float, string, bool.\n","* id() function can be conveniently used to compare the assigned memory address of a variable. In particular, if two variables share the same ID, then they point to the same content in memory; if a variable after updating its value keeps the same ID, then it is a mutable variable type.\n","* list() casts an input argument to a list variable output."]},{"cell_type":"markdown","metadata":{},"source":["# Exercises\n","\n","1. Given a list variable: a = [2, 9, 1, \"2\", \"s\", 6], write a single line of code to sort the variable using the built-in sort() function. Explain the result.\n","\n","2. Write a code to type cast a string \"immutable\" into a list type variable, called input_list. Then remove the first two elements in input_list using the built-in method pop(). Finally, type cast the resulting input_list back to a string and print out.\n","\n","3. Continue with the above program. Now starting from a string \"mutable\", please type cast the variable to a list type, then add two new elements \"m\" and \"i\" one by one to the head of the list. Finally, type cast the resulting list back to a string and print out. Hint: The result should be \"immutable\".\n","\n","4. *sorted()* is a useful built-in function that can sort either a list or a string as input. Write a code to demonstrate of the output result of the function when the input argument is a list or a string.\n","\n","5. Debug"]},{"cell_type":"code","execution_count":10,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["['1', '2', '2', '6', '9', 's']\n","mutable\n","immutable\n"]}],"source":["a = [2, 9, 1, \"2\", \"s\", 6]\n","a = [str(b) for b in a]\n","a.sort()\n","print(a)\n","#Q2\n","string = \"immutable\"\n","input_list = list(string)\n","input_list.pop(0); input_list.pop(0)\n","print(\"\".join(input_list))\n","#Q3\n","input_list.insert(0,\"m\")\n","input_list.insert(0,\"i\")\n","print(\"\".join(input_list))"]},{"cell_type":"code","execution_count":14,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["['a', 'b', 'c', 'c', 'd', 'g']\n"]}],"source":["#Q4\n","strlist = [\"b\", \"a\", \"c\", \"c\", \"d\", \"g\"]\n","intlist = [1,2,6,6,4,3,9,0]\n","print(sorted(strlist))\n","print(sorted(intlist))"]},{"cell_type":"code","execution_count":29,"metadata":{"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["[' ', ' ', 'a', 'e', 'g', 'i', 'n', 'r', 's', 's', 't', 't', 't']\n"]}],"source":["\"\"\"Q5 DEBUG\"\"\"\n","a = \"a test string\"\n","\n","a_result = sorted(a)\n","print(a_result)\n","\n","# Please use the follow sorted() approach to again sort a string\n","# a_result = sorted(a)"]},{"cell_type":"markdown","metadata":{},"source":["6. Debug"]},{"cell_type":"code","execution_count":18,"metadata":{"execution":{"iopub.execute_input":"2021-07-20T18:46:57.546529Z","iopub.status.busy":"2021-07-20T18:46:57.546166Z","iopub.status.idle":"2021-07-20T18:46:57.552343Z","shell.execute_reply":"2021-07-20T18:46:57.551479Z","shell.execute_reply.started":"2021-07-20T18:46:57.546496Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["abcdefghij\n"]}],"source":["# Please concatenate two strings from the elements of two lists\n","List = ['abcde']\n","List = List + (list('fghij'))\n","string = \"\".join(List)\n","print(string)"]},{"cell_type":"markdown","metadata":{},"source":["# Challenges\n","\n","1. Create a list of lists with the variable name L. The first element of L is itself a list [1, 2], and the second element of L is another list ['a', 'b']. Then, swap the elements between L[0][1] and L[1][0]. Please note that a swap should be coded as a generic operation so as to not hard set the given values of L.\n","\n","2. Modify the above program: Use id() function to print out the ID of variable L before and after the swap operation. Observe that the ID numbers remain the same, and discuss the reason why.\n","\n","3. Further modify the above program: Use id() function to print out the ID values of elements L[0][1] and L[1][0] before and after the swap. Observe the change on the ID numbers and discuss the reason why."]},{"cell_type":"code","execution_count":27,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["4428352960\n","[[1, 'a'], ['a', 'b']]\n","[[1, 'a'], [2, 'b']]\n","4428352960\n","4369382192\n","4368247120\n"]}],"source":["#Q1\n","\n","L = [[1,2],[\"a\",\"b\"]]\n","print(id(L))\n","\n","print(id(L[0][1]))\n","print(id(L[1][0]))\n","\n","a= L[0][1]\n","L[0][1]=L[1][0]\n","print(L)\n","L[1][0]=a\n","print(L)\n","\n","#Q2\n","print(id(L))\n","#ID nums remain same because small\n","\n","\n","#Q3\n","#they change because they are two seperate different things\n","print(id(L[0][1]))\n","print(id(L[1][0]))"]}],"metadata":{"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":30646,"isGpuEnabled":false,"isInternetEnabled":true,"language":"python","sourceType":"notebook"},"kernelspec":{"display_name":"Python 3","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.18"}},"nbformat":4,"nbformat_minor":4} diff --git a/Part One/1-5-conditions-and-loops.ipynb b/Part One/1-5-conditions-and-loops.ipynb index 38cc357..ef54774 100644 --- a/Part One/1-5-conditions-and-loops.ipynb +++ b/Part One/1-5-conditions-and-loops.ipynb @@ -1 +1 @@ -{"cells":[{"cell_type":"markdown","metadata":{},"source":["**Learning Python -- The Programming Language for Artificial Intelligence and Data Science**\n","\n","**Lecture 5: Conditions and Loops**\n","\n","**By Allen Y. Yang, PhD**\n","\n","(c) Copyright Intelligent Racing Inc., 2021-2024. All rights reserved. Materials may NOT be distributed or used for any commercial purposes."]},{"cell_type":"markdown","metadata":{},"source":["# Keywords\n","\n","* **Flow control statements**: A high-level language such as Python usually follows a sequential order to execute multiple lines of code. Exceptions are given to flow-control statements where the flow of the execution may be altered. Flow-control statements can be either selective (using if statements) or repetitive (using while and for statements)."]},{"cell_type":"markdown","metadata":{},"source":["# if-elif-else\n","\n","The *if* statement allows a program to deviate from a linear order of execution, and can choose to execute different code blocks based on the evaluation of the condition as defined in the if statement. \n","\n","The basic syntax of the *if* statement is as follows:\n","\n"," if CONDITION: \n"," BLOCK_1\n"," BLOCK_2\n"," \n","In this code structure, if the CONDITION expression is True, then BLOCK 1 will be executed; if False, then BLOCK_1 will be skipped. Then BLOCK_2 will be executed regardless of the *if* statement. Please note that the result of the CONDITION expression must be a boolean value.\n","\n","Another syntax involving the *if* statement is as follows:\n","\n"," if CONDITION: \n"," BLOCK_1\n"," else:\n"," BLOCK_2\n"," BLOCK_3\n"," \n","In this code structure, if the CONDITION expression is True, then BLOCK_1 will be executed but not BLOCK_2; however, if False, then BLOCK_2 will be executed but not BLOCK_1. This order matches the English meaning of the phrase *if - else -*. After that, BLOCK_3 will be executed regardless of the CONDITION value.\n","\n","In another syntax, an *if* code block can check a series of conditions sequentially. This is created by the use of the *elif* statement, a shorthand for *else if*:\n","\n"," if CONDITION_1: \n"," BLOCK_1\n"," elif CONDITION_2:\n"," BLOCK_2\n"," else:\n"," BLOCK_3\n"," BLOCK_4\n"," \n","Note that the above code is equivalent to the code below:\n","\n"," if CONDITION_1: \n"," BLOCK_1\n"," else:\n"," if CONDITION_2:\n"," BLOCK_2\n"," else:\n"," BLOCK_3\n"," BLOCK_4\n"," \n","First, let us consider the following sample code:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["\"\"\"\n","Rock-Paper-Scissors Example Code\n","\"\"\"\n","import random\n","\n","# Ask user to input R or P or S\n","print('Please input [R] Rock, [P] Paper, or [S] Scissors: ')\n","user_input = input()\n","user_input = user_input.upper() # convert lower case to upper case\n","\n","if user_input !='R' and user_input !='P' and user_input !='S': # Doing sanity test\n"," print(\"Input not supported\")\n","else:\n"," computer_input = random.choice(['R', 'P', 'S'])\n"," print('Computer input is ', computer_input)\n"," if computer_input == user_input:\n"," print(\"Draw!\")\n"," elif (computer_input=='R' and user_input == 'S') or \\\n"," (computer_input=='S' and user_input == 'P') or \\\n"," (computer_input=='P' and user_input == 'R'):\n"," print('You Lose!')\n"," else:\n"," print('You Win!')\n","\n"]},{"cell_type":"markdown","metadata":{},"source":["The code block implements a simple Rock-Paper-Scissors game, in which the user's guess input is acquired in between lines 7 and 9. In line 9, we use the string type's built-in method *upper()* to identify lower and upper cases of \"R\", \"P\", and \"S\".\n","\n","When a program requires to process user input, it is highly recommended that the program always check whether the user input is within the expected range of input values. In the Rock-Paper-Scissors game, if a user inputs text other than \"R\", \"P\", or \"S\", clearly the input should be rejected out right because responses to other values are not defined in the game. In line 11, we see the first use of the *if* statement:\n","\n"," if CONDITION: \n"," STATEMENTS\n","\n","Note that in the definition of the *if* statement, the CONDITION expression must be followed by a colon mark. This is because after the CONDITION expression and the colon sign, the code may include a block of multiple statements which will be either executed together or skipped together. Such statements who are bundled together are called a code block. \n","\n","In addition to the use of colon, a code block also must satisfy the requirement of indentation, namely, all statements in the same block (and hence will be executed sequentially) must start with the same size of indentation. Note that Python does not explicitly set the amount of indentation, as long as all indentation in one code block is consistent. Further, Python requires that the very first code block that is at the same level as the first statement of the code must not have any indentation.\n","\n","These requirements about the use of indentation to group statements into code blocks are quite unique in Python compared to many of its predecessors (such as C++ and Java). A beginner shall pay special attention to the proper use of colons and indentation.\n","\n","Next, we use print() function to illustrate the change of the code flow under the *if* statement."]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["print('Level-0 code block')\n","\n","if False:\n"," print('Level-1 code block')\n","else:\n"," print('Another level-1 code block')\n"," \n","print('Back to level-0 code block')"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["print('Level-0 code block')\n","\n","if False:\n"," print('Level-1 code block')\n","elif False:\n"," print('Another level-1 code block')\n","else:\n"," print('Yet another level-1 code block')\n","\n","print('Back to level-0 code block')"]},{"cell_type":"markdown","metadata":{},"source":["# while Loop\n","\n","The *while CONDITION* statement will check the CONDITION expression in a loop. As long as the CONDITION remains True, its subsequent code block will be executed. But the loop will terminate if and when the CONDITION becomes False\n","\n","Let us see the following sample code:"]},{"cell_type":"code","execution_count":1,"metadata":{"execution":{"iopub.execute_input":"2021-06-24T08:01:33.288843Z","iopub.status.busy":"2021-06-24T08:01:33.288330Z","iopub.status.idle":"2021-06-24T08:01:33.295374Z","shell.execute_reply":"2021-06-24T08:01:33.294375Z","shell.execute_reply.started":"2021-06-24T08:01:33.288809Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["nohtyP\n"]}],"source":["\"\"\"\n","Reverse a string using while loop\n","\"\"\"\n","\n","input_string = \"Python\"\n","reverse_string = \"\"\n","index = len(input_string)\n","while index>0:\n"," index = index - 1\n"," reverse_string = reverse_string + input_string[index]\n"," \n","print(reverse_string)\n"," "]},{"cell_type":"markdown","metadata":{},"source":["In this sample, the task is to create a new string that reverses the characters from input_string. Since a string can be addressed by its index, the while loop is defined based on an index variable that starts from the last element of the string (i.e., len(input_string) - 1), then iteratively reduces the index until zero (i.e., the first element). Note that since string is immutable, every execution of line 10 inside the loop will allocate a new string object for the updated reverse_string object, although their variable name remains the same."]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["\"\"\"\n","An infinite while loop with break\n","\"\"\"\n","\n","animal_list = ['dog', 'cat','fish','pony','parrot','leopard','frog','mouse','snake']\n","\n","index = 0\n","while True:\n"," print('Would you like to adopt a '+ animal_list[index%9]+ '? [Y/N]')\n"," answer = input()\n"," if answer.lower()=='y':\n"," break\n"," index += 1\n","\n","print('We will have your ' + animal_list[index%9] + ' ready for pick up!')"]},{"cell_type":"markdown","metadata":{},"source":["The above code block demonstrates a quite persistent salesperson who always wants to sell you something. As a result, the program set the while loop condition to always be True. In each loop, the program will query user's input to ask whether the user would like to adopt one of the animals from the *animal_list*. This construction is called an infinite loop. In an infinite loop, the result can be finite so long as a termination condition is being checked inside the while block. In the above example, the termination condition is whether the user answers \"Y\" or \"y\" for agreeing to adopt an animal. When inside a while loop, the loop can be terminated by calling the *break* function. Upon executing *break*, the flow of the code will jump to the next statement after the while block.\n","\n","Using *break* inside a while loop will cause the loop to abort without satisfying the while condition. In Python, a while loop can be coded to execute a block of code only if the loop exits normally, namely, the while condition is indeed equal to False. This is achieved by using the *while -- else* combination. We use this combination in the sample code below:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["\"\"\"\n","Test prime number\n","\"\"\"\n","import math\n","n = int(input()) # input an integer\n","\n","if n<2: \n"," print(n, 'is not a prime number')\n","elif n ==2: \n"," print (n, 'is a prime number')\n","else:\n"," x = 2\n"," while x<=math.sqrt(n) + 1:\n"," if n % x == 0: # n = x * y for some y\n"," print(n, 'is not a prime number')\n"," break\n"," else: \n"," x = x + 1\n","\n"," else:\n"," print(n, 'is a prime number')"]},{"cell_type":"markdown","metadata":{},"source":["In this sample code, the goal is to test whether a human input integer is a prime number. The algorithm is so designed to use a while loop to test integers from 2 to square root of n + 1 whether n can be evenly divided. If the condition is true, then n *is* a prime number, and the algorithm will report it and *break*. On the other hand, if the condition is not true for the entire while loop, then when exits normally the code will report that n is a prime number.\n","\n","Note that the *while -- else* combination is quite unique to Python. In other more traditional languages when the while loop could not be followed by an *else* condition, then a standard coding practice would rely on a boolean flag, such as *is_prime_flag* used in the following sample code:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["\"\"\"\n","Test prime number: Not relying on while -- else\n","\"\"\"\n","import math\n","n = int(input()) # input an integer\n","\n","if n<2: \n"," print(n, 'is not a prime number')\n","elif n ==2: \n"," print (n, 'is a prime number')\n","else:\n"," x = 2\n"," is_prime_flag = True\n"," while x<=math.sqrt(n) + 1:\n"," if n % x == 0: # n = x * y for some y\n"," is_prime_flag = False\n"," break\n"," else: \n"," x = x + 1\n","\n"," if is_prime_flag:\n"," print(n, 'is a prime number')\n"," else:\n"," print(n, 'is not a prime number')"]},{"cell_type":"markdown","metadata":{},"source":["# for Loop\n","\n","Another supported loop statement is the *for* loop. A for loop will need to define an index that will go over a sequence of pre-defined values. The sequence can be defined in two basic ways:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["for i in range(0, 10, 2): \n"," print(i, end = ' ')\n","\n","print() \n","loop_list = ['The', 'date', 'is', [2, 20, 2020]]\n","for i in loop_list: print(i, end = \" \")"]},{"cell_type":"markdown","metadata":{},"source":["In the first example above, the index sequence is defined by the function *range(begin, end, step)*. In the example, we see that *range(0, 10, 2)* contains five numbers: 0, 2, 4, 6, 8, and as in slicing, the end number will not be taken.\n","\n","In the second example, the index sequence is in fact a list. The for loop simply causes the loop index to enumerate the values of the elements from the list. \n","\n","At this point, we shall highlight one major difference when setting up a loop using *for* statement compared to *while*. The index sequence defined in the *for* loop is created at the beginning of the *for* loop. If during the *for* loop, however, the code explicitly changes either the index value or the index sequence elements, the changes will not alter the pre-set control flow. To illustrate this, let us see the example below:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["for_string = \"abcde\"\n","for i in for_string:\n"," for_string = \"uvwxyz\"\n"," print(i, end = \" \")\n"," i = 5\n"," print(i, end = \" \")"]},{"cell_type":"markdown","metadata":{},"source":["We see in the above example that neither changing the *for* loop index sequence nor changing the index value changes the flow control of the *for* loop. At the beginning, the *for* loop index is set to enumerate the five string elements \"a\", \"b\", \"c\", \"d\", \"e\". \n","\n","We also recall that, similar to the *while* loop, a *for* loop can be aborted from inside the loop using *break*.\n","\n","Finally, let us see the following sample code."]},{"cell_type":"code","execution_count":2,"metadata":{"execution":{"iopub.execute_input":"2021-06-24T17:38:32.577582Z","iopub.status.busy":"2021-06-24T17:38:32.577124Z","iopub.status.idle":"2021-06-24T17:38:49.518045Z","shell.execute_reply":"2021-06-24T17:38:49.517256Z","shell.execute_reply.started":"2021-06-24T17:38:32.577543Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["Would you like to adopt a dog? [Y/N] Y\n","Would you like to adopt a cat? [Y/N] N\n","Would you like to adopt a fish? [Y/N] Y\n","Would you like to adopt a pony? [Y/N] Y\n","Would you like to adopt a parrot? [Y/N] N\n","Would you like to adopt a leopard? [Y/N] N\n","Would you like to adopt a frog? [Y/N] N\n","Would you like to adopt a mouse? [Y/N] N\n","Would you like to adopt a snake? [Y/N] N\n"]},{"name":"stdout","output_type":"stream","text":["We will have your ['dog', 'fish', 'pony'] ready for pick up!\n"]}],"source":["animal_list = ['dog', 'cat','fish','pony','parrot','leopard','frog','mouse','snake']\n","\n","index = 0\n","adopted_animals=[]\n","for i in animal_list:\n"," answer = input('Would you like to adopt a '+ i + '? [Y/N]')\n"," if answer.lower()!='y':\n"," continue\n","\n"," adopted_animals.append(i)\n","else:\n"," if not adopted_animals:\n"," print('Your adoption list is empty. See you next time!')\n"," else:\n"," print('We will have your ' + str(adopted_animals) + ' ready for pick up!')"]},{"cell_type":"markdown","metadata":{},"source":["In this sample code, we re-program another salesperson strategy, whereby the code uses the *for* loop to only ask the user to adopt animals from the *animal_list* once. In the *for* loop, if the user input to any of the animal name is \"Y\" or \"y\", then the animal name will be appended to the *adopted_animals* list. In the code, we see the use of *for -- else*. It serves the same function, that if a *for* loop terminates normally, the *else* code block will be executed. Similarly, if the code aborts inside the *for* loop, then the *else* block will not be executed.\n","\n","In line 9, we see a new flow control statement, called **continue**. *continue* acts differently than *break* inside a loop. When the flow of the code meets *continue*, it will then skip the rest of the code in the loop block, and immediately start from the beginning of the next loop. For a *while* loop, this means re-testing the while condition; for a *for* loop, this means setting the loop index to the next pre-defined value from the index sequence."]},{"cell_type":"markdown","metadata":{},"source":["Finally, we again use the toy example of reversing a list to compare the time efficiency in Python between using built-in methods and using explicit for or while loops. From the resulting time difference, we can see that it is much more efficient to be able to directly use built-in methods versus re-implementing these methods using a Python code block."]},{"cell_type":"code","execution_count":7,"metadata":{"execution":{"iopub.execute_input":"2021-06-24T17:59:00.373750Z","iopub.status.busy":"2021-06-24T17:59:00.373361Z","iopub.status.idle":"2021-06-24T18:00:05.300071Z","shell.execute_reply":"2021-06-24T18:00:05.298978Z","shell.execute_reply.started":"2021-06-24T17:59:00.373718Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["WHILE LOOP REVERSE: Total elapsed time: 63.72 seconds\n","REVERSE() METHOD: Total elapsed time: 1.01 seconds\n","REVERSE() METHOD: Total elapsed time: 0.19 seconds\n"]}],"source":["# Compare time complexity of reversing a list using three methods and over 1 million repeats\n","import time\n","\n","List = list('abcdefghijklmnopqrstuvwxyz'*10)\n","repeat_time = 1000000 # The source list is not long, but we ask repeat 1 million times\n","\n","# Method 1: Reverse a list using a while loop code block\n","tic = time.time()\n","for i in range(repeat_time):\n"," reversed_list = []\n"," index = len(List)\n"," while index>0:\n"," index = index -1\n"," reversed_list.append(List[index])\n","toc = time.time()\n","elapsed_time = toc - tic\n","print(\"WHILE LOOP REVERSE: Total elapsed time: %.2f seconds\" % elapsed_time)\n","\n","# Method 2: Reverse a list using built-in reverse() method\n","tic = time.time()\n","for i in range(repeat_time):\n"," reversed_list = List.copy()\n"," reversed_list.reverse()\n","toc = time.time()\n","elapsed_time = toc - tic\n","print(\"REVERSE() METHOD: Total elapsed time: %.2f seconds\" % elapsed_time)\n","\n","# Method 3: Reverse a list using built-in reverse() method without copy() penalty\n","reversed_list = List.copy()\n","tic = time.time()\n","for i in range(repeat_time):\n"," reversed_list.reverse()\n","toc = time.time()\n","elapsed_time = toc - tic\n","print(\"REVERSE() METHOD: Total elapsed time: %.2f seconds\" % elapsed_time)"]},{"cell_type":"markdown","metadata":{},"source":["# Summary\n","\n","* if -- elif -- else statements can alter the linear order of code execution based on the imposed conditions.\n","* while loop creates a looped code block based on a condition, which is evaluated in the beginning of every loop.\n","* while loop condition can be always True. In such case, the condition to terminate the loop can be tested inside the loop and use another command: break.\n","* while -- else statements will execute the code block after else upon the while loop exits. The while code block may skip the else code block only by using break, which then will redirect the code flow to after the else code block.\n","* for loop creates a looped code block by enumerating through a sequence of loop index values. Most noticeable about the loop index is that the index sequence will be determined only at the time the loop is created. If the index value is changed by user inside the loop, it will still be reset to its deterministic sequence when a new loop starts.\n","* continue statement from inside a loop will skip the remaining code in the loop and redirect the flow to start a new loop at the beginning of the code block."]},{"cell_type":"markdown","metadata":{},"source":["# Exercises\n","\n","1. According to Wikipedia, the following pseudo-code is an algorithm to determine a *year* variable as a positive integer represents a leap year. Please write a program, which will receive an integer input from the user as the value of the *year* variable, and then determine if it is a leap year.\n","\n",">\n","\n"," if (year is not divisible by 4) then (it is a common year)\n"," else if (year is not divisible by 100) then (it is a leap year)\n"," else if (year is not divisible by 400) then (it is a common year)\n"," else (it is a leap year) \n"," \n","2. Please use the combination of *while* loop and time.time() function, to create a loop that will run for exactly 5 seconds. Hint: Inside the loop, the code block can use *pass* statement.\n","\n","3. Please modify Rock-Paper-Scissors Example Code in the lecture to add a score variable, namely, when the user wins, the score will increase by one point; when the user loses, the score will decrease by one point. Create a loop structure starting from the initial value score = 0, and continue running the Rock-Paper-Scissors game until the score reaches three (3).\n","\n","4. Please reverse a string variable using the *for* loop.\n","\n","5. Debug"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["for i in range(10)\n"," print(i)\n","else\n"," pass"]},{"cell_type":"markdown","metadata":{},"source":["# Challenges\n","\n","1. Using a list and a *for* loop to calculate and save the Fibonacci number for the first 10 nonnegative integers. The Fibonacci number is defined as:\n","> Fibonacci (n) = Fibonacci (n-1) + Fibonacci (n-2) for any n>1;\n",">\n","> Fibonacci(0) = 0; Fibonacci(1) = 1.\n","\n","2. Use for loop to print the following pattern for n layers. For example, when n = 4, the pattern is:\n","\n","> 1\n",">\n","> 1 1\n",">\n","> 1 2 1\n",">\n","> 1 3 3 1\n",">\n","> 1 4 6 4 1"]}],"metadata":{"kernelspec":{"display_name":"Python 3","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.7.6"}},"nbformat":4,"nbformat_minor":4} +{"cells":[{"cell_type":"markdown","metadata":{},"source":["**Learning Python -- The Programming Language for Artificial Intelligence and Data Science**\n","\n","**Lecture 5: Conditions and Loops**\n","\n","**By Allen Y. Yang, PhD**\n","\n","(c) Copyright Intelligent Racing Inc., 2021-2024. All rights reserved. Materials may NOT be distributed or used for any commercial purposes."]},{"cell_type":"markdown","metadata":{},"source":["# Keywords\n","\n","* **Flow control statements**: A high-level language such as Python usually follows a sequential order to execute multiple lines of code. Exceptions are given to flow-control statements where the flow of the execution may be altered. Flow-control statements can be either selective (using if statements) or repetitive (using while and for statements)."]},{"cell_type":"markdown","metadata":{},"source":["# if-elif-else\n","\n","The *if* statement allows a program to deviate from a linear order of execution, and can choose to execute different code blocks based on the evaluation of the condition as defined in the if statement. \n","\n","The basic syntax of the *if* statement is as follows:\n","\n"," if CONDITION: \n"," BLOCK_1\n"," BLOCK_2\n"," \n","In this code structure, if the CONDITION expression is True, then BLOCK 1 will be executed; if False, then BLOCK_1 will be skipped. Then BLOCK_2 will be executed regardless of the *if* statement. Please note that the result of the CONDITION expression must be a boolean value.\n","\n","Another syntax involving the *if* statement is as follows:\n","\n"," if CONDITION: \n"," BLOCK_1\n"," else:\n"," BLOCK_2\n"," BLOCK_3\n"," \n","In this code structure, if the CONDITION expression is True, then BLOCK_1 will be executed but not BLOCK_2; however, if False, then BLOCK_2 will be executed but not BLOCK_1. This order matches the English meaning of the phrase *if - else -*. After that, BLOCK_3 will be executed regardless of the CONDITION value.\n","\n","In another syntax, an *if* code block can check a series of conditions sequentially. This is created by the use of the *elif* statement, a shorthand for *else if*:\n","\n"," if CONDITION_1: \n"," BLOCK_1\n"," elif CONDITION_2:\n"," BLOCK_2\n"," else:\n"," BLOCK_3\n"," BLOCK_4\n"," \n","Note that the above code is equivalent to the code below:\n","\n"," if CONDITION_1: \n"," BLOCK_1\n"," else:\n"," if CONDITION_2:\n"," BLOCK_2\n"," else:\n"," BLOCK_3\n"," BLOCK_4\n"," \n","First, let us consider the following sample code:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["\"\"\"\n","Rock-Paper-Scissors Example Code\n","\"\"\"\n","import random\n","\n","# Ask user to input R or P or S\n","print('Please input [R] Rock, [P] Paper, or [S] Scissors: ')\n","user_input = input()\n","user_input = user_input.upper() # convert lower case to upper case\n","\n","if user_input !='R' and user_input !='P' and user_input !='S': # Doing sanity test\n"," print(\"Input not supported\")\n","else:\n"," computer_input = random.choice(['R', 'P', 'S'])\n"," print('Computer input is ', computer_input)\n"," if computer_input == user_input:\n"," print(\"Draw!\")\n"," elif (computer_input=='R' and user_input == 'S') or \\\n"," (computer_input=='S' and user_input == 'P') or \\\n"," (computer_input=='P' and user_input == 'R'):\n"," print('You Lose!')\n"," else:\n"," print('You Win!')\n","\n"]},{"cell_type":"markdown","metadata":{},"source":["The code block implements a simple Rock-Paper-Scissors game, in which the user's guess input is acquired in between lines 7 and 9. In line 9, we use the string type's built-in method *upper()* to identify lower and upper cases of \"R\", \"P\", and \"S\".\n","\n","When a program requires to process user input, it is highly recommended that the program always check whether the user input is within the expected range of input values. In the Rock-Paper-Scissors game, if a user inputs text other than \"R\", \"P\", or \"S\", clearly the input should be rejected out right because responses to other values are not defined in the game. In line 11, we see the first use of the *if* statement:\n","\n"," if CONDITION: \n"," STATEMENTS\n","\n","Note that in the definition of the *if* statement, the CONDITION expression must be followed by a colon mark. This is because after the CONDITION expression and the colon sign, the code may include a block of multiple statements which will be either executed together or skipped together. Such statements who are bundled together are called a code block. \n","\n","In addition to the use of colon, a code block also must satisfy the requirement of indentation, namely, all statements in the same block (and hence will be executed sequentially) must start with the same size of indentation. Note that Python does not explicitly set the amount of indentation, as long as all indentation in one code block is consistent. Further, Python requires that the very first code block that is at the same level as the first statement of the code must not have any indentation.\n","\n","These requirements about the use of indentation to group statements into code blocks are quite unique in Python compared to many of its predecessors (such as C++ and Java). A beginner shall pay special attention to the proper use of colons and indentation.\n","\n","Next, we use print() function to illustrate the change of the code flow under the *if* statement."]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["print('Level-0 code block')\n","\n","if False:\n"," print('Level-1 code block')\n","else:\n"," print('Another level-1 code block')\n"," \n","print('Back to level-0 code block')"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["print('Level-0 code block')\n","\n","if False:\n"," print('Level-1 code block')\n","elif False:\n"," print('Another level-1 code block')\n","else:\n"," print('Yet another level-1 code block')\n","\n","print('Back to level-0 code block')"]},{"cell_type":"markdown","metadata":{},"source":["# while Loop\n","\n","The *while CONDITION* statement will check the CONDITION expression in a loop. As long as the CONDITION remains True, its subsequent code block will be executed. But the loop will terminate if and when the CONDITION becomes False\n","\n","Let us see the following sample code:"]},{"cell_type":"code","execution_count":1,"metadata":{"execution":{"iopub.execute_input":"2021-06-24T08:01:33.288843Z","iopub.status.busy":"2021-06-24T08:01:33.288330Z","iopub.status.idle":"2021-06-24T08:01:33.295374Z","shell.execute_reply":"2021-06-24T08:01:33.294375Z","shell.execute_reply.started":"2021-06-24T08:01:33.288809Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["nohtyP\n"]}],"source":["\"\"\"\n","Reverse a string using while loop\n","\"\"\"\n","\n","input_string = \"Python\"\n","reverse_string = \"\"\n","index = len(input_string)\n","while index>0:\n"," index = index - 1\n"," reverse_string = reverse_string + input_string[index]\n"," \n","print(reverse_string)\n"," "]},{"cell_type":"markdown","metadata":{},"source":["In this sample, the task is to create a new string that reverses the characters from input_string. Since a string can be addressed by its index, the while loop is defined based on an index variable that starts from the last element of the string (i.e., len(input_string) - 1), then iteratively reduces the index until zero (i.e., the first element). Note that since string is immutable, every execution of line 10 inside the loop will allocate a new string object for the updated reverse_string object, although their variable name remains the same."]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["\"\"\"\n","An infinite while loop with break\n","\"\"\"\n","\n","animal_list = ['dog', 'cat','fish','pony','parrot','leopard','frog','mouse','snake']\n","\n","index = 0\n","while True:\n"," print('Would you like to adopt a '+ animal_list[index%9]+ '? [Y/N]')\n"," answer = input()\n"," if answer.lower()=='y':\n"," break\n"," index += 1\n","\n","print('We will have your ' + animal_list[index%9] + ' ready for pick up!')"]},{"cell_type":"markdown","metadata":{},"source":["The above code block demonstrates a quite persistent salesperson who always wants to sell you something. As a result, the program set the while loop condition to always be True. In each loop, the program will query user's input to ask whether the user would like to adopt one of the animals from the *animal_list*. This construction is called an infinite loop. In an infinite loop, the result can be finite so long as a termination condition is being checked inside the while block. In the above example, the termination condition is whether the user answers \"Y\" or \"y\" for agreeing to adopt an animal. When inside a while loop, the loop can be terminated by calling the *break* function. Upon executing *break*, the flow of the code will jump to the next statement after the while block.\n","\n","Using *break* inside a while loop will cause the loop to abort without satisfying the while condition. In Python, a while loop can be coded to execute a block of code only if the loop exits normally, namely, the while condition is indeed equal to False. This is achieved by using the *while -- else* combination. We use this combination in the sample code below:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["\"\"\"\n","Test prime number\n","\"\"\"\n","import math\n","n = int(input()) # input an integer\n","\n","if n<2: \n"," print(n, 'is not a prime number')\n","elif n ==2: \n"," print (n, 'is a prime number')\n","else:\n"," x = 2\n"," while x<=math.sqrt(n) + 1:\n"," if n % x == 0: # n = x * y for some y\n"," print(n, 'is not a prime number')\n"," break\n"," else: \n"," x = x + 1\n","\n"," else:\n"," print(n, 'is a prime number')"]},{"cell_type":"markdown","metadata":{},"source":["In this sample code, the goal is to test whether a human input integer is a prime number. The algorithm is so designed to use a while loop to test integers from 2 to square root of n + 1 whether n can be evenly divided. If the condition is true, then n *is* a prime number, and the algorithm will report it and *break*. On the other hand, if the condition is not true for the entire while loop, then when exits normally the code will report that n is a prime number.\n","\n","Note that the *while -- else* combination is quite unique to Python. In other more traditional languages when the while loop could not be followed by an *else* condition, then a standard coding practice would rely on a boolean flag, such as *is_prime_flag* used in the following sample code:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["\"\"\"\n","Test prime number: Not relying on while -- else\n","\"\"\"\n","import math\n","n = int(input()) # input an integer\n","\n","if n<2: \n"," print(n, 'is not a prime number')\n","elif n ==2: \n"," print (n, 'is a prime number')\n","else:\n"," x = 2\n"," is_prime_flag = True\n"," while x<=math.sqrt(n) + 1:\n"," if n % x == 0: # n = x * y for some y\n"," is_prime_flag = False\n"," break\n"," else: \n"," x = x + 1\n","\n"," if is_prime_flag:\n"," print(n, 'is a prime number')\n"," else:\n"," print(n, 'is not a prime number')"]},{"cell_type":"markdown","metadata":{},"source":["# for Loop\n","\n","Another supported loop statement is the *for* loop. A for loop will need to define an index that will go over a sequence of pre-defined values. The sequence can be defined in two basic ways:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["for i in range(0, 10, 2): \n"," print(i, end = ' ')\n","\n","print() \n","loop_list = ['The', 'date', 'is', [2, 20, 2020]]\n","for i in loop_list: print(i, end = \" \")"]},{"cell_type":"markdown","metadata":{},"source":["In the first example above, the index sequence is defined by the function *range(begin, end, step)*. In the example, we see that *range(0, 10, 2)* contains five numbers: 0, 2, 4, 6, 8, and as in slicing, the end number will not be taken.\n","\n","In the second example, the index sequence is in fact a list. The for loop simply causes the loop index to enumerate the values of the elements from the list. \n","\n","At this point, we shall highlight one major difference when setting up a loop using *for* statement compared to *while*. The index sequence defined in the *for* loop is created at the beginning of the *for* loop. If during the *for* loop, however, the code explicitly changes either the index value or the index sequence elements, the changes will not alter the pre-set control flow. To illustrate this, let us see the example below:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["for_string = \"abcde\"\n","for i in for_string:\n"," for_string = \"uvwxyz\"\n"," print(i, end = \" \")\n"," i = 5\n"," print(i, end = \" \")"]},{"cell_type":"markdown","metadata":{},"source":["We see in the above example that neither changing the *for* loop index sequence nor changing the index value changes the flow control of the *for* loop. At the beginning, the *for* loop index is set to enumerate the five string elements \"a\", \"b\", \"c\", \"d\", \"e\". \n","\n","We also recall that, similar to the *while* loop, a *for* loop can be aborted from inside the loop using *break*.\n","\n","Finally, let us see the following sample code."]},{"cell_type":"code","execution_count":2,"metadata":{"execution":{"iopub.execute_input":"2021-06-24T17:38:32.577582Z","iopub.status.busy":"2021-06-24T17:38:32.577124Z","iopub.status.idle":"2021-06-24T17:38:49.518045Z","shell.execute_reply":"2021-06-24T17:38:49.517256Z","shell.execute_reply.started":"2021-06-24T17:38:32.577543Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["Would you like to adopt a dog? [Y/N] Y\n","Would you like to adopt a cat? [Y/N] N\n","Would you like to adopt a fish? [Y/N] Y\n","Would you like to adopt a pony? [Y/N] Y\n","Would you like to adopt a parrot? [Y/N] N\n","Would you like to adopt a leopard? [Y/N] N\n","Would you like to adopt a frog? [Y/N] N\n","Would you like to adopt a mouse? [Y/N] N\n","Would you like to adopt a snake? [Y/N] N\n"]},{"name":"stdout","output_type":"stream","text":["We will have your ['dog', 'fish', 'pony'] ready for pick up!\n"]}],"source":["animal_list = ['dog', 'cat','fish','pony','parrot','leopard','frog','mouse','snake']\n","\n","index = 0\n","adopted_animals=[]\n","for i in animal_list:\n"," answer = input('Would you like to adopt a '+ i + '? [Y/N]')\n"," if answer.lower()!='y':\n"," continue\n","\n"," adopted_animals.append(i)\n","else:\n"," if not adopted_animals:\n"," print('Your adoption list is empty. See you next time!')\n"," else:\n"," print('We will have your ' + str(adopted_animals) + ' ready for pick up!')"]},{"cell_type":"markdown","metadata":{},"source":["In this sample code, we re-program another salesperson strategy, whereby the code uses the *for* loop to only ask the user to adopt animals from the *animal_list* once. In the *for* loop, if the user input to any of the animal name is \"Y\" or \"y\", then the animal name will be appended to the *adopted_animals* list. In the code, we see the use of *for -- else*. It serves the same function, that if a *for* loop terminates normally, the *else* code block will be executed. Similarly, if the code aborts inside the *for* loop, then the *else* block will not be executed.\n","\n","In line 9, we see a new flow control statement, called **continue**. *continue* acts differently than *break* inside a loop. When the flow of the code meets *continue*, it will then skip the rest of the code in the loop block, and immediately start from the beginning of the next loop. For a *while* loop, this means re-testing the while condition; for a *for* loop, this means setting the loop index to the next pre-defined value from the index sequence."]},{"cell_type":"markdown","metadata":{},"source":["Finally, we again use the toy example of reversing a list to compare the time efficiency in Python between using built-in methods and using explicit for or while loops. From the resulting time difference, we can see that it is much more efficient to be able to directly use built-in methods versus re-implementing these methods using a Python code block."]},{"cell_type":"code","execution_count":7,"metadata":{"execution":{"iopub.execute_input":"2021-06-24T17:59:00.373750Z","iopub.status.busy":"2021-06-24T17:59:00.373361Z","iopub.status.idle":"2021-06-24T18:00:05.300071Z","shell.execute_reply":"2021-06-24T18:00:05.298978Z","shell.execute_reply.started":"2021-06-24T17:59:00.373718Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["WHILE LOOP REVERSE: Total elapsed time: 63.72 seconds\n","REVERSE() METHOD: Total elapsed time: 1.01 seconds\n","REVERSE() METHOD: Total elapsed time: 0.19 seconds\n"]}],"source":["# Compare time complexity of reversing a list using three methods and over 1 million repeats\n","import time\n","\n","List = list('abcdefghijklmnopqrstuvwxyz'*10)\n","repeat_time = 1000000 # The source list is not long, but we ask repeat 1 million times\n","\n","# Method 1: Reverse a list using a while loop code block\n","tic = time.time()\n","for i in range(repeat_time):\n"," reversed_list = []\n"," index = len(List)\n"," while index>0:\n"," index = index -1\n"," reversed_list.append(List[index])\n","toc = time.time()\n","elapsed_time = toc - tic\n","print(\"WHILE LOOP REVERSE: Total elapsed time: %.2f seconds\" % elapsed_time)\n","\n","# Method 2: Reverse a list using built-in reverse() method\n","tic = time.time()\n","for i in range(repeat_time):\n"," reversed_list = List.copy()\n"," reversed_list.reverse()\n","toc = time.time()\n","elapsed_time = toc - tic\n","print(\"REVERSE() METHOD: Total elapsed time: %.2f seconds\" % elapsed_time)\n","\n","# Method 3: Reverse a list using built-in reverse() method without copy() penalty\n","reversed_list = List.copy()\n","tic = time.time()\n","for i in range(repeat_time):\n"," reversed_list.reverse()\n","toc = time.time()\n","elapsed_time = toc - tic\n","print(\"REVERSE() METHOD: Total elapsed time: %.2f seconds\" % elapsed_time)"]},{"cell_type":"markdown","metadata":{},"source":["# Summary\n","\n","* if -- elif -- else statements can alter the linear order of code execution based on the imposed conditions.\n","* while loop creates a looped code block based on a condition, which is evaluated in the beginning of every loop.\n","* while loop condition can be always True. In such case, the condition to terminate the loop can be tested inside the loop and use another command: break.\n","* while -- else statements will execute the code block after else upon the while loop exits. The while code block may skip the else code block only by using break, which then will redirect the code flow to after the else code block.\n","* for loop creates a looped code block by enumerating through a sequence of loop index values. Most noticeable about the loop index is that the index sequence will be determined only at the time the loop is created. If the index value is changed by user inside the loop, it will still be reset to its deterministic sequence when a new loop starts.\n","* continue statement from inside a loop will skip the remaining code in the loop and redirect the flow to start a new loop at the beginning of the code block."]},{"cell_type":"markdown","metadata":{},"source":["# Exercises\n","\n","1. According to Wikipedia, the following pseudo-code is an algorithm to determine a *year* variable as a positive integer represents a leap year. Please write a program, which will receive an integer input from the user as the value of the *year* variable, and then determine if it is a leap year.\n","\n",">\n","\n"," if (year is not divisible by 4) then (it is a common year)\n"," else if (year is not divisible by 100) then (it is a leap year)\n"," else if (year is not divisible by 400) then (it is a common year)\n"," else (it is a leap year) \n"," \n","2. Please use the combination of *while* loop and time.time() function, to create a loop that will run for exactly 5 seconds. Hint: Inside the loop, the code block can use *pass* statement.\n","\n","3. Please modify Rock-Paper-Scissors Example Code in the lecture to add a score variable, namely, when the user wins, the score will increase by one point; when the user loses, the score will decrease by one point. Create a loop structure starting from the initial value score = 0, and continue running the Rock-Paper-Scissors game until the score reaches three (3).\n","\n","4. Please reverse a string variable using the *for* loop.\n","\n","5. Debug"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["leap year\n"]}],"source":["#Q1\n","year = int(input(\"year\"))\n","if year % 4 != 0:\n"," print(\"common year\")\n","elif year % 100 != 0:\n"," print(\"leap year\")\n","elif year % 400 != 0:\n"," print(\"common year\")\n","else:\n"," print(\"leap year\")\n"]},{"cell_type":"code","execution_count":16,"metadata":{},"outputs":[],"source":["\"\"\"Q2\"\"\"\n","import time\n","\n","while time.sleep(5):\n"," break"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":[]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["Please input [R] Rock, [P] Paper, or [S] Scissors: \n","Input not supported\n"]}],"source":["\"\"\"Q3\"\"\"\n","\"\"\"\n","Rock-Paper-Scissors With Score\n","\"\"\"\n","import random\n","score = 0\n","# Ask user to input R or P or S\n","print('Please input [R] Rock, [P] Paper, or [S] Scissors: ')\n","while score <3:\n"," user_input = input()\n"," user_input = user_input.upper() # convert lower case to upper case\n"," if user_input == \"STOP\":\n"," break\n"," else:\n"," if user_input !='R' and user_input !='P' and user_input !='S': # Doing sanity test\n"," print(\"Input not supported\")\n"," else:\n"," computer_input = random.choice(['R', 'P', 'S'])\n"," print('Computer input is ', computer_input)\n"," if computer_input == user_input:\n"," print(\"Draw!\")\n"," elif (computer_input=='R' and user_input == 'S') or \\\n"," (computer_input=='S' and user_input == 'P') or \\\n"," (computer_input=='P' and user_input == 'R'):\n"," print('You Lose!')\n"," score -=1\n"," print(score)\n"," else:\n"," print('You Win!')\n"," score +=1\n"," print(score)"]},{"cell_type":"code","execution_count":25,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["w\n","ow\n","row\n","drow\n","sdrow\n","sdrow\n"]}],"source":["\"\"\"Q4\"\"\"\n","original = \"words\"\n","new = ''\n","for i in original:\n"," new = i + new\n"," print(new)\n","print(new)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["for i in range(10):\n"," print(i)\n","else:\n"," pass"]},{"cell_type":"markdown","metadata":{},"source":["# Challenges\n","\n","1. Using a list and a *for* loop to calculate and save the Fibonacci number for the first 10 nonnegative integers. The Fibonacci number is defined as:\n","> Fibonacci (n) = Fibonacci (n-1) + Fibonacci (n-2) for any n>1;\n",">\n","> Fibonacci(0) = 0; Fibonacci(1) = 1.\n","\n","2. Use for loop to print the following pattern for n layers. For example, when n = 4, the pattern is:\n","\n","> 1\n",">\n","> 1 1\n",">\n","> 1 2 1\n",">\n","> 1 3 3 1\n",">\n","> 1 4 6 4 1"]},{"cell_type":"code","execution_count":22,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["[0, 1, 1]\n","[0, 1, 1, 2]\n","[0, 1, 1, 2, 3]\n","[0, 1, 1, 2, 3, 5]\n","[0, 1, 1, 2, 3, 5, 8]\n","[0, 1, 1, 2, 3, 5, 8, 13]\n","[0, 1, 1, 2, 3, 5, 8, 13, 21]\n","[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]\n","[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]\n"]}],"source":["a = [0,1]\n","for i in range(2,11):\n"," a.append(a[i-1]+a[i-2])\n"," print(a)"]},{"cell_type":"code","execution_count":24,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["1\n","11\n","121\n","1331\n"]}],"source":["npattern = 1\n","for i in range(5):\n"," print(npattern)\n"," npattern = npattern * 11\n"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","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.18"}},"nbformat":4,"nbformat_minor":4} diff --git a/Part One/1-6-functions.ipynb b/Part One/1-6-functions.ipynb index 58149e5..8c5f817 100644 --- a/Part One/1-6-functions.ipynb +++ b/Part One/1-6-functions.ipynb @@ -1 +1 @@ -{"cells":[{"cell_type":"markdown","metadata":{},"source":["**Learning Python -- The Programming Language for Artificial Intelligence and Data Science**\n","\n","**Lecture 6: Functions**\n","\n","**By Allen Y. Yang, PhD**\n","\n","(c) Copyright Intelligent Racing Inc., 2021-2024. All rights reserved. Materials may NOT be distributed or used for any commercial purposes."]},{"cell_type":"markdown","metadata":{},"source":["# Keywords\n","\n","In our course so far, we have discussed three types of functions. A function is a block of code that is packed together that is given a function name and possibly a list of input arguments and and output results. \n","\n","* Built-in stand-alone functions: Examples include print(), type(), int(), and range().\n","* Imported functions: Examples include math.floor(), time.time(), and random.randint().\n","* Methods in classes: Examples include list.reverse(), list.append(), and string.lower().\n"," \n","In this lecture, we will learn how to code user-defined Python functions.\n","\n","# Define Functions"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-25T17:11:43.653389Z","iopub.status.busy":"2021-06-25T17:11:43.653016Z","iopub.status.idle":"2021-06-25T17:11:43.659763Z","shell.execute_reply":"2021-06-25T17:11:43.659119Z","shell.execute_reply.started":"2021-06-25T17:11:43.653358Z"},"trusted":true},"outputs":[],"source":["def print_hello_world():\n"," '''The function prints Hello World! string.'''\n","\n"," print(\"Hello World!\")\n","\n","def print_string(s):\n"," \"\"\"The function prints an input string.\n"," Input:\n"," s: a string\n"," Output:\n"," n/a\n"," \"\"\"\n","\n"," print(s)\n"," \n","print_hello_world()\n","print_string('Hello New World!')\n","print(print_string.__doc__)\n","\n","# Test function return value when return value is not provided\n","\n","print(print_hello_world()) # Return is None of NoneType"]},{"cell_type":"markdown","metadata":{},"source":["The above sample code defines two functions: *print_hello_world()* and *print_string()*. To define a function, the code shall start with a keyword *def*, followed by the name of the function provided by the programmer. \n","\n","A function also may or may not have a list of input arguments. In the first function, since the purpose of the function is always to print \"Hello World!\", no additional input is needed. The use of a pair of empty parentheses indicates that the function has no input argument.\n","\n","In the second example, *print_string()* codes a print function, but its function code does not specify what is the message to be printed. Instead, the function uses the input argument *s*. When the function is called, the function call must specify the input argument value. This value then will be assigned to variable *s*.\n","\n","There is one more common component in both *print_hello_world()* and *print_string()*, that is the inclusion of a comment section right next to the definition of function name and its input arguments, namely, right after the colon mark. The comment section is required to use the triple quotation marks, and they can be either single or double quotation marks. There is a name for the comment created as such, called *docstring*. \n","\n","Different from other types of code comments, the use of docstrings is strongly recommended. Developers can use docstrings to document the main design goal and logic of the function and a list of input/output arguments. Docstrings help third-party users to better interface with the functions without going through the detailed coding implementation.\n","\n","The content of docstrings will be also assigned as a built-in attribute of the functions or methods that include docstrings, called *.__doc__*. In the above example, the function's docstring can be printed out by: *print(print_string.__doc__)*\n","\n","To emphasize its generality, built-in Python functions and methods also include docstrings extensively. For example, below let us review the docstring of Python's own *print()* function:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-25T17:09:25.217804Z","iopub.status.busy":"2021-06-25T17:09:25.217461Z","iopub.status.idle":"2021-06-25T17:09:25.221886Z","shell.execute_reply":"2021-06-25T17:09:25.221225Z","shell.execute_reply.started":"2021-06-25T17:09:25.217771Z"},"trusted":true},"outputs":[],"source":["print(print.__doc__)"]},{"cell_type":"markdown","metadata":{},"source":["# Function Arguments\n","\n","In the previous example, we have seen a user-defined function may include a list of arguments. Let us consider the two distinct cases when the function arguments may be of mutable type or of immutable type."]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-25T15:37:45.378539Z","iopub.status.busy":"2021-06-25T15:37:45.378153Z","iopub.status.idle":"2021-06-25T15:37:45.385628Z","shell.execute_reply":"2021-06-25T15:37:45.384847Z","shell.execute_reply.started":"2021-06-25T15:37:45.378495Z"},"trusted":true},"outputs":[],"source":["# Sample code to use mutable and immutable function arguments\n","def func_test(L = ['a', 'b'], S = 'ab'):\n"," L.append('c')\n"," S = S + 'c'\n"," return L, S\n","\n","# Without return, mutable argument changes affect outside\n","# But immutable argument changes do not\n","L = ['a', 'b']; S = 'ab'\n","func_test(L, S) \n","print('{0}, {1}'.format(L,S))\n","\n","# When argument values are not assigned, default value\n","# must be defined\n","L, S = func_test() \n","print('{0}, {1}'.format(L,S))\n","\n","# Default value of a mutable argument only used ONCE\n","L, S = func_test()\n","print('{0}, {1}'.format(L,S))"]},{"cell_type":"markdown","metadata":{},"source":["Let us unpack the above three lines of result returns when the same functions are called three times.\n","\n","1. When a function includes a list of arguments, it is allowed that the code also declare a default value when no value is provided when the function is called in the code. The first line of printed results indicates this case, when *L, S = func_test()* does not provide the argument values, and the default values of *L = ['a', 'b']* and *S = 'ab'* are used. \n","2. It may come as a surprise that while the same function is called the second time in the same fashion, the returned values of *L* and *S* are different from the first time. This is because an important rule defined in Python regarding the type of an input argument to be mutable or immutable:\n"," * When an argument type is immutable, then its default value will be reset to the one listed in the function definition. In the above example, *S = 'ab'* is immutable string type. Therefore, when its input value is not provided by the function call, the default value is always used.\n"," * When an argument type is mutable, then its default value will be set only once based on the value in the function definition. Subsequently, the default value of a mutable argument remains the same from the last time the function is called. In the above example, when the function is first called, the mutable argument *L* has changed its value to ['a', 'b', 'c']. Therefore, when the function is called the second time without explicit setting the argument value, the default value of *L* is the same value from the last function call. Adding another 'c' to this value will return the result ['a', 'b', 'c', 'c'].\n"," * The third line of printed result illustrates the situation that when a mutable argument is changed inside a function, the modified value is carried over to the outside of the function regardless if the value is part of the function output. However, when an immutable argument is modified inside a function, it is not carried over to change the variable value outside the function. This conclusion makes sense because when the function executes the statement *S = S + 'c'*, the assigned new string *S* is a local string object that has different memory address and ID than the string *S* of the same name outside the function. \n"," \n"," \n","In conclusion, when arguments are passed into a function, mutable variables share the same objects inside and outside the function, while immutable variables do not share the same objects. As a result, changes on immutable variables inside a function do not change the variables of the same names outside. Such immutable variables inside a function are called local variables.\n","\n","Contrast to local variables, Python also defines a keyword called *global* to define global variables. When a variable is declared as global, then Python will force all variables of the same name to share the same object and memory address. Consequently, changing the value of a global variable in one place will affect all the other places in the code when the same variable is used. Let us see the example below."]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-25T20:06:30.826638Z","iopub.status.busy":"2021-06-25T20:06:30.826017Z","iopub.status.idle":"2021-06-25T20:06:30.837867Z","shell.execute_reply":"2021-06-25T20:06:30.836546Z","shell.execute_reply.started":"2021-06-25T20:06:30.826589Z"},"trusted":true},"outputs":[],"source":["S = 'ab'\n","\n","def func_test_global(L = ['a', 'b']):\n"," global S\n"," \n"," L.append('c')\n"," S = S + 'c'\n"," return L, S\n","\n","L, S = func_test_global() \n","print('{0}, {1}'.format(L,S))\n","print('id(S): ',id(S))\n","\n","L, S = func_test_global()\n","print('{0}, {1}'.format(L,S))\n","print('id(S): ',id(S))\n","\n","func_test_global(L)\n","print('{0}, {1}'.format(L,S))\n","print('id(S): ',id(S))"]},{"cell_type":"markdown","metadata":{},"source":["In the above example, we change the definition of the function to *func_test_global()* for variable *S* to be declared as global. When we now see the returned results, the value of the immutable variable *S* is carried over between outside and inside the function, as if the string type is a mutable variable type."]},{"cell_type":"markdown","metadata":{},"source":["# Define an insert sort function\n","\n","Previously, we have seen the use of several built-in sorting functions. Let us see a few examples below:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-25T16:09:29.487926Z","iopub.status.busy":"2021-06-25T16:09:29.48762Z","iopub.status.idle":"2021-06-25T16:09:29.494786Z","shell.execute_reply":"2021-06-25T16:09:29.493856Z","shell.execute_reply.started":"2021-06-25T16:09:29.487899Z"},"trusted":true},"outputs":[],"source":["List = [7, 4, 3, 8, 5, 6, 1, 9, 2, 0]\n","List.sort()\n","print(List)\n","List.sort(reverse=True)\n","print(List)\n","\n","String = \"cdeab\"\n","sorted_result = sorted(String)\n","print(sorted_result)\n","new_String = ''.join(sorted_result)\n","print(new_String)"]},{"cell_type":"markdown","metadata":{},"source":["Next, we design a sorting function called *insert sort*:"]},{"cell_type":"code","execution_count":1,"metadata":{"execution":{"iopub.execute_input":"2024-03-03T04:13:15.159516Z","iopub.status.busy":"2024-03-03T04:13:15.158949Z","iopub.status.idle":"2024-03-03T04:13:29.069685Z","shell.execute_reply":"2024-03-03T04:13:29.068514Z","shell.execute_reply.started":"2024-03-03T04:13:15.159479Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["*** Insert Sort ***\n","Elapsed Time: 13.869377613067627\n","[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]\n","*** Python Sort ***\n","Elapsed Time: 0.0016734600067138672\n","[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]\n"]}],"source":["import random\n","import time\n","\n","def insert_sort(input_list):\n"," ''' A custom function to sort number sequences using insert sort\n"," Parameters:\n"," Input: input_list - Expecting a list of numerical numbers\n","\n"," Output: input_list - sorted list\n"," '''\n"," if type(input_list)!=list:\n"," input_list = list(input_list)\n","\n"," for index in range(1, len(input_list)):\n","\n"," # Compare and sort elements one by one\n"," current = input_list[index]\n","\n"," # Verify the type of each element\n"," if type(current)!=int and type(current)!=float:\n"," current = float(current)\n","\n"," # Insert to previous sorted sub-list\n"," while index>0 and input_list[index-1]>current:\n"," # Insert iteratively until insert condition is False\n"," input_list[index] = input_list[index-1]\n"," input_list[index-1] = current\n"," index -=1\n"," \n"," return input_list\n","\n","# Generate a sufficiently long list for sorting\n","sample_count = 10000\n","random_input = random.sample(range(0, sample_count),sample_count)\n","\n","# ******** Method 1: Insert Sort ********\n","print('*** Insert Sort ***')\n","result = random_input.copy()\n","begin_time = time.time()\n","insert_sort(result)\n","\n","# tic-toc\n","elapsed_time = time.time() - begin_time\n","print('Elapsed Time: ', elapsed_time)\n","print(result[0:20])\n","\n","# ******** Method 2: Built-in Timsort ******\n","print('*** Python Sort ***')\n","result = random_input.copy()\n","begin_time = time.time()\n","result.sort()\n","\n","# tic-toc\n","elapsed_time = time.time() - begin_time\n","print('Elapsed Time: ', elapsed_time)\n","print(result[0:20])"]},{"cell_type":"markdown","metadata":{},"source":["Please refer to the lecture video for the important discussion about the difference in the time complexity between a user-defined insert sort and the built-in Python sort function."]},{"cell_type":"markdown","metadata":{},"source":["# Summary\n","\n","* A custom-defined function can be declared by the keyword: def, followed by the function name, a list or input arguments within a pair of parentheses, and then a colon that indicates the function code block that follows.\n","* Input arguments may be mutable or immutable type. A mutable input argument shares the same memory address and value as the corresponding variable outside the function, while an immutable input argument merely creates a copy of the corresponding variable outside the function. As a result, any changes to a mutable input argument will change the variable outside the function, but changes to an immutable input argument will only affect itself inside the function.\n","* Variables inside a function can be passed to the outside by the command: return.\n","* Another way to pass variable with the same name and value to inside a function is by declaring a global variable. A global variable only has one instance both inside and outside the function.\n","* A custom-defined function can be imported from a .py file by the command: import."]},{"cell_type":"markdown","metadata":{},"source":["# Exercises\n","\n","1. Please code a function *calculation(arg_1, arg_2)*, which takes in two arguments and calculate the multiplication result and float division result of them and then returns a list-type value of both results.\n","\n","2. Please code an *AND()* boolean function, which will take in two input boolean arguments, and output its logic *and* result. Please correctly include the function docstring and argument type checking to verify the input variables are boolean type.\n","\n","3. Please code a function called shorten_string(). The function takes in one string-type argument, and then will remove its first character and last character and return the result. If the input string is shorter than length-2, then the function shall return an empty string. If the input argument is not a string type, the function shall also return an empty string. Hint: Please remember to check the variable type of the input argument.\n","\n","4. In the lecture, an insert sort algorithm with default ascending order is discussed. Please modify the code that allows the function to sort a list in either ascending order or descending order, by setting a second argument *reverse* to be True or False.\n","\n","5. Debug:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-05-28T06:18:24.648443Z","iopub.status.busy":"2021-05-28T06:18:24.648017Z","iopub.status.idle":"2021-05-28T06:18:24.665754Z","shell.execute_reply":"2021-05-28T06:18:24.664012Z","shell.execute_reply.started":"2021-05-28T06:18:24.648408Z"},"trusted":true},"outputs":[],"source":["result_1 = int('2020 year')\n","\n","int = 0\n","float = int(10.57)"]},{"cell_type":"markdown","metadata":{},"source":["# Challenges\n","\n","1. Please code a timer function called tic_toc(). The first time calling the function, it will be set in the TIC state, calling time.time() to save the current system clock time; The second time calling the function, it will then be set in the TOC state, returning the time difference between the current system clock time and the previously saved system clock time. After the TOC state return, the function will be reset to wait for the next TIC state function call. Hint: The saved system clock time and the function's state whether it is in the first tic state or the second toc state can be defined as global variables. \n","\n","2. Please code a circular_shift() function, which takes in two arguments: a string called *input_string* and an integer called *direction*. When *direction = -1*, the function will return a string that circularly shifts *input_string* elements to the left by one position. When *direction = 1*, the function will return a string that circularly shifts *input_string* elements to the right by one position. Hint: In here, circular shift means whenever an element is shifted outside the range of the existing string, it will be added back to the string from the opposite side such that the total length and all elements remain the same after circular shift."]}],"metadata":{"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":30042,"isGpuEnabled":false,"isInternetEnabled":false,"language":"python","sourceType":"notebook"},"kernelspec":{"display_name":"Python 3","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.7.6"}},"nbformat":4,"nbformat_minor":4} +{"cells":[{"cell_type":"markdown","metadata":{},"source":["**Learning Python -- The Programming Language for Artificial Intelligence and Data Science**\n","\n","**Lecture 6: Functions**\n","\n","**By Allen Y. Yang, PhD**\n","\n","(c) Copyright Intelligent Racing Inc., 2021-2024. All rights reserved. Materials may NOT be distributed or used for any commercial purposes."]},{"cell_type":"markdown","metadata":{},"source":["# Keywords\n","\n","In our course so far, we have discussed three types of functions. A function is a block of code that is packed together that is given a function name and possibly a list of input arguments and and output results. \n","\n","* Built-in stand-alone functions: Examples include print(), type(), int(), and range().\n","* Imported functions: Examples include math.floor(), time.time(), and random.randint().\n","* Methods in classes: Examples include list.reverse(), list.append(), and string.lower().\n"," \n","In this lecture, we will learn how to code user-defined Python functions.\n","\n","# Define Functions"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-25T17:11:43.653389Z","iopub.status.busy":"2021-06-25T17:11:43.653016Z","iopub.status.idle":"2021-06-25T17:11:43.659763Z","shell.execute_reply":"2021-06-25T17:11:43.659119Z","shell.execute_reply.started":"2021-06-25T17:11:43.653358Z"},"trusted":true},"outputs":[],"source":["def print_hello_world():\n"," '''The function prints Hello World! string.'''\n","\n"," print(\"Hello World!\")\n","\n","def print_string(s):\n"," \"\"\"The function prints an input string.\n"," Input:\n"," s: a string\n"," Output:\n"," n/a\n"," \"\"\"\n","\n"," print(s)\n"," \n","print_hello_world()\n","print_string('Hello New World!')\n","print(print_string.__doc__)\n","\n","# Test function return value when return value is not provided\n","\n","print(print_hello_world()) # Return is None of NoneType"]},{"cell_type":"markdown","metadata":{},"source":["The above sample code defines two functions: *print_hello_world()* and *print_string()*. To define a function, the code shall start with a keyword *def*, followed by the name of the function provided by the programmer. \n","\n","A function also may or may not have a list of input arguments. In the first function, since the purpose of the function is always to print \"Hello World!\", no additional input is needed. The use of a pair of empty parentheses indicates that the function has no input argument.\n","\n","In the second example, *print_string()* codes a print function, but its function code does not specify what is the message to be printed. Instead, the function uses the input argument *s*. When the function is called, the function call must specify the input argument value. This value then will be assigned to variable *s*.\n","\n","There is one more common component in both *print_hello_world()* and *print_string()*, that is the inclusion of a comment section right next to the definition of function name and its input arguments, namely, right after the colon mark. The comment section is required to use the triple quotation marks, and they can be either single or double quotation marks. There is a name for the comment created as such, called *docstring*. \n","\n","Different from other types of code comments, the use of docstrings is strongly recommended. Developers can use docstrings to document the main design goal and logic of the function and a list of input/output arguments. Docstrings help third-party users to better interface with the functions without going through the detailed coding implementation.\n","\n","The content of docstrings will be also assigned as a built-in attribute of the functions or methods that include docstrings, called *.__doc__*. In the above example, the function's docstring can be printed out by: *print(print_string.__doc__)*\n","\n","To emphasize its generality, built-in Python functions and methods also include docstrings extensively. For example, below let us review the docstring of Python's own *print()* function:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-25T17:09:25.217804Z","iopub.status.busy":"2021-06-25T17:09:25.217461Z","iopub.status.idle":"2021-06-25T17:09:25.221886Z","shell.execute_reply":"2021-06-25T17:09:25.221225Z","shell.execute_reply.started":"2021-06-25T17:09:25.217771Z"},"trusted":true},"outputs":[],"source":["print(print.__doc__)"]},{"cell_type":"markdown","metadata":{},"source":["# Function Arguments\n","\n","In the previous example, we have seen a user-defined function may include a list of arguments. Let us consider the two distinct cases when the function arguments may be of mutable type or of immutable type."]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-25T15:37:45.378539Z","iopub.status.busy":"2021-06-25T15:37:45.378153Z","iopub.status.idle":"2021-06-25T15:37:45.385628Z","shell.execute_reply":"2021-06-25T15:37:45.384847Z","shell.execute_reply.started":"2021-06-25T15:37:45.378495Z"},"trusted":true},"outputs":[],"source":["# Sample code to use mutable and immutable function arguments\n","def func_test(L = ['a', 'b'], S = 'ab'):\n"," L.append('c')\n"," S = S + 'c'\n"," return L, S\n","\n","# Without return, mutable argument changes affect outside\n","# But immutable argument changes do not\n","L = ['a', 'b']; S = 'ab'\n","func_test(L, S) \n","print('{0}, {1}'.format(L,S))\n","\n","# When argument values are not assigned, default value\n","# must be defined\n","L, S = func_test() \n","print('{0}, {1}'.format(L,S))\n","\n","# Default value of a mutable argument only used ONCE\n","L, S = func_test()\n","print('{0}, {1}'.format(L,S))"]},{"cell_type":"markdown","metadata":{},"source":["Let us unpack the above three lines of result returns when the same functions are called three times.\n","\n","1. When a function includes a list of arguments, it is allowed that the code also declare a default value when no value is provided when the function is called in the code. The first line of printed results indicates this case, when *L, S = func_test()* does not provide the argument values, and the default values of *L = ['a', 'b']* and *S = 'ab'* are used. \n","2. It may come as a surprise that while the same function is called the second time in the same fashion, the returned values of *L* and *S* are different from the first time. This is because an important rule defined in Python regarding the type of an input argument to be mutable or immutable:\n"," * When an argument type is immutable, then its default value will be reset to the one listed in the function definition. In the above example, *S = 'ab'* is immutable string type. Therefore, when its input value is not provided by the function call, the default value is always used.\n"," * When an argument type is mutable, then its default value will be set only once based on the value in the function definition. Subsequently, the default value of a mutable argument remains the same from the last time the function is called. In the above example, when the function is first called, the mutable argument *L* has changed its value to ['a', 'b', 'c']. Therefore, when the function is called the second time without explicit setting the argument value, the default value of *L* is the same value from the last function call. Adding another 'c' to this value will return the result ['a', 'b', 'c', 'c'].\n"," * The third line of printed result illustrates the situation that when a mutable argument is changed inside a function, the modified value is carried over to the outside of the function regardless if the value is part of the function output. However, when an immutable argument is modified inside a function, it is not carried over to change the variable value outside the function. This conclusion makes sense because when the function executes the statement *S = S + 'c'*, the assigned new string *S* is a local string object that has different memory address and ID than the string *S* of the same name outside the function. \n"," \n"," \n","In conclusion, when arguments are passed into a function, mutable variables share the same objects inside and outside the function, while immutable variables do not share the same objects. As a result, changes on immutable variables inside a function do not change the variables of the same names outside. Such immutable variables inside a function are called local variables.\n","\n","Contrast to local variables, Python also defines a keyword called *global* to define global variables. When a variable is declared as global, then Python will force all variables of the same name to share the same object and memory address. Consequently, changing the value of a global variable in one place will affect all the other places in the code when the same variable is used. Let us see the example below."]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-25T20:06:30.826638Z","iopub.status.busy":"2021-06-25T20:06:30.826017Z","iopub.status.idle":"2021-06-25T20:06:30.837867Z","shell.execute_reply":"2021-06-25T20:06:30.836546Z","shell.execute_reply.started":"2021-06-25T20:06:30.826589Z"},"trusted":true},"outputs":[],"source":["S = 'ab'\n","\n","def func_test_global(L = ['a', 'b']):\n"," global S\n"," \n"," L.append('c')\n"," S = S + 'c'\n"," return L, S\n","\n","L, S = func_test_global() \n","print('{0}, {1}'.format(L,S))\n","print('id(S): ',id(S))\n","\n","L, S = func_test_global()\n","print('{0}, {1}'.format(L,S))\n","print('id(S): ',id(S))\n","\n","func_test_global(L)\n","print('{0}, {1}'.format(L,S))\n","print('id(S): ',id(S))"]},{"cell_type":"markdown","metadata":{},"source":["In the above example, we change the definition of the function to *func_test_global()* for variable *S* to be declared as global. When we now see the returned results, the value of the immutable variable *S* is carried over between outside and inside the function, as if the string type is a mutable variable type."]},{"cell_type":"markdown","metadata":{},"source":["# Define an insert sort function\n","\n","Previously, we have seen the use of several built-in sorting functions. Let us see a few examples below:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-25T16:09:29.487926Z","iopub.status.busy":"2021-06-25T16:09:29.48762Z","iopub.status.idle":"2021-06-25T16:09:29.494786Z","shell.execute_reply":"2021-06-25T16:09:29.493856Z","shell.execute_reply.started":"2021-06-25T16:09:29.487899Z"},"trusted":true},"outputs":[],"source":["List = [7, 4, 3, 8, 5, 6, 1, 9, 2, 0]\n","List.sort()\n","print(List)\n","List.sort(reverse=True)\n","print(List)\n","\n","String = \"cdeab\"\n","sorted_result = sorted(String)\n","print(sorted_result)\n","new_String = ''.join(sorted_result)\n","print(new_String)"]},{"cell_type":"markdown","metadata":{},"source":["Next, we design a sorting function called *insert sort*:"]},{"cell_type":"code","execution_count":55,"metadata":{"execution":{"iopub.execute_input":"2024-03-03T04:13:15.159516Z","iopub.status.busy":"2024-03-03T04:13:15.158949Z","iopub.status.idle":"2024-03-03T04:13:29.069685Z","shell.execute_reply":"2024-03-03T04:13:29.068514Z","shell.execute_reply.started":"2024-03-03T04:13:15.159479Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["*** Insert Sort ***\n"]},{"ename":"TypeError","evalue":"'list' object is not callable","output_type":"error","traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)","Cell \u001b[0;32mIn[55], line 40\u001b[0m\n\u001b[1;32m 38\u001b[0m result \u001b[38;5;241m=\u001b[39m random_input\u001b[38;5;241m.\u001b[39mcopy()\n\u001b[1;32m 39\u001b[0m begin_time \u001b[38;5;241m=\u001b[39m time\u001b[38;5;241m.\u001b[39mtime()\n\u001b[0;32m---> 40\u001b[0m \u001b[43minsert_sort\u001b[49m\u001b[43m(\u001b[49m\u001b[43mresult\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 42\u001b[0m \u001b[38;5;66;03m# tic-toc\u001b[39;00m\n\u001b[1;32m 43\u001b[0m elapsed_time \u001b[38;5;241m=\u001b[39m time\u001b[38;5;241m.\u001b[39mtime() \u001b[38;5;241m-\u001b[39m begin_time\n","Cell \u001b[0;32mIn[55], line 12\u001b[0m, in \u001b[0;36minsert_sort\u001b[0;34m(input_list)\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m''' A custom function to sort number sequences using insert sort\u001b[39;00m\n\u001b[1;32m 6\u001b[0m \u001b[38;5;124;03mParameters:\u001b[39;00m\n\u001b[1;32m 7\u001b[0m \u001b[38;5;124;03mInput: input_list - Expecting a list of numerical numbers\u001b[39;00m\n\u001b[1;32m 8\u001b[0m \n\u001b[1;32m 9\u001b[0m \u001b[38;5;124;03mOutput: input_list - sorted list\u001b[39;00m\n\u001b[1;32m 10\u001b[0m \u001b[38;5;124;03m'''\u001b[39;00m\n\u001b[1;32m 11\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mtype\u001b[39m(input_list)\u001b[38;5;241m!=\u001b[39m\u001b[38;5;28mlist\u001b[39m:\n\u001b[0;32m---> 12\u001b[0m input_list \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mlist\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43minput_list\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 14\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m index \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mrange\u001b[39m(\u001b[38;5;241m1\u001b[39m, \u001b[38;5;28mlen\u001b[39m(input_list)):\n\u001b[1;32m 15\u001b[0m \n\u001b[1;32m 16\u001b[0m \u001b[38;5;66;03m# Compare and sort elements one by one\u001b[39;00m\n\u001b[1;32m 17\u001b[0m current \u001b[38;5;241m=\u001b[39m input_list[index]\n","\u001b[0;31mTypeError\u001b[0m: 'list' object is not callable"]}],"source":["import random\n","import time\n","\n","def insert_sort(input_list):\n"," ''' A custom function to sort number sequences using insert sort\n"," Parameters:\n"," Input: input_list - Expecting a list of numerical numbers\n","\n"," Output: input_list - sorted list\n"," '''\n"," if type(input_list)!=list:\n"," input_list = list(input_list)\n","\n"," for index in range(1, len(input_list)):\n","\n"," # Compare and sort elements one by one\n"," current = input_list[index]\n","\n"," # Verify the type of each element\n"," if type(current)!=int and type(current)!=float:\n"," current = float(current)\n","\n"," # Insert to previous sorted sub-list\n"," while index>0 and input_list[index-1]>current:\n"," # Insert iteratively until insert condition is False\n"," input_list[index] = input_list[index-1]\n"," input_list[index-1] = current\n"," index -=1\n"," \n"," return input_list\n","\n","# Generate a sufficiently long list for sorting\n","sample_count = 10000\n","random_input = random.sample(range(0, sample_count),sample_count)\n","\n","# ******** Method 1: Insert Sort ********\n","print('*** Insert Sort ***')\n","result = random_input.copy()\n","begin_time = time.time()\n","insert_sort(result)\n","\n","# tic-toc\n","elapsed_time = time.time() - begin_time\n","print('Elapsed Time: ', elapsed_time)\n","print(result[0:20])\n","\n","# ******** Method 2: Built-in Timsort ******\n","print('*** Python Sort ***')\n","result = random_input.copy()\n","begin_time = time.time()\n","result.sort()\n","\n","# tic-toc\n","elapsed_time = time.time() - begin_time\n","print('Elapsed Time: ', elapsed_time)\n","print(result[0:20])"]},{"cell_type":"markdown","metadata":{},"source":["Please refer to the lecture video for the important discussion about the difference in the time complexity between a user-defined insert sort and the built-in Python sort function."]},{"cell_type":"markdown","metadata":{},"source":["# Summary\n","\n","* A custom-defined function can be declared by the keyword: def, followed by the function name, a list or input arguments within a pair of parentheses, and then a colon that indicates the function code block that follows.\n","* Input arguments may be mutable or immutable type. A mutable input argument shares the same memory address and value as the corresponding variable outside the function, while an immutable input argument merely creates a copy of the corresponding variable outside the function. As a result, any changes to a mutable input argument will change the variable outside the function, but changes to an immutable input argument will only affect itself inside the function.\n","* Variables inside a function can be passed to the outside by the command: return.\n","* Another way to pass variable with the same name and value to inside a function is by declaring a global variable. A global variable only has one instance both inside and outside the function.\n","* A custom-defined function can be imported from a .py file by the command: import."]},{"cell_type":"markdown","metadata":{},"source":["# Exercises\n","\n","1. Please code a function *calculation(arg_1, arg_2)*, which takes in two arguments and calculate the multiplication result and float division result of them and then returns a list-type value of both results.\n","\n","2. Please code an *AND()* boolean function, which will take in two input boolean arguments, and output its logic *and* result. Please correctly include the function docstring and argument type checking to verify the input variables are boolean type.\n","\n","3. Please code a function called shorten_string(). The function takes in one string-type argument, and then will remove its first character and last character and return the result. If the input string is shorter than length-2, then the function shall return an empty string. If the input argument is not a string type, the function shall also return an empty string. Hint: Please remember to check the variable type of the input argument.\n","\n","4. In the lecture, an insert sort algorithm with default ascending order is discussed. Please modify the code that allows the function to sort a list in either ascending order or descending order, by setting a second argument *reverse* to be True or False.\n","\n","5. Debug:"]},{"cell_type":"code","execution_count":4,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["[12, 3.0]\n"]}],"source":["\n","def calculation(arg_1,arg_2):\n"," multresult = int(arg_1) * int(arg_2)\n"," floatresult = int(arg_1) / int(arg_2)\n"," final_list = [multresult, floatresult]\n"," print(final_list)\n","calculation(input(\"num1\"), input(\"num2\"))\n","\n"]},{"cell_type":"code","execution_count":41,"metadata":{},"outputs":[{"data":{"text/plain":["False"]},"execution_count":41,"metadata":{},"output_type":"execute_result"}],"source":["\n","def AND(a,b):\n"," '''docstring\n"," returns true if both true, otherwise false\n"," '''\n"," if a == \"t\" and b == a:\n"," return True\n"," else:\n"," return False\n","\n","\n","inp = input(\"t/f\"),input(\"t/f\")\n","AND(inp[0],inp[1])\n"," "]},{"cell_type":"code","execution_count":45,"metadata":{},"outputs":[{"data":{"text/plain":["' few word'"]},"execution_count":45,"metadata":{},"output_type":"execute_result"}],"source":["string = 'a few words'\n","\n","def shorten_string(a):\n"," shortstring=''\n"," if len(a) <= 2 or type(a) != str:\n"," return shortstring\n"," else:\n"," shortstring = list(string)\n"," shortstring.pop(0)\n"," shortstring.pop(-1)\n"," return \"\".join(shortstring)\n","shorten_string(string)\n"," \n"," "]},{"cell_type":"code","execution_count":62,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["[2340, 234.0, 88.0, 65.0, 37.0, 34.0, 1.0, 1, 0]\n"]}],"source":["import random\n","import time\n","\n","\n","def insert_sort(input_list, reverse):\n"," ''' A custom function to sort number sequences using insert sort\n"," Parameters:\n"," Input: input_list - Expecting a list of numerical numbers\n","\n"," Output: input_list - sorted list\n"," '''\n"," if type(input_list)!=list:\n"," input_list = list(input_list)\n"," for index in range(1, len(input_list)):\n"," # Compare and sort elements one by one\n"," current = input_list[index]\n","\n"," # Verify the type of each element\n"," if type(current)!=int and type(current)!=float:\n"," current = float(current)\n","\n"," # Insert to previous sorted sub-list\n"," while index>0 and input_list[index-1]>current:\n"," # Insert iteratively until insert condition is False\n"," input_list[index] = input_list[index-1]\n"," input_list[index-1] = current\n"," index -=1\n"," if reverse == True:\n"," input_list.reverse()\n"," \n"," print(input_list)\n","# Generate a sufficiently long list for sorting\n","insert_sort([0,1,2340,65,234,1,34,37,88], True)"]},{"cell_type":"code","execution_count":24,"metadata":{"execution":{"iopub.execute_input":"2021-05-28T06:18:24.648443Z","iopub.status.busy":"2021-05-28T06:18:24.648017Z","iopub.status.idle":"2021-05-28T06:18:24.665754Z","shell.execute_reply":"2021-05-28T06:18:24.664012Z","shell.execute_reply.started":"2021-05-28T06:18:24.648408Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["\n","\n","\n"]}],"source":["result_1 = str(\"2020 year\")\n","\n","int = 0\n","float = 10.57\n","print(type(float))\n","print(type(int))\n","print(type(result_1))"]},{"cell_type":"markdown","metadata":{},"source":["# Challenges\n","\n","1. Please code a timer function called tic_toc(). The first time calling the function, it will be set in the TIC state, calling time.time() to save the current system clock time; The second time calling the function, it will then be set in the TOC state, returning the time difference between the current system clock time and the previously saved system clock time. After the TOC state return, the function will be reset to wait for the next TIC state function call. Hint: The saved system clock time and the function's state whether it is in the first tic state or the second toc state can be defined as global variables. \n","\n","2. Please code a circular_shift() function, which takes in two arguments: a string called *input_string* and an integer called *direction*. When *direction = -1*, the function will return a string that circularly shifts *input_string* elements to the left by one position. When *direction = 1*, the function will return a string that circularly shifts *input_string* elements to the right by one position. Hint: In here, circular shift means whenever an element is shifted outside the range of the existing string, it will be added back to the string from the opposite side such that the total length and all elements remain the same after circular shift."]},{"cell_type":"markdown","metadata":{},"source":[]},{"cell_type":"code","execution_count":117,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["None\n"]}],"source":["import time\n","tic = 1\n","toc = 0\n","state = None\n","current_time = toc\n","def tic_toc():\n"," global current_time\n"," global state\n"," #flips between tic and toc\n"," if state == tic:\n"," state = toc\n"," time_dif = time.time() - current_time\n"," return time_dif\n"," else:\n"," state = tic\n"," current_time = time.time()\n","\n","tic_toc()\n","print(tic_toc())\n"]},{"cell_type":"code","execution_count":8,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["23451\n"]}],"source":["def circular_shift(input_string,direction):\n"," string = list(input_string)\n"," t = string[:-direction % len(string)]\n"," t2 = string[-direction % len(string):]\n"," return \"\".join(t2+t)\n","s = input()\n","d = int(input())\n","print(circular_shift(s,d))"]}],"metadata":{"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":30042,"isGpuEnabled":false,"isInternetEnabled":false,"language":"python","sourceType":"notebook"},"kernelspec":{"display_name":"Python 3","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.18"}},"nbformat":4,"nbformat_minor":4} diff --git a/Part One/1-7-tuples-and-dictionaries.ipynb b/Part One/1-7-tuples-and-dictionaries.ipynb index 75f4394..5611783 100644 --- a/Part One/1-7-tuples-and-dictionaries.ipynb +++ b/Part One/1-7-tuples-and-dictionaries.ipynb @@ -1 +1 @@ -{"cells":[{"cell_type":"markdown","metadata":{},"source":["**Learning Python -- The Programming Language for Artificial Intelligence and Data Science**\n","\n","**Lecture 7: Tuples and Dictionaries**\n","\n","**By Allen Y. Yang, PhD**\n","\n","(c) Copyright Intelligent Racing Inc., 2021-2024. All rights reserved. Materials may NOT be distributed or used for any commercial purposes.\n"]},{"cell_type":"markdown","metadata":{},"source":["# Keywords\n","\n","* **tuple**: Keyword for the tuple data type in Python. Tuple is an immutable type.\n","* **dict**: Keyword for the dictionary data type in Python. Dictionary is a mutable type. Each element in a dictionary is a (key, value) pair.\n","* **Histogram**: A data structure that represents a list of unique elements and their frequency of occurances within some data."]},{"cell_type":"markdown","metadata":{},"source":["# Tuples\n","\n","A tuple variable stores an ordered sequence of values. The following examples show the three ways a tuple object can be assigned:\n"," 1. Using a pair of parentheses: t = ('A', 'B')\n"," 2. A sequence of values separated by commas without the parentheses: t = 'a', 'b', 'c', 'd', 'e'\n"," 3. Using tuple() function: t = tuple('abcde')"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-26T09:07:42.457852Z","iopub.status.busy":"2021-06-26T09:07:42.456626Z","iopub.status.idle":"2021-06-26T09:07:42.468893Z","shell.execute_reply":"2021-06-26T09:07:42.467966Z","shell.execute_reply.started":"2021-06-26T09:07:42.457801Z"},"trusted":true},"outputs":[],"source":["t = 'a', 'b', 'c', 'd', 'e'\n","print(t == tuple('abcde') )\n","\n","t = ('A', 'B') + t[2:]\n","print(t)\n","\n","print(() == tuple())"]},{"cell_type":"markdown","metadata":{},"source":["In many ways, tuples are similar to lists in Python. In the above example, we see that elements in a tuple can be addressed using square brackets just like in a list. Concatenating two tuples into a new tuple variable is denoted using the \"+\" operator. In the last example, an empty tuple is denoted using *()* or *tuple()*.\n","\n","A tuple may contain just one element. However, the way to declare a single-element tuple must be different from the way to declare a single variable. See the examples below:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-26T09:09:02.34211Z","iopub.status.busy":"2021-06-26T09:09:02.341229Z","iopub.status.idle":"2021-06-26T09:09:02.351823Z","shell.execute_reply":"2021-06-26T09:09:02.350894Z","shell.execute_reply.started":"2021-06-26T09:09:02.342069Z"},"trusted":true},"outputs":[],"source":["t = 'a', # Single-element tuple\n","s = ('a') # a string\n","print(type(t), type(s))\n","\n","t = 0, # Single-element tuple\n","i = (0) # an integer\n","print(type(t), type(i))"]},{"cell_type":"markdown","metadata":{},"source":["Different from lists, however, tuples are immutable type variables. Once created, tuple elements cannot be changed without forcing Python to create a new object. The following statement will return runtime error:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["t = 'a', 'b'\n","t[1] = 'c'"]},{"cell_type":"markdown","metadata":{},"source":["However, if tuple elements are themselves mutable, then the values of the elements can be changed without changing the tuple object itself. See the following examples:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-26T09:11:38.945441Z","iopub.status.busy":"2021-06-26T09:11:38.945046Z","iopub.status.idle":"2021-06-26T09:11:38.953129Z","shell.execute_reply":"2021-06-26T09:11:38.952125Z","shell.execute_reply.started":"2021-06-26T09:11:38.945404Z"},"trusted":true},"outputs":[],"source":["# Negative example: when the elements are not mutable\n","string = 'A'; number = 1\n","t = string, number\n","print('Initial value: ', t)\n","number = 2\n","print('Values are immutable: ', t)\n","\n","# Positive example: when the elements are mutable\n","l = list('abc')\n","t = l, l\n","print('Initial value: ', t)\n","l[2] = 'a'\n","print('Mutable elements: ', t)"]},{"cell_type":"markdown","metadata":{},"source":["In Python, a sequence of values can be compared to another sequence. The comparison is performed from left to right element-wise, with two exceptions:\n"," 1. Circuit breaker: When comparing \"<\" or \">\" relationships, the comparison will return True or False when the first non-identical position satisfies either \"<\" or \">\" relationships, and will disregard any possible situation after the position.\n"," 2. Element-wise comparison must be of the same type: When comparing two elements at the same sequence position, they must have the same type. Otherwise Python will return \"TypeError\"."]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["# String comparison with circuit breaker\n","print('abc'>'a')\n","\n","# Tuple comparison with circuit breaker\n","print( (1, 2, 'c') > (1, 2))\n","print( (1, 2, 'c') > (1, 3, 3))\n","\n","# List comparison\n","print([1, 2, 'c'] > [1, 2, 'a'])\n","print([1, 2, 'c'] > [1, 2, 3])"]},{"cell_type":"markdown","metadata":{},"source":["Another scenario where tuples are used is in defining input and output values of functions. Let us see some examples below:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-27T10:15:42.352307Z","iopub.status.busy":"2021-06-27T10:15:42.351928Z","iopub.status.idle":"2021-06-27T10:15:42.359526Z","shell.execute_reply":"2021-06-27T10:15:42.358292Z","shell.execute_reply.started":"2021-06-27T10:15:42.352258Z"},"trusted":true},"outputs":[],"source":["email = 'allenyang@berkeley.edu'\n","name, address = email.split('@') # return of the string.split() method is a tuple\n","print(name, address)\n","\n","poem = 'Roses are red, Violets are blue, Sugar is sweet'\n","a, b = poem.split(',', 1)\n","print(a, b)\n","\n","a, b, c = poem.split(',')\n","print(c)"]},{"cell_type":"markdown","metadata":{},"source":["In the above sample code, a built-in method of string type *split()* will return a tuple. For example, if we split a string of an email address using the pattern \"@\", then the substring before \"@\" will be assigned to the first element of the tuple *name* and the substring after \"@\" will be assigned to the second element \"address\". In the second example, *split()* may return a tuple of multiple elements, if the split pattern appears in multiple locations of the string. \n","\n","Tuples can be used to hold return values from a function. They can also be used to hold input argument values of variable length. Let us see another simple example below:"]},{"cell_type":"code","execution_count":5,"metadata":{"execution":{"iopub.execute_input":"2022-07-30T20:58:38.382747Z","iopub.status.busy":"2022-07-30T20:58:38.382339Z","iopub.status.idle":"2022-07-30T20:58:38.392033Z","shell.execute_reply":"2022-07-30T20:58:38.390298Z","shell.execute_reply.started":"2022-07-30T20:58:38.382708Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["3\n","4\n","Input length: 1\n","args = (1,)\n","1\n"]}],"source":["print(max(2, 3))\n","print(max(2, 3, 4))\n","\n","def my_max(*args):\n"," print('Input length: ', len(args))\n"," print('args = ', args)\n"," return(max(args))\n","\n","print(my_max(1))"]},{"cell_type":"markdown","metadata":{},"source":["In the above sample code, we see that some Python functions accept input arguments of variable length, such as *max()* and *print()*. In addition, using tuples, we can define our own functions that accept arguments of variable length. In the definition of *my_max()*, we use a reserved symbol \"*\" to indicate that args should be treated as a tuple to hold multiple input values of variable length. In Python, the \"*\" symbol used in this scenario is called the **packing operator**."]},{"cell_type":"markdown","metadata":{},"source":["# Dictionaries\n","\n","Dictionary can be viewed as a generalization of the list type. In the list type, each element is indexed by an unique integer in ascending order. In dictionaries, each element is a **(key, value)** pair. Furthermore, the value of a dictionary entry will be addressed by its corresponding unique key. \n","\n","Let us see a few examples below:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-27T10:58:35.412219Z","iopub.status.busy":"2021-06-27T10:58:35.41183Z","iopub.status.idle":"2021-06-27T10:58:35.419596Z","shell.execute_reply":"2021-06-27T10:58:35.418606Z","shell.execute_reply.started":"2021-06-27T10:58:35.412188Z"},"trusted":true},"outputs":[],"source":["D = {1: 'one', 2: 'two', 3: 'three'}\n","print(len(D))\n","print(D[2])\n","\n","English_to_Chinese={} # Define an empty dictionary\n","English_to_Chinese['one'] = '一'\n","English_to_Chinese['two'] = '二'\n","English_to_Chinese['three'] = '三'\n","\n","del(English_to_Chinese['two'])\n","print(English_to_Chinese)"]},{"cell_type":"markdown","metadata":{},"source":["In the first example above, the dictionary *D* has three entries, i.e., first entry has the (key, value) pair as (1, 'one), etc. Retrieving the entry value corresponding to the key value of 2 is by *D[2]*.\n","\n","The second example demonstrates more powerful ways to retrieve a dictionary's entry values. The key can be other variable types such as strings. We can create a dictionary for translation purposes, where a unique English word is used as the key, and its corresponding entry value is its translation in another language such as Chinese. \n","\n","In here, we should note that any immutable type value can be assigned to be a key. However, not all variable type can be used as a key value. Let us consider the counterexample below:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["English_to_Chinese={} \n","English_to_Chinese[[1, 2, 3]] = 'variable'"]},{"cell_type":"markdown","metadata":{},"source":["If you run the above code, Python will return a runtime error: \"TypeError: unhashable type: 'list'\". It shows list type variables cannot be used as valid keys. The concept of **hashable** mentioned in the error message will be discussed in more detail in the next class. \n","\n","One of the widely used applications for dictionaries is the construction of histograms. A histogram is a representation of the frequency of distinct data values. A histogram can be conveniently stored in a dictionary:\n","\n"," * Key: distinct data values\n"," * Value: occurrence frequency (absolute count or relative percentage)\n"," \n","Let us see the sample code below:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-25T23:47:20.030401Z","iopub.status.busy":"2021-06-25T23:47:20.030046Z","iopub.status.idle":"2021-06-25T23:47:20.045078Z","shell.execute_reply":"2021-06-25T23:47:20.044023Z","shell.execute_reply.started":"2021-06-25T23:47:20.030371Z"},"trusted":true},"outputs":[],"source":["# Build a character histogram\n","\n","histogram = dict()\n","text = 'We can know only that we know nothing. \\\n"," And that is the highest degree of human wisdom.' # From War and Peace\n","\n","for c in text:\n"," if c.isalpha(): # Test alphabet property\n"," c = c.lower() # Identify uppercase and lowercase\n"," if c in histogram:\n"," histogram[c] += 1\n"," else:\n"," histogram[c] = 1\n","\n","for key in histogram:\n"," print(key, end = ' ')\n","print()\n","for key in histogram:\n"," print(histogram[key], end = ' ')"]},{"cell_type":"markdown","metadata":{},"source":["In the above sample code, a *for* loop enumerates all the characters in the text variable. The goal is to calculate the occurrence count of distinct English letters. The histogram only calculates valid alphabets and also ignores the difference between uppercase and lowercase. Therefore, the algorithm used *c.isalpha()* and *c.lower()* functions. \n","\n","The expression *c in histogram* uses the *in* operator to search if the value of *c* is already a key in the dictionary. If yes, then the algorithm will add the occurrence plus one; otherwise, the dictionary will create a new (key, value) pair with the new value assigned to the initial value of 1.\n","\n","The final output of the alphabet histogram from the *text* quote indicates there are five occurrences of letter \"w\" or \"W\", seven occurrences of letter \"e\" or \"E\", etc.\n","\n","Next, we consider a problem of inverting a dictionary (key, value) pair. That is, creating a new dictionary whereby the existing dictionary's values are keys in the new dictionary, and vice versa. \n","\n","Let us first see the solution code:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-26T08:35:21.923474Z","iopub.status.busy":"2021-06-26T08:35:21.92287Z","iopub.status.idle":"2021-06-26T08:35:21.940109Z","shell.execute_reply":"2021-06-26T08:35:21.938759Z","shell.execute_reply.started":"2021-06-26T08:35:21.923436Z"},"trusted":true},"outputs":[],"source":["# Build a histogram dictionary\n","histogram = dict()\n","text = 'We can know only that we know nothing. And that is the highest degree of human wisdom'\n","\n","for c in text:\n"," if c.isalpha():\n"," c = c.lower()\n"," if c in histogram:\n"," histogram[c] += 1\n"," else:\n"," histogram[c] = 1\n","\n","def invert_dictionary(input_dictionary):\n"," '''\n"," Invert the mapping between keys and values of a dictionary\n"," Parameters\n"," Input: input_dictionary - a dict type\n"," Output: result - dict result\n"," '''\n","\n"," if type(input_dictionary)!=dict:\n"," raise TypeError('Argument must be dict type.')\n","\n"," result = dict()\n"," for key in input_dictionary:\n"," value = input_dictionary[key]\n"," if value not in result:\n"," result[value] = [key]\n"," else:\n"," result[value].append(key)\n","\n"," return result\n","\n","# print out the histogram\n","for key in histogram:\n"," print(key, end = ' ')\n","print()\n","for key in histogram:\n"," print(histogram[key], end = ' ')\n","print()\n","# Call invert_dictionary\n","print(\"Invert the histogram ...\")\n","inverse = invert_dictionary(histogram)\n","print(inverse)\n"]},{"cell_type":"markdown","metadata":{},"source":["The function *invert_dictionary() creates and returns a new dictionary, whose keys are the value entries of the input dictionary and the values are the keys of the input dictionary. In line 25, the *for* loop enumerates all the unique entries of *input_dictionary* with their key value assigned to the variable *key*. Then the corresponding *value* entry is retrieved in line 26. In lines 27 to 30, the *result* dictionary accepts *value* as its key value.\n","\n","Specifically, since *value* is retrieved from *input_dictionary*, it may not be unique. In the above case, multiple alphabets may have the same occurrence. Therefore, those keys from *input_dictionary* will need to be organized into a list corresponding to using their occurrence number as the key in the new dictionary.\n","\n","Python supports very efficient search to determine if a key is in a dictionary or not using the operator *in*, such as in line 27 we have: *if value not in result:*\n","\n","To demonstrate how efficient is the key search implemented in Python, let us evaluate another sample code below:"]},{"cell_type":"code","execution_count":2,"metadata":{"execution":{"iopub.execute_input":"2023-06-29T17:51:04.351670Z","iopub.status.busy":"2023-06-29T17:51:04.351398Z","iopub.status.idle":"2023-06-29T17:51:11.287255Z","shell.execute_reply":"2023-06-29T17:51:11.286626Z","shell.execute_reply.started":"2023-06-29T17:51:04.351644Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["Reading text file from url ... done\n","Generating 1M random tickers ... done\n","Searching a size-10 dictionary 1M times takes: 0.18244504928588867s\n","Searching a size-1000 dictionary 1M times takes: 0.14960026741027832s\n","Searching a size-5203 dictionary 1M times takes: 0.1539783477783203s\n"]}],"source":["# IMPORTANT: to run this script, you need to enable Internet in Kaggle. Default Kaggle kernels do not have Internet access\n","# Enabling Internet is on the right side of the Settings menu. \n","# If you run this Jupyter Notebook on your own computer, then make sure your computer has Internet access\n","\n","from urllib.request import urlopen\n","import random\n","import time\n","\n","Dictionary10 = dict()\n","Dictionary1000 = dict()\n","DictionaryTotal = dict()\n","file_url = \"http://www.nasdaqtrader.com/dynamic/symdir/nasdaqlisted.txt\" \n","\n","# Put IO functions in try -- finally\n","print('Reading text file from url ... ', end = ' ')\n","file = urlopen(file_url)\n","\n","# Create three dictionaries of different lengths\n","count = 0\n","for line in file:\n"," decoded_line = line.decode(\"utf-8\")\n"," count += 1\n"," ticker, info = decoded_line.split('|',1)\n"," if count<=10:\n"," Dictionary10[ticker] = info\n"," if count<=1000:\n"," Dictionary1000[ticker] = info\n"," DictionaryTotal[ticker] = info\n","\n","print('done')\n","\n","# Create 1M queries to time the performance of three dictionaries\n","print('Generating 1M random tickers ... ', end = ' ')\n","trial_total = 1000000\n","TICKER_LETTER = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'\n","search_list = []\n","for index in range(trial_total):\n"," new_random_ticker = ''\n"," for letter_index in range(random.randint(1,5)):\n"," new_random_ticker = new_random_ticker + (random.choice(TICKER_LETTER))\n","\n"," search_list.append(new_random_ticker)\n","print('done')\n","\n","# Test speed for query Dictionary10\n","begin_time = time.time()\n","for index in range(trial_total):\n"," query_result = search_list[index] in Dictionary10\n","elapsed_time = time.time() - begin_time\n","print(\"Searching a size-{0} dictionary 1M times takes: {1}s\".format(len(Dictionary10),\n"," elapsed_time))\n","\n","# Test speed for query Dictionary1000\n","begin_time = time.time()\n","for index in range(trial_total):\n"," query_result = search_list[index] in Dictionary1000\n","elapsed_time = time.time() - begin_time\n","print(\"Searching a size-{0} dictionary 1M times takes: {1}s\".format(len(Dictionary1000),\n"," elapsed_time))\n","\n","# Test speed for query DictionaryTotal\n","begin_time = time.time()\n","for index in range(trial_total):\n"," query_result = search_list[index] in DictionaryTotal\n","elapsed_time = time.time() - begin_time\n","print(\"Searching a size-{0} dictionary 1M times takes: {1}s\".format(len(DictionaryTotal),\n"," elapsed_time))"]},{"cell_type":"markdown","metadata":{},"source":["In this sample code, we demonstrated using a new Python function *urlopen()* from the imported module *urllib.request* to retrieve a text file from the Internet. The file \"nasdaqlisted.txt\" is a public file that lists all the companies currently listed on the United States Nasdaq stock exchange. Naturally, for any public company, it has a unique ticker number for trading, such as AAPL for Apple and GOOGL for Google's parent company Alphabet Inc.\n","\n","In the code, we break down each line in the text file to retrieve its ticker string as the key and the rest of the string as the value for one dictionary entry. For comparison purposes, we constructed three dictionaries of different sizes, from 10 entries to 1000 entries to the total 4023 entries.\n","\n","Then we will randomly query 1 million times to check if a random ticker string exists as a key in the three dictionaries. We observe the final results for their total time complexity. We see that regardless of the size of the dictionary, the random 1 million key query roughly costs the same amount of time, which is somewhat counter-intuitive. \n","\n","The constant-time key search algorithm in Python is related to the concept of \"hashable\" that we have mentioned above. We will explain in more details in the next lecture. For now, the good news is regardless of the size of a dictionary, the search for its keys seems to be quite efficient."]},{"cell_type":"markdown","metadata":{},"source":["Finally, the sample code below demonstrates how to convert the tuple type and dictionary type variables in Python. The code uses the cast functions *list()* and *dict()*. Grouping two lists of the same length into tuple pairs is by the function *zip()*."]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2022-04-30T23:09:30.509730Z","iopub.status.busy":"2022-04-30T23:09:30.509071Z","iopub.status.idle":"2022-04-30T23:09:30.520548Z","shell.execute_reply":"2022-04-30T23:09:30.519500Z","shell.execute_reply.started":"2022-04-30T23:09:30.509692Z"},"trusted":true},"outputs":[],"source":["# From list to zip into tuples\n","list1 = [1, 2, 3, 4]\n","list2 = ['one', 'two', 'three', 'four']\n","list3 = ['uno', 'due', 'tre']\n","for pair in zip(list1, list2, list3): print(pair)\n","# (1, 'one', 'uno')\n","# (2, 'two', 'due')\n","# (3, 'three', 'tre')\n","\n","print(\"List of tuples: \", list(zip(list1, list2, list3)))\n","\n","# From tuple to dictionary\n","tuple_list = [(1, 'one'), (2, 'two'), (3, 'three')]\n","D = dict(tuple_list)\n","print(\"Dictionary: \", D)"]},{"cell_type":"markdown","metadata":{},"source":["# Exercises\n","\n","1. Following the example of my_max() function in the lecture, code another function my_min(), which returns the minimal value from a variable number of input arguments.\n","\n","2. Use type() function to verify the variable type of two assignment values: ('a'), ('a', ). Please discuss the difference.\n","\n","3. Use id() function to code an example, which demonstrates that dictionary is a mutable variable type.\n","\n","4. Create a simple English-to-Italian dictionary, with the following five (key, value) pairs:\n","> (\"one\", \"uno\"), (\"two\", \"due\"), (\"three\", \"tre\"), (\"four\", \"quattro\"), (\"five\", \"cinque\")\n","\n"," Please code a program that performs the following two functions (can be implemented separately or jointly):\n"," 1. When a user inputs an English word from the dictionary keys, output the corresponding value to translate English to Italian.\n"," 2. When a user inputs an Italian word from the dictionary values, output the corresponding key to translate Italian to English.\n"," 3. In any of the above cases, it the corresponding (key, value) is not found, display a message: Translation is not available.\n"," \n","\n","5. Although a tuple is an immutable type, sometimes Python may successfully modify values inside a tuple, such as the code below. Explain why?"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-05-28T12:10:26.003097Z","iopub.status.busy":"2021-05-28T12:10:26.002738Z","iopub.status.idle":"2021-05-28T12:10:26.00935Z","shell.execute_reply":"2021-05-28T12:10:26.008289Z","shell.execute_reply.started":"2021-05-28T12:10:26.003065Z"},"trusted":true},"outputs":[],"source":["T = (0.11, [30, 35], 20, [40,45], 50)\n","T[1][0] = 3\n","print(T)"]},{"cell_type":"markdown","metadata":{},"source":["# Challenges\n","\n","1. Take the same text input:\n",">text = 'We can know only that we know nothing. And that is the highest degree of human wisdom.' # From War and Peace\n","\n"," Now use a dictionary type to construct a histogram. Each key in the dictionary is a unique word in the text string by identifying upper case and lower case letters, and the corresponding value is the count of occurrence in the text. For example, \"we\" is a valid key phrase from the text, and its occurrence value in the histogram is 2. Similarly, the histogram value for \"that\" is also two. Hint: Use string.split() to split a string into words separated by spaces. Use string.lower() to convert all words to lower cases, and remember to ignore symbols from the text as they are not counted as words."]}],"metadata":{"kernelspec":{"display_name":"Python 3","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.7.6"}},"nbformat":4,"nbformat_minor":4} +{"cells":[{"cell_type":"markdown","metadata":{},"source":["**Learning Python -- The Programming Language for Artificial Intelligence and Data Science**\n","\n","**Lecture 7: Tuples and Dictionaries**\n","\n","**By Allen Y. Yang, PhD**\n","\n","(c) Copyright Intelligent Racing Inc., 2021-2024. All rights reserved. Materials may NOT be distributed or used for any commercial purposes.\n"]},{"cell_type":"markdown","metadata":{},"source":["# Keywords\n","\n","* **tuple**: Keyword for the tuple data type in Python. Tuple is an immutable type.\n","* **dict**: Keyword for the dictionary data type in Python. Dictionary is a mutable type. Each element in a dictionary is a (key, value) pair.\n","* **Histogram**: A data structure that represents a list of unique elements and their frequency of occurances within some data."]},{"cell_type":"markdown","metadata":{},"source":["# Tuples\n","\n","A tuple variable stores an ordered sequence of values. The following examples show the three ways a tuple object can be assigned:\n"," 1. Using a pair of parentheses: t = ('A', 'B')\n"," 2. A sequence of values separated by commas without the parentheses: t = 'a', 'b', 'c', 'd', 'e'\n"," 3. Using tuple() function: t = tuple('abcde')"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-26T09:07:42.457852Z","iopub.status.busy":"2021-06-26T09:07:42.456626Z","iopub.status.idle":"2021-06-26T09:07:42.468893Z","shell.execute_reply":"2021-06-26T09:07:42.467966Z","shell.execute_reply.started":"2021-06-26T09:07:42.457801Z"},"trusted":true},"outputs":[],"source":["t = 'a', 'b', 'c', 'd', 'e'\n","print(t == tuple('abcde') )\n","\n","t = ('A', 'B') + t[2:]\n","print(t)\n","\n","print(() == tuple())"]},{"cell_type":"markdown","metadata":{},"source":["In many ways, tuples are similar to lists in Python. In the above example, we see that elements in a tuple can be addressed using square brackets just like in a list. Concatenating two tuples into a new tuple variable is denoted using the \"+\" operator. In the last example, an empty tuple is denoted using *()* or *tuple()*.\n","\n","A tuple may contain just one element. However, the way to declare a single-element tuple must be different from the way to declare a single variable. See the examples below:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-26T09:09:02.34211Z","iopub.status.busy":"2021-06-26T09:09:02.341229Z","iopub.status.idle":"2021-06-26T09:09:02.351823Z","shell.execute_reply":"2021-06-26T09:09:02.350894Z","shell.execute_reply.started":"2021-06-26T09:09:02.342069Z"},"trusted":true},"outputs":[],"source":["t = 'a', # Single-element tuple\n","s = ('a') # a string\n","print(type(t), type(s))\n","\n","t = 0, # Single-element tuple\n","i = (0) # an integer\n","print(type(t), type(i))"]},{"cell_type":"markdown","metadata":{},"source":["Different from lists, however, tuples are immutable type variables. Once created, tuple elements cannot be changed without forcing Python to create a new object. The following statement will return runtime error:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["t = 'a', 'b'\n","t[1] = 'c'"]},{"cell_type":"markdown","metadata":{},"source":["However, if tuple elements are themselves mutable, then the values of the elements can be changed without changing the tuple object itself. See the following examples:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-26T09:11:38.945441Z","iopub.status.busy":"2021-06-26T09:11:38.945046Z","iopub.status.idle":"2021-06-26T09:11:38.953129Z","shell.execute_reply":"2021-06-26T09:11:38.952125Z","shell.execute_reply.started":"2021-06-26T09:11:38.945404Z"},"trusted":true},"outputs":[],"source":["# Negative example: when the elements are not mutable\n","string = 'A'; number = 1\n","t = string, number\n","print('Initial value: ', t)\n","number = 2\n","print('Values are immutable: ', t)\n","\n","# Positive example: when the elements are mutable\n","l = list('abc')\n","t = l, l\n","print('Initial value: ', t)\n","l[2] = 'a'\n","print('Mutable elements: ', t)"]},{"cell_type":"markdown","metadata":{},"source":["In Python, a sequence of values can be compared to another sequence. The comparison is performed from left to right element-wise, with two exceptions:\n"," 1. Circuit breaker: When comparing \"<\" or \">\" relationships, the comparison will return True or False when the first non-identical position satisfies either \"<\" or \">\" relationships, and will disregard any possible situation after the position.\n"," 2. Element-wise comparison must be of the same type: When comparing two elements at the same sequence position, they must have the same type. Otherwise Python will return \"TypeError\"."]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["# String comparison with circuit breaker\n","print('abc'>'a')\n","\n","# Tuple comparison with circuit breaker\n","print( (1, 2, 'c') > (1, 2))\n","print( (1, 2, 'c') > (1, 3, 3))\n","\n","# List comparison\n","print([1, 2, 'c'] > [1, 2, 'a'])\n","print([1, 2, 'c'] > [1, 2, 3])"]},{"cell_type":"markdown","metadata":{},"source":["Another scenario where tuples are used is in defining input and output values of functions. Let us see some examples below:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-27T10:15:42.352307Z","iopub.status.busy":"2021-06-27T10:15:42.351928Z","iopub.status.idle":"2021-06-27T10:15:42.359526Z","shell.execute_reply":"2021-06-27T10:15:42.358292Z","shell.execute_reply.started":"2021-06-27T10:15:42.352258Z"},"trusted":true},"outputs":[],"source":["email = 'allenyang@berkeley.edu'\n","name, address = email.split('@') # return of the string.split() method is a tuple\n","print(name, address)\n","\n","poem = 'Roses are red, Violets are blue, Sugar is sweet'\n","a, b = poem.split(',', 1)\n","print(a, b)\n","\n","a, b, c = poem.split(',')\n","print(c)"]},{"cell_type":"markdown","metadata":{},"source":["In the above sample code, a built-in method of string type *split()* will return a tuple. For example, if we split a string of an email address using the pattern \"@\", then the substring before \"@\" will be assigned to the first element of the tuple *name* and the substring after \"@\" will be assigned to the second element \"address\". In the second example, *split()* may return a tuple of multiple elements, if the split pattern appears in multiple locations of the string. \n","\n","Tuples can be used to hold return values from a function. They can also be used to hold input argument values of variable length. Let us see another simple example below:"]},{"cell_type":"code","execution_count":5,"metadata":{"execution":{"iopub.execute_input":"2022-07-30T20:58:38.382747Z","iopub.status.busy":"2022-07-30T20:58:38.382339Z","iopub.status.idle":"2022-07-30T20:58:38.392033Z","shell.execute_reply":"2022-07-30T20:58:38.390298Z","shell.execute_reply.started":"2022-07-30T20:58:38.382708Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["3\n","4\n","Input length: 1\n","args = (1,)\n","1\n"]}],"source":["print(max(2, 3))\n","print(max(2, 3, 4))\n","\n","def my_max(*args):\n"," print('Input length: ', len(args))\n"," print('args = ', args)\n"," return(max(args))\n","\n","print(my_max(1))"]},{"cell_type":"markdown","metadata":{},"source":["In the above sample code, we see that some Python functions accept input arguments of variable length, such as *max()* and *print()*. In addition, using tuples, we can define our own functions that accept arguments of variable length. In the definition of *my_max()*, we use a reserved symbol \"*\" to indicate that args should be treated as a tuple to hold multiple input values of variable length. In Python, the \"*\" symbol used in this scenario is called the **packing operator**."]},{"cell_type":"markdown","metadata":{},"source":["# Dictionaries\n","\n","Dictionary can be viewed as a generalization of the list type. In the list type, each element is indexed by an unique integer in ascending order. In dictionaries, each element is a **(key, value)** pair. Furthermore, the value of a dictionary entry will be addressed by its corresponding unique key. \n","\n","Let us see a few examples below:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-27T10:58:35.412219Z","iopub.status.busy":"2021-06-27T10:58:35.41183Z","iopub.status.idle":"2021-06-27T10:58:35.419596Z","shell.execute_reply":"2021-06-27T10:58:35.418606Z","shell.execute_reply.started":"2021-06-27T10:58:35.412188Z"},"trusted":true},"outputs":[],"source":["D = {1: 'one', 2: 'two', 3: 'three'}\n","print(len(D))\n","print(D[2])\n","\n","English_to_Chinese={} # Define an empty dictionary\n","English_to_Chinese['one'] = '一'\n","English_to_Chinese['two'] = '二'\n","English_to_Chinese['three'] = '三'\n","\n","del(English_to_Chinese['two'])\n","print(English_to_Chinese)"]},{"cell_type":"markdown","metadata":{},"source":["In the first example above, the dictionary *D* has three entries, i.e., first entry has the (key, value) pair as (1, 'one), etc. Retrieving the entry value corresponding to the key value of 2 is by *D[2]*.\n","\n","The second example demonstrates more powerful ways to retrieve a dictionary's entry values. The key can be other variable types such as strings. We can create a dictionary for translation purposes, where a unique English word is used as the key, and its corresponding entry value is its translation in another language such as Chinese. \n","\n","In here, we should note that any immutable type value can be assigned to be a key. However, not all variable type can be used as a key value. Let us consider the counterexample below:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["English_to_Chinese={} \n","English_to_Chinese[[1, 2, 3]] = 'variable'"]},{"cell_type":"markdown","metadata":{},"source":["If you run the above code, Python will return a runtime error: \"TypeError: unhashable type: 'list'\". It shows list type variables cannot be used as valid keys. The concept of **hashable** mentioned in the error message will be discussed in more detail in the next class. \n","\n","One of the widely used applications for dictionaries is the construction of histograms. A histogram is a representation of the frequency of distinct data values. A histogram can be conveniently stored in a dictionary:\n","\n"," * Key: distinct data values\n"," * Value: occurrence frequency (absolute count or relative percentage)\n"," \n","Let us see the sample code below:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-25T23:47:20.030401Z","iopub.status.busy":"2021-06-25T23:47:20.030046Z","iopub.status.idle":"2021-06-25T23:47:20.045078Z","shell.execute_reply":"2021-06-25T23:47:20.044023Z","shell.execute_reply.started":"2021-06-25T23:47:20.030371Z"},"trusted":true},"outputs":[],"source":["# Build a character histogram\n","\n","histogram = dict()\n","text = 'We can know only that we know nothing. \\\n"," And that is the highest degree of human wisdom.' # From War and Peace\n","\n","for c in text:\n"," if c.isalpha(): # Test alphabet property\n"," c = c.lower() # Identify uppercase and lowercase\n"," if c in histogram:\n"," histogram[c] += 1\n"," else:\n"," histogram[c] = 1\n","\n","for key in histogram:\n"," print(key, end = ' ')\n","print()\n","for key in histogram:\n"," print(histogram[key], end = ' ')"]},{"cell_type":"markdown","metadata":{},"source":["In the above sample code, a *for* loop enumerates all the characters in the text variable. The goal is to calculate the occurrence count of distinct English letters. The histogram only calculates valid alphabets and also ignores the difference between uppercase and lowercase. Therefore, the algorithm used *c.isalpha()* and *c.lower()* functions. \n","\n","The expression *c in histogram* uses the *in* operator to search if the value of *c* is already a key in the dictionary. If yes, then the algorithm will add the occurrence plus one; otherwise, the dictionary will create a new (key, value) pair with the new value assigned to the initial value of 1.\n","\n","The final output of the alphabet histogram from the *text* quote indicates there are five occurrences of letter \"w\" or \"W\", seven occurrences of letter \"e\" or \"E\", etc.\n","\n","Next, we consider a problem of inverting a dictionary (key, value) pair. That is, creating a new dictionary whereby the existing dictionary's values are keys in the new dictionary, and vice versa. \n","\n","Let us first see the solution code:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-26T08:35:21.923474Z","iopub.status.busy":"2021-06-26T08:35:21.92287Z","iopub.status.idle":"2021-06-26T08:35:21.940109Z","shell.execute_reply":"2021-06-26T08:35:21.938759Z","shell.execute_reply.started":"2021-06-26T08:35:21.923436Z"},"trusted":true},"outputs":[],"source":["# Build a histogram dictionary\n","histogram = dict()\n","text = 'We can know only that we know nothing. And that is the highest degree of human wisdom'\n","\n","for c in text:\n"," if c.isalpha():\n"," c = c.lower()\n"," if c in histogram:\n"," histogram[c] += 1\n"," else:\n"," histogram[c] = 1\n","\n","def invert_dictionary(input_dictionary):\n"," '''\n"," Invert the mapping between keys and values of a dictionary\n"," Parameters\n"," Input: input_dictionary - a dict type\n"," Output: result - dict result\n"," '''\n","\n"," if type(input_dictionary)!=dict:\n"," raise TypeError('Argument must be dict type.')\n","\n"," result = dict()\n"," for key in input_dictionary:\n"," value = input_dictionary[key]\n"," if value not in result:\n"," result[value] = [key]\n"," else:\n"," result[value].append(key)\n","\n"," return result\n","\n","# print out the histogram\n","for key in histogram:\n"," print(key, end = ' ')\n","print()\n","for key in histogram:\n"," print(histogram[key], end = ' ')\n","print()\n","# Call invert_dictionary\n","print(\"Invert the histogram ...\")\n","inverse = invert_dictionary(histogram)\n","print(inverse)\n"]},{"cell_type":"markdown","metadata":{},"source":["The function *invert_dictionary() creates and returns a new dictionary, whose keys are the value entries of the input dictionary and the values are the keys of the input dictionary. In line 25, the *for* loop enumerates all the unique entries of *input_dictionary* with their key value assigned to the variable *key*. Then the corresponding *value* entry is retrieved in line 26. In lines 27 to 30, the *result* dictionary accepts *value* as its key value.\n","\n","Specifically, since *value* is retrieved from *input_dictionary*, it may not be unique. In the above case, multiple alphabets may have the same occurrence. Therefore, those keys from *input_dictionary* will need to be organized into a list corresponding to using their occurrence number as the key in the new dictionary.\n","\n","Python supports very efficient search to determine if a key is in a dictionary or not using the operator *in*, such as in line 27 we have: *if value not in result:*\n","\n","To demonstrate how efficient is the key search implemented in Python, let us evaluate another sample code below:"]},{"cell_type":"code","execution_count":2,"metadata":{"execution":{"iopub.execute_input":"2023-06-29T17:51:04.351670Z","iopub.status.busy":"2023-06-29T17:51:04.351398Z","iopub.status.idle":"2023-06-29T17:51:11.287255Z","shell.execute_reply":"2023-06-29T17:51:11.286626Z","shell.execute_reply.started":"2023-06-29T17:51:04.351644Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["Reading text file from url ... done\n","Generating 1M random tickers ... done\n","Searching a size-10 dictionary 1M times takes: 0.18244504928588867s\n","Searching a size-1000 dictionary 1M times takes: 0.14960026741027832s\n","Searching a size-5203 dictionary 1M times takes: 0.1539783477783203s\n"]}],"source":["# IMPORTANT: to run this script, you need to enable Internet in Kaggle. Default Kaggle kernels do not have Internet access\n","# Enabling Internet is on the right side of the Settings menu. \n","# If you run this Jupyter Notebook on your own computer, then make sure your computer has Internet access\n","\n","from urllib.request import urlopen\n","import random\n","import time\n","\n","Dictionary10 = dict()\n","Dictionary1000 = dict()\n","DictionaryTotal = dict()\n","file_url = \"http://www.nasdaqtrader.com/dynamic/symdir/nasdaqlisted.txt\" \n","\n","# Put IO functions in try -- finally\n","print('Reading text file from url ... ', end = ' ')\n","file = urlopen(file_url)\n","\n","# Create three dictionaries of different lengths\n","count = 0\n","for line in file:\n"," decoded_line = line.decode(\"utf-8\")\n"," count += 1\n"," ticker, info = decoded_line.split('|',1)\n"," if count<=10:\n"," Dictionary10[ticker] = info\n"," if count<=1000:\n"," Dictionary1000[ticker] = info\n"," DictionaryTotal[ticker] = info\n","\n","print('done')\n","\n","# Create 1M queries to time the performance of three dictionaries\n","print('Generating 1M random tickers ... ', end = ' ')\n","trial_total = 1000000\n","TICKER_LETTER = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'\n","search_list = []\n","for index in range(trial_total):\n"," new_random_ticker = ''\n"," for letter_index in range(random.randint(1,5)):\n"," new_random_ticker = new_random_ticker + (random.choice(TICKER_LETTER))\n","\n"," search_list.append(new_random_ticker)\n","print('done')\n","\n","# Test speed for query Dictionary10\n","begin_time = time.time()\n","for index in range(trial_total):\n"," query_result = search_list[index] in Dictionary10\n","elapsed_time = time.time() - begin_time\n","print(\"Searching a size-{0} dictionary 1M times takes: {1}s\".format(len(Dictionary10),\n"," elapsed_time))\n","\n","# Test speed for query Dictionary1000\n","begin_time = time.time()\n","for index in range(trial_total):\n"," query_result = search_list[index] in Dictionary1000\n","elapsed_time = time.time() - begin_time\n","print(\"Searching a size-{0} dictionary 1M times takes: {1}s\".format(len(Dictionary1000),\n"," elapsed_time))\n","\n","# Test speed for query DictionaryTotal\n","begin_time = time.time()\n","for index in range(trial_total):\n"," query_result = search_list[index] in DictionaryTotal\n","elapsed_time = time.time() - begin_time\n","print(\"Searching a size-{0} dictionary 1M times takes: {1}s\".format(len(DictionaryTotal),\n"," elapsed_time))"]},{"cell_type":"markdown","metadata":{},"source":["In this sample code, we demonstrated using a new Python function *urlopen()* from the imported module *urllib.request* to retrieve a text file from the Internet. The file \"nasdaqlisted.txt\" is a public file that lists all the companies currently listed on the United States Nasdaq stock exchange. Naturally, for any public company, it has a unique ticker number for trading, such as AAPL for Apple and GOOGL for Google's parent company Alphabet Inc.\n","\n","In the code, we break down each line in the text file to retrieve its ticker string as the key and the rest of the string as the value for one dictionary entry. For comparison purposes, we constructed three dictionaries of different sizes, from 10 entries to 1000 entries to the total 4023 entries.\n","\n","Then we will randomly query 1 million times to check if a random ticker string exists as a key in the three dictionaries. We observe the final results for their total time complexity. We see that regardless of the size of the dictionary, the random 1 million key query roughly costs the same amount of time, which is somewhat counter-intuitive. \n","\n","The constant-time key search algorithm in Python is related to the concept of \"hashable\" that we have mentioned above. We will explain in more details in the next lecture. For now, the good news is regardless of the size of a dictionary, the search for its keys seems to be quite efficient."]},{"cell_type":"markdown","metadata":{},"source":["Finally, the sample code below demonstrates how to convert the tuple type and dictionary type variables in Python. The code uses the cast functions *list()* and *dict()*. Grouping two lists of the same length into tuple pairs is by the function *zip()*."]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2022-04-30T23:09:30.509730Z","iopub.status.busy":"2022-04-30T23:09:30.509071Z","iopub.status.idle":"2022-04-30T23:09:30.520548Z","shell.execute_reply":"2022-04-30T23:09:30.519500Z","shell.execute_reply.started":"2022-04-30T23:09:30.509692Z"},"trusted":true},"outputs":[],"source":["# From list to zip into tuples\n","list1 = [1, 2, 3, 4]\n","list2 = ['one', 'two', 'three', 'four']\n","list3 = ['uno', 'due', 'tre']\n","for pair in zip(list1, list2, list3): print(pair)\n","# (1, 'one', 'uno')\n","# (2, 'two', 'due')\n","# (3, 'three', 'tre')\n","\n","print(\"List of tuples: \", list(zip(list1, list2, list3)))\n","\n","# From tuple to dictionary\n","tuple_list = [(1, 'one'), (2, 'two'), (3, 'three')]\n","D = dict(tuple_list)\n","print(\"Dictionary: \", D)"]},{"cell_type":"markdown","metadata":{},"source":["# Exercises\n","\n","1. Following the example of my_max() function in the lecture, code another function my_min(), which returns the minimal value from a variable number of input arguments.\n","\n","2. Use type() function to verify the variable type of two assignment values: ('a'), ('a', ). Please discuss the difference.\n","\n","3. Use id() function to code an example, which demonstrates that dictionary is a mutable variable type.\n","\n","4. Create a simple English-to-Italian dictionary, with the following five (key, value) pairs:\n","> (\"one\", \"uno\"), (\"two\", \"due\"), (\"three\", \"tre\"), (\"four\", \"quattro\"), (\"five\", \"cinque\")\n","\n"," Please code a program that performs the following two functions (can be implemented separately or jointly):\n"," 1. When a user inputs an English word from the dictionary keys, output the corresponding value to translate English to Italian.\n"," 2. When a user inputs an Italian word from the dictionary values, output the corresponding key to translate Italian to English.\n"," 3. In any of the above cases, it the corresponding (key, value) is not found, display a message: Translation is not available.\n"," \n","\n","5. Although a tuple is an immutable type, sometimes Python may successfully modify values inside a tuple, such as the code below. Explain why?"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":[]},{"cell_type":"code","execution_count":4,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["Input length: 6\n","args = (1, 2, 3, 4, 5, 6)\n","1\n"," \n"]}],"source":["\n","#1\n","def my_min(*args):\n"," print('Input length: ', len(args))\n"," print('args = ', args)\n"," return(min(args))\n","\n","print(my_min(1,2,3,4,5,6))\n","#2\n","x=('a')\n","y=('a',)\n","print(type(x), type(y))"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["#3\n","d = {'a': 1, 'b': 2, 'c': 3}\n","print(id(d))\n","d['a']=[4]\n","print(id(d))"]},{"cell_type":"code","execution_count":14,"metadata":{},"outputs":[],"source":["#4\n","words = {\"one\": \"uno\", \"two\": \"due\", \"three\": \"tre\", \"four\": \"quattro\", \"five\": \"cinque\"}\n","\n","def dictionary(word):\n"," word = input(\"type word\")\n","\n"," if word in words:\n"," return words[word]\n"," for english, italian in words.items():\n"," if word == italian:\n"," return english\n"," elif word == english:\n"," return italian\n"," else:\n"," print(\"Translation not available\")\n"]},{"cell_type":"code","execution_count":20,"metadata":{"execution":{"iopub.execute_input":"2021-05-28T12:10:26.003097Z","iopub.status.busy":"2021-05-28T12:10:26.002738Z","iopub.status.idle":"2021-05-28T12:10:26.00935Z","shell.execute_reply":"2021-05-28T12:10:26.008289Z","shell.execute_reply.started":"2021-05-28T12:10:26.003065Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["[30, 35]\n"]}],"source":["ls = [30,35]\n","T = (0.11, [30, 35], 20, [40,45], 50)\n","ls = 10\n","print(T[1])"]},{"cell_type":"markdown","metadata":{},"source":["# Challenges\n","\n","1. Take the same text input:\n",">text = 'We can know only that we know nothing. And that is the highest degree of human wisdom.' # From War and Peace\n","\n"," Now use a dictionary type to construct a histogram. Each key in the dictionary is a unique word in the text string by identifying upper case and lower case letters, and the corresponding value is the count of occurrence in the text. For example, \"we\" is a valid key phrase from the text, and its occurrence value in the histogram is 2. Similarly, the histogram value for \"that\" is also two. Hint: Use string.split() to split a string into words separated by spaces. Use string.lower() to convert all words to lower cases, and remember to ignore symbols from the text as they are not counted as words."]},{"cell_type":"code","execution_count":17,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["{'we': 2, 'can': 1, 'know': 2, 'only': 1, 'that': 2, 'and': 1, 'is': 1, 'the': 1, 'highest': 1, 'degree': 1, 'of': 1, 'human': 1}\n"]}],"source":["text = 'We can know only that we know nothing. And that is the highest degree of human wisdom.' # From War and Peace\n","dic = {}\n","for c in text.split(\" \"):\n"," if c.isalpha():\n"," c = c.lower()\n"," if c in dic:\n"," dic[c] +=1\n"," else:\n"," dic[c] = 1 \n","print(dic)\n"]}],"metadata":{"kernelspec":{"display_name":"Python 3","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.18"}},"nbformat":4,"nbformat_minor":4} diff --git a/Part One/1-8-sets-and-hashing.ipynb b/Part One/1-8-sets-and-hashing.ipynb index a6a72c3..18726b5 100644 --- a/Part One/1-8-sets-and-hashing.ipynb +++ b/Part One/1-8-sets-and-hashing.ipynb @@ -1 +1 @@ -{"cells":[{"cell_type":"markdown","metadata":{},"source":["**Learning Python -- The Programming Language for Artificial Intelligence and Data Science**\n","\n","**Lecture 8: Sets and Hashing**\n","\n","**By Allen Y. Yang, PhD**\n","\n","(c) Copyright Intelligent Racing Inc., 2021-2024. All rights reserved. Materials may NOT be distributed or used for any commercial purposes.\n"]},{"cell_type":"markdown","metadata":{},"source":["# Keywords\n","\n","* **set**: Keyword for the set data type in Python. A set variable may contain from empty set to multiple set elements, where every element must be unique in its value in the set.\n","* **Subset**: A subset of a set contains exclusively a portion of the set's elements, and hence the subset cannot contain any elements that are not in the set.\n","* **Superset**: The set with respect to its subset in the above definition is also called a superset. In other words, a superset of a set must contain all the elements in the set.\n","* **Hashing**: Hashing refers to a calculation by a hash function that maps an object to a fixed value.\n","* **MD5**: A popular hash function that generates 128-bit hash value, also known as Message-Digest algorithm version 5."]},{"cell_type":"markdown","metadata":{},"source":["# Definition\n","\n","A set is a collection of unique elements. Elements in a set act just like keys in a dictionary but without the (key, value) correspondence.\n","\n","Also both dictionary and set are mutable types. Therefore, updating a set or a dictionary by adding or removing elements or entries, respectively, does not force Python to create new objects."]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-28T02:40:13.591515Z","iopub.status.busy":"2021-06-28T02:40:13.591071Z","iopub.status.idle":"2021-06-28T02:40:13.601129Z","shell.execute_reply":"2021-06-28T02:40:13.599407Z","shell.execute_reply.started":"2021-06-28T02:40:13.59149Z"},"trusted":true},"outputs":[],"source":["D = {} # Important note, a pair of brackets is reserved to define an empty dictionary\n","D[1] = 'one'\n","\n","S = set() # Define an empty set used set() function\n","S.add('three') # add one element\n","print(S)\n","S.update(['t','h','r','e','e']) # Extend a set with another set\n","print(S)\n","\n","for element in S:\n"," print(element, end = ', ')\n"," \n","print()\n","print('e' in S) # Search if an element exists in a set"]},{"cell_type":"markdown","metadata":{},"source":["We see from the above example, that when elements are added into a set, its ordering is not defined in Python, namely, Python may store the elements in any particular order depending on the implementation. A user should not assume any particular order when using a *for* loop to enumerate its elements.\n","\n","Also pay attention to the result of *S.update('three')*. First *update()* function extends a set by adding another set or equivalent list or string. In the example, because set elements are required to be unique, so the two 'e' letters only appear once in the result after the set updates.\n","\n","In the last sample code, the operator *in* returns a boolean to verify if a variable value is in the set of elements. If the value is equal to one of the set elements, then the result is True; and vice versa."]},{"cell_type":"markdown","metadata":{},"source":["The following code shows removing an element of a set. \n"," * remove() method will enforce that the intended value for removal must exist in the set. If the value is not in the set, the function will return a runtime error, such as the last statement in the sample code below.\n"," * discard() method behaves the same as remove(), except that it does not enforce that the removal value must be in the set. In such cases, discard() simply ignores the operation without raising any messages."]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-28T02:43:28.430327Z","iopub.status.busy":"2021-06-28T02:43:28.430041Z","iopub.status.idle":"2021-06-28T02:43:28.461224Z","shell.execute_reply":"2021-06-28T02:43:28.459895Z","shell.execute_reply.started":"2021-06-28T02:43:28.430302Z"},"trusted":true},"outputs":[],"source":["S = {'h', 't', 'three', 'e', 'r'}\n","S.discard('two')\n","S.discard('t')\n","print(S)\n","\n","S.remove('t')"]},{"cell_type":"markdown","metadata":{},"source":["# Set Operations\n","\n"," * Union: s | t, or s.union(t).\n"," * Intersection: s & t, or s.intersection(t).\n"," * Difference: s - t, or s.difference(t).\n"," * Exclusive Or (XOR): s ^ t, or s.symmetric_difference(t)\n"," * Is superset: s.issuperset(t)\n"," * Is subset: s.issubset(t)\n"," \n","Note: XOR takes exclusive elements not in the intersection of the two sets."]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-28T02:48:02.715684Z","iopub.status.busy":"2021-06-28T02:48:02.715409Z","iopub.status.idle":"2021-06-28T02:48:02.725594Z","shell.execute_reply":"2021-06-28T02:48:02.724594Z","shell.execute_reply.started":"2021-06-28T02:48:02.71566Z"},"trusted":true},"outputs":[],"source":["s = {1, 2, 3, 4}; t = {3, 4, 5, 6}\n","\n","u = s | t\n","print(\"Union: \", u)\n","print(\"Intersection: \", s & t)\n","print(\"Difference: \", s - t)\n","\n","xor = s^t\n","print(\"XOR: \", xor)\n","\n","print(u.issuperset(s))\n","print(xor.issubset(s))"]},{"cell_type":"markdown","metadata":{},"source":["# Hashing\n","\n","Another similarity between dictionary and set is that searching elements in a set is very efficients, close to a constant-time operation regardless of the length of the set. The fundamental algorithm to achieve near constant-time search performance is using *hashing* techniques.\n","\n","First, let us revise the dictionary search example in the last lecture to use the set structure:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-28T02:54:00.708102Z","iopub.status.busy":"2021-06-28T02:54:00.707736Z","iopub.status.idle":"2021-06-28T02:54:06.309084Z","shell.execute_reply":"2021-06-28T02:54:06.307637Z","shell.execute_reply.started":"2021-06-28T02:54:00.708071Z"},"trusted":true},"outputs":[],"source":["# IMPORTANT: to run this script, you need to enable Internet in Kaggle. Default Kaggle kernels do not have Internet access\n","# Enabling Internet is on the right side of the Settings menu. \n","# If you run this Jupyter Notebook on your own computer, then make sure your computer has Internet access\n","\n","from urllib.request import urlopen\n","import random\n","import time\n","\n","Set10 = set()\n","Set1000 = set()\n","SetTotal = set()\n","file_url = \"http://www.nasdaqtrader.com/dynamic/symdir/nasdaqlisted.txt\" \n","\n","# Put IO functions in try -- finally\n","print('Reading text file from url ... ', end = ' ')\n","file = urlopen(file_url)\n","\n","# Create three dictionaries of different lengths\n","count = 0\n","for line in file:\n"," decoded_line = line.decode(\"utf-8\")\n"," count += 1\n"," ticker, info = decoded_line.split('|',1)\n"," if count<=10:\n"," Set10.add(ticker) \n"," if count<=1000:\n"," Set1000.add(ticker)\n"," SetTotal.add(ticker)\n","\n","print('done')\n","\n","# Create 1M queries to time the performance of three dictionaries\n","print('Generating 1M random tickers ... ', end = ' ')\n","trial_total = 1000000\n","TICKER_LETTER = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'\n","search_list = []\n","for index in range(trial_total):\n"," new_random_ticker = ''\n"," for letter_index in range(random.randint(1,5)):\n"," new_random_ticker = new_random_ticker + (random.choice(TICKER_LETTER))\n","\n"," search_list.append(new_random_ticker)\n","print('done')\n","\n","# Test speed for query Dictionary10\n","begin_time = time.time()\n","for index in range(trial_total):\n"," query_result = search_list[index] in Set10\n","elapsed_time = time.time() - begin_time\n","print(\"Searching a size-{0} set 1M times takes: {1}s\".format(len(Set10),\n"," elapsed_time))\n","\n","# Test speed for query Dictionary1000\n","begin_time = time.time()\n","for index in range(trial_total):\n"," query_result = search_list[index] in Set1000\n","elapsed_time = time.time() - begin_time\n","print(\"Searching a size-{0} set 1M times takes: {1}s\".format(len(Set1000),\n"," elapsed_time))\n","\n","# Test speed for query DictionaryTotal\n","begin_time = time.time()\n","for index in range(trial_total):\n"," query_result = search_list[index] in SetTotal\n","elapsed_time = time.time() - begin_time\n","print(\"Searching a size-{0} dictionary 1M times takes: {1}s\".format(len(SetTotal),\n"," elapsed_time))"]},{"cell_type":"markdown","metadata":{},"source":["We see in the above sample code that searching set elements costs close to constant time regardless whether the size of the set is 10 or 1000 or 4023. In order to support hash search, valid elements in a set must be **hashable**. Next, we discuss this concept"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-28T02:59:19.882424Z","iopub.status.busy":"2021-06-28T02:59:19.882132Z","iopub.status.idle":"2021-06-28T02:59:19.898851Z","shell.execute_reply":"2021-06-28T02:59:19.897093Z","shell.execute_reply.started":"2021-06-28T02:59:19.882397Z"},"trusted":true},"outputs":[],"source":["i = 5; print(i.__hash__())\n","\n","s = \"Hello World!\"; print(s.__hash__())\n","\n","t = (i, s); print(t.__hash__())\n","\n","Set = {s}; print(Set.__hash__())"]},{"cell_type":"markdown","metadata":{},"source":["The above sample code returns three integer numbers and one runtime error. Let us take a closer look. \n","\n","In Python, if a variable type is hashable, there will be a built-in method called *__hash__()*. One can treat the hash function return as a hashtag that uniquely represent the content of the variable in the memory. The hash search will use the hash function as *the Table of Contents* in a real-world dictionary to quickly determine if a complex element value is in a dictionary or a set:\n"," * If the hash values of all set elements are not equal to the hash value of a query, then it is guaranteed that the query is *not* in the set;\n"," * If the hash value of a query is equal to one of the existing hash values, then the search algorithm only needs to compare with those that have the same hash value.\n"," \n","In other words, a hash function is a many-to-one mapping.\n","\n","To achieve this goal, a variable will calculate its hash value at the inception of its values. As a result, Python does not allow a hashable variable to change its content after its creation. This is equivalent to putting contents in a dictionary: After the Table of Contents is created, the content itself is not allowed to change. Otherwise, the reference from the Table of Contents does not faithfully represent the content. \n","\n","**Consequently, we say that a set does not have a hash value because it is a mutable variable type. Furthermore, its elements must be immutable so that each of its elements has a hash value to facilitate fast search of the set elements.**\n","\n","Let us see another example below:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["Set = {[1, 2]} # This assignment will return an error because adding a list (mutable) as a set element is not permitted"]},{"cell_type":"markdown","metadata":{},"source":["Hash function creates a Table of Contents to capture a snapshot of potentially complex data content. Examples include a typical library's call number system, and storing user passwords in modern operating systems. Next, let us create a very simple hash function called division hashing:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-28T06:09:13.297562Z","iopub.status.busy":"2021-06-28T06:09:13.297147Z","iopub.status.idle":"2021-06-28T06:09:13.308548Z","shell.execute_reply":"2021-06-28T06:09:13.307633Z","shell.execute_reply.started":"2021-06-28T06:09:13.297527Z"},"trusted":true},"outputs":[],"source":["quotes = ['We can know only that we know nothing. And that is the highest degree of human wisdom.',\\\n"," 'Nothing is so necessary for a young man as the company of intelligent women.',\\\n"," 'The strongest of all warriors are these two — Time and Patience.',\\\n"," 'There is no greatness where there is not simplicity, goodness, and truth.'\n"," ]\n","\n","def division_hashing(text):\n"," global hash_prime_number\n"," hash_prime_number = 101\n","\n"," sum = 0\n"," for c in text:\n"," sum = sum*256 + ord(c) # Change a character to its ASCII value\n","\n"," return sum % hash_prime_number\n","\n","print(division_hashing(quotes[0]), division_hashing(quotes[1]), division_hashing(quotes[2]),\n"," division_hashing(quotes[3]))\n","\n","# Altered text with high probability leads to different hash value\n","print(division_hashing(quotes[0][1:]), division_hashing(quotes[1][1:]), division_hashing(quotes[2][1:]),\n"," division_hashing(quotes[3][1:])) "]},{"cell_type":"markdown","metadata":{},"source":["In the above sample code, we see hashing can be conveniently used to verify the integrity of the data. For the four quotes, we use *division_hashing()* function to obtain a hash value for each quote. If some of the text is altered in the quotes (for example, due to transmission error or due to malicious attack), a user can be alerted that with high probability, their corresponding hash values are also different."]},{"cell_type":"markdown","metadata":{},"source":["Finally, various Python implementations often come with a library of supported built-in hash functions, called hashlib. One can call *hashlib.algorithms_guaranteed* to display a list of supported methods in different systems."]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-28T06:15:17.130626Z","iopub.status.busy":"2021-06-28T06:15:17.130298Z","iopub.status.idle":"2021-06-28T06:15:17.140028Z","shell.execute_reply":"2021-06-28T06:15:17.139131Z","shell.execute_reply.started":"2021-06-28T06:15:17.130597Z"},"trusted":true},"outputs":[],"source":["import hashlib\n","print(hashlib.algorithms_guaranteed)\n","\n","hash_md5 = hashlib.md5()\n","text = b'We can know only that we know nothing.'\n","hash_md5.update(text)\n","print(hash_md5.hexdigest())\n","\n","hash_md5 = hashlib.md5()\n","print(type('W'.encode('utf-8')), type('W'))\n","for b in text:\n"," hash_md5.update(chr(b).encode('utf-8'))\n"," \n","print(hash_md5.hexdigest())\n"]},{"cell_type":"markdown","metadata":{},"source":["We can see from the above sample code, that we can select any of the supported algorithms in Python to create a hash function object. In the example, we choose MD5 algorithm. The algorithm uses the built-in method *update()* to calculate the hash value of a text string. The function can be called to process the entire text as a whole, or iteratively letter by letter as demonstrated in the *for* loop. The hex digest of the hash values are identical from two approaches.\n","\n","Finally, we note that the hash functions in hashlib receive encoding content only as byte streams. A byte stream provides the underlying data of the variable stored in the memory regardless of its type. The conversion from a string type to a byte type is by either adding a letter *b* prefix in front of the text, or calling a built-in function *encode('utf-8')*."]},{"cell_type":"markdown","metadata":{},"source":["# Exercises\n","\n","1. Given a dictionary input, please code a function that extracts all its key elements into a set variable as the return value of the function.\n","\n","2. Implement your own version of the set function *issuperset(set_1, set_2)*, which takes in two set input arguments and output True if the first argument is a superset of the second argument.\n","\n","3. Implement your own version of the set function *XOR(set_1, set_2)*, which takes in two set input arguments and output their symmetric difference.\n","\n","4. Let a quote string equal to 'We can know only that we know nothing. And that is the highest degree of human wisdom.' Then use the above division_hashing() function to find a substring from the quote string that has the same hash value. Note, if hash_prime_number = 101 as set in the code above, the hash value of the full quote string should be 20.\n","\n","5. Debug:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-05-28T12:53:16.599147Z","iopub.status.busy":"2021-05-28T12:53:16.598801Z","iopub.status.idle":"2021-05-28T12:53:16.614194Z","shell.execute_reply":"2021-05-28T12:53:16.612537Z","shell.execute_reply.started":"2021-05-28T12:53:16.59912Z"},"trusted":true},"outputs":[],"source":["animals = ['cat', 'dog', 'horse', 'cow']\n","print(animals[0].__hash__())\n","print(animals.__hash__())"]},{"cell_type":"markdown","metadata":{},"source":["# Challenges\n","\n","1. In calculating the hash value of an integer shown below, we can notice that in many Python implementations, its hash value is exactly equal to the integer value. This is purposely designed to prevent assigning duplicate copies of small integer values in memory. In other words, for small integer values, each integer value is reserved to have only one copy and one memory address.\n","\n","> i = 5; print(i.\\_\\_hash\\_\\_())\n","\n","Write a program to test in your Python environment, what is the smallest integer value where the integer value and its hash value differ. Hint: This value will need to be very large, due to the fact that Python 3 supports storing integers of arbitrary large value but a hash function value cannot be arbitrarily large. As a result, if your strategy is to increase the i value in a loop, it will be wise to adaptively change the step size such that the loop does not have to enumerate all the integers before the smallest integer value that satsifies the condition is found.\n","\n","\n","2. Assume a transaction string is \"Satoshi sends 50 BTC to Hal\". Then use SHA256 hash algorithm to hash the string plus 4 consecutive random bytes called nounce, namely, string + nounce_1 + nounce_2 + nounce_3 + nounce_4.\n","\n","The process should continue trying the combinations of the string plus 4 consecutive random bytes until the hexdigest() result has one leading \"0\" in the hash string. Then change the requirement for the number of leading \"0\"s to two digits and three digits, and observe the change in time complexity in finding one combination.\n","\n","(Hint: 1. Generating one random byte can be done by random.randint(0,255); 2. Adding the string and four bytes into the hash value can be done by using the update() function first on the string and then consecutively on the four randomly generated bytes)"]}],"metadata":{"kernelspec":{"display_name":"Python 3","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.6.4"}},"nbformat":4,"nbformat_minor":4} +{"cells":[{"cell_type":"markdown","metadata":{},"source":["**Learning Python -- The Programming Language for Artificial Intelligence and Data Science**\n","\n","**Lecture 8: Sets and Hashing**\n","\n","**By Allen Y. Yang, PhD**\n","\n","(c) Copyright Intelligent Racing Inc., 2021-2024. All rights reserved. Materials may NOT be distributed or used for any commercial purposes.\n"]},{"cell_type":"markdown","metadata":{},"source":["# Keywords\n","\n","* **set**: Keyword for the set data type in Python. A set variable may contain from empty set to multiple set elements, where every element must be unique in its value in the set.\n","* **Subset**: A subset of a set contains exclusively a portion of the set's elements, and hence the subset cannot contain any elements that are not in the set.\n","* **Superset**: The set with respect to its subset in the above definition is also called a superset. In other words, a superset of a set must contain all the elements in the set.\n","* **Hashing**: Hashing refers to a calculation by a hash function that maps an object to a fixed value.\n","* **MD5**: A popular hash function that generates 128-bit hash value, also known as Message-Digest algorithm version 5."]},{"cell_type":"markdown","metadata":{},"source":["# Definition\n","\n","A set is a collection of unique elements. Elements in a set act just like keys in a dictionary but without the (key, value) correspondence.\n","\n","Also both dictionary and set are mutable types. Therefore, updating a set or a dictionary by adding or removing elements or entries, respectively, does not force Python to create new objects."]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-28T02:40:13.591515Z","iopub.status.busy":"2021-06-28T02:40:13.591071Z","iopub.status.idle":"2021-06-28T02:40:13.601129Z","shell.execute_reply":"2021-06-28T02:40:13.599407Z","shell.execute_reply.started":"2021-06-28T02:40:13.59149Z"},"trusted":true},"outputs":[],"source":["D = {} # Important note, a pair of brackets is reserved to define an empty dictionary\n","D[1] = 'one'\n","\n","S = set() # Define an empty set used set() function\n","S.add('three') # add one element\n","print(S)\n","S.update(['t','h','r','e','e']) # Extend a set with another set\n","print(S)\n","\n","for element in S:\n"," print(element, end = ', ')\n"," \n","print()\n","print('e' in S) # Search if an element exists in a set"]},{"cell_type":"markdown","metadata":{},"source":["We see from the above example, that when elements are added into a set, its ordering is not defined in Python, namely, Python may store the elements in any particular order depending on the implementation. A user should not assume any particular order when using a *for* loop to enumerate its elements.\n","\n","Also pay attention to the result of *S.update('three')*. First *update()* function extends a set by adding another set or equivalent list or string. In the example, because set elements are required to be unique, so the two 'e' letters only appear once in the result after the set updates.\n","\n","In the last sample code, the operator *in* returns a boolean to verify if a variable value is in the set of elements. If the value is equal to one of the set elements, then the result is True; and vice versa."]},{"cell_type":"markdown","metadata":{},"source":["The following code shows removing an element of a set. \n"," * remove() method will enforce that the intended value for removal must exist in the set. If the value is not in the set, the function will return a runtime error, such as the last statement in the sample code below.\n"," * discard() method behaves the same as remove(), except that it does not enforce that the removal value must be in the set. In such cases, discard() simply ignores the operation without raising any messages."]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-28T02:43:28.430327Z","iopub.status.busy":"2021-06-28T02:43:28.430041Z","iopub.status.idle":"2021-06-28T02:43:28.461224Z","shell.execute_reply":"2021-06-28T02:43:28.459895Z","shell.execute_reply.started":"2021-06-28T02:43:28.430302Z"},"trusted":true},"outputs":[],"source":["S = {'h', 't', 'three', 'e', 'r'}\n","S.discard('two')\n","S.discard('t')\n","print(S)\n","\n","S.remove('t')"]},{"cell_type":"markdown","metadata":{},"source":["# Set Operations\n","\n"," * Union: s | t, or s.union(t).\n"," * Intersection: s & t, or s.intersection(t).\n"," * Difference: s - t, or s.difference(t).\n"," * Exclusive Or (XOR): s ^ t, or s.symmetric_difference(t)\n"," * Is superset: s.issuperset(t)\n"," * Is subset: s.issubset(t)\n"," \n","Note: XOR takes exclusive elements not in the intersection of the two sets."]},{"cell_type":"code","execution_count":1,"metadata":{"execution":{"iopub.execute_input":"2021-06-28T02:48:02.715684Z","iopub.status.busy":"2021-06-28T02:48:02.715409Z","iopub.status.idle":"2021-06-28T02:48:02.725594Z","shell.execute_reply":"2021-06-28T02:48:02.724594Z","shell.execute_reply.started":"2021-06-28T02:48:02.71566Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["Union: {1, 2, 3, 4, 5, 6}\n","Intersection: {3, 4}\n","Difference: {1, 2}\n","XOR: {1, 2, 5, 6}\n","True\n","False\n"]}],"source":["s = {1, 2, 3, 4}; t = {3, 4, 5, 6}\n","\n","u = s | t\n","print(\"Union: \", u)\n","print(\"Intersection: \", s & t)\n","print(\"Difference: \", s - t)\n","\n","xor = s^t\n","print(\"XOR: \", xor)\n","\n","print(u.issuperset(s))\n","print(xor.issubset(s))"]},{"cell_type":"markdown","metadata":{},"source":["# Hashing\n","\n","Another similarity between dictionary and set is that searching elements in a set is very efficients, close to a constant-time operation regardless of the length of the set. The fundamental algorithm to achieve near constant-time search performance is using *hashing* techniques.\n","\n","First, let us revise the dictionary search example in the last lecture to use the set structure:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-28T02:54:00.708102Z","iopub.status.busy":"2021-06-28T02:54:00.707736Z","iopub.status.idle":"2021-06-28T02:54:06.309084Z","shell.execute_reply":"2021-06-28T02:54:06.307637Z","shell.execute_reply.started":"2021-06-28T02:54:00.708071Z"},"trusted":true},"outputs":[],"source":["# IMPORTANT: to run this script, you need to enable Internet in Kaggle. Default Kaggle kernels do not have Internet access\n","# Enabling Internet is on the right side of the Settings menu. \n","# If you run this Jupyter Notebook on your own computer, then make sure your computer has Internet access\n","\n","from urllib.request import urlopen\n","import random\n","import time\n","\n","Set10 = set()\n","Set1000 = set()\n","SetTotal = set()\n","file_url = \"http://www.nasdaqtrader.com/dynamic/symdir/nasdaqlisted.txt\" \n","\n","# Put IO functions in try -- finally\n","print('Reading text file from url ... ', end = ' ')\n","file = urlopen(file_url)\n","\n","# Create three dictionaries of different lengths\n","count = 0\n","for line in file:\n"," decoded_line = line.decode(\"utf-8\")\n"," count += 1\n"," ticker, info = decoded_line.split('|',1)\n"," if count<=10:\n"," Set10.add(ticker) \n"," if count<=1000:\n"," Set1000.add(ticker)\n"," SetTotal.add(ticker)\n","\n","print('done')\n","\n","# Create 1M queries to time the performance of three dictionaries\n","print('Generating 1M random tickers ... ', end = ' ')\n","trial_total = 1000000\n","TICKER_LETTER = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'\n","search_list = []\n","for index in range(trial_total):\n"," new_random_ticker = ''\n"," for letter_index in range(random.randint(1,5)):\n"," new_random_ticker = new_random_ticker + (random.choice(TICKER_LETTER))\n","\n"," search_list.append(new_random_ticker)\n","print('done')\n","\n","# Test speed for query Dictionary10\n","begin_time = time.time()\n","for index in range(trial_total):\n"," query_result = search_list[index] in Set10\n","elapsed_time = time.time() - begin_time\n","print(\"Searching a size-{0} set 1M times takes: {1}s\".format(len(Set10),\n"," elapsed_time))\n","\n","# Test speed for query Dictionary1000\n","begin_time = time.time()\n","for index in range(trial_total):\n"," query_result = search_list[index] in Set1000\n","elapsed_time = time.time() - begin_time\n","print(\"Searching a size-{0} set 1M times takes: {1}s\".format(len(Set1000),\n"," elapsed_time))\n","\n","# Test speed for query DictionaryTotal\n","begin_time = time.time()\n","for index in range(trial_total):\n"," query_result = search_list[index] in SetTotal\n","elapsed_time = time.time() - begin_time\n","print(\"Searching a size-{0} dictionary 1M times takes: {1}s\".format(len(SetTotal),\n"," elapsed_time))"]},{"cell_type":"markdown","metadata":{},"source":["We see in the above sample code that searching set elements costs close to constant time regardless whether the size of the set is 10 or 1000 or 4023. In order to support hash search, valid elements in a set must be **hashable**. Next, we discuss this concept"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-28T02:59:19.882424Z","iopub.status.busy":"2021-06-28T02:59:19.882132Z","iopub.status.idle":"2021-06-28T02:59:19.898851Z","shell.execute_reply":"2021-06-28T02:59:19.897093Z","shell.execute_reply.started":"2021-06-28T02:59:19.882397Z"},"trusted":true},"outputs":[],"source":["i = 5; print(i.__hash__())\n","\n","s = \"Hello World!\"; print(s.__hash__())\n","\n","t = (i, s); print(t.__hash__())\n","\n","Set = {s}; print(Set.__hash__())"]},{"cell_type":"markdown","metadata":{},"source":["The above sample code returns three integer numbers and one runtime error. Let us take a closer look. \n","\n","In Python, if a variable type is hashable, there will be a built-in method called *__hash__()*. One can treat the hash function return as a hashtag that uniquely represent the content of the variable in the memory. The hash search will use the hash function as *the Table of Contents* in a real-world dictionary to quickly determine if a complex element value is in a dictionary or a set:\n"," * If the hash values of all set elements are not equal to the hash value of a query, then it is guaranteed that the query is *not* in the set;\n"," * If the hash value of a query is equal to one of the existing hash values, then the search algorithm only needs to compare with those that have the same hash value.\n"," \n","In other words, a hash function is a many-to-one mapping.\n","\n","To achieve this goal, a variable will calculate its hash value at the inception of its values. As a result, Python does not allow a hashable variable to change its content after its creation. This is equivalent to putting contents in a dictionary: After the Table of Contents is created, the content itself is not allowed to change. Otherwise, the reference from the Table of Contents does not faithfully represent the content. \n","\n","**Consequently, we say that a set does not have a hash value because it is a mutable variable type. Furthermore, its elements must be immutable so that each of its elements has a hash value to facilitate fast search of the set elements.**\n","\n","Let us see another example below:"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["Set = {[1, 2]} # This assignment will return an error because adding a list (mutable) as a set element is not permitted"]},{"cell_type":"markdown","metadata":{},"source":["Hash function creates a Table of Contents to capture a snapshot of potentially complex data content. Examples include a typical library's call number system, and storing user passwords in modern operating systems. Next, let us create a very simple hash function called division hashing:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-28T06:09:13.297562Z","iopub.status.busy":"2021-06-28T06:09:13.297147Z","iopub.status.idle":"2021-06-28T06:09:13.308548Z","shell.execute_reply":"2021-06-28T06:09:13.307633Z","shell.execute_reply.started":"2021-06-28T06:09:13.297527Z"},"trusted":true},"outputs":[],"source":["quotes = ['We can know only that we know nothing. And that is the highest degree of human wisdom.',\\\n"," 'Nothing is so necessary for a young man as the company of intelligent women.',\\\n"," 'The strongest of all warriors are these two — Time and Patience.',\\\n"," 'There is no greatness where there is not simplicity, goodness, and truth.'\n"," ]\n","\n","def division_hashing(text):\n"," global hash_prime_number\n"," hash_prime_number = 101\n","\n"," sum = 0\n"," for c in text:\n"," sum = sum*256 + ord(c) # Change a character to its ASCII value\n","\n"," return sum % hash_prime_number\n","\n","print(division_hashing(quotes[0]), division_hashing(quotes[1]), division_hashing(quotes[2]),\n"," division_hashing(quotes[3]))\n","\n","# Altered text with high probability leads to different hash value\n","print(division_hashing(quotes[0][1:]), division_hashing(quotes[1][1:]), division_hashing(quotes[2][1:]),\n"," division_hashing(quotes[3][1:])) "]},{"cell_type":"markdown","metadata":{},"source":["In the above sample code, we see hashing can be conveniently used to verify the integrity of the data. For the four quotes, we use *division_hashing()* function to obtain a hash value for each quote. If some of the text is altered in the quotes (for example, due to transmission error or due to malicious attack), a user can be alerted that with high probability, their corresponding hash values are also different."]},{"cell_type":"markdown","metadata":{},"source":["Finally, various Python implementations often come with a library of supported built-in hash functions, called hashlib. One can call *hashlib.algorithms_guaranteed* to display a list of supported methods in different systems."]},{"cell_type":"code","execution_count":20,"metadata":{"execution":{"iopub.execute_input":"2021-06-28T06:15:17.130626Z","iopub.status.busy":"2021-06-28T06:15:17.130298Z","iopub.status.idle":"2021-06-28T06:15:17.140028Z","shell.execute_reply":"2021-06-28T06:15:17.139131Z","shell.execute_reply.started":"2021-06-28T06:15:17.130597Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["{'shake_128', 'md5', 'sha3_384', 'sha3_256', 'sha3_512', 'shake_256', 'blake2b', 'sha224', 'sha1', 'sha256', 'sha3_224', 'sha384', 'sha512', 'blake2s'}\n","e0da48d356c6e6a3fd817b6f1d0a103e\n"," \n","e0da48d356c6e6a3fd817b6f1d0a103e\n"]}],"source":["import hashlib\n","print(hashlib.algorithms_guaranteed)\n","\n","hash_md5 = hashlib.md5()\n","text = b'We can know only that we know nothing.'\n","hash_md5.update(text)\n","print(hash_md5.hexdigest())\n","\n","hash_md5 = hashlib.md5()\n","print(type('W'.encode('utf-8')), type('W'))\n","for b in text:\n"," hash_md5.update(chr(b).encode('utf-8'))\n"," \n","print(hash_md5.hexdigest())\n"]},{"cell_type":"markdown","metadata":{},"source":["We can see from the above sample code, that we can select any of the supported algorithms in Python to create a hash function object. In the example, we choose MD5 algorithm. The algorithm uses the built-in method *update()* to calculate the hash value of a text string. The function can be called to process the entire text as a whole, or iteratively letter by letter as demonstrated in the *for* loop. The hex digest of the hash values are identical from two approaches.\n","\n","Finally, we note that the hash functions in hashlib receive encoding content only as byte streams. A byte stream provides the underlying data of the variable stored in the memory regardless of its type. The conversion from a string type to a byte type is by either adding a letter *b* prefix in front of the text, or calling a built-in function *encode('utf-8')*."]},{"cell_type":"markdown","metadata":{},"source":["# Exercises\n","\n","1. Given a dictionary input, please code a function that extracts all its key elements into a set variable as the return value of the function.\n","\n","2. Implement your own version of the set function *issuperset(set_1, set_2)*, which takes in two set input arguments and output True if the first argument is a superset of the second argument.\n","\n","3. Implement your own version of the set function *XOR(set_1, set_2)*, which takes in two set input arguments and output their symmetric difference.\n","\n","4. Let a quote string equal to 'We can know only that we know nothing. And that is the highest degree of human wisdom.' Then use the above division_hashing() function to find a substring from the quote string that has the same hash value. Note, if hash_prime_number = 101 as set in the code above, the hash value of the full quote string should be 20.\n","\n","5. Debug:"]},{"cell_type":"code","execution_count":36,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["15\n"]}],"source":["words = {1:'hi', 2:'there', 3:'i', 4:'like', 5:'coding'}\n","sum = 0\n","for num, word in words.items():\n"," sum = sum + num\n","print(sum)\n","\n"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def issuperset(s1,s2):\n"," for i in s2:\n"," if i not in s1:\n"," return False\n"," else:\n"," return True"]},{"cell_type":"code","execution_count":38,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["{1, 2, 3, 7, 8, 9}\n"]}],"source":["s = {0,1,2,3,4,5,6}\n","t = {4,5,6,7,8,9,0}\n","xor = s^t\n","print(xor)"]},{"cell_type":"code","execution_count":39,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["y\n","20\n"]}],"source":["quote ='We can know only that we know nothing. And that is the highest degree of human wisdom.'\n","\n","def division_hashing(text):\n"," global hash_prime_number\n"," hash_prime_number = 101\n","\n"," sum = 0\n"," for c in text:\n"," sum = sum*256 + ord(c) # Change a character to its ASCII value\n","\n"," return sum % hash_prime_number\n","full_quote_hash = division_hashing(quote)\n","found = False\n","substring_result = \"\"\n","for start in range(len(quote)):\n"," for end in range(start + 1, len(quote) + 1):\n"," substring = quote[start:end]\n"," if division_hashing(substring) == full_quote_hash:\n"," substring_result = substring\n"," found = True\n"," if found:\n"," break\n","print(substring_result)\n","print(division_hashing(substring_result))"]},{"cell_type":"code","execution_count":19,"metadata":{"execution":{"iopub.execute_input":"2021-05-28T12:53:16.599147Z","iopub.status.busy":"2021-05-28T12:53:16.598801Z","iopub.status.idle":"2021-05-28T12:53:16.614194Z","shell.execute_reply":"2021-05-28T12:53:16.612537Z","shell.execute_reply.started":"2021-05-28T12:53:16.59912Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["-1079949055487237530\n","-1808365694993378974\n"]}],"source":["animals = ['cat', 'dog', 'horse', 'cow']\n","print(animals[0].__hash__())\n","print(''.join(animals).__hash__())\n"," "]},{"cell_type":"markdown","metadata":{},"source":[]},{"cell_type":"markdown","metadata":{},"source":["# Challenges\n","\n","1. In calculating the hash value of an integer shown below, we can notice that in many Python implementations, its hash value is exactly equal to the integer value. This is purposely designed to prevent assigning duplicate copies of small integer values in memory. In other words, for small integer values, each integer value is reserved to have only one copy and one memory address.\n","\n","> i = 5; print(i.\\_\\_hash\\_\\_())\n","\n","Write a program to test in your Python environment, what is the smallest integer value where the integer value and its hash value differ. Hint: This value will need to be very large, due to the fact that Python 3 supports storing integers of arbitrary large value but a hash function value cannot be arbitrarily large. As a result, if your strategy is to increase the i value in a loop, it will be wise to adaptively change the step size such that the loop does not have to enumerate all the integers before the smallest integer value that satsifies the condition is found.\n","\n","\n","2. Assume a transaction string is \"Satoshi sends 50 BTC to Hal\". Then use SHA256 hash algorithm to hash the string plus 4 consecutive random bytes called nounce, namely, string + nounce_1 + nounce_2 + nounce_3 + nounce_4.\n","\n","The process should continue trying the combinations of the string plus 4 consecutive random bytes until the hexdigest() result has one leading \"0\" in the hash string. Then change the requirement for the number of leading \"0\"s to two digits and three digits, and observe the change in time complexity in finding one combination.\n","\n","(Hint: 1. Generating one random byte can be done by random.randint(0,255); 2. Adding the string and four bytes into the hash value can be done by using the update() function first on the string and then consecutively on the four randomly generated bytes)"]},{"cell_type":"code","execution_count":47,"metadata":{},"outputs":[{"ename":"KeyboardInterrupt","evalue":"","output_type":"error","traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)","Cell \u001b[0;32mIn[47], line 10\u001b[0m\n\u001b[1;32m 8\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m i, i\u001b[38;5;241m.\u001b[39m\u001b[38;5;21m__hash__\u001b[39m()\n\u001b[1;32m 9\u001b[0m i \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m step\n\u001b[0;32m---> 10\u001b[0m \u001b[43mfind_diff_hash_integer\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n","Cell \u001b[0;32mIn[47], line 5\u001b[0m, in \u001b[0;36mfind_diff_hash_integer\u001b[0;34m()\u001b[0m\n\u001b[1;32m 3\u001b[0m step \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m10\u001b[39m\u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39m\u001b[38;5;241m6\u001b[39m\n\u001b[1;32m 4\u001b[0m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[0;32m----> 5\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[43mi\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[38;5;21;43m__hash__\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;241m!=\u001b[39m i:\n\u001b[1;32m 6\u001b[0m step \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mmax\u001b[39m(\u001b[38;5;241m1\u001b[39m, step \u001b[38;5;241m/\u001b[39m\u001b[38;5;241m/\u001b[39m \u001b[38;5;241m10\u001b[39m)\n\u001b[1;32m 7\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m step \u001b[38;5;241m==\u001b[39m \u001b[38;5;241m1\u001b[39m:\n","\u001b[0;31mKeyboardInterrupt\u001b[0m: "]}],"source":["def find_diff_hash_integer():\n"," i = 1\n"," step = 10**6\n"," while True:\n"," if i.__hash__() != i:\n"," step = max(1, step // 10)\n"," if step == 1:\n"," return i, i.__hash__()\n"," i += step\n","find_diff_hash_integer()"]},{"cell_type":"code","execution_count":45,"metadata":{},"outputs":[{"data":{"text/plain":["'0da8d4dc075323a7cd8d9aaf4317dc311d6ba710751d0bb5812f6aebb98de861'"]},"execution_count":45,"metadata":{},"output_type":"execute_result"}],"source":["import hashlib\n","import random\n","text = 'Satoshi sends 50 BTC to Hal'\n","\n","def find_hash(transaction, zeros='0'):\n"," nounce = bytearray(4)\n"," hash_prefix = zeros\n","\n"," while True:\n"," for i in range(4):\n"," nounce[i] = random.randint(0,255)\n"," data = transaction.encode() + bytes(nounce)\n","\n"," hash = hashlib.sha256()\n"," hash.update(data)\n"," hash_result = hash.hexdigest()\n","\n"," if hash_result.startswith(hash_prefix):\n"," return hash_result\n","find_hash(text,'0')"]}],"metadata":{"kernelspec":{"display_name":"Python 3","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.18"}},"nbformat":4,"nbformat_minor":4} diff --git a/Part One/1-9-classes-and-object-oriented-programmming-i.ipynb b/Part One/1-9-classes-and-object-oriented-programmming-i.ipynb index 7043cd0..5d5e8c2 100644 --- a/Part One/1-9-classes-and-object-oriented-programmming-i.ipynb +++ b/Part One/1-9-classes-and-object-oriented-programmming-i.ipynb @@ -1 +1 @@ -{"cells":[{"cell_type":"markdown","metadata":{},"source":["**Learning Python -- The Programming Language for Artificial Intelligence and Data Science**\n","\n","**Lecture 9: Classes and Object-Oriented Programming I**\n","\n","**By Allen Y. Yang, PhD**\n","\n","(c) Copyright Intelligent Racing Inc., 2021-2024. All rights reserved. Materials may NOT be distributed or used for any commercial purposes.\n"]},{"cell_type":"markdown","metadata":{},"source":["# Keywords\n","\n","Earlier in this course, we have mentioned that every Python variable type is defined as a class; assigning a variable creates an object instance of its type class. In this lecture and next, we cover introductory characteristics of Python programming using classes, a very powerful programming approach also known as **object-oriented programming** (OOP).\n","\n","* Encapsulation: In OOP, encapsulation means bundling the variable data and a set of built-in methods applied on the data together into a class object.\n","* Attribute: A name reserved for encapsulated variables defined in a class.\n","* Method: A name reserved for encapsulated functions defined in a class.\n","* Scope: Every Python variable, function, class attribute and method has a scope, which refers to whether Python interpreter may locate the correct data value or function implementation in memory."]},{"cell_type":"markdown","metadata":{},"source":["# Encapsulation\n","\n","> The first purpose of creating a class is to encapsulate data values and methods applied to the data together. For example,"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["s = 'Hello'\n","print(type(s))\n","print(s.__hash__())\n","print(s.upper())\n","print(s.replace('o', 'o World!'))"]},{"cell_type":"markdown","metadata":{},"source":["In the above example, a string-type variable s not only stores the string data themselves, but also a list of methods we can directly call by quoting the variable itself without extra function definition or import. Those functions defined in a class are also called **methods**, to differentiate from those stand-alone function definitions.\n","\n","Another impact of encapsulation is to create a new scope of variable values that a program can access. Those variables associated with a class are called its **attributes**.\n"," * Local: variables defined only in function code blocks.\n"," * Global: variables declared in a *global* environment.\n"," * Class: methods can access to its own attribute data by class or by its object instances.\n"," \n","Let us first see how a class is declared below:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-28T20:40:09.409198Z","iopub.status.busy":"2021-06-28T20:40:09.408829Z","iopub.status.idle":"2021-06-28T20:40:09.420902Z","shell.execute_reply":"2021-06-28T20:40:09.419622Z","shell.execute_reply.started":"2021-06-28T20:40:09.409165Z"},"trusted":true},"outputs":[],"source":["class Person:\n"," ''' An example class to show Python Class definitions'''\n","\n"," retirement_age = 65 # Class attribute\n","\n"," def __init__(self, first_name, last_name, age = None):\n"," if type(first_name)!=str or type(last_name)!=str:\n"," raise TypeError('Person class initialzed with the unsupported types.')\n","\n"," self.first_name = first_name\n"," self.last_name = last_name\n"," self.age = age\n"," \n","x1 = Person('John', 'Smith') # a Person-class variable is created\n","print(\"Instance attribute: \",x1.first_name)\n","print(\"Class attribute: \", x1.retirement_age)\n","print(\"Class attribute: \", Person.retirement_age)"]},{"cell_type":"markdown","metadata":{},"source":["A class is declared using *class* keyword followed by its given name and then a colon mark \":\". Every Python class should have a built-in method called \\__init__(), which is called by Python automatically whenever an object of the class is created. For example, when we run a simple code:\n"," \n"," i = 5\n"," \n","Then the \\__init__() function of the integer class is called to create an integer object to hold the value 5. The process is automatic without need for the user to intervene.\n","\n","An important task in the \\__init__() function is to initialize the class variables or attributes. In the example above, we see that three attributes are defined, namely, *self.first_name*, *self.last_name*, *self.age*. They are called instance attributes because each Person class object should have its own attribute values for first_name, last_name, and age. \n","\n","The use of keyword **self** indicates the scope of the attributes is within each class instance, and the reference to the instance is indeed by the variable *self*, which is passed into the function \\__init__() automatically by Python without the need for the user to intervene. In the code above, when *x1* is created as a Person-class variable, *self* then will reference the variable *x1*.\n","\n","In addition to *self*, we can see that the function __init__() requests two additional arguments: *first_name* and *last_name*. In this part, *first_name* and *last_name* without the prefix *self* represent the input arguments, whose scope is only local; while *self.first_name* and *self.last_name* with the prefix *self.* represent the instance attributes whose scope is only within each instance of the class.\n","\n","The second type of scope in Python classes is called **class attributes**. In the example, *retirement_age* is a class attribute. The value of a class attribute is associated with the class that will remain the same for all class objects. A class attribute is defined in the same scope of other class methods, and it must be initiated with a default value at the time the class type is defined. \n","\n","In the sample code, since *retirement_age* is a class attribute, we can query its value by referencing *Person.retirement_age* without actually creating a class variable. This is equivalent to querying the same attribute from *x1.retirement_age*.\n","\n","Finally, we see the case that not all instance attributes need to be assigned a value in function __init__(). In the sample code, *self.age* is initially set to a default value **None**. The value is a special *NoneType* class object. It is special in Python in the sense that there will ever be only one instance of the class. Any references of the NoneType value only point to the same object. Consequently, one can verify if the age attribute is None or is equal to None:\n","\n"," * x1.age == None # When compare value\n"," * x1.age is None # When compare object ID"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-07-23T16:37:53.561334Z","iopub.status.busy":"2021-07-23T16:37:53.560956Z","iopub.status.idle":"2021-07-23T16:37:53.575051Z","shell.execute_reply":"2021-07-23T16:37:53.573822Z","shell.execute_reply.started":"2021-07-23T16:37:53.561298Z"},"trusted":true},"outputs":[],"source":["class Person:\n"," ''' An example class to show Python Class definitions'''\n","\n"," retirement_age = 65 # Class attribute\n","\n"," def __init__(self, first_name, last_name, age = None):\n"," if type(first_name)!=str or type(last_name)!=str:\n"," raise TypeError('Person class initialzed with the unsupported types.')\n","\n"," self.first_name = first_name\n"," self.last_name = last_name\n"," self.age = age\n","\n"," def set_age(self, age):\n"," if age<0:\n"," raise ValueError('age attribute in Person must be nonnegative.')\n","\n"," self.age = age\n","\n"," def years_until_retirement(selfish): # The use of selfish is a joke, it serves the same purpose as \"self\"\n"," until_retirement_year = Person.retirement_age - selfish.age\n"," if until_retirement_year<=0:\n"," print('This person has retired')\n"," else:\n"," print('This person has {0} years until retirement'.format(until_retirement_year))\n"," \n","x1 = Person('John', 'Smith')\n","Person.retirement_age = 80\n","x1.retirement_age = 65\n","print(x1.retirement_age)\n","print(Person.retirement_age)\n","\n","x1.age = 40\n","\n","x1.years_until_retirement()"]},{"cell_type":"markdown","metadata":{},"source":["Next, we expand the definition of the Person class, and see examples of how instance attributes and class attributes are updated by the class' methods.\n","\n","First, it may come as a surprise that Python does not have protected status for attributes in classes. As such, the value of any attribute can be freely queried or assigned to new values outside the class definition.\n","\n","On the other hand, directly altering attributes of a class is not recommended in OOP. One leading reason is that the attribute values may need to satisfy some conditions for it to be valid. For example, it is reasonable to require that a person's age is nonnegative. So if the *age* attribute is modified outside the class, this condition may not be enforced, and the object may end up storing an invalid attribute value.\n","\n","A principled way to remedy this concern is to provide built-in methods to update class attributes. In the built-in methods then, all the boundary conditions that concern the validity of the attribute value should be imposed. One example if the method *set_age()* in the Person class. In its definition, it first checks if an age input is nonnegative. Only when the input argument passes this condition, then the attribute value is updated. Otherwise, it will raise a runtime error using the function *ValueError()*.\n","\n","To update an instance attribute, Python needs to pass in a reference of the instance object when the object's attribute is being updated, because the scope of an instance attribute is only within that particular class instance. We can see in *set_age(self, age)*, this is done by passing in the first argument as *self*. Once the method is defined with the first argument used for referencing a class instance, the passing of the instance reference is automatic by Python and it does not require user's intervention.\n","\n","A somewhat unexpected rule in Python is that *self* itself is not a reserved keyword, it is merely the first argument included in class methods that need a reference to its class instance. To this end, the argument actually can be called by any valid variable name. For example, in the subsequent method definition *years_until_retirement()*, for just the purpose of making this point, we rename this argument \"selfish\". Hence, referencing the instance attribute *age* will become *selfish.age* instead of *self.age*. \n","\n","Note that the fact that the reference to a class instance \"self\" can be renamed to other valid name does not mean that practitioners should pursue this option at will. Using the naming \"self\" is universally adopted by most programmers. Therefore, following this naming convention improves the readability of your code when shared with others.\n","\n","In the same function *years_until_retirement()*, we also see how the function can reference a class attribute *retirement_age*. This is done by providing the class name as the prefix: *Person.retirement_age*. Since *retirement_age* is shared by all instances of the class, *Person.retirement_age* is a proper way to reference it. "]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2022-08-12T17:15:09.116841Z","iopub.status.busy":"2022-08-12T17:15:09.116487Z","iopub.status.idle":"2022-08-12T17:15:09.131332Z","shell.execute_reply":"2022-08-12T17:15:09.130323Z","shell.execute_reply.started":"2022-08-12T17:15:09.116809Z"},"trusted":true},"outputs":[],"source":["from datetime import date \n","\n","class Person:\n"," ''' An example class to show Python Class definitions'''\n","\n"," retirement_age = 65 # Class attribute\n","\n"," def __init__(self, first_name, last_name, age = None):\n"," if type(first_name)!=str or type(last_name)!=str:\n"," raise TypeError('Person class initialzed with the unsupported types.')\n","\n"," self.first_name = first_name\n"," self.last_name = last_name\n"," self.age = age\n","\n"," def set_age(self, age):\n"," if age<0:\n"," raise ValueError('age attribute in Person must be nonnegative.')\n","\n"," self.age = age\n","\n"," def years_until_retirement(selfish): # The use of selfish is a joke, it serves the same purpose as \"self\"\n"," until_retirement_year = Person.retirement_age - selfish.age\n"," if until_retirement_year<=0:\n"," print('This person has retired')\n"," else:\n"," print('This person has {0} years until retirement'.format(until_retirement_year))\n"," \n"," @classmethod\n"," def fromBirth(cls, first_name, last_name, birth_year):\n"," if type(birth_year)!=int:\n"," raise TypeERror('birth_year must be an int')\n"," \n"," if birth_year > date.today().year:\n"," raise ValueError('Given birth year is greater than current year.')\n"," \n"," return cls(first_name, last_name, date.today().year - birth_year)\n"," \n"," @staticmethod\n"," def isAdult(age):\n"," return age > 18\n","\n","x2 = Person.fromBirth('John', 'Smith', 1990)\n","print(x2.age)\n","\n","x3 = x2.fromBirth('Jane', 'Doe', 2000)\n","print(x2.age, x3.age)\n","print(id(x2), id(x3))"]},{"cell_type":"markdown","metadata":{},"source":["Next, we discuss the type of methods in a Python class. There are three types:\n","\n"," * Instance Methods: Most common method type in class definition. They interact with instance attributes via \"self\" reference variable.\n"," * Class Methods: They interact with class attributes and other class methods via \"@classmethod\" decorator.\n"," * Static Methods: Typically utility functions that do not interact with either instance or class attributes. The decorator is \"@staticmethod\".\n"," \n","In the above expanded Person class, we can see the declaration of a class method called *fromBirth()*. When a method is declared as a class method, the first argument passed into the function will reference the class itself but not the individual instances. Because of this reason, by naming convention practitioners have chosen a different name called \"cls\", rather than using \"self\" for those instance methods. \n","\n","The purpose of the class method *fromBirth()* is quite useful for classes. When a code creates a class object, if we need to have alternative ways to initialize the object, we can define a class method with a different set of arguments. In the above example, the argument for calculating the age of a person is provided by their birth year. Therefore, one can indirectly calculate the age by using the current year minus the birth year. The imported module date has a built-in function *today()* to read today's date from the computer. The return then has an attribute called year.\n","\n","Since \"cls\" references the Person class, then the following statement will trigger Python to call the default *\\__init__()* method with the proper age now calculated from the birth year:\n","\n"," cls(first_name, last_name, date.today().year - birth_year)\n"," \n"," \n","We also see an example of static methods. Static methods do not carry any references to the class or its instances in its argument list. It is a convenient place to encapsulate useful utility functions. In the above example, validating whether an age is greater than 18 is a useful utility function to describe a person, but the condition is independently verified separated from any instance or class attributes."]},{"cell_type":"markdown","metadata":{},"source":["Finally, we examine the mutability of class objects. In Python, all user-defined class objects are mutable. As such, we shall pay attention when we assign or copy one class object to another."]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["from datetime import date \n","\n","class Person:\n"," ''' An example class to show Python Class definitions'''\n","\n"," retirement_age = 65 # Class attribute\n","\n"," def __init__(self, first_name, last_name, age = None):\n"," if type(first_name)!=str or type(last_name)!=str:\n"," raise TypeError('Person class initialzed with the unsupported types.')\n","\n"," self.first_name = first_name\n"," self.last_name = last_name\n"," self.age = age\n","\n"," def set_age(self, age):\n"," if age<0:\n"," raise ValueError('age attribute in Person must be nonnegative.')\n","\n"," self.age = age\n","\n"," def years_until_retirement(selfish): # The use of selfish is a joke, it serves the same purpose as \"self\"\n"," until_retirement_year = Person.retirement_age - selfish.age\n"," if until_retirement_year<=0:\n"," print('This person has retired')\n"," else:\n"," print('This person has {0} years until retirement'.format(until_retirement_year))\n"," \n"," @classmethod\n"," def fromBirth(cls, first_name, last_name, birth_year):\n"," if type(birth_year)!=int:\n"," raise TypeERror('birth_year must be an int')\n"," \n"," if birth_year > date.today().year:\n"," raise ValueError('Given birth year is greater than current year.')\n"," \n"," return cls(first_name, last_name, date.today().year - birth_year)\n"," \n"," @staticmethod\n"," def isAdult(age):\n"," return age > 18\n"," \n","x1 = Person('John', 'Smith')\n","print('x1 ID:', id(x1))\n","\n","# Make some change in x1, this does not change the ID\n","x1.age = 70; print('x1 ID:', id(x1))\n","\n","# Assignment points two variables to the same mutable object\n","x2 = x1; print(id(x1)==id(x2))\n","\n","import copy\n","x3 = copy.copy(x1); print('x1 x3 IDs:', id(x1), id(x3))"]},{"cell_type":"markdown","metadata":{},"source":["For a mutable variable, assignments such as *x2 = x1* in Python mean making variable name *x2* to reference to the same object as referenced by *x1*. In other words, there is still only one object. Consequently, changing the object content from x1 variable's stand point will also change the same content referenced by x2. \n","\n","To precisely create two independent objects but copy the content to be identical, Python offers a module function called *copy.copy()*. We see the use of this technique at the end of the above sample code. We may verify from this exercise that *x1* and *x2* share the same ID number, while *x1* and *x3* are different."]},{"cell_type":"markdown","metadata":{},"source":["# Exercises\n","\n","1. In Python design a class named Vehicle. A Vehicle class should contain the following attributes:\n"," \n"," * Brand: string type\n"," * Model: string type\n"," * Type: set type{'sedan', 'convertible', 'SUV', 'truck', 'coupe', 'van'}\n"," * Fuel_level:float type in percentage\n","\n","Among the four attributes, the first three should be initiated when an object is created. For the last attribute, design a check_fuel_level() method to print the fuel_level value, and design a set_fuel_level() method to set the fuel_level value.\n","\n","2. When the functions check_fuel_level() and set_fuel_level() are not encapsulated as built-in methods for the Vehicle class, please code the two functions as stand-alone functions that will take a Vehicle class variable as the input argument. Please also summarize the advantages of encapsulation of attributes and methods in object-oriented programming.\n","\n","3. Design a League class, representing multiple basketball teams competing in a league. The class attributes should at least include the total team number and each team name. Also design relevant methods to set these attributes.\n","\n","4. Further expand the above League class, and add an attribute called schedule. The schedule lists a sequence of matches where two teams will play a match against each other. Assuming there are 16 teams as an example, design an algorithm to automatically generate 30 matches where one team to play with the rest 15 teams twice, one at home court and one at guest court."]},{"cell_type":"markdown","metadata":{},"source":["# Challenges\n","\n","1. Design a class named BankAccount. Attributes in BankAccount should include customer_ID and cash_position. Please define and implement relevant methods to update the content of the attributes, such as set and get customer_ID, deposit and withdraw cash_position value. Please also include necessary sanity checking to make sure the attribute values are not out of bound in real world use cases. For example, cash_position should never be negative."]}],"metadata":{"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":30042,"isGpuEnabled":false,"isInternetEnabled":true,"language":"python","sourceType":"notebook"},"kernelspec":{"display_name":"Python 3","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.6.4"}},"nbformat":4,"nbformat_minor":4} +{"cells":[{"cell_type":"markdown","metadata":{},"source":["**Learning Python -- The Programming Language for Artificial Intelligence and Data Science**\n","\n","**Lecture 9: Classes and Object-Oriented Programming I**\n","\n","**By Allen Y. Yang, PhD**\n","\n","(c) Copyright Intelligent Racing Inc., 2021-2024. All rights reserved. Materials may NOT be distributed or used for any commercial purposes.\n"]},{"cell_type":"markdown","metadata":{},"source":["# Keywords\n","\n","Earlier in this course, we have mentioned that every Python variable type is defined as a class; assigning a variable creates an object instance of its type class. In this lecture and next, we cover introductory characteristics of Python programming using classes, a very powerful programming approach also known as **object-oriented programming** (OOP).\n","\n","* Encapsulation: In OOP, encapsulation means bundling the variable data and a set of built-in methods applied on the data together into a class object.\n","* Attribute: A name reserved for encapsulated variables defined in a class.\n","* Method: A name reserved for encapsulated functions defined in a class.\n","* Scope: Every Python variable, function, class attribute and method has a scope, which refers to whether Python interpreter may locate the correct data value or function implementation in memory."]},{"cell_type":"markdown","metadata":{},"source":["# Encapsulation\n","\n","> The first purpose of creating a class is to encapsulate data values and methods applied to the data together. For example,"]},{"cell_type":"code","execution_count":null,"metadata":{"trusted":true},"outputs":[],"source":["s = 'Hello'\n","print(type(s))\n","print(s.__hash__())\n","print(s.upper())\n","print(s.replace('o', 'o World!'))"]},{"cell_type":"markdown","metadata":{},"source":["In the above example, a string-type variable s not only stores the string data themselves, but also a list of methods we can directly call by quoting the variable itself without extra function definition or import. Those functions defined in a class are also called **methods**, to differentiate from those stand-alone function definitions.\n","\n","Another impact of encapsulation is to create a new scope of variable values that a program can access. Those variables associated with a class are called its **attributes**.\n"," * Local: variables defined only in function code blocks.\n"," * Global: variables declared in a *global* environment.\n"," * Class: methods can access to its own attribute data by class or by its object instances.\n"," \n","Let us first see how a class is declared below:"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-06-28T20:40:09.409198Z","iopub.status.busy":"2021-06-28T20:40:09.408829Z","iopub.status.idle":"2021-06-28T20:40:09.420902Z","shell.execute_reply":"2021-06-28T20:40:09.419622Z","shell.execute_reply.started":"2021-06-28T20:40:09.409165Z"},"trusted":true},"outputs":[],"source":["class Person:\n"," ''' An example class to show Python Class definitions'''\n","\n"," retirement_age = 65 # Class attribute\n","\n"," def __init__(self, first_name, last_name, age = None):\n"," if type(first_name)!=str or type(last_name)!=str:\n"," raise TypeError('Person class initialzed with the unsupported types.')\n","\n"," self.first_name = first_name\n"," self.last_name = last_name\n"," self.age = age\n"," \n","x1 = Person('John', 'Smith') # a Person-class variable is created\n","print(\"Instance attribute: \",x1.first_name)\n","print(\"Class attribute: \", x1.retirement_age)\n","print(\"Class attribute: \", Person.retirement_age)"]},{"cell_type":"markdown","metadata":{},"source":["A class is declared using *class* keyword followed by its given name and then a colon mark \":\". Every Python class should have a built-in method called \\__init__(), which is called by Python automatically whenever an object of the class is created. For example, when we run a simple code:\n"," \n"," i = 5\n"," \n","Then the \\__init__() function of the integer class is called to create an integer object to hold the value 5. The process is automatic without need for the user to intervene.\n","\n","An important task in the \\__init__() function is to initialize the class variables or attributes. In the example above, we see that three attributes are defined, namely, *self.first_name*, *self.last_name*, *self.age*. They are called instance attributes because each Person class object should have its own attribute values for first_name, last_name, and age. \n","\n","The use of keyword **self** indicates the scope of the attributes is within each class instance, and the reference to the instance is indeed by the variable *self*, which is passed into the function \\__init__() automatically by Python without the need for the user to intervene. In the code above, when *x1* is created as a Person-class variable, *self* then will reference the variable *x1*.\n","\n","In addition to *self*, we can see that the function __init__() requests two additional arguments: *first_name* and *last_name*. In this part, *first_name* and *last_name* without the prefix *self* represent the input arguments, whose scope is only local; while *self.first_name* and *self.last_name* with the prefix *self.* represent the instance attributes whose scope is only within each instance of the class.\n","\n","The second type of scope in Python classes is called **class attributes**. In the example, *retirement_age* is a class attribute. The value of a class attribute is associated with the class that will remain the same for all class objects. A class attribute is defined in the same scope of other class methods, and it must be initiated with a default value at the time the class type is defined. \n","\n","In the sample code, since *retirement_age* is a class attribute, we can query its value by referencing *Person.retirement_age* without actually creating a class variable. This is equivalent to querying the same attribute from *x1.retirement_age*.\n","\n","Finally, we see the case that not all instance attributes need to be assigned a value in function __init__(). In the sample code, *self.age* is initially set to a default value **None**. The value is a special *NoneType* class object. It is special in Python in the sense that there will ever be only one instance of the class. Any references of the NoneType value only point to the same object. Consequently, one can verify if the age attribute is None or is equal to None:\n","\n"," * x1.age == None # When compare value\n"," * x1.age is None # When compare object ID"]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2021-07-23T16:37:53.561334Z","iopub.status.busy":"2021-07-23T16:37:53.560956Z","iopub.status.idle":"2021-07-23T16:37:53.575051Z","shell.execute_reply":"2021-07-23T16:37:53.573822Z","shell.execute_reply.started":"2021-07-23T16:37:53.561298Z"},"trusted":true},"outputs":[],"source":["class Person:\n"," ''' An example class to show Python Class definitions'''\n","\n"," retirement_age = 65 # Class attribute\n","\n"," def __init__(self, first_name, last_name, age = None):\n"," if type(first_name)!=str or type(last_name)!=str:\n"," raise TypeError('Person class initialzed with the unsupported types.')\n","\n"," self.first_name = first_name\n"," self.last_name = last_name\n"," self.age = age\n","\n"," def set_age(self, age):\n"," if age<0:\n"," raise ValueError('age attribute in Person must be nonnegative.')\n","\n"," self.age = age\n","\n"," def years_until_retirement(selfish): # The use of selfish is a joke, it serves the same purpose as \"self\"\n"," until_retirement_year = Person.retirement_age - selfish.age\n"," if until_retirement_year<=0:\n"," print('This person has retired')\n"," else:\n"," print('This person has {0} years until retirement'.format(until_retirement_year))\n"," \n","x1 = Person('John', 'Smith')\n","Person.retirement_age = 80\n","x1.retirement_age = 65\n","print(x1.retirement_age)\n","print(Person.retirement_age)\n","\n","x1.age = 40\n","\n","x1.years_until_retirement()"]},{"cell_type":"markdown","metadata":{},"source":["Next, we expand the definition of the Person class, and see examples of how instance attributes and class attributes are updated by the class' methods.\n","\n","First, it may come as a surprise that Python does not have protected status for attributes in classes. As such, the value of any attribute can be freely queried or assigned to new values outside the class definition.\n","\n","On the other hand, directly altering attributes of a class is not recommended in OOP. One leading reason is that the attribute values may need to satisfy some conditions for it to be valid. For example, it is reasonable to require that a person's age is nonnegative. So if the *age* attribute is modified outside the class, this condition may not be enforced, and the object may end up storing an invalid attribute value.\n","\n","A principled way to remedy this concern is to provide built-in methods to update class attributes. In the built-in methods then, all the boundary conditions that concern the validity of the attribute value should be imposed. One example if the method *set_age()* in the Person class. In its definition, it first checks if an age input is nonnegative. Only when the input argument passes this condition, then the attribute value is updated. Otherwise, it will raise a runtime error using the function *ValueError()*.\n","\n","To update an instance attribute, Python needs to pass in a reference of the instance object when the object's attribute is being updated, because the scope of an instance attribute is only within that particular class instance. We can see in *set_age(self, age)*, this is done by passing in the first argument as *self*. Once the method is defined with the first argument used for referencing a class instance, the passing of the instance reference is automatic by Python and it does not require user's intervention.\n","\n","A somewhat unexpected rule in Python is that *self* itself is not a reserved keyword, it is merely the first argument included in class methods that need a reference to its class instance. To this end, the argument actually can be called by any valid variable name. For example, in the subsequent method definition *years_until_retirement()*, for just the purpose of making this point, we rename this argument \"selfish\". Hence, referencing the instance attribute *age* will become *selfish.age* instead of *self.age*. \n","\n","Note that the fact that the reference to a class instance \"self\" can be renamed to other valid name does not mean that practitioners should pursue this option at will. Using the naming \"self\" is universally adopted by most programmers. Therefore, following this naming convention improves the readability of your code when shared with others.\n","\n","In the same function *years_until_retirement()*, we also see how the function can reference a class attribute *retirement_age*. This is done by providing the class name as the prefix: *Person.retirement_age*. Since *retirement_age* is shared by all instances of the class, *Person.retirement_age* is a proper way to reference it. "]},{"cell_type":"code","execution_count":null,"metadata":{"execution":{"iopub.execute_input":"2022-08-12T17:15:09.116841Z","iopub.status.busy":"2022-08-12T17:15:09.116487Z","iopub.status.idle":"2022-08-12T17:15:09.131332Z","shell.execute_reply":"2022-08-12T17:15:09.130323Z","shell.execute_reply.started":"2022-08-12T17:15:09.116809Z"},"trusted":true},"outputs":[],"source":["from datetime import date \n","\n","class Person:\n"," ''' An example class to show Python Class definitions'''\n","\n"," retirement_age = 65 # Class attribute\n","\n"," def __init__(self, first_name, last_name, age = None):\n"," if type(first_name)!=str or type(last_name)!=str:\n"," raise TypeError('Person class initialzed with the unsupported types.')\n","\n"," self.first_name = first_name\n"," self.last_name = last_name\n"," self.age = age\n","\n"," def set_age(self, age):\n"," if age<0:\n"," raise ValueError('age attribute in Person must be nonnegative.')\n","\n"," self.age = age\n","\n"," def years_until_retirement(selfish): # The use of selfish is a joke, it serves the same purpose as \"self\"\n"," until_retirement_year = Person.retirement_age - selfish.age\n"," if until_retirement_year<=0:\n"," print('This person has retired')\n"," else:\n"," print('This person has {0} years until retirement'.format(until_retirement_year))\n"," \n"," @classmethod\n"," def fromBirth(cls, first_name, last_name, birth_year):\n"," if type(birth_year)!=int:\n"," raise TypeERror('birth_year must be an int')\n"," \n"," if birth_year > date.today().year:\n"," raise ValueError('Given birth year is greater than current year.')\n"," \n"," return cls(first_name, last_name, date.today().year - birth_year)\n"," \n"," @staticmethod\n"," def isAdult(age):\n"," return age > 18\n","\n","x2 = Person.fromBirth('John', 'Smith', 1990)\n","print(x2.age)\n","\n","x3 = x2.fromBirth('Jane', 'Doe', 2000)\n","print(x2.age, x3.age)\n","print(id(x2), id(x3))"]},{"cell_type":"markdown","metadata":{},"source":["Next, we discuss the type of methods in a Python class. There are three types:\n","\n"," * Instance Methods: Most common method type in class definition. They interact with instance attributes via \"self\" reference variable.\n"," * Class Methods: They interact with class attributes and other class methods via \"@classmethod\" decorator.\n"," * Static Methods: Typically utility functions that do not interact with either instance or class attributes. The decorator is \"@staticmethod\".\n"," \n","In the above expanded Person class, we can see the declaration of a class method called *fromBirth()*. When a method is declared as a class method, the first argument passed into the function will reference the class itself but not the individual instances. Because of this reason, by naming convention practitioners have chosen a different name called \"cls\", rather than using \"self\" for those instance methods. \n","\n","The purpose of the class method *fromBirth()* is quite useful for classes. When a code creates a class object, if we need to have alternative ways to initialize the object, we can define a class method with a different set of arguments. In the above example, the argument for calculating the age of a person is provided by their birth year. Therefore, one can indirectly calculate the age by using the current year minus the birth year. The imported module date has a built-in function *today()* to read today's date from the computer. The return then has an attribute called year.\n","\n","Since \"cls\" references the Person class, then the following statement will trigger Python to call the default *\\__init__()* method with the proper age now calculated from the birth year:\n","\n"," cls(first_name, last_name, date.today().year - birth_year)\n"," \n"," \n","We also see an example of static methods. Static methods do not carry any references to the class or its instances in its argument list. It is a convenient place to encapsulate useful utility functions. In the above example, validating whether an age is greater than 18 is a useful utility function to describe a person, but the condition is independently verified separated from any instance or class attributes."]},{"cell_type":"markdown","metadata":{},"source":["Finally, we examine the mutability of class objects. In Python, all user-defined class objects are mutable. As such, we shall pay attention when we assign or copy one class object to another."]},{"cell_type":"code","execution_count":14,"metadata":{"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["x1 ID: 4496301072\n","x1 ID: 4496301072\n","True\n","x1 x3 IDs: 4496301072 4420116432\n"]}],"source":["from datetime import date \n","\n","class Person:\n"," ''' An example class to show Python Class definitions'''\n","\n"," retirement_age = 65 # Class attribute\n","\n"," def __init__(self, first_name, last_name, age = None):\n"," if type(first_name)!=str or type(last_name)!=str:\n"," raise TypeError('Person class initialzed with the unsupported types.')\n","\n"," self.first_name = first_name\n"," self.last_name = last_name\n"," self.age = age\n","\n"," def set_age(self, age):\n"," if age<0:\n"," raise ValueError('age attribute in Person must be nonnegative.')\n","\n"," self.age = age\n","\n"," def years_until_retirement(selfish): # The use of selfish is a joke, it serves the same purpose as \"self\"\n"," until_retirement_year = Person.retirement_age - selfish.age\n"," if until_retirement_year<=0:\n"," print('This person has retired')\n"," else:\n"," print('This person has {0} years until retirement'.format(until_retirement_year))\n"," \n"," @classmethod\n"," def fromBirth(cls, first_name, last_name, birth_year):\n"," if type(birth_year)!=int:\n"," raise TypeERror('birth_year must be an int')\n"," \n"," if birth_year > date.today().year:\n"," raise ValueError('Given birth year is greater than current year.')\n"," \n"," return cls(first_name, last_name, date.today().year - birth_year)\n"," \n"," @staticmethod\n"," def isAdult(age):\n"," return age > 18\n"," \n","x1 = Person('John', 'Smith')\n","print('x1 ID:', id(x1))\n","\n","# Make some change in x1, this does not change the ID\n","x1.age = 70; print('x1 ID:', id(x1))\n","\n","# Assignment points two variables to the same mutable object\n","x2 = x1; print(id(x1)==id(x2))\n","\n","import copy\n","x3 = copy.copy(x1); print('x1 x3 IDs:', id(x1), id(x3))"]},{"cell_type":"markdown","metadata":{},"source":["For a mutable variable, assignments such as *x2 = x1* in Python mean making variable name *x2* to reference to the same object as referenced by *x1*. In other words, there is still only one object. Consequently, changing the object content from x1 variable's stand point will also change the same content referenced by x2. \n","\n","To precisely create two independent objects but copy the content to be identical, Python offers a module function called *copy.copy()*. We see the use of this technique at the end of the above sample code. We may verify from this exercise that *x1* and *x2* share the same ID number, while *x1* and *x3* are different."]},{"cell_type":"markdown","metadata":{},"source":["# Exercises\n","\n","1. In Python design a class named Vehicle. A Vehicle class should contain the following attributes:\n"," \n"," * Brand: string type\n"," * Model: string type\n"," * Type: set type{'sedan', 'convertible', 'SUV', 'truck', 'coupe', 'van'}\n"," * Fuel_level:float type in percentage\n","\n","Among the four attributes, the first three should be initiated when an object is created. For the last attribute, design a check_fuel_level() method to print the fuel_level value, and design a set_fuel_level() method to set the fuel_level value.\n","\n","2. When the functions check_fuel_level() and set_fuel_level() are not encapsulated as built-in methods for the Vehicle class, please code the two functions as stand-alone functions that will take a Vehicle class variable as the input argument. Please also summarize the advantages of encapsulation of attributes and methods in object-oriented programming.\n","\n","3. Design a League class, representing multiple basketball teams competing in a league. The class attributes should at least include the total team number and each team name. Also design relevant methods to set these attributes.\n","\n","4. Further expand the above League class, and add an attribute called schedule. The schedule lists a sequence of matches where two teams will play a match against each other. Assuming there are 16 teams as an example, design an algorithm to automatically generate 30 matches where one team to play with the rest 15 teams twice, one at home court and one at guest court."]},{"cell_type":"code","execution_count":81,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["convertible\n"]},{"data":{"text/plain":["'Fuel at 20% '"]},"execution_count":81,"metadata":{},"output_type":"execute_result"}],"source":["#Q1-2\n","class Vehicle:\n"," VT = {'sedan', 'convertible', 'SUV', 'truck', 'coupe', 'van'}\n"," def __init__(self, brand, model, type, fuel_level):\n"," if type not in self.VT:\n"," raise ValueError(f\"Invalid vehicle type. Valid types are: {self.VT}\")\n"," self.brand = brand\n"," self.model = model\n"," self.type = type\n"," self.fuel_level = 0\n","\n"," \n"," def set_fuel_level(self, level):\n"," self.fuel_level = level\n","\n"," def check_fuel_level(self):\n"," return \"Fuel at \" + str(self.fuel_level) + \"% \"\n"," \n","def set_fuel_level(vehicle, level):\n"," car1.fuel_level = level\n","\n","def check_fuel_level(vehicle):\n"," return \"Fuel at \" + str(car1.fuel_level) + \"% \"\n"," \n","\n","car1 = Vehicle(\"mazda\", \"miata\", \"convertible\", 10)\n","print(car1.type)\n","car1.set_fuel_level(20)\n","car1.check_fuel_level()\n","\n","\n"]},{"cell_type":"code","execution_count":90,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["{'t5', 't2', 't6', 't7', 't8', 't3', 't4'}\n"]}],"source":["#Q3-4\n","class League:\n"," teams = set()\n"," totalTeams = 16\n"," schedule = {\"Home\": dict(), \"Away\": dict()}\n"," @classmethod\n"," def addTeam(cls, *Team) -> None:\n"," for t in Team:\n"," cls.teams.add(t)\n"," cls.totalTeams += len(Team)\n"," @classmethod\n"," def getTeam(cls, *Team) -> bool:\n"," for t in Team:\n"," if t not in cls.teams:\n"," return False\n"," return True\n"," @classmethod\n"," def removeTeam(cls, *Team) -> None:\n"," for t in Team:\n"," cls.teams.discard(t)\n"," cls.totalTeams = len(cls.teams)\n"," @classmethod\n"," def setSchedule(cls,Team) -> None:\n"," if Team not in cls.teams:\n"," raise Exception\n"," diff = cls.teams.difference(set({Team}))\n"," print(diff)\n"," cls.schedule[\"Home\"] = diff\n"," cls.schedule[\"Away\"] = diff\n","\n","\n","League.addTeam(\"t1\", \"t2\", \"t3\", \"t4\", \"t5\", \"t6\", \"t7\", \"t8\")\n","League.setSchedule(\"t1\")\n","League.teams.remove(\"t1\")\n","\n","\n","\n"," \n","\n"," \n","\n","\n"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":[]},{"cell_type":"markdown","metadata":{},"source":["# Challenges\n","\n","1. Design a class named BankAccount. Attributes in BankAccount should include customer_ID and cash_position. Please define and implement relevant methods to update the content of the attributes, such as set and get customer_ID, deposit and withdraw cash_position value. Please also include necessary sanity checking to make sure the attribute values are not out of bound in real world use cases. For example, cash_position should never be negative."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":[]},{"cell_type":"code","execution_count":102,"metadata":{},"outputs":[],"source":["class BankAccount:\n","\n"," customer_ID = \"\"\n"," cash_position=0\n"," amnt = 0\n"," @classmethod\n"," def GetCustomerID(cls,identification):\n"," customer_ID = str(identification)\n"," @classmethod\n"," def TNSCTN(cls):\n"," wthdORdpst = input(\"withdraw or deposit?\")\n"," if wthdORdpst == \"withdraw\":\n"," amnt=int(input(\"how much withdraw?\"))\n"," if amnt - cls.cash_position <=0:\n"," cls.cash_position-=amnt\n"," amnt = 0\n"," print(\"NEW BALANCE:\",cls.cash_position)\n"," elif wthdORdpst ==\"deposit\":\n"," amnt=int(input(\"how much deposit?\"))\n"," if amnt >=0:\n"," cls.cash_position+=amnt\n"," amnt = 0\n"," print(\"NEW BALANCE:\",cls.cash_position)\n","\n","\n","\n","\n","BankAccount.GetCustomerID(\"glizzygobbler23\")\n","BankAccount.TNSCTN()\n","\n"," "]}],"metadata":{"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":30042,"isGpuEnabled":false,"isInternetEnabled":true,"language":"python","sourceType":"notebook"},"kernelspec":{"display_name":"Python 3","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.18"}},"nbformat":4,"nbformat_minor":4} diff --git a/Part Two/2-1-motto.py b/Part Two/2-1-motto.py new file mode 100644 index 0000000..f070b0e --- /dev/null +++ b/Part Two/2-1-motto.py @@ -0,0 +1,13 @@ +import os + +path = os.path.dirname(os.path.abspath(__file__)) + +file = open(path+'/'+'motto.txt','w') +file.write("Fiat Lux!") + + +file.close() + +file = open(path+'/'+'motto.txt','a') +file.write("\nLight Speed!") +file.close() \ No newline at end of file diff --git a/Part Two/2-2-Flags.py b/Part Two/2-2-Flags.py new file mode 100644 index 0000000..2ad45f3 --- /dev/null +++ b/Part Two/2-2-Flags.py @@ -0,0 +1,25 @@ +from matplotlib import image +from matplotlib import pyplot +import os + +# Read an image file +path = os.path.dirname(os.path.abspath(__file__)) +wales_flag = path + '/' + 'wales-flag-xs.jpg' +lenna = path + '/' + 'lenna.bmp' +bottom = image.imread(lenna) +top = image.imread(wales_flag) +# Display image information + + + +# Add some color boundaries to modify an image array +edited = bottom.copy() +for width in range(top.shape[1]): + for height in range(top.shape[0]): + edited[height][512-250+width] = [ + (top[height][width][0]), + (top[height][width][1]), + (top[height][width][2]), + ] +image.imsave(path+'/'+'lenna_with_dragon.jpg', edited) +pyplot.imshow(edited) diff --git a/Part Two/2-3-sense-sensibility.py b/Part Two/2-3-sense-sensibility.py new file mode 100644 index 0000000..96e7601 --- /dev/null +++ b/Part Two/2-3-sense-sensibility.py @@ -0,0 +1,43 @@ +import PyPDF2 +import os +import string +import time + +start_time = time.time() + +# Open PDF file +path = os.path.dirname(os.path.abspath(__file__)) +file_path = os.path.join(path, 'Sense_and_sensibility.pdf') +file_handle = open(file_path, 'rb') +pdfReader = PyPDF2.PdfReader(file_handle) + +frequency_table = {} + +# Iterating thru pages +for page_num in range(len(pdfReader.pages)): + page_object = pdfReader.pages[page_num] + page_text = page_object.extract_text() + words = page_text.split("\n") + for extracted_string in words: + # Clean the selected word + list_of_string = extracted_string.split() + for chars in list_of_string: + if chars.isdigit(): + pass + if chars.isalpha(): + if chars in frequency_table: + frequency_table[chars] += 1 + else: + frequency_table[chars] = 1 + else: + chars = chars[:-1] + if chars in frequency_table: + frequency_table[chars] += 1 + else: + frequency_table[chars] = 1 +end_time = time.time() +file_handle.close() + +total_distinct_words = len(frequency_table) +print("Total number of unique words: " + str(total_distinct_words)) +print ("time taken: " + str(end_time-start_time)) \ No newline at end of file diff --git a/Part Two/3-1.ipynb b/Part Two/3-1.ipynb new file mode 100644 index 0000000..4996e3a --- /dev/null +++ b/Part Two/3-1.ipynb @@ -0,0 +1,73 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from datetime import datetime\n", + "import matplotlib.pyplot as plt\n", + "from mpl_toolkits import mplot3d\n", + "import os\n", + "import numpy as np\n", + "\n", + "\n", + "v = np.array([2., 2., 4.]) \n", + "\n", + "fig = plt.figure()\n", + "ax = plt.axes(projection = \"3d\")\n", + "\n", + "e0 = np.linspace(0, v[0],1000)\n", + "e1 = np.linspace(0, v[1],1000)\n", + "e2 = np.linspace(0, v[2],1000)\n", + "ax.plot3D(e0,0,0,'red')\n", + "ax.plot3D(0,e1,0,'green')\n", + "ax.plot3D(0,0,e2,'blue')\n", + "#Ask JOSH19!!!" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "base", + "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.18" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Part Two/3-2-MatrixDotProd.py b/Part Two/3-2-MatrixDotProd.py new file mode 100644 index 0000000..f0e5c43 --- /dev/null +++ b/Part Two/3-2-MatrixDotProd.py @@ -0,0 +1,13 @@ +import numpy as np + +p1 = np.array([[6,-9,1],[4,24,8]]) +p1result = p1*2 + +mi = np.eye(2) +m2result = mi@p1 + +p2 = np.array([[4,3],[3,2]]) +p3 = np.array([[-2,3],[3,-4]]) + +rop3p2 = p2@p3 +print(rop3p2) \ No newline at end of file diff --git a/Part Two/3-3-Slicing.py b/Part Two/3-3-Slicing.py new file mode 100644 index 0000000..f8893da --- /dev/null +++ b/Part Two/3-3-Slicing.py @@ -0,0 +1,8 @@ +import numpy as np +a3 = np.array([list(range(i * 10,i * 10+6)) for i in range(6)]) +print(a3) +a = a3[:,1] +b=a3[1, 2:4] +c=a3[2:4, 4:] +d=a3[2::2, ::2] +print(a,b,c,d) diff --git a/Part Two/3-4.py b/Part Two/3-4.py new file mode 100644 index 0000000..dac5222 --- /dev/null +++ b/Part Two/3-4.py @@ -0,0 +1,21 @@ +import numpy as np + +def swap_Rows(M,a,b): + if a >= M.shape[0] or b >= M.shape[0] or a<0 or b<0: + raise IndexError("Row Indicies are out of bounds") + temp = M[a].copy() + M[[a,b],:]=M[[b,a],:] + + + return M +def swap_Columns(M,a,b): + if a >= M.shape[1] or b >= M.shape[1] or a<0 or b<0: + raise IndexError("Row Indicies are out of bounds") + temp = M[:,a].copy() + M[:,a]=M[:,b] + M[:,b] = temp + M[:,[a,b]]=M[:,[b,a]] + return M + +v = np.array([[0,1,2],[3,4,5]]) +- \ No newline at end of file diff --git a/Part Two/3-5-MatrixArray.py b/Part Two/3-5-MatrixArray.py new file mode 100644 index 0000000..c01a337 --- /dev/null +++ b/Part Two/3-5-MatrixArray.py @@ -0,0 +1,12 @@ +import numpy as np +sample_list = [1,2,3,4,5,6,7,8,9,10,11,12] +def set_array(l,r,c): + sample_array = np.array(l) + shaped_array = sample_array.reshape(r,c) + return(shaped_array) + +x=set_array(sample_list,4,3) +y=set_array(sample_list,3,4) + +z=x@y +print(z) \ No newline at end of file diff --git a/Part Two/Sense_and_sensibility.pdf b/Part Two/Sense_and_sensibility.pdf new file mode 100644 index 0000000..b504950 Binary files /dev/null and b/Part Two/Sense_and_sensibility.pdf differ diff --git a/samples/lenna.bmp b/Part Two/lenna.bmp similarity index 100% rename from samples/lenna.bmp rename to Part Two/lenna.bmp diff --git a/Part Two/lenna_with_dragon.jpg b/Part Two/lenna_with_dragon.jpg new file mode 100644 index 0000000..22a8331 Binary files /dev/null and b/Part Two/lenna_with_dragon.jpg differ diff --git a/Part Two/wales-flag-xs.jpg b/Part Two/wales-flag-xs.jpg new file mode 100644 index 0000000..464da9f Binary files /dev/null and b/Part Two/wales-flag-xs.jpg differ diff --git a/Part Two/wales.jpg b/Part Two/wales.jpg new file mode 100644 index 0000000..977d82e Binary files /dev/null and b/Part Two/wales.jpg differ diff --git a/samples/airplane.bmp b/airplane.bmp similarity index 100% rename from samples/airplane.bmp rename to airplane.bmp diff --git a/clock.py b/clock.py new file mode 100644 index 0000000..6c282b0 --- /dev/null +++ b/clock.py @@ -0,0 +1,57 @@ +## This is course material for Introduction to Python Scientific Programming +## Example code: matplotlib_clock.py +## Author: Allen Y. Yang +## +## (c) Copyright 2020. Intelligent Racing Inc. Not permitted for commercial use + +from datetime import datetime +import matplotlib.pyplot as plt +import os +import numpy as np + +# Initialization, define some constant +path = os.path.dirname(os.path.abspath(__file__)) +filename = path + '/airplane.bmp' +background = plt.imread(filename) + +second_hand_length = 200 +second_hand_width = 2 +minute_hand_length = 150 +minute_hand_width = 6 +hour_hand_length = 100 +hour_hand_width = 10 + +center = np.array([256, 256]) +def clock_hand_vector(angle, length): + return np.array([length * np.sin(angle), -length * np.cos(angle)]) + +# draw an image background +fig, ax = plt.subplots() +ax.set_axis_off() +while True: + plt.imshow(background) + + # First retrieve the time + now_time = datetime.now() + hour = now_time.hour + if hour>12: hour = hour - 12 + minute = now_time.minute + second = now_time.second + + # Calculate end points of hour, minute, second + rps = second/60*2*np.pi + rpm = (minute + second / 60) / 60 * 2 * np.pi + rph = (hour + minute / 60 + second / 3600) / 12 * 2 * np.pi + rpgmth = hour + 7/ 24 * 2 * np.pi + hour_vector = clock_hand_vector(rph, hour_hand_length) + minute_vector = clock_hand_vector(rpm, minute_hand_length) + second_vector = clock_hand_vector(rps, second_hand_length) + gmt_vector = clock_hand_vector(rpgmth, second_hand_length) + + plt.arrow(center[0], center[1], hour_vector[0], hour_vector[1], head_length = 3, linewidth = hour_hand_width, color = 'black') + plt.arrow(center[0], center[1], gmt_vector[0], gmt_vector[1], head_length = 3, linewidth = hour_hand_width, color = 'yellow') + plt.arrow(center[0], center[1], minute_vector[0], minute_vector[1], linewidth = minute_hand_width, color = 'black') + plt.arrow(center[0], center[1], second_vector[0], second_vector[1], linewidth = second_hand_width, color = 'red') + + plt.pause(0.01) + plt.clf() diff --git a/graph.py b/graph.py new file mode 100644 index 0000000..e69de29 diff --git a/learning_rates.ipynb b/learning_rates.ipynb new file mode 100644 index 0000000..d8913f9 --- /dev/null +++ b/learning_rates.ipynb @@ -0,0 +1,25 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#.0001\n", + "#.01\n", + "#.5\n", + "\"\"\"if you choose a value too small you will take forever\"\"\"\n", + "\"\"\"if you choose a value too large you might go past the limits of the graph\"\"\"\n", + "\"\"\"even if you had infinite time and resources, you should still not choose too small of a value because of local minima.\"\"\"" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/samples/read_image.py b/samples/read_image.py index 74c3ce7..225378e 100644 --- a/samples/read_image.py +++ b/samples/read_image.py @@ -22,8 +22,7 @@ plot_data = data.copy() for width in range(512): for height in range(10): - plot_data[height][width] = [255, 0, 0] # Alternatively plot_data[height][width][:] = [255, 0, 0] - plot_data[511-height][width] = [0,0,255] + # Write the modified images image.imsave(path+'/'+'lenna-mod.jpg', plot_data) diff --git a/samples/widget_slider.py b/samples/widget_slider.py index 4b06a98..d10ad39 100644 --- a/samples/widget_slider.py +++ b/samples/widget_slider.py @@ -19,8 +19,8 @@ # Create two sliders axcolor = 'lightgoldenrodyellow' -axfreq = plt.axes([0.25, 0.1, 0.65, 0.03], facecolor=axcolor) -axamp = plt.axes([0.25, 0.15, 0.65, 0.03], facecolor=axcolor) +axfreq = plt.axes([0.25, 0.1, 0.65, 0.05], facecolor=axcolor) +axamp = plt.axes([0.25, 0.15, 0.65, 0.05], facecolor=axcolor) sfreq = Slider(axfreq, 'Freq', 0.1, 30.0, valinit=f0, valstep=delta_f) samp = Slider(axamp, 'Amp', 0.1, 10.0, valinit=a0, valstep=delta_a)