├── Section 03 ├── 25_data.txt ├── 25_read_and_write_demo.py ├── 28_Python_specifics_bonus_section.txt ├── 11_lists_1_notes.py ├── 10_collections_intro_notes.py ├── 5_strings_3_notes.py ├── 3_strings_1_notes.py ├── 4_strings_2_notes.py ├── 23_student_2_notes.py ├── 22_student_1_notes.py ├── 24_student_3_notes.py ├── 6_nums_notes.py ├── 29_Lambda_expressions_intro.txt └── 27_student_6_notes.py ├── Section 01 ├── Thumbs.db ├── 3_python_book.pdf ├── 3_ebook_python.txt └── 3_python_book.html ├── README.md ├── Section 05 ├── 4_bisection_recur.py ├── 8_hash_algo_a.py ├── 3_bisection_iter.py ├── 6_bisec_search_proj.py ├── 6_demos.py ├── 13_project_script copy.py ├── 13_hash_algo.py ├── 11_hash_algo_d.py ├── 15_linked_list_a.py ├── 16_linked_list_demo.py ├── 29_job_scheduler.py └── 26_bst_demo.py ├── Section 04 ├── 15_merge_sort_recur_divide.py ├── 12_merge_sort_demo_starter.py ├── 5_selection_sort_demo.py ├── 14_merge_demo_complete.py ├── 3_bubble_sort_demo.py └── 20_merge_sort_demo.py ├── LICENSE └── Section 02 └── 2_command_line_basics.txt /Section 03/25_data.txt: -------------------------------------------------------------------------------- 1 | mashrur,hossain:python,ruby,javascript 2 | joe,schmo:python,ruby,javascript 3 | john,schmoe:python,ruby,javascript 4 | -------------------------------------------------------------------------------- /Section 01/Thumbs.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Project-based-Python-Algorithms-Data-Structures/HEAD/Section 01/Thumbs.db -------------------------------------------------------------------------------- /Section 01/3_python_book.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Project-based-Python-Algorithms-Data-Structures/HEAD/Section 01/3_python_book.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Python-3-Project-based-Python-Algorithms-Data-Structures 5 | Code Repository for Python 3: Project-based Python, Algorithms, Data Structures, Published by Packt 6 | -------------------------------------------------------------------------------- /Section 05/4_bisection_recur.py: -------------------------------------------------------------------------------- 1 | def bisection_recur(n, arr, start, stop): 2 | if start > stop: 3 | return f"{n} not found in list" 4 | else: 5 | mid = (start + stop)//2 6 | if n == arr[mid]: 7 | return f"{n} found at index: {mid}" 8 | elif n > arr[mid]: 9 | return bisection_recur(n, arr, mid+1, stop) 10 | else: 11 | return bisection_recur(n, arr, start, mid-1) 12 | -------------------------------------------------------------------------------- /Section 05/8_hash_algo_a.py: -------------------------------------------------------------------------------- 1 | class AlgoHashTable: 2 | 3 | def __init__(self, size): 4 | self.size = size 5 | self.hash_table = [[] for _ in range(self.size)] 6 | 7 | def create_buckets(self): 8 | pass 9 | 10 | def set_val(self, key, value): 11 | pass 12 | 13 | def get_val(self, key): 14 | pass 15 | 16 | def __str__(self): 17 | return "".join(str(item) for item in self.hash_table) 18 | 19 | hash_table = AlgoHashTable(256) 20 | print(hash_table) 21 | -------------------------------------------------------------------------------- /Section 04/15_merge_sort_recur_divide.py: -------------------------------------------------------------------------------- 1 | def divide_arr(arr): 2 | if len(arr) < 2: 3 | print(f"Base condition reached with {arr}") 4 | return arr[:] 5 | else: 6 | middle = len(arr)//2 7 | print("Current list to work with:", arr) 8 | print("Left split:", arr[:middle]) 9 | print("Right split:", arr[middle:]) 10 | l1 = divide_arr(arr[:middle]) 11 | l2 = divide_arr(arr[middle:]) 12 | # implied return None 13 | 14 | l = [8, 6, 2, 5] 15 | divide_arr(l) 16 | -------------------------------------------------------------------------------- /Section 04/12_merge_sort_demo_starter.py: -------------------------------------------------------------------------------- 1 | def merge_sorted(arr1,arr2): 2 | print("Merge function called with lists below:") 3 | print(f"left: {arr1} and right: {arr2}") 4 | sorted_arr = [] 5 | i, j = 0, 0 6 | print(f"Left list index i is {i} and has value: {arr1[i]}") 7 | print(f"Right list index j is {j} and has value: {arr2[j]}") 8 | return sorted_arr 9 | 10 | # xxxxxxxxxxxxxxxx Program Execution xxxxxxxxxxxxxxxx 11 | l1 = [1,4,6,8,10] 12 | l2 = [2,3,5,7,8,9] 13 | print(f"Un-merged list: {merge_sorted(l1,l2)}") 14 | -------------------------------------------------------------------------------- /Section 05/3_bisection_iter.py: -------------------------------------------------------------------------------- 1 | def bisection_iter(n, arr): 2 | start = 0 3 | stop = len(arr)-1 4 | while start <= stop: 5 | mid = (start + stop)//2 6 | if n == arr[mid]: 7 | return f"{n} found at index: {mid}" 8 | elif n > arr[mid]: 9 | start = mid + 1 10 | else: 11 | stop = mid - 1 12 | return f"{n} not found in list" 13 | 14 | # I have left the create list function out since you can 15 | # create it anyway you like, I personally would recommend 16 | # list comprehension 17 | -------------------------------------------------------------------------------- /Section 03/25_read_and_write_demo.py: -------------------------------------------------------------------------------- 1 | ## Reading from and writing to files 2 | # We learned the basics of using context managers to read data 3 | # from a file, make sure to have the data.txt file in the same 4 | # directory as this file, otherwise you have to provide the full 5 | # path to the file 6 | filename = 'data.txt' 7 | with open(filename) as f: 8 | for line in f: 9 | print(line.strip()) 10 | 11 | # We then looked at how to append a new line to the file 12 | record_to_add = "jane,doe:c,ruby,javascript" 13 | with open(filename, "a+") as to_write: 14 | to_write.write(record_to_add+"\n") 15 | -------------------------------------------------------------------------------- /Section 03/28_Python_specifics_bonus_section.txt: -------------------------------------------------------------------------------- 1 | I intend to post some bonus Python specific content for the remainder of 2 | section 3. This content does not apply to Algorithms and Data Structures that 3 | we will explore in sections 4 and 5 but will be good coding practice since it 4 | relates to the Python programming language and will be good to know for coding 5 | interviews. I have so far added the following topics: 6 | 7 | 1) Lambda expressions 8 | 2) Generators - under the hood (how they work) 9 | 3) Create your own generators using yield 10 | 11 | I will update this lecture as I add more content and exercises. Enjoy! 12 | -------------------------------------------------------------------------------- /Section 04/5_selection_sort_demo.py: -------------------------------------------------------------------------------- 1 | def selection_sort(arr): 2 | """ 3 | Use selection sort algorithm to sort a list of numbers 4 | """ 5 | spot_marker = 0 6 | while spot_marker < len(arr): 7 | for num in range(spot_marker, len(arr)): 8 | if arr[num] < arr[spot_marker]: 9 | arr[spot_marker], arr[num] = arr[num], arr[spot_marker] 10 | spot_marker += 1 11 | 12 | l = [6, 8, 1, 4, 10, 7, 8, 9, 3, 2, 5] # original case 13 | # l = [10, 9, 8, 8, 7, 6, 5, 4, 3, 2, 1] # worst case 14 | # l = [1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 10] # best case 15 | selection_sort(l) 16 | print(l) 17 | -------------------------------------------------------------------------------- /Section 04/14_merge_demo_complete.py: -------------------------------------------------------------------------------- 1 | def merge_sorted(arr1,arr2): 2 | sorted_arr = [] 3 | i, j = 0, 0 4 | while i < len(arr1) and j < len(arr2): 5 | if arr1[i] < arr2[j]: 6 | sorted_arr.append(arr1[i]) 7 | i += 1 8 | else: 9 | sorted_arr.append(arr2[j]) 10 | j += 1 11 | while i < len(arr1): 12 | sorted_arr.append(arr1[i]) 13 | i += 1 14 | while j < len(arr2): 15 | sorted_arr.append(arr2[j]) 16 | j += 1 17 | return sorted_arr 18 | 19 | # xxxxxxxxxxxxxxxx Program Execution xxxxxxxxxxxxxxxx 20 | l1 = [1,4,6,8,10] 21 | l2 = [2,3,5,7,8,9] 22 | print(f"Merged list: {merge_sorted(l1,l2)}") 23 | -------------------------------------------------------------------------------- /Section 04/3_bubble_sort_demo.py: -------------------------------------------------------------------------------- 1 | def bubble_sort(arr): 2 | """ 3 | Fill in this docstring to practice writing docstrings 4 | along with summarizing what the function does 5 | """ 6 | swap_happened = True 7 | while swap_happened: 8 | print('bubble sort status: ' + str(arr)) 9 | swap_happened = False 10 | for num in range(len(arr)-1): 11 | if arr[num] > arr[num+1]: 12 | swap_happened = True 13 | arr[num], arr[num+1] = arr[num+1], arr[num] 14 | 15 | l = [6, 8, 1, 4, 10, 7, 8, 9, 3, 2, 5] # original case 16 | # l = [10, 9, 8, 8, 7, 6, 5, 4, 3, 2, 1] # worst case 17 | # l = [1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 10] # best case 18 | bubble_sort(l) 19 | -------------------------------------------------------------------------------- /Section 01/3_ebook_python.txt: -------------------------------------------------------------------------------- 1 | E-book 2 | 3 | I built an e-book which covers the content from the introductory "Python 4 | in-depth" section of this course. This book can be used in conjunction with the 5 | section (recommended for beginners) or by itself (if you have some prior 6 | experience). There are text notes with each video lecture in the course as well, 7 | so usage of this book is fully optional and not required. 8 | 9 | Feel free to download a copy for yourself. There are two versions available for 10 | your convenience - html and pdf. 11 | 12 | Book chapters: Command Line basics, Strings, Print formatting, Numbers, 13 | Branching (if/elif/else blocks), Lists, Dictionaries, Sets, For loops and 14 | Generators, While loops, Functions and Classes. 15 | -------------------------------------------------------------------------------- /Section 03/11_lists_1_notes.py: -------------------------------------------------------------------------------- 1 | ## Lists, both lectures 1 and 2 2 | 3 | # I have 3 lists declared below, play around with the following 4 | # functions, methods, slices or other ones you like on them 5 | # len(), min(), max(), append(), insert(), extend(), remove(), pop() 6 | # reverse() 7 | # Note: Some methods/functions above are covered in lecture 1 8 | # while others are covered in lecture 2 9 | 10 | my_list = [15, 6, 7, 8, 35, 12, 14, 4, 10, 15] 11 | my_strings_list = ["comp sci", "physics", "elec engr", "philosophy"] 12 | my_new_list = ["art", "econ"] 13 | print(f"Ints: {my_list}") 14 | print(f"Strings: {my_strings_list}") 15 | print(f"New list: {my_new_list}") 16 | 17 | # Write your function or method code here 18 | 19 | print("My altered lists...") 20 | print(my_list) 21 | print(my_strings_list) 22 | print(my_new_list) 23 | -------------------------------------------------------------------------------- /Section 03/10_collections_intro_notes.py: -------------------------------------------------------------------------------- 1 | ## Lists, dictionaries, sets and tuples 2 | # Example list, mutable, maintains order, indexed 3 | # Documentation: https://docs.python.org/3/library/stdtypes.html#list 4 | my_list = [1,2,3,4,5,6,7,'hello','world'] 5 | print(my_list) 6 | 7 | # Example dictionary, key, value pairs 8 | # Documentation: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict 9 | my_dict = {'name':'john','course':'python'} 10 | print(my_dict) 11 | 12 | # Example set, note: sets don't allow duplicates 13 | # Documentation: https://docs.python.org/3/library/stdtypes.html#set-types-set-frozenset 14 | my_set = {1,6,2,'java','ruby',8,9,10,21,1000,'python',6} 15 | print(my_set) # One 6 from the set declaration will be removed 16 | 17 | # Example tuple, immutable 18 | # Documentation: https://docs.python.org/3/library/stdtypes.html#tuple 19 | my_tuple = ('hello','world','night','king','says','bye',8,3) 20 | print(my_tuple) 21 | -------------------------------------------------------------------------------- /Section 04/20_merge_sort_demo.py: -------------------------------------------------------------------------------- 1 | def merge_sorted(arr1,arr2): 2 | sorted_arr = [] 3 | i, j = 0, 0 4 | while i < len(arr1) and j < len(arr2): 5 | if arr1[i] < arr2[j]: 6 | sorted_arr.append(arr1[i]) 7 | i += 1 8 | else: 9 | sorted_arr.append(arr2[j]) 10 | j += 1 11 | while i < len(arr1): 12 | sorted_arr.append(arr1[i]) 13 | i += 1 14 | while j < len(arr2): 15 | sorted_arr.append(arr2[j]) 16 | j += 1 17 | return sorted_arr 18 | 19 | def divide_arr(arr): 20 | if len(arr) < 2: 21 | return arr[:] 22 | else: 23 | middle = len(arr)//2 24 | l1 = divide_arr(arr[:middle]) 25 | l2 = divide_arr(arr[middle:]) 26 | return merge_sorted(l1, l2) 27 | 28 | # xxxxxxxxxxxx Program Execution xxxxxxxxxxxx 29 | l = [6, 8, 1, 4, 10, 7, 8, 9, 3, 2, 5] 30 | print(divide_arr(l)) 31 | # xxxxxxxxxxxx End Program xxxxxxxxxxxx 32 | 33 | # orig list = [6, 8, 1, 4, 10, 7, 8, 9, 3, 2, 5] 34 | -------------------------------------------------------------------------------- /Section 05/6_bisec_search_proj.py: -------------------------------------------------------------------------------- 1 | from demos import bisection_iter, analyze_func, generate_emails 2 | 3 | # Add domains to list below for additional email extensions 4 | list_of_domains = ['yaexample.com','goexample.com','example.com'] 5 | 6 | # Generate emails 7 | emails = generate_emails(10,list_of_domains,1000000) 8 | 9 | # Test email to add to list of emails, followed by append to list 10 | email = 'mashrur@example.com' 11 | emails.append(email) 12 | 13 | # Sort list of generated emails 14 | sorted_emails = sorted(emails) 15 | 16 | # Run bisection search to find test email in sorted list of emails 17 | index, found = bisection_iter(email, sorted_emails) 18 | 19 | # Print result from function 20 | print(found) 21 | 22 | # Print index returned by function used on the list of sorted emails 23 | if index: print(f"element at index: {index} is {sorted_emails[index]}") 24 | 25 | # Run analysis of functions 26 | analyze_func(bisection_iter, email, sorted_emails) 27 | analyze_func(generate_emails, 10, list_of_domains, 1000000) 28 | -------------------------------------------------------------------------------- /Section 03/5_strings_3_notes.py: -------------------------------------------------------------------------------- 1 | ## Methods and functions we can use on String objects 2 | # Official documentation: https://docs.python.org/3/library 3 | # We ran the following functions on strings 4 | # len(), type(), id(), dir() 5 | # We ran the following string methods 6 | # capitalize(), upper(), lower(), strip(), find() 7 | # split(), join() 8 | # Test them out on the variable declaration below, two examples 9 | # provided 10 | greeting = "hello" 11 | print(len(greeting)) 12 | print(greeting.capitalize()) 13 | 14 | # We then looked at two ways of importing 15 | # first the whole module like below 16 | # import string 17 | 18 | # Then we just imported what we need from the module like below 19 | # from string import ascii_lowercase 20 | 21 | # I have some variables defined below, go ahead and try 22 | # the various functions and methods on them, I've started 23 | # below by using the len function on the welcome_message 24 | # variable 25 | greeting = "hello" 26 | user = "jon snow" 27 | welcome_message = "hello jon snow, and welcome to the Algorithms course" 28 | print(len(welcome_message)) 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Section 03/3_strings_1_notes.py: -------------------------------------------------------------------------------- 1 | # Strings - this is how you type a comment in python 2 | 3 | 'Hello world using single quotes' 4 | "Hello world using double quotes" 5 | """Hello world using 6 | triple quotes, also known as 7 | multi-line strings""" 8 | 9 | # To print an object to the screen, use the print function 10 | print('Hello world') # This will print Hello World in the terminal 11 | 12 | # To use an apostrophe ' in your print statement, wrap your 13 | # string in double quotes like below 14 | print("I'm using an apostrophe in this statement") 15 | 16 | # To use quotes within your statement, wrap your string in 17 | # single quotes like below 18 | print('This is a "quote from a book" which I like') 19 | 20 | # Variables are used to reference objects in Python, think 21 | # of variables as memory addresses. They should be named 22 | # using snake case 23 | message = "Hello! welcome to the Algorithm's course" 24 | name = "Mashrur" 25 | 26 | # You can use them in your print function to print to the screen 27 | print(message) 28 | print(name) 29 | 30 | # We can combine strings and print them out using the print function 31 | # the comma will automatically result in a space in-between the 32 | # strings 33 | print(message, name) 34 | -------------------------------------------------------------------------------- /Section 05/6_demos.py: -------------------------------------------------------------------------------- 1 | import time 2 | from random import choice 3 | from string import ascii_lowercase as letters 4 | 5 | def bisection_iter(n, arr): 6 | start = 0 7 | stop = len(arr)-1 8 | while start <= stop: 9 | mid = (start + stop)//2 10 | if n == arr[mid]: 11 | return mid, f"{n} found at index: {mid}" # return tuple 12 | elif n > arr[mid]: 13 | start = mid + 1 14 | else: 15 | stop = mid - 1 16 | return None, f"{n} not found in list" 17 | 18 | def analyze_func(func_name, *args): 19 | tic = time.time() 20 | func_name(*args) # accept variable list of arguments 21 | toc = time.time() 22 | seconds = toc-tic 23 | print(f"{func_name.__name__.capitalize()}\t-> Elapsed time: {seconds:.5f}") 24 | 25 | ###### functions to generate lists of random emails ###### 26 | def generate_name(length_of_name): 27 | return ''.join(choice(letters) for i in range(length_of_name)) 28 | 29 | def get_domain(list_of_domains): 30 | return choice(list_of_domains) 31 | 32 | def generate_emails(length_of_name, list_of_domains, total_emails): 33 | emails = [] 34 | for num in range(total_emails): 35 | emails.append(generate_name(length_of_name)+"@"+get_domain(list_of_domains)) 36 | return emails 37 | -------------------------------------------------------------------------------- /Section 05/13_project_script copy.py: -------------------------------------------------------------------------------- 1 | from random import choice 2 | from string import ascii_lowercase as letters 3 | 4 | list_of_domains = ['yaexample.com','goexample.com','example.com'] 5 | 6 | quotes = [ 'Luck is what happens when preparation meets opportunity', 7 | 'All cruelty springs from weakness', 8 | 'Begin at once to live, and count each separate day as a separate life', 9 | 'Throw me to the wolves and I will return leading the pack'] 10 | 11 | def generate_name(length_of_name): 12 | return ''.join(choice(letters) for i in range(length_of_name)) 13 | 14 | def get_domain(list_of_domains): 15 | return choice(list_of_domains) 16 | 17 | def get_quotes(list_of_quotes): 18 | return choice(list_of_quotes) 19 | 20 | def generate_records(length_of_name, list_of_domains, total_records, list_of_quotes): 21 | with open("data.txt", "w") as to_write: 22 | for num in range(total_records): 23 | key = generate_name(length_of_name)+"@"+get_domain(list_of_domains) 24 | value = get_quotes(quotes) 25 | to_write.write(key + ":" + value + "\n") 26 | to_write.write("mashrur@example.com:Don't let me leave Murph\n") 27 | to_write.write("evgeny@example.com:All I do is win win win no matter what!\n") 28 | 29 | generate_records(10, list_of_domains, 100000, quotes) 30 | -------------------------------------------------------------------------------- /Section 03/4_strings_2_notes.py: -------------------------------------------------------------------------------- 1 | # We can use string concatenation and add strings 2 | # together 3 | message = "Welcome to the course" 4 | name = "Mashrur" 5 | print(message + name) 6 | 7 | # We can add an empty space in there too 8 | print(message + " " + name) 9 | 10 | # Strings are sequences of characters which are indexed 11 | # We can index into a string by using square bracket notation 12 | movie_name = "Interstellar" 13 | print(movie_name[0]) # This will print 'I' which is at index 0 14 | print(movie_name[1]) # This will print 'n' which is at index 1 15 | print(movie_name[11]) # This will print 'r' which is the last character 16 | print(movie_name[-1]) # This will print 'r', last character 17 | print(movie_name[-2]) # This will print 'a', second to last character 18 | 19 | # We can use slicing to select portions of the string 20 | print(movie_name[0:5]) # This will print 'Inter' 21 | print(movie_name[:5]) # We can leave the starting 0 out if we start at 22 | # at the beginning to get 'Inter' as well 23 | print(movie_name[5:]) # This will print 'stellar', begin index to end 24 | print(movie_name[:]) # This will print "Interstellar", begin to end 25 | print(movie_name[5:9]) # This will print 'stel' 26 | 27 | # You can specify step size as optional third argument 28 | print(movie_name[5:9:2]) # This will print 'se', moving forward by 29 | # 2 steps instead of the default 1 30 | print(movie_name[::2]) # This will print "Itrtla" 31 | print(movie_name[::-1]) # This will reverse the string and print 32 | # 'ralletsretnI' 33 | -------------------------------------------------------------------------------- /Section 05/13_hash_algo.py: -------------------------------------------------------------------------------- 1 | class AlgoHashTable: 2 | 3 | def __init__(self, size): 4 | self.size = size 5 | self.hash_table = self.create_buckets() 6 | 7 | def create_buckets(self): 8 | return [[] for _ in range(self.size)] 9 | 10 | def set_val(self, key, value): 11 | hashed_key = hash(key)%self.size 12 | bucket = self.hash_table[hashed_key] 13 | found_key = False 14 | for index, record in enumerate(bucket): 15 | record_key, record_value = record 16 | if record_key == key: 17 | found_key = True 18 | break 19 | if found_key: 20 | bucket[index] = (key, value) 21 | else: 22 | bucket.append((key, value)) 23 | 24 | def get_val(self, key): 25 | hashed_key = hash(key)%self.size 26 | bucket = self.hash_table[hashed_key] 27 | found_key = False 28 | for index, record in enumerate(bucket): 29 | record_key, record_value = record 30 | if record_key == key: 31 | found_key = True 32 | break 33 | if found_key: 34 | return record_value 35 | else: 36 | return "No record found with that email address" 37 | 38 | def __str__(self): 39 | return "".join(str(item) for item in self.hash_table) 40 | 41 | hash_table = AlgoHashTable(256) 42 | with open("data.txt") as f: 43 | for line in f: 44 | key, value = line.split(":") 45 | hash_table.set_val(key, value) 46 | 47 | print(hash_table.get_val('mashrur@example.com')) 48 | print(hash_table.get_val('evgeny@example.com')) 49 | -------------------------------------------------------------------------------- /Section 02/2_command_line_basics.txt: -------------------------------------------------------------------------------- 1 | Below are some helpful commands to use to navigate the command line/terminal in 2 | your computer. If you are not familiar with it at this time, don't worry about 3 | it at all. I'll discuss what it is in the videos if/when I use it. Instructions 4 | are provided for both Windows and Mac/Linux. 5 | 6 | There is a command line basics video in the beginning of the next section as 7 | well but having these now might help in this development section. Again, I will 8 | mention it in the videos if/when I use the terminal/command line or if I use any 9 | commands associated with it. Just keep them handy for now. 10 | 11 | Good luck and I hope you find these useful! 12 | 13 | To launch the terminal in a Mac: Go to your applications/launchpad or spotlight 14 | search and start typing term and the terminal will pop-up. You can click on it 15 | to launch it. 16 | 17 | To launch the command line in Windows: Go to your search bar and start type cmd  18 | and the command line app will pop up. Alternatively you can use the PowerShell 19 | terminal by looking for that app instead. 20 | 21 | To see which directory you are currently in: 22 | Windows-> cd 23 | Mac/Linux-> pwd 24 | 25 | To list files and folder under current directory: 26 | Windows-> dir 27 | Mac/Linux-> ls 28 | 29 | To move to a directory listed under current directory: 30 | Windows, Mac/Linux-> cd name_of_directory 31 | 32 | To move up one directory from current directory: 33 | Windows, Mac/Linux-> cd .. 34 | 35 | To create a new directory under the current directory: 36 | Windows, Mac/Linux-> mkdir name_of_directory 37 | 38 | To clear the terminal/command prompt screen: 39 | Windows-> cls 40 | Mac/Linux-> clear 41 | -------------------------------------------------------------------------------- /Section 03/23_student_2_notes.py: -------------------------------------------------------------------------------- 1 | ## Adding methods to the student class 2 | 3 | # The add and remove course methods were added to the class in the video 4 | # The class looked like below at the end of it 5 | # I have added some execution code to the bottom for demo purposes 6 | 7 | class Student: 8 | 9 | def __init__(self, first, last, courses=None): 10 | self.first_name = first 11 | self.last_name = last 12 | if courses == None: 13 | self.courses = [] 14 | else: 15 | self.courses = courses 16 | 17 | def add_course(self, course): 18 | if course not in self.courses: 19 | self.courses.append(course) 20 | else: 21 | print(f"{self.first_name} is already \ 22 | enrolled in the {course} course") 23 | 24 | def remove_course(self, course): 25 | if course in self.courses: 26 | self.courses.remove(course) 27 | else: 28 | print(f"{course} not found") 29 | 30 | courses_1 = ['python','rails','javascript'] 31 | courses_2 = ['java','rails','c'] 32 | mashrur = Student('mashrur','hossain',courses_1) 33 | john = Student('john','doe',courses_2) 34 | print(mashrur.first_name, mashrur.last_name, mashrur.courses) 35 | print(john.first_name, john.last_name, john.courses) 36 | print("Courses added to students") 37 | mashrur.add_course('c') 38 | john.add_course('c++') 39 | print(mashrur.first_name, mashrur.last_name, mashrur.courses) 40 | print(john.first_name, john.last_name, john.courses) 41 | print("Courses removed from students") 42 | mashrur.remove_course('rails') 43 | john.remove_course('java') 44 | print(mashrur.first_name, mashrur.last_name, mashrur.courses) 45 | print(john.first_name, john.last_name, john.courses) 46 | -------------------------------------------------------------------------------- /Section 05/11_hash_algo_d.py: -------------------------------------------------------------------------------- 1 | class AlgoHashTable: 2 | 3 | def __init__(self, size): 4 | self.size = size 5 | self.hash_table = self.create_buckets() 6 | 7 | def create_buckets(self): 8 | return [[] for _ in range(self.size)] 9 | 10 | def set_val(self, key, value): 11 | hashed_key = hash(key)%self.size 12 | bucket = self.hash_table[hashed_key] 13 | found_key = False 14 | for index, record in enumerate(bucket): 15 | record_key, record_value = record 16 | if record_key == key: 17 | found_key = True 18 | break 19 | if found_key: 20 | bucket[index] = (key, value) 21 | else: 22 | bucket.append((key, value)) 23 | 24 | def get_val(self, key): 25 | hashed_key = hash(key)%self.size 26 | bucket = self.hash_table[hashed_key] 27 | found_key = False 28 | for index, record in enumerate(bucket): 29 | record_key, record_value = record 30 | if record_key == key: 31 | found_key = True 32 | break 33 | if found_key: 34 | return record_value 35 | else: 36 | return "No record found with that email address" 37 | 38 | def __str__(self): 39 | return "".join(str(item) for item in self.hash_table) 40 | 41 | hash_table = AlgoHashTable(256) 42 | hash_table.set_val('mashrur@example.com',{'first_name':'Mashrur','last_name':'Hossain'}) 43 | hash_table.set_val('evgeny@example.com',{'first_name':'Evgeny','last_name':'CoderElite'}) 44 | hash_table.set_val('tyrion@example.com',{'first_name':'Tyrion','last_name':'BadAdvice'}) 45 | print(hash_table) 46 | print(hash_table.get_val('tyrion@example.com')) 47 | -------------------------------------------------------------------------------- /Section 03/22_student_1_notes.py: -------------------------------------------------------------------------------- 1 | ## Creating a custom Student class 2 | # We can create a class using the class keyword 3 | class Student: 4 | pass 5 | 6 | # We can create instances of the class, or instances 7 | # of student objects basically by calling the class name followed 8 | # by () like below: 9 | mashrur = Student() 10 | john = Student() 11 | 12 | # Note above mashrur and john are separate instances of students 13 | # They are not the same object even though are both type student 14 | 15 | # You can add attributes to your objects like below, note: we 16 | # aim to work with first_name, last_name and eventually courses 17 | # attributes 18 | mashrur.first_name = 'mashrur' 19 | mashrur.last_name = 'hossain' 20 | john.first_name = 'john' 21 | john.last_name = 'doe' 22 | 23 | # You can access the attributes associated with each student object 24 | print(mashrur.first_name, mashrur.last_name) 25 | print(john.first_name, john.last_name) 26 | 27 | # You can use the special __init__() method to assign the attributes 28 | # when the instance of the object is created. It takes in a default 29 | # first argument which is called self by convention in python, self 30 | # is the object being created itself, followed by any other 31 | # attributes you wish to initialize. We modified the class like below: 32 | 33 | class Student: 34 | 35 | def __init__(self, first, last, courses=None): 36 | self.first_name = first 37 | self.last_name = last 38 | if courses == None: 39 | self.courses = [] 40 | else: 41 | self.courses = courses 42 | 43 | courses_1 = ['python','rails','javascript'] 44 | courses_2 = ['java','rails','c'] 45 | mashrur = Student('mashrur','hossain',courses_1) 46 | john = Student('john','doe',courses_2) 47 | print(mashrur.first_name, mashrur.last_name, mashrur.courses) 48 | print(john.first_name, john.last_name, john.courses) 49 | -------------------------------------------------------------------------------- /Section 05/15_linked_list_a.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | 3 | def __init__(self, data=None): 4 | self.data = data 5 | self.next = None 6 | 7 | def __str__(self): 8 | return f"{self.data}" 9 | 10 | class LinkedList: 11 | 12 | def __init__(self): 13 | self.head = None 14 | self.tail = None 15 | 16 | def append_val(self, x): 17 | '''add x to the end of the list''' 18 | if not isinstance(x, Node): 19 | x = Node(x) 20 | if self.head == None: 21 | self.head = x 22 | else: 23 | self.tail.next = x 24 | self.tail = x 25 | 26 | def __str__(self): 27 | # [5->4->10->1] 28 | to_print = "" 29 | curr = self.head 30 | while curr is not None: 31 | to_print += str(curr.data) + "->" 32 | curr = curr.next 33 | if to_print: 34 | return "[" + to_print[:-2] + "]" 35 | return "[]" 36 | 37 | def add_to_start(self, x): 38 | '''add x to the left of the list making it the head''' 39 | pass 40 | 41 | def search_val(self, x): 42 | '''return indices where x was found''' 43 | pass 44 | 45 | def remove_val_by_index(self, x): 46 | '''remove and return value at index x provided as parameter''' 47 | pass 48 | 49 | def length(self): 50 | '''return the length of the list, rep'd by number of nodes''' 51 | pass 52 | 53 | def reverse_list_recur(self, current, previous): 54 | '''reverse the sequence of node pointers in the linked list''' 55 | # Given [1->2->3->4->5] reverse pointers [1<-2<-3<-4<-5] 56 | # Turning list to [5->4->3->2->1] 57 | 58 | my_list = LinkedList() 59 | print(my_list) 60 | my_list.append_val(1) 61 | my_list.append_val(2) 62 | my_list.append_val(3) 63 | my_list.append_val(4) 64 | my_list.append_val(5) 65 | print(my_list) 66 | -------------------------------------------------------------------------------- /Section 03/24_student_3_notes.py: -------------------------------------------------------------------------------- 1 | ## Adding special methods to the student class 2 | 3 | # We first added the __str__() special method to get a string 4 | # representation of our student object, we also added the __len__() 5 | # and __repr__() methods, you can code out the __str__() method 6 | # to see the __repr__() method in action 7 | # The class looked like below at the end of it 8 | # I have added some execution code to the bottom for demo purposes 9 | 10 | class Student: 11 | 12 | def __init__(self, first, last, courses=None): 13 | self.first_name = first 14 | self.last_name = last 15 | if courses == None: 16 | self.courses = [] 17 | else: 18 | self.courses = courses 19 | 20 | def add_course(self, course): 21 | if course not in self.courses: 22 | self.courses.append(course) 23 | else: 24 | print(f"{self.first_name} is already \ 25 | enrolled in the {course} course") 26 | 27 | def remove_course(self, course): 28 | if course in self.courses: 29 | self.courses.remove(course) 30 | else: 31 | print(f"{course} not found") 32 | 33 | def __len__(self): 34 | return len(self.courses) 35 | 36 | def __repr__(self): 37 | return f"Student('{self.first_name}','{self.last_name}',{self.courses})" 38 | 39 | def __str__(self): 40 | return f"First name: {self.first_name.capitalize()}\nLast name: {self.last_name.capitalize()}\ 41 | \nCourses: {', '.join(map(str.capitalize, self.courses))}" 42 | 43 | 44 | courses_1 = ['python','rails','javascript'] 45 | courses_2 = ['java','rails','c'] 46 | mashrur = Student('mashrur','hossain',courses_1) 47 | john = Student('john','doe',courses_2) 48 | print(mashrur) 49 | print(john) 50 | print(f"{mashrur.first_name.capitalize()} is enrolled in {len(mashrur)} courses") 51 | print(f"{john.first_name.capitalize()} is enrolled in {len(john)} courses") 52 | -------------------------------------------------------------------------------- /Section 05/16_linked_list_demo.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | 3 | def __init__(self, data=None): 4 | self.data = data 5 | self.next = None 6 | 7 | def __str__(self): 8 | return f"{self.data}" 9 | 10 | class LinkedList: 11 | 12 | def __init__(self): 13 | self.head = None 14 | self.tail = None 15 | 16 | def append_val(self, x): 17 | '''add x to the end of the list''' 18 | if not isinstance(x, Node): 19 | x = Node(x) 20 | if self.head == None: 21 | self.head = x 22 | else: 23 | self.tail.next = x 24 | self.tail = x 25 | 26 | def __str__(self): 27 | # [5->4->10->1] 28 | to_print = "" 29 | curr = self.head 30 | while curr is not None: 31 | to_print += str(curr.data) + "->" 32 | curr = curr.next 33 | if to_print: 34 | return "[" + to_print[:-2] + "]" 35 | return "[]" 36 | 37 | def add_to_start(self, x): 38 | '''add x to the left of the list making it the head''' 39 | pass 40 | 41 | def search_val(self, x): 42 | '''return indices where x was found''' 43 | pass 44 | 45 | def remove_val_by_index(self, x): 46 | '''remove and return value at index x provided as parameter''' 47 | pass 48 | 49 | def length(self): 50 | '''return the length of the list, rep'd by number of nodes''' 51 | pass 52 | 53 | def reverse_list_recur(self, current, previous): 54 | '''reverse the sequence of node pointers in the linked list''' 55 | # Given [1->2->3->4->5] reverse pointers [1<-2<-3<-4<-5] 56 | # Turning list to [5->4->3->2->1] 57 | if self.head == None: 58 | return 59 | elif current.next == None: 60 | self.tail = self.head 61 | current.next = previous 62 | self.head = current 63 | else: 64 | next = current.next 65 | current.next = previous 66 | self.reverse_list_recur(next, current) 67 | 68 | my_list = LinkedList() 69 | my_list.append_val(1) 70 | my_list.append_val(2) 71 | my_list.append_val(3) 72 | my_list.append_val(4) 73 | my_list.append_val(5) 74 | print(my_list) 75 | my_list.reverse_list_recur(my_list.head, None) 76 | print(my_list) 77 | -------------------------------------------------------------------------------- /Section 03/6_nums_notes.py: -------------------------------------------------------------------------------- 1 | ## Numbers - Integers, floats, math, type casting, user input 2 | # Documentation on integers and floats can be found below: 3 | # https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex 4 | 5 | # To check the type of an object, use the type function, example below 6 | num_1 = 10 7 | print(type(num_1)) # This should return class 'int' 8 | 9 | # Standard math operations can be performed using +, -, *, / 10 | print(4+4) # output 8 11 | print(10-5) # output 5 12 | print(10*2) # output 20 13 | print(10/2) # output 5.0 14 | 15 | # Division will give you a float (decimal value number) by default 16 | # To perform floor division, or remove the decimal you can use // 17 | print(10//3) # output 3 18 | 19 | # You can use the mod operator % to find the remainder 20 | print(10%3) # output 1 21 | print(10%2) # output 0 22 | 23 | # To take a number to the power of another number, you can use 24 | # the ** operator 25 | print(5**2) # output 25 (this is 5 squared) 26 | print(5**3) # output 125 (this is 5 cubed) 27 | 28 | # You can import the math module and use functions provided by it 29 | import math 30 | print(math.pow(10,5)) # output 100000.0, this is 10 to the power 5 31 | 32 | # Similarly you can import the random module and generate random 33 | # integers using the randint function 34 | import random 35 | print(random.randint(0,1000)) # output random integer between 0 36 | # and 1000 37 | 38 | # You can use other functions on numbers as well 39 | num_2 = -10 40 | print(abs(num_2)) # abs or absolute value function will remove 41 | # the negative sign from the number, output 10 42 | 43 | # To convert the string "10" to the int 10, you can cast it to an int 44 | my_string = "10" 45 | my_num = int(my_string) # this will convert "10" to int 10 46 | print(type(my_num)) # output will be class 'int' 47 | 48 | # Similarly you can convert an int to a string, 10 to "10" 49 | # by casting it to a string 50 | my_new_string = str(my_num) # this will convert 10 to "10" 51 | print(type(my_new_string)) # output will be class 'str' 52 | 53 | # The multiplication program presented in the video is below 54 | # using input from the user using the input function 55 | print("Welcome to the multiplication program") 56 | print("-"*30) 57 | num_1 = int(input("Enter a number to multiply-> ")) 58 | num_2 = int(input("Enter a second number to multiply-> ")) 59 | result = num_1 * num_2 60 | print(result) 61 | -------------------------------------------------------------------------------- /Section 03/29_Lambda_expressions_intro.txt: -------------------------------------------------------------------------------- 1 | Lambda expressions 2 | 3 | Lambda expressions are another way of creating functions. We have already seen 4 | how to create functions using the def keyword followed by the name of the 5 | function and optional parameters. Lambda expressions are another way of 6 | accomplishing this, but they have a unique general characteristic which is that 7 | they are usually 'one time use' functions. Basically functions you want to 8 | create on the go without intending to formalize or re-use them again. They are 9 | also sometimes referred to as 'anonymous functions'. 10 | 11 | Some features of lambda expressions: 12 | - They are declared using the lambda keyword, instead of the def keyword. 13 | - They don't have names, which is why they are referred to as 'anonymous' 14 | functions. 15 | - The optional parameters list immediate follow the lambda keyword followed by 16 | a colon : and then the expression itself, example below: 17 | 18 | lambda x, y: x * y 19 | Notes on the line above: 20 | a) Notice there is no name, it's simply lambda, followed by the parameter list 21 | (in this case x and y). 22 | b) The return after the colon is implied. 23 | c) This creates a regular function object. 24 | 25 | - You can assign it to a variable, pass it as an argument to another function 26 | etc. 27 | 28 | Example use: 29 | 30 | Let's see the lambda expression we defined above in action, first as an argument 31 | to another function. 32 | 33 | def my_math_func(x, f): 34 | return f(x) 35 | 36 | Consider the function above. This function takes a variable x and another 37 | function f as arguments. Then it returns f(x), implying that it is passing x as 38 | an argument to the function f and returning the value. 39 | 40 | Now, let's assume we are trying to model f(x) as returning the cubed value of x 41 | or f(x) = x^3 and we want to accomplish our my_math_func to do this. Then 42 | instead of creating a separate function to find the cube of a number, we can 43 | simply pass this as a lambda expression as the argument while calling 44 | my_math_func like below: 45 | 46 | def my_math_func(x, f):     47 | return f(x) 48 | 49 | my_math_func(2, lambda x: x**3) 50 | 51 | If you want to print this value, and are not using Jupyter Notebooks, then you 52 | want to replace the last line with the line below: 53 | 54 | print(my_math_func(2, lambda x: x**3)) 55 | 56 | You can also specify default values for parameters in lambda expressions. For 57 | example, if you wanted your function to not just return the cube but add a 58 | separate number to it which you provide as a constant, you can do so like below: 59 | 60 | Math expression: f(x, y) = x^3 + y, with y with a fixed value of 100 for the 61 | first iteration. 62 | 63 | Equivalent code: lambda x, y=100: x**3 + y 64 | 65 | Let's use this lambda expression to see how we can assign this to a variable 66 | below: 67 | 68 | my_equation = lambda x, y=100: x**3 + y 69 | print(my_equation(3)) # This should print 127 70 | print(my_equation(5)) # This should print 225 71 | 72 | Personal notes on lambda functions: 73 | - Try to keep them simple, if you want to use assignments within your function 74 | then you cannot use lambda expressions. 75 | - When thinking of using lambda expressions: think 1 line, single expression, 76 | not going to re-use. 77 | -------------------------------------------------------------------------------- /Section 03/27_student_6_notes.py: -------------------------------------------------------------------------------- 1 | # We filled in the code for the add_to_file method and also added 2 | # the prep_to_write function as a static method to the student class. 3 | 4 | # We then looked at inheritance and built the StudentAthlete class below it. 5 | # Additionally we looked at the isinstance function which will come 6 | # in handy while working with objects. 7 | # The completed code for both classes is below, along with some starter 8 | # execution code 9 | 10 | class Student: 11 | 12 | def __init__(self, first, last, courses=None): 13 | self.first_name = first 14 | self.last_name = last 15 | if courses == None: 16 | self.courses = [] 17 | else: 18 | self.courses = courses 19 | 20 | def add_course(self, course): 21 | if course not in self.courses: 22 | self.courses.append(course) 23 | else: 24 | print(f"{self.first_name} is already \ 25 | enrolled in the {course} course") 26 | 27 | def remove_course(self, course): 28 | if course in self.courses: 29 | self.courses.remove(course) 30 | else: 31 | print(f"{course} not found") 32 | 33 | def find_in_file(self, filename): 34 | with open(filename) as f: 35 | for line in f: 36 | first_name, last_name, course_details = Student.prep_record(line.strip()) 37 | student_read_in = Student(first_name, last_name, course_details) 38 | if self == student_read_in: 39 | return True 40 | return False 41 | 42 | def add_to_file(self, filename): 43 | if self.find_in_file(filename): 44 | return "Record already exists" 45 | else: 46 | record_to_add = Student.prep_to_write(self.first_name, self.last_name, self.courses) 47 | with open(filename, "a+") as to_write: 48 | to_write.write(record_to_add+"\n") 49 | return "Record added" 50 | 51 | 52 | @staticmethod 53 | def prep_record(line): 54 | line = line.split(":") 55 | first_name, last_name = line[0].split(",") 56 | course_details = line[1].rstrip().split(",") 57 | return first_name, last_name, course_details 58 | 59 | @staticmethod 60 | def prep_to_write(first_name, last_name, courses): 61 | full_name = first_name+','+last_name 62 | courses = ",".join(courses) 63 | return full_name+':'+courses 64 | 65 | def __eq__(self, other): 66 | return self.first_name == other.first_name \ 67 | and self.last_name == other.last_name 68 | 69 | def __len__(self): 70 | return len(self.courses) 71 | 72 | def __repr__(self): 73 | return f"Student('{self.first_name}','{self.last_name}',{self.courses})" 74 | 75 | def __str__(self): 76 | return f"First name: {self.first_name.capitalize()}\nLast name: {self.last_name.capitalize()}\ 77 | \nCourses: {', '.join(map(str.capitalize, self.courses))}" 78 | 79 | class StudentAthlete(Student): 80 | 81 | def __init__(self, first, last, courses=None, sport=None): 82 | super().__init__(first, last, courses) 83 | self.sport = sport 84 | 85 | courses = ["python","ruby","javascript"] 86 | jane = StudentAthlete("jane","doe",courses,"hockey") 87 | print(jane.sport) 88 | print(isinstance(jane, Student)) # This will test whether Jane is an object of the Student class 89 | -------------------------------------------------------------------------------- /Section 05/29_job_scheduler.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from bst_demo import BSTDemo, Node 3 | 4 | def get_job_input_details(): 5 | start_time = input("Enter the time in hh:mm format, example 18:30 or 6:30-> ") 6 | while True: 7 | try: 8 | datetime.strptime(start_time, '%H:%M') 9 | except ValueError: 10 | print("Incorrect time format, should be hh:mm") 11 | start_time = input("Enter the time in hh:mm format, ex 18:30 or 6:30-> ") 12 | else: 13 | break 14 | duration_of_job = input("Enter the duration of the job in minutes, ex 60-> ") 15 | while True: 16 | try: 17 | int(duration_of_job) 18 | except ValueError: 19 | print("Please enter a number for number of minutes") 20 | duration_of_job = input("Enter the duration of the job in minutes, ex 60-> ") 21 | else: 22 | break 23 | job_name = input("Enter the name of the job (case sensitive)-> ") 24 | return start_time, duration_of_job, job_name 25 | 26 | my_tree = BSTDemo() 27 | 28 | with open("data.txt") as f: 29 | for line in f: 30 | my_tree.insert(line) 31 | 32 | while True: 33 | print("Please choose an option from the list below:") 34 | print("Press 1 to view today's scheduled jobs") 35 | print("Press 2 to add a job to today's schedule") 36 | print("Press 3 to remove a job from the schedule") 37 | print("Press 4 to quit") 38 | selection = input("Enter your choice-> ") 39 | try: 40 | entry = int(selection) 41 | except ValueError: 42 | print("Please enter a number between 1 and 4") 43 | continue 44 | if int(selection) == 1: 45 | my_tree.in_order() 46 | elif int(selection) == 2: 47 | print("You have chosen to add a job to the schedule") 48 | start_time, duration_of_job, job_name = get_job_input_details() 49 | line = start_time+","+duration_of_job+","+job_name 50 | num = my_tree.length() 51 | my_tree.insert(line) 52 | if num == my_tree.length()-1: 53 | with open("data.txt", "a+") as to_write: 54 | to_write.write(line+"\n") 55 | input("Press any key to continue... ") 56 | elif int(selection) == 3: 57 | print("You have chosen to remove a job from the schedule") 58 | start_time, duration_of_job, job_name = get_job_input_details() 59 | key_to_find = datetime.strptime(start_time, '%H:%M').time() 60 | result = my_tree.find_val(key_to_find) 61 | if result: 62 | if result.name_of_job == job_name and result.duration == duration_of_job: 63 | print("Removing job:") 64 | print(result) 65 | my_tree.delete_val(key_to_find) 66 | print("Job successfully removed") 67 | with open("data.txt", "r") as f: 68 | lines = f.readlines() 69 | with open("data.txt", "w") as f: 70 | for line in lines: 71 | if line.strip("\n") != start_time+","+duration_of_job+","+job_name: 72 | f.write(line) 73 | input("Press any key to continue... ") 74 | else: 75 | print("The name and/or duration of job did not match, delete failed") 76 | input("Press any key to continue... ") 77 | else: 78 | print("Job not found") 79 | input("Press any key to continue... ") 80 | elif int(selection) == 4: 81 | print("Exiting program...") 82 | break 83 | else: 84 | print("Please enter a number between 1 and 4") 85 | -------------------------------------------------------------------------------- /Section 05/26_bst_demo.py: -------------------------------------------------------------------------------- 1 | # Final implementation 2 | 3 | class Node: 4 | def __init__(self, key): 5 | self.data = key 6 | self.left_child = None 7 | self.right_child = None 8 | 9 | class BSTDemo: 10 | def __init__(self): 11 | self.root = None 12 | 13 | def insert(self, key): 14 | if not isinstance(key, Node): 15 | key = Node(key) 16 | if self.root == None: 17 | self.root = key 18 | else: 19 | self._insert(self.root, key) 20 | 21 | def _insert(self, curr, key): 22 | if key.data > curr.data: 23 | if curr.right_child == None: 24 | curr.right_child = key 25 | else: 26 | self._insert(curr.right_child, key) 27 | elif key.data < curr.data: 28 | if curr.left_child == None: 29 | curr.left_child = key 30 | else: 31 | self._insert(curr.left_child, key) 32 | 33 | def in_order(self): 34 | self._in_order(self.root) 35 | print("") 36 | 37 | def _in_order(self, curr): 38 | if curr: 39 | self._in_order(curr.left_child) 40 | print(curr.data, end=" ") 41 | self._in_order(curr.right_child) 42 | 43 | def pre_order(self): 44 | '''root, left, right''' 45 | pass 46 | 47 | def _pre_order(self, curr): 48 | pass 49 | 50 | def post_order(self): 51 | '''left, right, root''' 52 | pass 53 | 54 | def _post_order(self, curr): 55 | pass 56 | 57 | def find_val(self, key): 58 | return self._find_val(self.root, key) 59 | 60 | def _find_val(self, curr, key): 61 | if curr: 62 | if key == curr.data: 63 | return "Value found in tree" 64 | elif key < curr.data: 65 | return self._find_val(curr.left_child, key) 66 | else: 67 | return self._find_val(curr.right_child, key) 68 | return "Value not found in tree" 69 | 70 | def min_right_subtree(self, curr): 71 | if curr.left_child == None: 72 | return curr 73 | else: 74 | return self.min_right_subtree(curr.left_child) 75 | 76 | def delete_val(self, key): 77 | self._delete_val(self.root, None, None, key) 78 | 79 | def _delete_val(self, curr, prev, is_left, key): 80 | if curr: 81 | if key == curr.data: 82 | if curr.left_child and curr.right_child: 83 | min_child = self.min_right_subtree(curr.right_child) 84 | curr.data = min_child.data 85 | self._delete_val(curr.right_child, curr, False, min_child.data) 86 | elif curr.left_child == None and curr.right_child == None: 87 | if prev: 88 | if is_left: 89 | prev.left_child = None 90 | else: 91 | prev.right_child = None 92 | else: 93 | self.root = None 94 | elif curr.left_child == None: 95 | if prev: 96 | if is_left: 97 | prev.left_child = curr.right_child 98 | else: 99 | prev.right_child = curr.right_child 100 | else: 101 | self.root = curr.right_child 102 | else: 103 | if prev: 104 | if is_left: 105 | prev.left_child = curr.left_child 106 | else: 107 | prev.right_child = curr.left_child 108 | else: 109 | self.root = curr.left_child 110 | elif key < curr.data: 111 | self._delete_val(curr.left_child, curr, True, key) 112 | elif key > curr.data: 113 | self._delete_val(curr.right_child, curr, False, key) 114 | else: 115 | print(f"{key} not found in Tree") 116 | 117 | tree = BSTDemo() 118 | tree.insert("F") 119 | tree.insert("C") 120 | print("Test deleting leaf node which is left child of parent") 121 | tree.in_order() 122 | tree.delete_val("C") 123 | tree.in_order() 124 | tree.insert("G") 125 | print("Test deleting leaf node which is right child of parent") 126 | tree.in_order() 127 | tree.delete_val("G") 128 | tree.in_order() 129 | tree.insert("A") 130 | print("Test deleting parent/root node which has one child") 131 | tree.in_order() 132 | tree.delete_val("F") 133 | tree.in_order() 134 | print("Test deleting root node which has no children") 135 | tree.in_order() 136 | tree.delete_val("A") 137 | tree.in_order() 138 | tree.insert("F") 139 | tree.insert("C") 140 | tree.insert("G") 141 | tree.insert("A") 142 | tree.insert("B") 143 | tree.insert("K") 144 | tree.insert("E") 145 | tree.insert("H") 146 | tree.insert("D") 147 | tree.insert("I") 148 | tree.insert("M") 149 | tree.insert("J") 150 | tree.insert("L") 151 | tree.in_order() 152 | tree.delete_val("F") 153 | tree.in_order() 154 | tree.in_order() 155 | tree.delete_val("K") 156 | tree.in_order() 157 | tree.in_order() 158 | tree.delete_val("C") 159 | tree.in_order() 160 | tree.delete_val("Z") 161 | -------------------------------------------------------------------------------- /Section 01/3_python_book.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | python_book 9 | 10 | 11 | 325 | 326 | 465 | 466 | 467 | 468 | 469 | 470 | 471 |

