<jupyter_start><jupyter_text>Homework 0 - Introduction to Python and Google Colabratory The hands-on assignments (תרגילים רטובים) in this course will be given in python using a free online platform provided by google called Colab. The purpose of this assignment is to go over the basic of the Python language and to let you practice using the Colab platform.You do not need to submit this homework assignment, but it is mandatory to go over it.Enjoy 🙂  About PythonPython is a programing language which has gain massive popularity in recent years both for building large systems and for quick prototyping and doing research. Here are a few motivational points for why we decided to use Python, and why should you learn it?- It is an extremely powerful programing language and yet very simple to learn and to get started with. ([See what XKCD has to say about it](https://xkcd.com/353/))- It is currently one of the most popular programming languages. ([It is the 3rd most popular language, after C and Java](https://www.tiobe.com/tiobe-index/))- Almost all new frameworks in the field of machine learning are python oriented: PyTorch, TensorFlow, Keras, etc.- It has a ridiculously rich set of packages for almost any task. From basic mathematical tools([basic numeric](http://www.numpy.org/), [advance numerics](https://www.scipy.org/scipylib/index.html), [symbolic math](https://www.sympy.org) [data handling](https://pandas.pydata.org/), [visualizing](https://mode.com/blog/python-data-visualization-libraries), etc.), through a range of advanced frameworks for [deep learning](https://www.kdnuggets.com/2017/02/python-deep-learning-frameworks-overview.html), [distributed computing](https://wiki.python.org/moin/ParallelProcessing), [web servers](https://www.djangoproject.com/), etc.- It's open-source (=> free++) with a huge community behind it. Learning PythonLearning the basic concepts of the language is relatively easy and is enough in order to get started. In this homework assignment we will try to briefly go through those basics, in order to get you up and running. However, this assignment does not come near of covering all the aspects of the language, and it is expected (and perfectly fine), that if this is your first time using Python it will take you a while (and maybe some frustration) to get the hang of it. This assignment does assume that you do have some minimal experience in programing.You will probably need every now and then to relay on the web for help to solve bugs and to understand parts of the code. There is a large number of great resources available on-line including the official documentation and sited dedicated for learning Python. These resources usually explain thing in a more detailed way that this introduction which. We have listed a few additional resources at the end of this assignment. You are encouraged to check them out whenever you get stuck.  ColabThe hands-on part (תרגילים רטובים) of the homework assignment in this course will be given in the form of Colaboratory notebooks such as this one. Colaboratory is a free service offered by Google, which is based on the [Jupyter project](https://jupyter.org/) which allows you to run such notebooks on any computer. The Colab platforms allows any one with a Google account to run Python code on Google's servers. Creating a Google accountIn order to save these notebooks you must be have Google account and by logged into it. In the rare case that you don't already have an account, you can create one [here](https://accounts.google.com/SignUp). The notbooksThe notebooks used by Colab (and Jupyter in general) are simply a mixture of cells of rich text, such as the one you are reading, along with Python code cells, such as this one:<jupyter_code>x = 3
print('x is equal to', x)<jupyter_output><empty_output><jupyter_text>Running a cellTo run a specific code cell, such as the one above, select the cell using the mouse or the up/down keys, and press **Shift+Enter**. Run the code cell above. You should see the output of this cell beneath it.  Making your own copy of the notebookBy following the [link](https://colab.research.google.com/github/technion046195/technion046195/blob/master/content/assignments/assignment_0_ex.ipynb)on the course's website, you have opened the public copy of this notebook. In this state you should see the following logo (Github's): ![](https://github.githubassets.com/favicons/favicon.svg) next to the file name at the top of the page. In the public copy you can make changes, but you won't be able to save them.In order to make your own copy of this notebook press the "Copy to drive" button at the top area of the this page.  The notebook's name (at the top) should change to "**Copy of .ipynb**" and the logo should change to Google's drive icon: ![](https://ssl.gstatic.com/images/branding/product/1x/drive_2020q4_32dp.png). Notebooks name and locationBy default, the new copy which you have just created is saved into your [Google drive](https://drive.google.com/drive) under a folder called **Colab Notebooks**. You can view this folder by selecting **Locate in Drive** from the **file** menu.**Recommended**: You can rename the notebook by clicking on the notebook's name at the top of page.Now we are ready to start editing 👍  Saving and loading notebooks- To save a notebook press **Ctrl+S** (or select **save** from the **file** menu).- To load a saved notebook, open the **Colab Notebooks** folder in your [Google drive](https://drive.google.com/drive), and double click on the saved notebook. (If you did not rename it should appear as "**Copy of .ipynb**") Note that in Google Drive you can have more than 1 file with the same name in the same folder.✍️ Before you start, make sure that you can save and load this notebook. Start be replacing the text in the code cell below with our own ID numbers. Then save and load this notebook, and make sure that the IDs were indeed saved.<jupyter_code>## %%%%%%%%%%%%%%% Your code here - Begin %%%%%%%%%%%%%%%
## Replace the test bellow with our own
my_text = '!!!Change Me!!!'
## %%%%%%%%%%%%%%% Your code here - End %%%%%%%%%%%%%%%%%

print('You wrote:', my_text)<jupyter_output><empty_output><jupyter_text>Expending all cellsWhen you open a new notebook some sections might be hidden (collapsed), showing only the title of the section with a "↳  cells hidden" note beneath it. Before you start, make show you expand all the sections. You can do so by pressing **Ctrl+[** or by clicking on all the "↳  cells hidden" lines.  ✍️ Your assignmentsIn these assignments, you will have to follow the instruction in the notebooks and fill in the missing code blocks according to the instructions. The cells containing the instructions will be marked with ✍️. The line of code which you will be expected to fill in will be surrounded by the two following comments:``` python %%%%%%%%%%%%%%% Your code here - Begin %%%%%%%%%%%%%%% %%%%%%%%%%%%%%% Your code here - End %%%%%%%%%%%%%%%%%```For example: fill in the following code block to assign the value $x+10$ to the variable $y$ (replace the ellipsis, "...", bellow with your code):<jupyter_code>x = 3
## %%%%%%%%%%%%%%% Your code here - Begin %%%%%%%%%%%%%%%
## assign x + 10 to y:
y = x + 10
## %%%%%%%%%%%%%%% Your code here - End %%%%%%%%%%%%%%%%%

print('y is equal to:', y)<jupyter_output><empty_output><jupyter_text>Running orderThe notebook engine lets you run the code cells in any order you prefer, but to make everything work as it was designed to, you need to make sure you run the code cell in the correct order (the order in which they appear in the notebook). If you want to start over and reset the Python's state (all code cells which were previously ran) press **Ctrl+M I** (**Ctrl+M** followed by **I**), or select **Restart runtime...** from the **Runtime** menu.Restart the runtime and rerun all 3 code cells above this line.  The last line in a code cellFor convenience, if the last line of a code cell returns some value, it will be presented in cell's output bellow the code. For example, run the following cell:<jupyter_code>## The output of this code cell would be y to the power of 2
x = 3.14
print('This is a ordinary print line')
x ** 2  # The result of this line will appear in the output as well<jupyter_output><empty_output><jupyter_text>A Comment About Python Versions3 major versions of Python have been released so far. The latest (and most popular) version today is Python 3. These notebooks use it by default so you don't have to worry about it.Do keep in mind, however, that there are still a lot of places which still use Python 2. When looking at on-line resources keep in mind that there are some minor syntax changes between the two versions.  Python Basics  The *print()* functionThe *print()* function simply prints the value of it's argument. The function will transform any argument into a string.The function can receive multiple arguments. In this case the function will print the values of all of the arguments separated by a single space.(We will use here the special character "\n" (new line) to insert and empty line between the outputs)<jupyter_code>## When given a string, well ... it will print that string:
print('1) This is a string\n')

## When given multiple inputs it will print each one of then seperated by a single space.
print('2)', '"Me first"', '"I will be second"', '"Why do I always have to be last?"\n') 

## The print function can also recive number as input.
print('3) If we add the number', 2, 'to the number', 3, 'we will get', 23, '(or maybe not, I was never very good in calculus).\n')

## In python any object can be converted into a string, and therefore printed.
print('4) This is a python list:', [1, 1, 2, 3, 5, 8, 13], '\n')<jupyter_output><empty_output><jupyter_text>✍️ Print the sentence 'The result of 83 x 29 is:' followed by the result. (Don't calculate the is yourself, write the equation as one of the print's arguments)<jupyter_code>## %%%%%%%%%%%%%%% Your code here - Begin %%%%%%%%%%%%%%%
## print 'The result of 83 x 29 is:' followed by the result.
print(...
## %%%%%%%%%%%%%%% Your code here - End %%%%%%%%%%%%%%%%%<jupyter_output><empty_output><jupyter_text>CommentsComments are created using the hash ('') symbol. Anything after the  symbol will be ignored.In our course, for clarity, we will use a double hash ('') for comments containing instructions or documentations (as oppose to lines of code which where commented out)<jupyter_code># This is a comment
## But in our course we will use this form for documentation.
print('Always look on the bright side of life...') # Comments can be added at the end of the line<jupyter_output><empty_output><jupyter_text>VariablesPython is a dynamically typed language (like Javascript, Matlab and others). This means that any variable can be assigned with any type of value. Some of the build in types in Python are:- int: Integer- float: floating point- bool: Boolean (True/False)- str: string(For the more advance readers: Python is in fact an object oriented language, which means that **everything** in Python is a object. A variable is simply a reference to a object)<jupyter_code>var1 = 5
print("1) var1 = ", var1)
print('The type of the variable "var1" is:', type(var1), '\n')

var2 = var1 * 3.5
print("2) var2 = ", var2)
print('The type of the variable "var2" is:', type(var2), '\n')

var3 = var2 + 2 +4j
print("3) var3 = ", var3)
print('The type of the variable "var3" is:', type(var3), '\n')

var4 = False
print("4) var4 = ", var4)
print('The type of the variable "var4" is:', type(var4), '\n')

var5 = 'supercalifragilisticexpialidocious'
print("5) var5 = ", var5)
print('The type of the variable "var5" is:', type(var5), '\n')

var6 = None  ## This is a special type which can be use to indicate none
print("6) var6 = ", var6)
print('The type of the variable "var6" is:', type(var6), '\n')<jupyter_output><empty_output><jupyter_text>✍️ Calculate the square root of 2 and store in the variable "irrational_var" and then print it. Use ** as the power operator (equivalent to ^ in Matlab)<jupyter_code>## %%%%%%%%%%%%%%% Your code here - Begin %%%%%%%%%%%%%%%
...
## %%%%%%%%%%%%%%% Your code here - End %%%%%%%%%%%%%%%%%
print('The suqare root of 2 is:', irrational_var)<jupyter_output><empty_output><jupyter_text>✍️ - Define a boolean variable named *not_false* and assign the value *True* to it. (note that Python is case sensitive)- Add the value complex value of *2+1j* to it and save it to the variable *var7*- Print the type of *var7*<jupyter_code>## %%%%%%%%%%%%%%% Your code here - Begin %%%%%%%%%%%%%%%
not_false = ...
var7 = ...
print("7) var7 = ", var7)
print('The type of the variable "var7" is:', ...
## %%%%%%%%%%%%%%% Your code here - End %%%%%%%%%%%%%%%%%<jupyter_output><empty_output><jupyter_text>List and Tuples*Lists* and *tuples* are sequences of variables. The difference between the two is that after defining a tuple it's values can not be changed.List are defined using squared brackets "[,]" and tuples are defined using regular brackets "(,)"<jupyter_code>tens = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
hundreds = (0, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000)

print('The type of the variable "tens" is:', type(tens))
print('The type of the variable "hundreds" is:', type(hundreds))<jupyter_output><empty_output><jupyter_text>List and tuples can contain mixed types of variable (Note: compared to Matlab, list are similar to cells. Matlab's arrays are similar to NumPy arrays in Python, which we will discuss below)<jupyter_code>mixed_list = ['pi', 3.14, 3, True, ['Another', 'list', 'inside', 'a', 'list']]<jupyter_output><empty_output><jupyter_text>Function for SequencesPython defines the following build in function for working with sequences. For example:<jupyter_code>## The "len" function returns the length of a sequences
print('The length of tens is: ', len(tens), '\n')

## The "sorted" function returns a sorted version a sequences
print('The sorted version of (3, 1, 4, 1, 5) is: ', sorted((3, 1, 4, 1, 5)), '\n')<jupyter_output><empty_output><jupyter_text>Indexing Lists, Tuples and StringsIndexing (extracting values from a sequence by index) in done using square brackets.Indexing in Python can be done in a few ways:(**For Matlab users**: Python's indexing starts from 0 and not 1!! (like any other programing language (a part from Matlab))<jupyter_code>## Extract a single value at index: my_list[index]
print('1) tens[5]=', tens[5], '\n')

## Negative indecies count from the end of the list / tuple: my_list[-index] = my_list[len(my_list) - index]
print('2) tens[-2]=', tens[-2], '\n')

## A range of a values can be extracted using: my_list[begin:end].
## !!! Important: this returns the values between begin and end not including end !!!
print('3) tens[0:4]=', tens[0:4], '. ! Note that 40 does not apper here\n')

## A range can also be extracted with a given step using:  my_list[begin:end:step].
print('4) tens[1:-3:2]=', tens[1:-3:2], '\n')

## If one of the values in the begin:end or in begin:end:step is omitted then the defualt values are used.
## Default: begin = 0
## Default: end = -1
## Default: step = 1

## A range starting from 0:
print('5) tens[:5]=', tens[:5], '\n')

## A range ending at -1 (last item - including)
print('6) tens[2:]=', tens[2:], '\n')

## All items in the list with a step site of 2
print('7) tens[::2]=', tens[::2], '\n')

## Negtive steps can be used as well. The values starting at 7 down to 2, not including 2:
print('8) tens[7:2:-1]=', tens[7:2:-1], '\n')<jupyter_output><empty_output><jupyter_text>✍️ Use the "sorted" function and indexing to sort the letters in your name and to print the first 5 letters of the sorted name.<jupyter_code>## %%%%%%%%%%%%%%% Your code here - Begin %%%%%%%%%%%%%%%
my_full_name = ...
my_sorted_name = ...
print(f'The first 5 letters in my sorted name are {...
## %%%%%%%%%%%%%%% Your code here - End %%%%%%%%%%%%%%%%%<jupyter_output><empty_output><jupyter_text>As stated above, lists can be updated:<jupyter_code>tens[2] = 'Twenty'
print(tens)<jupyter_output><empty_output><jupyter_text>New values can also be appended to the end of the list:<jupyter_code>tens.append(110)
print(tens)<jupyter_output><empty_output><jupyter_text>Lists and tuples can also be concatenated using the "+" operator:<jupyter_code>print("('a', 'b', 'c') + ('e', 'f', 'g')=",  ('a', 'b', 'c') + ('e', 'f', 'g'))<jupyter_output><empty_output><jupyter_text>✍️ Complete the following missing indexing:<jupyter_code>marvel_super_heros = ['Iron Man', 'Captain America', 'Thor']  # This is a list
dc_super_heros = ('Superman', 'Batman', 'Wonder Woman')  # This is a tuple

## %%%%%%%%%%%%%%% Your code here - Begin %%%%%%%%%%%%%%%
print('First super hero in the Marvel list: ', marvel_super_heros[...
print('Last super hero in the DC list: ', ...

## Set the second Marvel super hero to Spider Man:
marvel_super_heros[...
print('The marval list with Spider Man as the second item:', marvel_super_heros)

## Now add Hulk and Deadpool to the Marvel list
## You can either use append or the "+" operator
marvel_super_heros ...
print('The marval list Hulk and Deadpool added at the end:', marvel_super_heros)
## %%%%%%%%%%%%%%% Your code here - End %%%%%%%%%%%%%%%%%<jupyter_output><empty_output><jupyter_text>StringsStrings in python can be define using by either a single quote ' or double quote ". They are both equivalent and you can choose either one of them. Double quote can be used to define string which contain a single quote and vise versa. In our course we will mostly be using a single quote to define strings.Python offers a large variety of built-in functions for handling strings we will demonstrate just a few of them:(for a complete list check the [official documentation](https://docs.python.org/3.9/library/string.html))<jupyter_code>## Strings can be concatinated using the "+" operator
print('1) begining' + '+' + 'end', '\n')

## Special charecters begin with a "\". We have alrady used the \n special charecter to insters new lines
## To insert the "\" charecter to a string use "\\"
print('2) The \\n special charecter insters a new line:\nThis will be in a new line.\n')
## \t - inserts a tab:
print('3) The \\t special charecter insters a new tab:\tI am tabbed, woo hoo!\n')
## \' and \" can be used to inster ' or ":
print('4) The \\\' special charecter insters a \'.\n')

## The str function transforms a object into a string
converted_num = str(3.14)
print('5) The type of the variable "converted_num" is:', type(converted_num), '\n')

## The string object has many method that can be used.
## For example, the "find" method finds the first occurance of a given string in the original string:
print('6) The string "cd" appears in the string "abcdefg" at postion: ', 'abcdefg'.find('cd'), '\n')

## The "split" method splits a string to a list of string according to a given delimiter:
print("7) 'dog,cat,t-rex'.split(',')=", 'dog,cat,t-rex'.split(','), '\n')

## Strings can be index in a way similar to lists and tuples
print("8) 'abcdefg'[::2]=", 'abcdefg'[::2], '\n')

## Python strings use Unicode encoding, which means that they can contain chercters in any language
print('9) עברית קשה שפה\n' )

## and even emojis
print('10)🦏 💕 🐴 => 🦄\n')<jupyter_output><empty_output><jupyter_text>Formating stringsPython expressions can be incorporated directly into strings using f-strings. f-strings are regular string, except for the fact that curly brackets can be used to evaluate expressions inside a string.f-strings are define by adding an "f" before the quote mark:<jupyter_code>print(f'1) the result of 3+5 is {3 + 5}\n')

## The epression inside the curly barckes can also contain variables:
name = 'Bob'
print(f'2) Hi {name}')<jupyter_output><empty_output><jupyter_text>f-strings can also be used to format the way numbers are printed by using the colons  ":" :(see [the official documentation](https://docs.python.org/3.8/library/string.htmlformat-specification-mini-language) for a complete list of formating options)<jupyter_code>pi = 3.14159265359
## The number of digits after the decimal point can be define is the following way
print(f'1) Pi with only 4 significant digits: {pi:.4}\n')
print(f'2) Pi with 10 significant digits: {pi:.10}\n')
print(f'3) Pi with zero padding in front so that it will contain 9 charecters: {pi:09.3}\n')
print(f'4) Pi using an exponant notation: {pi:.3e}\n')<jupyter_output><empty_output><jupyter_text>Note: f-strings where only introduced in version 3.6. There are also two "older" ways to use formated strings in Python, you can read more about them [here](https://realpython.com/python-f-strings/).  DictionariesDictionaries store pairs of key and values. The keys can used as indices to retrieve the stored values. Any type of object can be used as a key (and any object can be stored as a value). Dictionaries are defined using curly brackets:``` pythonmy_dict = {'key1': 'value1', 'key2': 'value2'}```example:<jupyter_code>game_of_throne_kingdom_rulers = {
    'The North': 'Stark',
    'The Rock': 'Lannister',
    'Dorne': 'Martell',
    'The Riverlands': 'Tullys',
    'Iron Islands': 'Greyjoys',
    'The Stormlands': 'Baratheons',
    'The Vale': 'Arryn',
    'The Reach': 'Tyrells',
    'The Crownlands': 'Targaryens',
}

print(f"The rulers of The North are: {game_of_throne_kingdom_rulers['The North']}\n")

## the list of keys can be retrived using ".keys()":
print(f"The list of kindoms is : {game_of_throne_kingdom_rulers.keys()}\n")

## the list of values can be retrived using ".values()":
print(f"The list of kindoms is : {game_of_throne_kingdom_rulers.values()}\n")

## Updating a value
game_of_throne_kingdom_rulers['The Reach'] = 'Bronn'
print(f"The kindom of The Reach has been given to: {game_of_throne_kingdom_rulers['The Reach']}\n")<jupyter_output><empty_output><jupyter_text>ConditionsConditions in Python are written in the following way:``` pythonif some_condition:    do_1    do_2elif some_other_condition    do_3else:    do_4```The "elif" and the "else" sections are optional.**Note the indentations!!!**Note that the do statements are indented (shifted to the right by leading spaces) relative to the if statement. **In Python indentations are very important** and define where the inner section of the if begins and ends. Comparison operators| Operator | Description || --- | --- || a == b | a is equal to b || a != b | a is not equality to b || a > b | a is larger then b || a >= b | a is larger equal to  b || a < b | a smaller then b || a <= b | a is smaller or equal to b | Binary operators| Description | Operator || --- | --- || not | not condition1 || and | condition1 and consition2 || or | condition1 or consition2 |<jupyter_code>a = 5
b = 6
if a > b:
    print(f'{a} is larger then {b}')
elif a == b:
    print(f'{a} is equal to {b}')
else:
    print(f'{a} is smaller then {b}')<jupyter_output><empty_output><jupyter_text>The "in" conditionThe "in" operator can be used to check is a value is a member of a sequence such as a list or a string. For example:<jupyter_code>fruits = '🍎🍐🍊'  # This is a string
vegtables = ['🥬', '🥕', '🥔']  # This is a list of strings
fruits_that_impose_as_vegtables = ('🍅', '🥑', '🥒')  # This is a tuple of strings

food = '🍅'
if food in fruits:
    print(f'{food} is a fruit.')
elif food in vegtables:
    print(f'{food} is a vetgtable.')
elif food in fruits_that_impose_as_vegtables:
    print(f'{food} is a fruit, that is offten mistaken as a vetgtable.')
else:  
    print(f'I have no idea what {food} is ¯\_(ツ)_/¯')<jupyter_output><empty_output><jupyter_text>✍️ Complete the condition bellow to check if "food" is in any of the above lists. (use "or")<jupyter_code>food = '🍕'
## %%%%%%%%%%%%%%% Your code here - Begin %%%%%%%%%%%%%%%
if ...
    print(f'{food} is a fruit or a vegtable.')
else:
    print(f'{food} is neither a fruit nor a vegtable. Maybe you shouldn\'t eat it')
## %%%%%%%%%%%%%%% Your code here - End %%%%%%%%%%%%%%%%%<jupyter_output><empty_output><jupyter_text>While LoopWhile loops are written in Python in the following way:``` pythonwhile condition:    do_1    do_2```Here as well, the indentation defines are the loop begin and end.<jupyter_code>## This code prints the Fibonacci serise up to the first number larger then 100
last = 1
current = 1

print(last)
print(current)
while current < 100:
    next = last + current
    last = current
    current = next
    print(current)<jupyter_output><empty_output><jupyter_text>For LoopIn Python, for loops run over a sequences (or more precisely, over an [iterator](https://jakevdp.github.io/WhirlwindTourOfPython/10-iterators.html)). For loops are written in the following way:``` pythonfor value in sequence:    do_something_with_value```Example:<jupyter_code>for food in fruits:
    print(f'I like {food}')<jupyter_output><empty_output><jupyter_text>In order to run over a range of numbers the "range" function can be used in one of the following manners:``` pythonrange(end)range(begin, end)range(begin, end, step)```<jupyter_code>print('1) running over range(5):')
for num in range(5):
    print('\t', num)

print('2) running over range(3, 7):')
for num in range(3, 7):
    print('\t', num)

print('3) running over range(1, 6, 2):')
for num in range(1, 6, 2):
    print('\t', num)<jupyter_output><empty_output><jupyter_text>✍️ Complete the code bellow to print the letters in the 3rd, 5th and 6th position in your full name.<jupyter_code>## %%%%%%%%%%%%%%% Your code here - Begin %%%%%%%%%%%%%%%
letters_indices_to_print = (3, 5, 6)
for index in ...
    print(f'The leter in the {index} position is "{my_full_name[...
## %%%%%%%%%%%%%%% Your code here - End %%%%%%%%%%%%%%%%%<jupyter_output><empty_output><jupyter_text>FunctionsFunctions in python are defined using the "def" command:``` pythondef my_function(argument1, argument2):    do_something_with_the_arguments    return result```The return statement is optional and can be omitted. In such cases the function will return the "None" object.For example:<jupyter_code>def power(x, y):
    print('calculating x to the power of y')
    return x ** y

print(f'2 to the power of 8 is: {power(2, 8)}')<jupyter_output><empty_output><jupyter_text>A function can have multiple return result separated by a comma``` pythondef my_function(argument1, argument2):    do_something_with_the_arguments    return result1, result2```This function in fact returns the Tuple (result1, results2)For example, the function bellow returns the two roots of a second order polynomial:$$x_{1,2}=\frac{-b\pm\sqrt{b^2-4ac}}{2a}$$<jupyter_code>def find_roots(a, b, c):
    x1 = (-b + (b ** 2 - 4 * a * c) ** 0.5) / 2 / a
    x2 = (-b - (b ** 2 - 4 * a * c) ** 0.5) / 2 / a
    return x1, x2

## Option A - save each root in it's own variable
root1, root2 = find_roots(2, -14, 24)
print(f'The first root is {root1} and the second root is {root2}')

## Option B - save the two roots in a single Tuple
roots = find_roots(2, -14, 24)
print(f'The first root is {roots[0]} and the second root is {roots[1]}')<jupyter_output><empty_output><jupyter_text>The arguments of the functions can be passed to the function either by position or by name. All of the examples bellow are equivalent<jupyter_code>roots = find_roots(2, -14, 24)
roots = find_roots(a=2, b=-14, c=24)
roots = find_roots(c=24, b=-14, a=2)
roots = find_roots(2, c=24, b=-14)<jupyter_output><empty_output><jupyter_text>The arguments can also has a default value<jupyter_code>def greeter(name, greeting='Hello'):
    print(f'{greeting} {name}, how are you today?')

greeter('<NAME>')
greeter('<NAME>', 'Howdy')
greeter('<NAME>', greeting='Good morning')<jupyter_output><empty_output><jupyter_text>Defining functions in-linePython also offers a way to define function in-line using the "lambda" command:``` pythonmy_function = lambda arg1, arg2: do_something_with_args```(It is sometimes consider to be bad practice to use this method, but in many cases it makes the code cleaner)<jupyter_code>power = lambda x, y: x ** y

print(f'2 to the power of 8 is: {power(2, 8)}')<jupyter_output><empty_output><jupyter_text>ClassesIn order to deeply understand what are classes and how to use them you need to know a bit about object-oriented programing. In order to keep things simple, we will not dive into that subject and instead try to give a shallow explanation, which will be enough for you in order to start using them.When we define a new class, we in fact define a new type of object. Each class can contain internal variables and methods (functions). For example, lets say that we want to define a class which represents a pet with its name, current state and the some methods, which we can use to interact with it.The methods and inner variable of the object can be accessed using dot ".", for example:``` ptyhonmy_pet.namemy_pet.feed()```Lets us first look at an example, and then explain it:<jupyter_code>## Creating the class
class Pet:
    ## Defining the methods of the class:

    def __init__(self, name, pet_type='dog'):
        ## 1. The __init__ method is a special method which is called when the class is created.
        ## 2. The self argument represent the object it self so we can interact with it.

        ## Store the arguments to inner variables of the currect object (self).
        self.name = name  # Save the argument name to the inner variable name 
        self.pet_type = pet_type

        ## Set initial values for some other inner variables
        self.hungry = True  # Set the initial value of the inner variable hungry.
        self.needs_to_poop = False
        self.tired = False
    
    def print_state(self, greeting='Hi'):
        ## Check the vaules of the inner variables to define the pet's state:
        if self.hungry:
            state = 'I need to eat'
        elif self.needs_to_poop:
            state = 'I need to poop'
        elif self.tired:
            state = 'I need to sleep'
        else:
            state = 'I am just fine.'
        print(f'{greeting} I am {self.name} the {self.pet_type}, and {state}\n')
    
    def feed(self):
        ## Update initial variables
        self.hungry = False
        self.needs_to_poop = True

    def take_a_walk(self):
        self.needs_to_poop = False
        self.tired = True

    def take_a_nap(self):
        self.tired = False
        self.hungry = True

my_pet = Pet('Snoopy')

print(f'The type of my_pet is: {type(my_pet)}\n')
print(f'The name of my_pet is: {my_pet.name}\n')

my_pet.print_state()

print(f'Feeding {my_pet.name}')
my_pet.feed()
my_pet.print_state()

print(f'Taking {my_pet.name} for a walk')
my_pet.take_a_walk()
my_pet.print_state()

print(f'{my_pet.name} is taking a nap')
my_pet.take_a_nap()
my_pet.print_state()<jupyter_output><empty_output><jupyter_text>In the above example we have defined a class using the "class" command:``` pythonclass my_class:    def method1(self, arg1, arg2):        do_something        return result    def method2(self, arg3):        do_something_else``` The "self" argumentNote that all the classes methods receive "self" as their first argument. This variable will hold the object itself so we can interact with it inside the methods. The "\_\_init\_\_" functionPython allows us to define a few special method, which are called in specific cases. One important such method is  \_\_init\_\_. This method is called each time a we create a new object. The arguments of the \_\_init\_\_ method are argument which are used when creating a new object.In the above example, the \_\_init\_\_ function receives the argument 'Snoopy', which has been used in the line constructing the object:``` pythonmy_pet = Pet('Snoopy')```  PackagesWhat really gives Python it's power is the larger variety of packages which contain additional objects (variables, functions, classes, etc.). These packages can be easily imported and used as part of our code.In general each package needs to be installed separately using the "pip install" command. In practice, the packages which we will need in our course are already installed on the Colab servers.In this exercise we will show how to import and use a popular package called [NumPy](https://numpy.org/) which offers a larger set of tools for doing numerical work. This package is highly influenced by Matlab and many of the functions in the package have a similar signature (name, arguments return values) to the equivalent functions in Matlab.In addition to NumPy, in this course we will also be using the following packages:- [Matplotlib](matplotlib) - A ploting library with tries to mimic Matlab's plotting commands.- [Pandas](https://pandas.pydata.org/) - A package for working with datasets.- [scikit-learn](https://scikit-learn.org/) - A package offers a huge set of machine learning tools.- [PyTorch](https://pytorch.org/) - A package for working with neural networksImporting packages in Python is done using the "import" command:``` pythonimport my_package```This will search of the "my_package" package and import it as a new object named "my_package". We can also rename the package when importing it using:``` pythonimport my_package as alternative_name```For example it is common to import the NumPy package using the shorter name "np":``` pythonimport numpy as np```(there is also a way to import only a specific object from a package, but we will not cover that here).  NumPyWe shall start by importing NumPy<jupyter_code>import numpy as np<jupyter_output><empty_output><jupyter_text>The most basic object in NumPy is the "ndarray" (N dimensional array). These objects highly resembles Matlab's basic variables. The "array" function converts a sequence to an ndarray:<jupyter_code>my_array = np.array([2, 4, 1, 7])
print(f'The type of my_array is {type(my_array)}')
print(my_array)<jupyter_output><empty_output><jupyter_text>It also converts a list of list into a 2D array:<jupyter_code>my_2d_array = np.array([[2, 4, 1, 7], [6, 3, 2, 5]])
print(f'The type of my_array is {type(my_array)}')
print(my_2d_array)<jupyter_output><empty_output><jupyter_text>The function "random.randint" generates an array of random integers (from a given range):``` pythonnp.random.randint(low, high, size_as_a_tuple)```<jupyter_code>print(np.random.randint(0, 10, (3, 4)))<jupyter_output><empty_output><jupyter_text>By default, when using mathematical operations, such as addition and multiplications, they are performed element wise (on each element individually). (**Note** that this differs form matlab where the default is to treat every thing as a matrix)<jupyter_code>matrix1 = np.random.randint(0, 10, (3, 3))
print(f'matrix1:\n{matrix1}\n')
matrix2 = np.random.randint(0, 10, (3, 3))
print(f'matrix2:\n{matrix2}\n')

print(f'The product of the two matrices is:\n{matrix1 * matrix2}\n')<jupyter_output><empty_output><jupyter_text>Matrix multiplication can be performed using the "@" operator:<jupyter_code>matrix = np.random.randint(0, 10, (3, 4))
print(f'matrix:\n{matrix}\n')
column_vec = np.random.randint(0, 10, (4, 1))
print(f'column_vec:\n{column_vec}\n')

print(f'The matrix multiplication of the two matrices is:\n{matrix @ column_vec}\n')<jupyter_output><empty_output><jupyter_text>✍️ Edit the code below so that it will print the multiplication table (לוח הכפל). The transpose of a 2D array *x* can be created by *x.T*.<jupyter_code>a = np.array([[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]])

## %%%%%%%%%%%%%%% Your code here - Begin %%%%%%%%%%%%%%%
## Fill in the following line so that multiplication_table will store the 11 x 11 multiplication table (of the numbers between 0 and 10).
multiplication_table = ...
## %%%%%%%%%%%%%%% Your code here - End %%%%%%%%%%%%%%%%%

print(multiplication_table)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12219297754382034, 'fraction_numerical': 0.014000112000896008, 'mean_word_length': 4.696172248803828, 'pattern_counts': {'":': 5, '<': 179, '<?xml version=': 0, '>': 181, 'https://': 24, 'lorem ipsum': 0, 'www.': 6, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '43', 'dir': 'jupyter-structured-clean-dedup', 'id': '560257', 'license': 'MIT', 'n_tokens_mistral': 11135, 'n_tokens_neox': 10603, 'n_words': 4715, 'path': 'content/assignments/assignment_0_ex.ipynb', 'repo_name': 'technion046195/technion046195', 'size': '35735'}