Python Crash Course

472 | 473 |

Python - Programming, Computer Science, Algorithms and Data Structures

474 | 475 |

Part 1, version 1.1

476 | 477 |

by Mashrur Hossain

478 | 479 |

© 2019 Matacode Inc. All rights reserved. No portion of this book may be reproduced in any form without permission from the publisher, except as permitted by U.S. copyright law.

480 | 481 |

Table of contents

482 | 483 |
    484 |
  1. Command line basics
  2. 485 |
  3. Strings
  4. 486 |
  5. Print formatting
  6. 487 |
  7. Numbers
  8. 488 |
  9. Branching with if/elif/else
  10. 489 |
  11. Lists
  12. 490 |
  13. Dictionaries
  14. 491 |
  15. Sets
  16. 492 |
  17. Tuples
  18. 493 |
  19. For-loops and generators
  20. 494 |
  21. While-loops
  22. 495 |
  23. Functions
  24. 496 |
  25. Classes 497 |
    498 |
    499 |
  26. 500 |
501 | 502 |

Command line Basics

503 | 504 |

The commands below will allow you to work through the content in this course without any difficulty.

505 | 506 |

To see which directory you are currently in:

507 | 508 |

Windows-> 509 | 510 | cd 511 |

512 | 513 |

Mac/Linux-> 514 | pwd

515 | 516 |

To list files and folder under current directory:

517 | 518 |

Windows-> dir

519 | 520 |

Mac/Linux-> ls

521 | 522 |

To move to a directory listed under current directory:

523 | 524 |

Windows, Mac/Linux-> cd name_of_directory

525 | 526 |

To move up one directory from current directory:

527 | 528 |

Windows, Mac/Linux-> cd ..

529 | 530 |

To create a new directory under the current directory:

531 | 532 |

Windows, Mac/Linux-> mkdir name_of_directory

533 | 534 |

To clear the terminal/command prompt screen:

535 | 536 |

Windows-> cls

537 | 538 |

Mac/Linux-> clear

539 | 540 |

Back to table of contents 541 |
542 |
543 |

544 | 545 |

Strings

546 | 547 |

Official Python documentation on strings can be found here: https://docs.python.org/3/library/stdtypes.html#text-sequence-type-str

548 | 549 |

Working with text in Python is accomplished by using strings. To create a string you can wrap the text you want to create/use with either single, double or triple quotes. The following are all valid strings.

550 | 551 |
'Hello world using single quotes'
 552 | "Hello world using double quotes"
 553 | """Hello world using
 554 | triple quotes, also known as
 555 | multi-line strings"""
556 | 557 |

To type a comment in Python you can use the # sign. Anything written after the # in a python file will be ignored by the Python interpreter. Example below.

558 | 559 |
# This is a comment
 560 | 
561 | 562 |

To print an object to the screen, you can use the print function.

563 | 564 |
print('Hello world') # This will print Hello world
565 | 566 |

To run the program that contains the code above, first make sure the file ends with an extension of .py
567 | Second, make sure you are in the same directory in your terminal as where the file is saved. So let’s say you have saved the code above in a file named lecture_1.py and you are in the same directory in your terminal where this file is saved. You can then enter the following command in your terminal to run the program. (Ignore the $ in the beginning of the line, that signifies the command prompt)

568 | 569 |
$ python lecture_1.py 
570 | 571 |

Running the command above will result in the following output to your terminal screen:

572 | 573 |
$ python lecture_1.py
 574 | Hello world 
575 | 576 |

To use an apostrophe ‘ in your print statement, you can wrap your string in double quotes.

577 | 578 |
print("I'm using an apostrophe in this statement")
579 | 580 |

To use quotes within your print statement, you can wrap your string in single quotes like below.

581 | 582 |
print('This is a "quote from a book" which I like')
583 | 584 |

Variables are used to reference objects in Python, you can think of variables as memory addresses. They should be named using snake case (lower case and words separated by underscore). Example of snake case -> my_name.

585 | 586 |
message = "Hello! welcome to the Algorithm's course"
 587 | name = "Mashrur"
588 | 589 |

You can use variables in your print function to print to the screen

590 | 591 |
message = "Hello! welcome to the Algorithm's course"
 592 | name = "Mashrur"
 593 | print(message)
 594 | print(name)
595 | 596 |

You can print multiple strings using the print function separating them by using commas in-between. A comma will automatically add a whitespace between the strings.

597 | 598 |
print(message, name)
599 | 600 |

Running the code above will result in the following output to your terminal screen:

601 | 602 |
$ python lecture_1.py
 603 | Hello! welcome to the Algorithm's course Mashrur 
604 | 605 |

String concatenation

606 | 607 |

You can use string concatenation and add multiple strings together using the + operator

608 | 609 |
message = "Welcome to the course"
 610 | name = "Mashrur"
 611 | print(message + name)
612 | 613 |

To add an empty space in between the two strings, which is not automatically added, you can concatenate an empty whitespace string in the middle of the two strings like below.

614 | 615 |
print(message + " " + name)
616 | 617 |

Running the code above will result in the following output to your terminal screen:

618 | 619 |
$ python lecture_1.py
 620 | Welcome to the course Mashrur
621 | 622 |

Indexing

623 | 624 |

Strings are sequences of characters which are indexed. We can index into a string by using square brackets. Try out the examples below in your editor.

625 | 626 |
movie_name = "Interstellar"
 627 | print(movie_name[0]) # This will print 'I' which is at index 0
 628 | print(movie_name[1]) # This will print 'n' which is at index 1
 629 | print(movie_name[11]) # This will print 'r' 
 630 | print(movie_name[-1]) # This will also print 'r', last item
 631 | print(movie_name[-2]) # This will print 'a', reverse index
632 | 633 |

You can also test this code out directly in the Python console or Python interactive shell which is provided by every python installation. To start the console you can type in python from your command line. Mine looks like below.

634 | 635 |
$ python
 636 | Python 3.6.0 |Anaconda custom (x86_64)| (default, Dec 23 2016, 13:19:00) 
 637 | [GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.57)] on darwin
 638 | Type "help", "copyright", "credits" or "license" for more information.
 639 | >>> 
640 | 641 |

Note: If you have both python 2 and 3 installed, you may need to specify python3 to start the console using Python 3

642 | 643 |

You can directly type Python code in this console, and you won’t need the print function to display the output. Example below.

644 | 645 |
>>> movie_name = "Interstellar"
 646 | >>> movie_name[0]
 647 | 'I'
 648 | >>> print(movie_name[0])
 649 | I
 650 | >>> 
 651 | 
652 | 653 |

To exit the console at any point you can type exit() like below.

654 | 655 |
>>> exit()
 656 | $ 
657 | 658 |

Strings are immutable

659 | 660 |

You cannot change the value of a specific string once it is created. For example, say you have the string my_name = “joan”. If you wanted to change the character 'a’ at index 2 to an ‘h’, essentially changing “joan” to “john”, you cannot use code like.

661 | 662 |
my_name = "joan"
 663 | my_name[2] = "h" # This line will result in an error
 664 | print(my_name)
665 | 666 |

If you ran the code above, you would get the following output.

667 | 668 |
$ python lecture_1.py 
 669 | Traceback (most recent call last):
 670 |   File "lecture_1.py", line 2, in <module>
 671 |     my_name[2] = "h"
 672 | TypeError: 'str' object does not support item assignment
 673 | $ 
674 | 675 |

The line “TypeError: ‘str’ object does not support item assignment” is letting you know that strings are immutable and their value cannot be changed. To make the change above you have to create a new string object and assign it like below (You can use the same variable name, but it will be a different string object).

676 | 677 |
my_name = "joan"
 678 | print(my_name)
 679 | my_name = "john" # This will create a new my_name string object
 680 | print(my_name)
681 | 682 |

If you ran the code above, you would get the output you were looking for.

683 | 684 |
$ python testing_practice.py 
 685 | joan
 686 | john
687 | 688 |

Slicing

689 | 690 |

You can use slicing to select portions of the string or substrings to work with. For example, if we used slicing notation on our movie_name variable defined above, it would look like below.

691 | 692 |
print(movie_name[0:5])  # This will print 'Inter'
 693 | print(movie_name[:5])   # We can omit the starting index 0, if we
 694 |                         # want to start at the beginning to get
 695 |                         # 'Inter' as well
 696 | print(movie_name[5:])   # This will print 'stellar', begin at 
 697 |                         # index 5 and go to the end
 698 | print(movie_name[:])    # This will print 'Interstellar', begin 
 699 |                         # to end
 700 | print(movie_name[5:9])  # This will print 'stel', index 5 to 8,
 701 |                         # excluding the stop index of 9
702 | 703 |

You can specify the step size as an optional third argument like below.

704 | 705 |
print(movie_name[5:9:2])    # This will print 'se', moving forward 
 706 |                             # by 2 steps instead of the default 1
 707 | print(movie_name[::2])      # This will print "Itrtla"
 708 | print(movie_name[::-1])     # This will reverse the string and
 709 |                             # print 'ralletsretnI'
710 | 711 |

Methods and functions

712 | 713 |

Python provides built-in methods and functions which we can run on string objects based on our needs. 714 | You can find more information on built-in functions by the python standard library here: https://docs.python.org/3/library

715 | 716 |

Try out some built-in functions on strings like below, see if you get the output you were expecting.

717 | 718 |
message = "Welcome to the course"
 719 | print(len(message)) # This gives us the number of characters 
 720 |                     # in the string
 721 | print(type(message)) # This gives us the type of object 
 722 |                     # message is, string in this case
 723 | print(id(message))  # This gives us an integer representation 
 724 |                     # of the message variable in memory
725 | 726 |

Running the code above would result in the following output in the terminal.

727 | 728 |
$ python lecture_1.py 
 729 | 21
 730 | <class 'str'>
 731 | 4530028168
 732 | $ 
733 | 734 |

You can also run built-in methods on string objects provided by python. Methods differ from functions in that methods run on specific objects (a specific string here) on which they are called. They are associated with that object type. We will explore these differences in much more detail as we progress through the course.

735 | 736 |

For now, think of the methods below as specific to string objects and to use them you can ‘chain’ them to the string using the dot or ‘.’ notation. Try out the code below to see what each method does to the string you run it on.

737 | 738 |
message = "Welcome to the course"
 739 | print(message.capitalize())
 740 | print(message.upper())
 741 | print(message.lower())
742 | 743 |

Running the code above will result in the following output.

744 | 745 |
$ python lecture_1.py 
 746 | Welcome to the algorithms course
 747 | WELCOME TO THE ALGORITHMS COURSE
 748 | welcome to the algorithms course
 749 | $ 
750 | 751 |

You can use the split() method to separate each word in a string and return a list of words. By default the split will happen on whitespace. You can also specify what to split on like below.

752 | 753 |
message = "Welcome to the Algorithms course"
 754 | print("Initial message:", message)
 755 | print(message.split())
 756 | print(message.split(" ")) # same output as above line
 757 | 
 758 | message = "Welcome-to-the-Algorithms-course"
 759 | print("Updated message:", message)
 760 | print(message.split("-")) # specifying to split on - instead of
 761 |                           # the default whitespace
 762 | 
763 | 764 |

Running the code above will result the following output.

765 | 766 |
$ python lecture_1.py 
 767 | Initial message: Welcome to the Algorithms course
 768 | ['Welcome', 'to', 'the', 'Algorithms', 'course']
 769 | ['Welcome', 'to', 'the', 'Algorithms', 'course']
 770 | Updated message: Welcome-to-the-Algorithms-course
 771 | ['Welcome', 'to', 'the', 'Algorithms', 'course']
 772 | $ 
773 | 774 |

You can convert the items from the resulting list back to a string by using the join() method. Example below.

775 | 776 |
message = "Welcome-to-the-Algorithms-course"
 777 | print("Initial message:", message)
 778 | message_list = message.split("-")
 779 | print("Converted to a list:", message_list)
 780 | new_message = " ".join(message_list)
 781 | print("Converted back to string:", new_message)
782 | 783 |

Running the code above will result in the following output.

784 | 785 |
$ python lecture_1.py 
 786 | Initial message: Welcome-to-the-Algorithms-course
 787 | Converted to a list: ['Welcome', 'to', 'the', 'Algorithms', 'course']
 788 | Converted back to string: Welcome to the Algorithms course
 789 | $ 
 790 | 
791 | 792 |

You can import more features and functionality available to strings by importing the string module in your code. Documentation can be found here: https://docs.python.org/3.7/library/string.html

793 | 794 |

If you want to import the entire string module you can use the following code on top of your python script.

795 | 796 |
import string
797 | 798 |

If you want to import a specific feature, let’s take the ascii_lowercase constant as an example, then you can do so like below.

799 | 800 |
from string import ascii_lowercase
801 | 802 |

You can then use it in your code.

803 | 804 |
from string import ascii_lowercase
 805 | print("Lowercase alphabet:", ascii_lowercase)
806 | 807 |

Running the code above will result in the following output.

808 | 809 |
$ python lecture_1.py 
 810 | Lowercase alphabet: abcdefghijklmnopqrstuvwxyz
811 | 812 |

This concludes our introductory look at strings. We will continue to learn more about them in upcoming chapters.

813 | 814 |

Back to table of contents 815 |
816 |
817 |

818 | 819 |

Print formatting

820 | 821 |

There are multiple ways you can format how text or objects are displayed by your print statements. We will explore 3 of them in this chapter.

822 | 823 |

Take the example below where we have a variable stock_price defined to be the 1100 (this 1100 is a number, an integer specifically, we will explore them more in the next chapter). One way to print this would be to use a comma after the first string within the print statement which we have seen already.

824 | 825 |
stock_price = 1100
 826 | print("Price of google stock is:", stock_price)
827 | 828 |

You can also use the format() method or f'strings. Let’s look at format() first.

829 | 830 |
stock_price = 1100
 831 | print("Price of google stock is: {}".format(stock_price))
832 | 833 |

Python simply takes the object listed within the parenthesis of the format method call and places it within the {} of the string.

834 | 835 |

You can do the same using f'strings.

836 | 837 |
stock_price = 1100
 838 | print(f"Price of google stock is: {stock_price}")
839 | 840 |

If all 3 methods above were placed one after the other and we ran such a script it would give us the following output in the terminal.

841 | 842 |
$ python lecture_2.py 
 843 | Price of google stock is: 1100
 844 | Price of google stock is: 1100
 845 | Price of google stock is: 1100
846 | 847 |

You can use multiple variables as well.

848 | 849 |
today_price = 1100
 850 | yesterday_price = 1000
 851 | print("Today's price:", today_price, "yesterday's price:", yesterday_price)
 852 | print("Today's price: {}, yesterday's price: {}".format(today_price, yesterday_price))
 853 | print(f"Today's price: {today_price}, yesterday's price: {yesterday_price}")
854 | 855 |

Output of the program above would be like below.

856 | 857 |
$ python lecture_2.py 
 858 | Today's price: 1100 yesterday's price: 1000
 859 | Today's price: 1100, yesterday's price: 1000
 860 | Today's price: 1100, yesterday's price: 1000
 861 | $ 
862 | 863 |

Special characters

864 | 865 |

You can use special characters within the quotation marks of strings. These characters have special meaning. 3 such characters are listed below.

866 | 867 |

\ - is an escape character, it escapes the special character 868 | following it in a string

869 | 870 |

\n - species a new line within the string

871 | 872 |

\t - adds a tab in it’s place within the string

873 | 874 |

Take the line of code below as an example.

875 | 876 |
print("My name is jon snow and not only do I know nothing but I also do nothing")
877 | 878 |

This statement can be broken up into multiple lines with \

879 | 880 |
print("My name is jon snow and \
 881 | not only do I know nothing but \
 882 | I also do nothing")
883 | 884 |

If you run the code above it will display the line as a single line. Python simply ‘escapes’ the implied new lines at the end of each line because of the \ character that was used.

885 | 886 |

\n can be used to print it in multiple lines.

887 | 888 |
print("My name is jon snow\nI know nothing\nI also do nothing")
889 | 890 |

You can run the code above to get the output below.

891 | 892 |
$ python lecture_2.py 
 893 | My name is jon snow
 894 | I know nothing
 895 | I also do nothing
896 | 897 |

\t can be used to introduce tabs within the lines

898 | 899 |
print("My name is jon snow\n \tI know nothing\n\t\tI also do nothing")
900 | 901 |

This concludes our look at print formatting and special characters. We will learn more about and practice using them in upcoming chapters.

902 | 903 |

Back to table of contents 904 |
905 |
906 |

907 | 908 |

Numbers

909 | 910 |

Numbers in Python (standard library) are represented using integers, floats and complex numbers. In this course we will cover integers and floats which are most common. 911 | Documentation on integers and floats can be found here: 912 | https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex

913 | 914 |

Integers are whole numbers, no decimals (example 1, 2, 10, 500). Floats on the other hand include decimals or digits after the decimal. 915 | To check the type of an object (this will work with other types beyond integers and floats), you can use the type function, example below.

916 | 917 |
num_1 = 10
 918 | print(type(num_1)) # This will return class 'int'
919 | 920 |

You can perform standard math operations using +, -, *, / operators

921 | 922 |
print(4+4) # output 8
 923 | print(10-5) # output 5
 924 | print(10*2) # output 20
 925 | print(10/2) # output 5.0
926 | 927 |

Division will give you a float (decimal value number) by default. 928 | To perform floor division, or remove the decimal you can use //

929 | 930 |
print(10//3) # output 3
931 | 932 |

You can use the mod operator % to find the remainder.

933 | 934 |
print(10%3) # output 1
 935 | print(10%2) # output 0
936 | 937 |

To take one number to the power of another number, you can use 938 | the ** operator

939 | 940 |
print(5**2) # output 25 (this is 5 squared)
 941 | print(5**3) # output 125 (this is 5 cubed)
942 | 943 |

You can import the math module and use functions provided by it. Documentation on the math module can be found here: https://docs.python.org/3/library/math.html

944 | 945 |
import math
 946 | print(math.pow(10,5)) # output 100000.0, this is 10 to the power 5
947 | 948 |

Similarly, you can import the random module and generate random 949 | integers using the randint function. More information on the random module can be found here: https://docs.python.org/3/library/random.html

950 | 951 |
import random
 952 | print(random.randint(0,1000))   # output random integer between 0
 953 |                                 # and 1000
954 | 955 |

You can use other functions on numbers as well. One such function is the abs function which returns the absolute value of a number (removes the negative sign)

956 | 957 |
num_2 = -10
 958 | print(abs(num_2))   # output 10
959 | 960 |

Type casting

961 | 962 |

You can convert one data type to another using type casting. To convert the string “10” to the integer 10, you can cast it to an int like below.

963 | 964 |
my_string = "10"
 965 | my_num = int(my_string) # this will convert "10" to int 10
 966 | print(type(my_num)) # output will be class 'int'
967 | 968 |

Similarly you can convert an int to a string, 10 to “10” by casting it to a string (str)

969 | 970 |
my_num = 10
 971 | my_new_string = str(my_num)     # this will convert 10 to "10"
 972 | print(type(my_new_string))      # output will be class 'str'
973 | 974 |

You can get input from the user of the program using the input() function. This is displayed in the video lecture on numbers where a multiplication program is used. The input received from the user is always in string format. Therefore, if you want to use that input to perform math operations, you have to cast it to an int or float first. The multiplication program code is shown below.

975 | 976 |
print("Welcome to the multiplication program")
 977 | print("-"*30)
 978 | num_1 = int(input("Enter a number to multiply-> "))
 979 | num_2 = int(input("Enter a second number to multiply-> "))
 980 | result = num_1 * num_2
 981 | print(result)
982 | 983 |

This concludes our introductory look at working with numbers. We will learn more about them as we continue to work with them in later chapters.

984 | 985 |

Back to table of contents 986 |
987 |
988 |

989 | 990 |

Branching and control flow using if/elif/else

991 | 992 |

You can control the execution flow of your program using if/elif and else blocks. This is known as branching. The if and elif expressions use conditional operators which evaluate to True or False.

993 | 994 |

Documentation on conditional operators can be found here: 995 | https://docs.python.org/3/library/stdtypes.html#comparisons

996 | 997 |

Branching is best explained with examples, some are below.

998 | 999 |
choice = '1'
1000 | if choice == '1':
1001 |     print('You have chosen option 1')   # make note of the indentation
1002 |                                         # of this line
1003 | elif choice == '2':
1004 |     print('You have chosen option 2')   # make note of the indentation
1005 |                                         # of this line as well
1006 | else:
1007 |     print('You have made an invalid choice')
1008 | 1009 |

if/elif/else blocks take the form of the condition followed by a : 1010 | and then the code you want executed if that condition is true underneath it. This block of code underneath the condition needs to be indented in by 1 tab in order to be considered as part of the code block associated with the condition.

1011 | 1012 |

Note: elif and else are optional based on your program’s requirements. You can just have an if condition without either an elif condition or an else. Or you can have an if/else condition without an elif. You can also have multiple elif conditions if you are testing for multiple conditions before getting to the else condition. In the example above if you wanted to see if the choice was 3, you could’ve simply added an additional elif condition.

1013 | 1014 |

Some more examples below. First of a boolean test using the not operator.

1015 | 1016 |
made_payment = True
1017 | a = 'Please pay monthly premium'
1018 | b = 'Welcome to your homepage'
1019 | 
1020 | if not made_payment:
1021 |     print(a)
1022 | else:
1023 |     print(b)
1024 | 1025 |

Second, you can combine comparison operators and conditional tests.

1026 | 1027 |
i = 20
1028 | j = 10
1029 | k = 30
1030 | 
1031 | if i < j and i < k:
1032 |     print("i is less than j and k")
1033 | elif i == j and i == k:
1034 |     print("i is equal to j and k")
1035 | else:
1036 |     print("i is greater than j")
1037 | 1038 |

You can also compress and simplify simple if/else blocks using ternary operators. Below is an example of using ternary operators to compress an if/else block used earlier.

1039 | 1040 |
made_payment = True
1041 | a = 'Please pay monthly premium'
1042 | b = 'Welcome to your homepage'
1043 | 
1044 | print(a) if not made_payment else print(b)
1045 | 1046 |

The last line in the code above can also be written as below.

1047 | 1048 |
print(b) if made_payment else print(a)
1049 | 1050 |

You can nest if/elif/else blocks within other if/elif/else blocks. Below we have an example of the multiplication program we initially saw in the numbers chapter. Here it has been modified to perform either multiplication or division based on user input and a nested if condition within it.

1051 | 1052 |
print("Welcome to the calc program")
1053 | print("-"*30)
1054 | choice = input("Choose 1 to multiply, 2 to divide-> ")
1055 | if choice == "1" or choice =="2":
1056 |     num_1 = int(input("Enter first number-> "))
1057 |     num_2 = int(input("Enter second number-> "))
1058 |     if choice == "1":
1059 |         print(f"{num_1} multiplied by {num_2} is: {num_1*num_2}")
1060 |     else:
1061 |         print(f"{num_1} divided by {num_2} is: {num_1/num_2}")
1062 | else:
1063 |     print("You've made an invalid selection")
1064 | 1065 |

This concludes our introductory look at branching with if/elif/else conditions. We will see a lot of examples and usage of branching as it’s one of the staples of programming in future chapters.

1066 | 1067 |

Back to table of contents 1068 |
1069 |
1070 |

1071 | 1072 |

Lists

1073 | 1074 |

Lists are compound data structures. They are collections of objects, mutable, they maintain order and are indexed. 1075 | You can find additional documentation on lists here: https://docs.python.org/3/library/stdtypes.html#list

1076 | 1077 |

Lists begin with an open square bracket [ and close with a closing square bracket ‘]’ and each element/object contained within the list is separated by commas. These objects can be other compound data structures like lists as well.

1078 | 1079 |

An example list is displayed below.

1080 | 1081 |
my_list = [1,2,3,4,5,6,7,'hello','world']
1082 | print(my_list)
1083 | 1084 |

You can run functions on lists like len(), min(), max(). Examples below.

1085 | 1086 |
my_list = [15, 6, 7, 8, 35, 12, 14, 4, 10, 15]
1087 | print(len(my_list)) # will return 10, number of integers in list
1088 | print(min(my_list)) # will return 4, minimum value in list
1089 | print(max(my_list)) # will return 35, maximum value in list
1090 | 1091 |

You can add objects to the list using the append() method. Append will add the object to the back of the list. Example below

1092 | 1093 |
my_strings_list = ["comp sci", "physics", "elec engr", "philosophy"]
1094 | print(my_strings_list)
1095 | my_strings_list.append("art")
1096 | print(my_strings_list)
1097 | 1098 |

If you run the code above, you’ll get output like below in your terminal.

1099 | 1100 |
$ python testing_practice.py 
1101 | ['comp sci', 'physics', 'elec engr', 'philosophy']
1102 | ['comp sci', 'physics', 'elec engr', 'philosophy', 'art']
1103 | 1104 |

If you use the append() method to add another list to your list, then the new list will show up in list form within your list. Example below.

1105 | 1106 |
my_strings_list = ["comp sci", "physics", "elec engr", "philosophy"]
1107 | print(my_strings_list)
1108 | my_new_list = ["art", "econ"]
1109 | my_strings_list.append(my_new_list)
1110 | print(my_strings_list)
1111 | 1112 |

The code above will result in the output below.

1113 | 1114 |
$ python testing_practice.py 
1115 | ['comp sci', 'physics', 'elec engr', 'philosophy']
1116 | ['comp sci', 'physics', 'elec engr', 'philosophy', ['art', 'econ']]
1117 | 1118 |

If you wanted to just add the elements in the new list to your list instead of as a list itself, then you can use the extend method.

1119 | 1120 |
my_strings_list = ["comp sci", "physics", "elec engr", "philosophy"]
1121 | print(my_strings_list)
1122 | my_new_list = ["art", "econ"]
1123 | my_strings_list.extend(my_new_list)
1124 | print(my_strings_list)
1125 | 1126 |

The code above will result in the output below.

1127 | 1128 |
$ python testing_practice.py 
1129 | ['comp sci', 'physics', 'elec engr', 'philosophy']
1130 | ['comp sci', 'physics', 'elec engr', 'philosophy', 'art', 'econ']
1131 | 1132 |

You can use indexing and slicing with lists just like with strings. Some examples below.

1133 | 1134 |
my_list = [15, 6, 7, 8, 35, 12, 14, 4, 10, 15]
1135 | print(my_list[0])   # this will return 15
1136 | print(my_list[5])   # this will return 12
1137 | print(my_list[4:])  # this will return a sublist from index 4 to the end
1138 | print(my_list[1:5]) # this will return a sublist from index 1 to index 4
1139 |                     # note: slice notation does not include the item at
1140 |                     # the stopping index provided
1141 | print(my_list[-1])  # this will return the last element of the list
1142 | print(my_list[:])   # this will return a copy of the entire list
1143 | print(my_list[::-1])    # this will return a reversed copy of the list
1144 | print(sorted(my_list))  # this will return a sorted copy of the list
1145 | print(my_list)      # original list un-altered, order is still the same
1146 | my_list.sort()      # this will sort the list in-place
1147 | print(my_list)      # now the list is in sorted order, original list order
1148 |                     # has been changed by the sort() method
1149 | 
1150 | 1151 |

Since lists are mutable you are able to change elements within the list without needing to re-assign to a new list. An example of this is displayed in the code block above when the sort() method sorts the same list (my_list) in place. The order of the elements in the list are altered by this method.

1152 | 1153 |

You can also use index notation to change elements in indices that you want, example below.

1154 | 1155 |
my_strings_list = ["comp sci", "physics", "elec engr", "philosophy"]
1156 | my_strings_list[1] = "art" # this will place "art" in place of "physics"
1157 | print(my_strings_list)
1158 | 1159 |

The code above will result in the output below.

1160 | 1161 |
$ python testing_practice.py 
1162 | ['comp sci', 'art', 'elec engr', 'philosophy']
1163 | 1164 |

This concludes our introductory look at lists. We will work with lists a lot in the course and while programming with Python in general. A large portion of the algorithms section of the course is done using lists.

1165 | 1166 |

Back to table of contents 1167 |
1168 |
1169 |

1170 | 1171 |

Dictionaries

1172 | 1173 |

Dictionaries are compound data types which are collections of key value pairs. An example dictionary is displayed below.

1174 | 1175 |
my_dictionary = {'name':'john doe','course':'python'}
1176 | 1177 |

Documentation on dictionaries can be found here: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict

1178 | 1179 |

To access a value associated with a key in a dictionary you will need to provide the key associated with the value. For example in the example dictionary if you wanted the course name from the dictionary, you can use the code below.

1180 | 1181 |
my_dictionary = {'name':'john doe','course':'python'}
1182 | print(my_dictionary['course']) # this will print python
1183 | 1184 |

Dictionaries can take various forms, all 3 below are valid dictionaries.

1185 | 1186 |
my_dictionary = {'name': 'mashrur', 'course': 'python', 'fav_food': 'ice cream'}
1187 | phone_dict = {'mashrur': '555-55-5555',
1188 |               'zoolander': '999-99-9999',
1189 |               'jon_snow': 'fail-o-so-bad'}
1190 | word_dict = {'a':
1191 |                 {
1192 |                  'apple': 'the round fruit of a tree of the rose family',
1193 |                  'ant': 'an insect which cleans up the floor'
1194 |                 },
1195 |              'b':
1196 |                 {
1197 |                  'bad': 'of poor quaity or low standard',
1198 |                  'business': 'season 8 of GOT'
1199 |                 }
1200 |             }
1201 | print(my_dictionary)
1202 | print(phone_dict)
1203 | print(word_dict)
1204 | 1205 |

You can have other data types and objects as the values in dictionaries. It is good practice to keep the keys as immutable data types like strings and integers so they cannot be altered.

1206 | 1207 |

You can re-assign the value associated with a key by using the key. For example in if you wanted to change the name from ‘mashrur’ to ‘john’ in my_dictionary above, you can use the code below.

1208 | 1209 |
my_dictionary['name'] = 'john' # this will re-assign 'mashrur' to 'john'
1210 | print(my_dictionary)
1211 | 1212 |

You can add new key, value pairs to the dictionary with the same notation. Example below.

1213 | 1214 |
my_dictionary['job'] = 'python programmer'
1215 | print(my_dictionary)
1216 | 1217 |

If you run the code above, the new key ‘job’ and value ‘python programmer’ would be added to the dictionary like below.

1218 | 1219 |
$ python lecture_dicts.py 
1220 | {'name': 'john', 'course': 'python', 'fav_food': 'ice cream', 'job': 'python programmer'} 
1221 | 1222 |

You can go deeper into dictionaries by providing keys followed by other keys in cases where you have dictionaries within dictionaries. Take the word_dict below as an example. To access the value associated with business, you can use the following code word_dict['b']['business'] like below.

1223 | 1224 |
word_dict = {'a':
1225 |                 {
1226 |                  'apple': 'the round fruit of a tree of the rose family',
1227 |                  'ant': 'an insect which cleans up the floor'
1228 |                 },
1229 |              'b':
1230 |                 {
1231 |                  'bad': 'of poor quaity or low standard',
1232 |                  'business': 'season 8 of GOT'
1233 |                 }
1234 |             }
1235 | 
1236 | print(word_dict['b']['business']) # this will print season 8 of GOT
1237 | 1238 |

Some methods you can use to get keys, values and key, value pairs as an iterable are shown below.

1239 | 1240 |
my_dictionary = {'name': 'john', 'course': 'python', 'fav_food': 'ice cream', 'job': 'python programmer'}
1241 |  
1242 | print(my_dictionary.keys()) # this will print all the keys in the dictionary
1243 | print(my_dictionary.values()) # this will print all the values
1244 | print(my_dictionary.items()) # this will print all the key, value pairs
1245 | 1246 |

If you run the code above, you will get the output below.

1247 | 1248 |
$ python lecture_dicts.py 
1249 | dict_keys(['name', 'course', 'fav_food', 'job'])
1250 | dict_values(['john', 'python', 'ice cream', 'python programmer'])
1251 | dict_items([('name', 'john'), ('course', 'python'), ('fav_food', 'ice cream'), ('job', 'python programmer')])
1252 | 1253 |

The items() method returns an iterable which you can iterate through to get the key, value combos one by one. To iterate through the dictionary using items() you can use a simply for loop like below.

1254 | 1255 |
my_dictionary = {'name': 'john', 'course': 'python', 'fav_food': 'ice cream', 'job': 'python programmer'}
1256 | for key, value in my_dictionary.items():
1257 |     print(f'Key: {key}, Value: {value}')
1258 | 1259 |

Running the code above will give you the following output.

1260 | 1261 |
$ python lecture_dicts.py 
1262 | Key: name, Value: john
1263 | Key: course, Value: python
1264 | Key: fav_food, Value: ice cream
1265 | Key: job, Value: python programmer
1266 | 1267 |

This concludes our introductory look at Dictionaries. Dictionaries are a staple of Python and used a lot. In the data structures section of the course we will build our own dictionary data structure from scratch.

1268 | 1269 |

Back to table of contents 1270 |
1271 |
1272 |

1273 | 1274 |

Sets

1275 | 1276 |

Sets are un-ordered collections of objects which don’t allow duplicates. An example set is shown below.

1277 | 1278 |
my_set = {1,6,2,'java','ruby',8,9,10,21,1000,'python'}
1279 | 1280 |

Since sets don’t allow duplicated, if you tried to create the same set above with an additional duplicate 6, it would simply discard the duplicate. Example below.

1281 | 1282 |
my_set = {1,6,2,'java','ruby',8,9,10,21,1000,'python',6}
1283 | print(my_set) # duplicate 6 will be discarded 
1284 | 1285 |

Documentation on sets can be found here: https://docs.python.org/3/library/stdtypes.html#set-types-set-frozenset

1286 | 1287 |

Sets are useful in a lot of ways. One use can be if you have a list with duplicates and you wanted to get rid of the duplicates. You can cast it to a set to get rid of duplicates.

1288 | 1289 |
my_list = [1,6,2,'java','ruby',8,9,10,21,1000,6,'python','java']
1290 | print(my_list)
1291 | my_set = set(my_list)
1292 | print(my_set)
1293 | # you can cast my_set back to a list if you wanted a list as output
1294 | 1295 |

You can find information in sets using ‘in’. Example below.

1296 | 1297 |
my_set = {1,6,2,'java','ruby',8,9,10,21,1000,'python'}
1298 | print('java' in my_set) # this will return True
1299 | print('javascript' in my_set) # this will return False
1300 | 1301 |

Sets are optimized to perform mathematical operations like finding unions, intersections, differences etc. Some examples using these methods are shown below.

1302 | 1303 |
my_set = {1,6,2,'java','ruby',8,9,10,21,1000,'python'}
1304 | programming_set = {'java','ruby','javascript','python','c'}
1305 | print(my_set.intersection(programming_set)) # elements in common in both sets
1306 | print(my_set.union(programming_set)) # all elements in both, no duplicates
1307 | print(my_set.difference(programming_set)) # in my_set, not in other
1308 | print(programming_set.difference(my_set)) # in programming_set, not in my_set
1309 | 1310 |

If you run the code above, it will give you the output below.

1311 | 1312 |
$ python lecture_sets.py 
1313 | {'python', 'ruby', 'java'}
1314 | {1, 2, 6, 8, 9, 10, 'python', 'c', 21, 'javascript', 'ruby', 1000, 'java'}
1315 | {1, 2, 6, 8, 9, 10, 1000, 21}
1316 | {'javascript', 'c'}
1317 | 1318 |

You can iterate through items in a set using a simple for loop, example below.

1319 | 1320 |
my_set = {1,6,2,'java','ruby',8,9,10,21,1000,'python'}
1321 | for item in my_set:
1322 |     print(item)
1323 | 1324 |

This concludes our introductory look at Sets. We will look at Tuples in the next chapter.

1325 | 1326 |

Back to table of contents 1327 |
1328 |
1329 |

1330 | 1331 |

Tuples

1332 | 1333 |

Tuples are immutable collections of objects which are indexed, which means they maintain order. Tuples behave a lot like lists except for the key fact that they are immutable unlike lists. So you cannot update an object inside a tuple, you have to use a new tuple. A tuple may look like the example below.

1334 | 1335 |
my_tuple = ('hello','world','night','king','says','bye',8,3)
1336 | 1337 |

Documentation on tuples can be found here: https://docs.python.org/3/library/stdtypes.html#tuple

1338 | 1339 |

You can use indexing and slicing notation to access objects or sublists of tuples like the example below.

1340 | 1341 |
my_random_tuple = ('first',1,7,6,4,5,8,'hi there',2,3,1,5,2,1,9,10)
1342 | print(my_random_tuple[7])
1343 | print(my_random_tuple[7:])
1344 | 1345 |

Running the code above will give you the output below.

1346 | 1347 |
$ python lecture_tuples.py 
1348 | hi there
1349 | ('hi there', 2, 3, 1, 5, 2, 1, 9, 10)
1350 | 1351 |

You can ‘unpack’ the elements in a tuple by using tuple unpacking

1352 | 1353 |
my_tuple = ('first_value','second_value','third_value')
1354 | first, second, third = my_tuple # this line is 'unpacking' the 
1355 |                                 # values of the tuple
1356 | print(f"First value: {first}\nSecond value: {second}\nThird value: {third}")
1357 | 1358 |

Tuple unpacking is very useful when you receive tuples as return values from functions which need to return multiple values. You will see examples of this in the functions chapter as well. The code above (using tuple unpacking) allows you to access individual pieces of the tuple to work with. Running it will give you the output below.

1359 | 1360 |
$ python lecture_tuples.py 
1361 | First value: first_value
1362 | Second value: second_value
1363 | Third value: third_value
1364 | 1365 |

You can find out if an object exists in your tuple using ‘in’.

1366 | 1367 |
my_random_tuple = ('first',1,7,6,4,5,8,'hi there',2,3,1,5,2,1,9,10)
1368 | print('hi there' in my_random_tuple) # this will return True
1369 | 1370 |

You can iterate through the objects in a tuple using a simple for loop just like lists.

1371 | 1372 |
my_random_tuple = ('first',1,7,6,4,5,8,'hi there',2,3,1,5,2,1,9,10)
1373 | for item in my_random_tuple:
1374 |     print(item)
1375 | 1376 |

Try running the index and count methods, along with the len function on the tuples defined above.

1377 | 1378 |

This concludes our introductory look at Tuples. We will look at iterators in the next chapter.

1379 | 1380 |

Back to table of contents 1381 |
1382 |
1383 |

1384 | 1385 |

For loops and generators

1386 | 1387 |

You can use for loops to iterate through iterables. Iterables can be collections of objects like lists, dictionaries, sets. They can also be sequences like strings.

1388 | 1389 |

Let’s look at using a simple for loop to iterate through the elements in the list below.

1390 | 1391 |
l = [6, 8, 1, 4, 10, 7, 8, 9, 3, 2, 5]
1392 | 
1393 | for num in l:
1394 |     print(num)
1395 | 1396 |

You can perform functions like calculating the sum of all the integers in that list like below:

1397 | 1398 |
l = [6, 8, 1, 4, 10, 7, 8, 9, 3, 2, 5]
1399 | 
1400 | sum = 0
1401 | for num in l:
1402 |     sum += num
1403 | print(f"Sum using list: {sum}")
1404 | 1405 |

To iterate through key, value pairs in a dictionary, you can use the items method. It returns tuples of key, value pairs which you can iterate through and perform any function necessary. An example of using this along with a for loop is shown below. The ‘unpacking’ for the key, value pair from each tuple takes place in the for loop declaration itself (for k, v in…).

1406 | 1407 |
my_dict = {'py': 'python', 'rb': 'ruby', 'js': 'javascript'}
1408 | for k, v in my_dict.items():
1409 |     print(f"Extension of .{k} means it's a {v} program")
1410 | 1411 |

Generators and Iterables

1412 | 1413 |

You can also use the range function to generate an iterable of a specific number, then use a for loop to run that many times as you look at the elements in the index generated like below.

1414 | 1415 |
l = [6, 8, 1, 4, 10, 7, 8, 9, 3, 2, 5]
1416 | 
1417 | sum = 0
1418 | for num in range(len(l)):
1419 |     sum += l[num]
1420 | print(f"Sum using range generator: {sum}")
1421 | 1422 |

Range is a very useful function to use if you don’t know ahead of time the number of iterations by the for loop to take place, example below where input from the user is used to define this number.

1423 | 1424 |
run_times = int(input("How many times do you want the program to run? "))
1425 | for num in range(run_times):
1426 |     print(f"Run: {num+1}")
1427 | 1428 |

You can generate a list of random integers using the range function 1429 | in combination with the randint function from the random module like below.

1430 | 1431 |
from random import randint
1432 | l1 = []
1433 | for num in range(25):
1434 |     l1.append(randint(1, 100))
1435 | 1436 |

You can do the same function above using list comprehension in 1 line as shown below.

1437 | 1438 |
from random import randint
1439 | l1 = [randint(1,100) for num in range(25)]
1440 | 1441 |

Another generator which is very useful is the zip function. The zip function is used quite often to merge values from two lists together to form an iterable of tuple values. It can cast to a list and printed out to show the merged list of tuples. An example is shown below.

1442 | 1443 |
l1 = ['.py','.js','.rb','.java','.c']
1444 | l2 = ['python','javascript','ruby','java','c']
1445 | tupled_list = list(zip(l2, l1))
1446 | print(tupled_list)
1447 | 1448 |

If you ran the code above, it would result in the output below.

1449 | 1450 |
$ python lecture_forloops.py 
1451 | [('python', '.py'), ('javascript', '.js'), ('ruby', '.rb'), ('java', '.java'), ('c', '.c')]
1452 | 1453 |

If one list is bigger than the other, the unmatched items are 1454 | simply ignored, for example if l2 looked like below:

1455 | 1456 |
l2 = ['python','javascript','ruby','java','c','c++']
1457 | 1458 |

And we ran the same code we did, c++ would be dropped and not included in any of the tuples, output displayed below.

1459 | 1460 |
$ python lecture_forloops.py 
1461 | [('python', '.py'), ('javascript', '.js'), ('ruby', '.rb'), ('java', '.java'), ('c', '.c')]
1462 | 1463 |

This concludes our introductory look at iterators with for loops and generators. We will look at while loops in the next chapter.

1464 | 1465 |

Back to table of contents 1466 |
1467 |
1468 |

1469 | 1470 |

While loops

1471 | 1472 |

You can use a simple while loop with a conditional test that 1473 | evaluates to either True or False (or the boolean directly) and the code within the block will keep running while the condition is true. The while loop will only stop when either the condition evaluation turns to false or a break keyword is used within the code block. An example of a while loop is shown below.

1474 | 1475 |
x = 0
1476 | while x < 10:
1477 |     print(x)
1478 |     x += 1
1479 | 1480 |

The while loop above will print values 0 through 9 and stop when 1481 | x reaches value of 10. At that point the condition x < 10 will 1482 | be false since 10 < 10 is false and it will exit out of the 1483 | while loop.

1484 | 1485 |

The example below shows the usage of a while loop to find a number in a generated list of numbers and the index at which it was found.

1486 | 1487 |
from random import randint
1488 | l1 = [randint(1,100) for num in range(1000)] # generated list of numbers
1489 | i = 0
1490 | num_to_search = 25  # the number to search
1491 | while i < len(l1):  # while loop won't run beyond the total number of 
1492 |                     # ints in the list
1493 |     if l1[i] == num_to_search:
1494 |         print(f"{num_to_search} found at index {i}")
1495 |         break   # if the number is found, break out of nearest loop
1496 |     i += 1
1497 | 1498 |

The break keyword above breaks out of the nearest loop it is in (it happens to be the while loop in this case). If there are nested loops and you want to find which loop the break keyword will break out of, then look for the nearest for/while loop above the break statement.

1499 | 1500 |

Notice in the code above how we used the variable i to track the index in the list (by initially setting it to 0 and incrementing it at every iteration). You can simplify this and use the enumerate function instead to get the index in an iteration through an iterable.

1501 | 1502 |

For example if you ran the same code above with a for loop without tracking i, you can use enumerate like below:

1503 | 1504 |
l1 = [randint(1,100) for num in range(1000)]
1505 | num_to_search = 50
1506 | for index, value in enumerate(l1):
1507 |     if value == num_to_search:
1508 |         print(f"{num_to_search} found at index {index}")
1509 |         break
1510 | 1511 |

While loops are most useful when you don’t know when the program 1512 | execution will end. An example would be in a program where a user clicks on an exit button to exit the program and thus stops the loop execution like in case of a menu shown below:

1513 | 1514 |
while True:
1515 |     print("Please choose an option from the list below:")
1516 |     print("Press 1 for selection 1")
1517 |     print("Press 2 for selection 2")
1518 |     print("Press 3 to quit")
1519 |     selection = input("Enter your choice-> ")
1520 |     if int(selection) == 3:
1521 |         break
1522 | 1523 |

This concludes our introductory look at while loops. In the next chapter we will start looking at building our own custom functions.

1524 | 1525 |

Back to table of contents 1526 |
1527 |
1528 |

1529 | 1530 |

Functions

1531 | 1532 |

You can build custom functions in Python using the def keyword followed by the name of the function. Functions may or may not take in arguments/parameters to process. A basic function example is provided below which does not take any arguments (no code within the open and close parenthesis after the function name).

1533 | 1534 |
def say_hello():
1535 |     print('Hello World!')
1536 | 1537 |

Functions by themselves are simply objects which are recognized by the python interpreter as functions when a script including them is executed. The code block inside the function is not executed by default. To actually run the code inside functions, they have to be called. For example, to run the function say_hello() defined above the following code could be used.

1538 | 1539 |
def say_hello():
1540 |     print('Hello World!')
1541 | 
1542 | say_hello() # this line calls the function
1543 | 1544 |

Running the code above would result in the output below.

1545 | 1546 |
$ python lecture_functions.py 
1547 | Hello World!
1548 | 1549 |

Functions can take arguments as parameters to use within their function code. The following function uses 2 parameters to perform the add operation defined within it. As a result, two numbers (strings will work too) will need to be provided as arguments when the function is called

1550 | 1551 |
def add_two_nums(num_1, num_2): # takes two arguments num_1 and num_2
1552 |     print(num_1 + num_2)
1553 | 
1554 | add_two_nums(4, 5) # 4 and 5 are provided in the function call
1555 | 1556 |

Functions usually return a value that they evaluate. In the example above, instead of printing out the summed value of the two numbers, the function can be made to return the value. This is done using the keyword return followed by the value. If nothing is specified by the explicit use of the return keyword, then the None datatype is returned by default by all functions to the caller of the function.

1557 | 1558 |
def add_two_nums(num_1, num_2):
1559 |     return num_1 + num_2
1560 | 
1561 | my_added_num = add_two_nums(4,5) # The return value from the function
1562 |                                 # call is being assigned to my_added_num
1563 | print(my_added_num)             # printing out value of my_added_num
1564 | 1565 |

We can do this without the variable my_added_num above.

1566 | 1567 |
print(add_two_nums(4,5))
1568 | 1569 |

Notice how we used the print function in the above examples to print out the value received from the function call. If we try to print the value of a function call that returns None, meaning no return value was specified, then it prints None to the screen.

1570 | 1571 |
def say_hello():
1572 |     print('Hello World!')
1573 | 
1574 | print(say_hello())
1575 | 1576 |

The code above will result in Hello World! being printed to the screen due to the code inside the function, followed by None, which is the return value from the function.

1577 | 1578 |

Output below.

1579 | 1580 |
$ python lecture_functions.py 
1581 | Hello World!
1582 | None
1583 | 1584 |

You can use doctstrings inside functions to provide information about them. They are created using triple-quoted strings. An example docstring is shown below.

1585 | 1586 |
def say_hello():
1587 |     """This function prints Hello World! to the screen"""
1588 |     print('Hello World!')
1589 | 1590 |

Docstrings can be very detailed as well.

1591 | 1592 |
def add_two_nums(num_1, num_2):
1593 |     """
1594 |     This function adds two objects
1595 |     Input: Two objects to add, num_1 and num_2
1596 |     Output: The value of the two objects added together
1597 |     """
1598 |     return num_1 + num_2
1599 | 1600 |

When a function returns multiple values separated by commas in its return statement, these values are returned as tuples which can then be unpacked and used.

1601 | 1602 |

In the functions video in the course a step by step example is shown on converting repeated/duplicate code and logic in a script to a function, clearning up the code. That example code is shown below, initially the starter code in the script followed by the ‘extracted’ logic code using a function.

1603 | 1604 |
# Initial code
1605 | print("Welcome to the program, what is your name?")
1606 | name_result = input("Enter your response here -> ")
1607 | print(f"Your response was {name_result}")
1608 | 
1609 | print("What did you think of the food you ate today?")
1610 | food_result = input("Enter your response here -> ")
1611 | print(f"Your response was {food_result}")
1612 | 
1613 | print("What tv show ending did you dislike the most ever?")
1614 | show_result = input("Enter your response here -> ")
1615 | print(f"Your response was {show_result}")
1616 | 1617 |


1618 | 1619 |
# Final code using a function
1620 | def get_input_from_user():
1621 |     return input("Enter your response here -> ")
1622 | 
1623 | print("Welcome to the program, what is your name?")
1624 | name_result = get_input_from_user()
1625 | 
1626 | print("What did you think of the food you ate today?")
1627 | food_result = get_input_from_user()
1628 | 
1629 | print("What tv show ending did you dislike the most ever?")
1630 | show_result = get_input_from_user()
1631 | 
1632 | print(f"To summarize: Your name is {name_result.capitalize()},\
1633 |  you ate {food_result} food today and hated the ending\
1634 |  of {show_result.upper()}")
1635 | 1636 |

This concludes our introductory look at functions. We will use custom-built functions a lot in this course and get a lot of practice in the process. In the next chapter we will start looking at creating custom objects in python using classes.

1637 | 1638 |

Back to table of contents 1639 |
1640 |
1641 |

1642 | 1643 |

Classes

1644 | 1645 |

You can create a custom object in Python by creating a class. In the course videos we create a custom Student class, at a minimum you can define it like below.

1646 | 1647 |
class Student:
1648 |     pass
1649 | 1650 |

The pass keyword indicates our intention to fill in code for the class later on.

1651 | 1652 |

We can create instances of the class, or instances of student objects by calling the class name followed by parenthesis () like below:

1653 | 1654 |
mashrur = Student()
1655 | john = Student()
1656 | 1657 |

Note: In the code above mashrur and john are separate instances of students. They are not the same object but are both type student (same class).

1658 | 1659 |

We want our student class to have attributes of firstname, lastname and eventually courses. You can add attributes to your objects like below.

1660 | 1661 |
mashrur.first_name = 'mashrur'
1662 | mashrur.last_name = 'hossain'
1663 | john.first_name = 'john'
1664 | john.last_name = 'doe'
1665 | 1666 |

Once you have written the code above, you can access the attributes associated with each student object like below.

1667 | 1668 |
print(mashrur.first_name, mashrur.last_name)
1669 | print(john.first_name, john.last_name)
1670 | 1671 |

You can use the special __init__() method to assign the attributes 1672 | when the instance of the object is created instead of assigning them as shown in the code above.

1673 | 1674 |

The init method takes a default first argument which is called self by convention in python. self is the object being created itself, followed by any other attributes you wish to initialize when the object is created.

1675 | 1676 |

In the video we modified the class like below to reflect setting up firstname, lastname and courses attributes followed by some execution code at the bottom so we could test it out.

1677 | 1678 |
class Student:
1679 | 
1680 |     def __init__(self, first, last, courses=None):
1681 |         self.first_name = first
1682 |         self.last_name = last
1683 |         if courses == None:
1684 |             self.courses = []
1685 |         else:
1686 |             self.courses = courses
1687 | 
1688 | courses_1 = ['python','rails','javascript']
1689 | courses_2 = ['java','rails','c']
1690 | mashrur = Student('mashrur','hossain',courses_1)
1691 | john = Student('john','doe',courses_2)
1692 | print(mashrur.first_name, mashrur.last_name, mashrur.courses)
1693 | print(john.first_name, john.last_name, john.courses)
1694 | 1695 |

Running the code above will give you the following output.

1696 | 1697 |
$ python lecture_objects.py 
1698 | mashrur hossain ['python', 'rails', 'javascript']
1699 | john doe ['java', 'rails', 'c']
1700 | 1701 |

Adding methods to the student class

1702 | 1703 |

You can add methods to your Student class. Regular methods look and feel almost the same as functions except they take self as an argument as well. In the Student class, the add and remove course methods were added in the course videos. 1704 | The class looked like below once they were added, along with some execution code.

1705 | 1706 |
class Student:
1707 | 
1708 |     def __init__(self, first, last, courses=None):
1709 |         self.first_name = first
1710 |         self.last_name = last
1711 |         if courses == None:
1712 |             self.courses = []
1713 |         else:
1714 |             self.courses = courses
1715 | 
1716 |     def add_course(self, course):
1717 |         if course not in self.courses:
1718 |             self.courses.append(course)
1719 |         else:
1720 |             print(f"{self.first_name} is already \
1721 | enrolled in the {course} course")
1722 | 
1723 |     def remove_course(self, course):
1724 |         if course in self.courses:
1725 |             self.courses.remove(course)
1726 |         else:
1727 |             print(f"{course} not found")
1728 | 
1729 | courses_1 = ['python','rails','javascript']
1730 | courses_2 = ['java','rails','c']
1731 | mashrur = Student('mashrur','hossain',courses_1)
1732 | john = Student('john','doe',courses_2)
1733 | print(mashrur.first_name, mashrur.last_name, mashrur.courses)
1734 | print(john.first_name, john.last_name, john.courses)
1735 | print("Courses added to students")
1736 | mashrur.add_course('c')
1737 | john.add_course('c++')
1738 | print(mashrur.first_name, mashrur.last_name, mashrur.courses)
1739 | print(john.first_name, john.last_name, john.courses)
1740 | print("Courses removed from students")
1741 | mashrur.remove_course('rails')
1742 | john.remove_course('java')
1743 | print(mashrur.first_name, mashrur.last_name, mashrur.courses)
1744 | print(john.first_name, john.last_name, john.courses)
1745 | 1746 |

Running the code above results in the following output.

1747 | 1748 |
$ python lecture_objects.py 
1749 | mashrur hossain ['python', 'rails', 'javascript']
1750 | john doe ['java', 'rails', 'c']
1751 | Courses added to students
1752 | mashrur hossain ['python', 'rails', 'javascript', 'c']
1753 | john doe ['java', 'rails', 'c', 'c++']
1754 | Courses removed from students
1755 | mashrur hossain ['python', 'javascript', 'c']
1756 | john doe ['rails', 'c', 'c++']
1757 | 1758 |

Adding special methods to the student class

1759 | 1760 |

You have already seen the __init__() special method we used earlier which gets called when a new object of the class is created. There are other special methods as well. For the Student class videos we explored the __str__() special method to get a string 1761 | representation of the Student object, we also added the __len__() 1762 | and __repr__() methods.

1763 | 1764 |

__len__() gives you the ability to display what you want when the len function is run with the object you are working with as the argument.

1765 | 1766 |

__repr__() is also a string representation of the class but it’s meant more for other developers and not users of your program. __repr__() is usually supposed to display how to create an instance of the class in question, it also has the characteristic that it will be called to get a string representation of the class if __str__() is not present.

1767 | 1768 |

At the conclusion of adding these special methods, the Student class looked like below.

1769 | 1770 |
class Student:
1771 | 
1772 |     def __init__(self, first, last, courses=None):
1773 |         self.first_name = first
1774 |         self.last_name = last
1775 |         if courses == None:
1776 |             self.courses = []
1777 |         else:
1778 |             self.courses = courses
1779 | 
1780 |     def add_course(self, course):
1781 |         if course not in self.courses:
1782 |             self.courses.append(course)
1783 |         else:
1784 |             print(f"{self.first_name} is already \
1785 | enrolled in the {course} course")
1786 | 
1787 |     def remove_course(self, course):
1788 |         if course in self.courses:
1789 |             self.courses.remove(course)
1790 |         else:
1791 |             print(f"{course} not found")
1792 | 
1793 |     def __len__(self):
1794 |         return len(self.courses)
1795 | 
1796 |     def __repr__(self):
1797 |         return f"Student('{self.first_name}','{self.last_name}',{self.courses})"
1798 | 
1799 |     def __str__(self):
1800 |         return f"First name: {self.first_name.capitalize()}\nLast name: {self.last_name.capitalize()}\
1801 |         \nCourses: {', '.join(map(str.capitalize, self.courses))}"
1802 | 
1803 | 
1804 | courses_1 = ['python','rails','javascript']
1805 | courses_2 = ['java','rails','c']
1806 | mashrur = Student('mashrur','hossain',courses_1)
1807 | john = Student('john','doe',courses_2)
1808 | print(mashrur)
1809 | print(john)
1810 | print(f"{mashrur.first_name.capitalize()} is enrolled in {len(mashrur)} courses")
1811 | print(f"{john.first_name.capitalize()} is enrolled in {len(john)} courses")
1812 | 1813 |

At this point in the course videos we explored adding the ability to work with files (persistent memory), then we explored two other topics with static methods and inheritance. These are best explained with demonstrations in the videos themselves so I recommend checking them out. The text lectures provided with the videos include code used in them as well.

1814 | 1815 |

This concludes our introductory look at the Python programming language. I hope you enjoyed this e-book and found the content useful. We will use concepts we learnt here heavily through the rest of the course so you will get a lot of opportunity to practice programming concepts with Python. I look forward to working with you through the rest of the course starting with the sorting Algorithms section.

1816 | 1817 |

Back to table of contents 1818 |
1819 |
1820 |

1821 | 1822 | 1823 | 1824 | 1827 | 1828 | 1831 | 1832 | 1835 | 1836 | 1837 | 1838 | 1839 | 1840 | --------------------------------------------------------------------------------