<jupyter_start><jupyter_text>Python Refresher This notebook contains a quick refresher of basic Python.  Control Structures  Iteration With Loops In Python, a `for` loop can be used to iterate through a *list* or *dictionary*. In contrast to the traditional `for` loop where we initialize `i` and increase/decrease it untill it reaches a certain end value, Python's `for` loop is actually `for-each` in nature, which means you always iterating over the items of the list.If you need the index values from the list while iterating, you could also use `enumerate`.**Example**<jupyter_code># Examples of iteration with for loops.

my_list = [0, 1, 2, 3, 4, 5]

# Print each value in my_list. Note you can use the "in" keyword to iterate over a list.
for item in my_list:
    print('The value of item is: ' + str(item))

# Print each index and value pair.
for i, value in enumerate(my_list):
    print('The index value is: ' + str(i) + '. The value at i is: ' + str(value))

# Print each number from 0 to 9 using a while loop.
i = 0
while(i < 10):
    print(i)
    i += 1

# Print each key and dictionary value. Note that you can use the "in" keyword 
# to iterate over dictionary keys.
my_dict = {'a': 'jill', 'b': 'tom', 'c': 'tim'}
for key in my_dict:
    print(key + ', ' + my_dict[key])
my_dict = {'a':[0, 1, 2, 3], 'b':[0, 1, 2, 3], 'c':[0, 1, 2, 3], 'd':[0, 1, 2, 3]}
i = 0
output = []
for key in my_dict:
    output.append(my_dict[key][i])
    i += 1
print(output)<jupyter_output>[0, 1, 2, 3]<jupyter_text>Conditional Statements<jupyter_code>num = 5
if num < 5:
    print('The number is smaller than 5.')
elif num == 5:
    print('The number equals 5.')
else:
    print('The number is greater than 5.')<jupyter_output>The number equals 5.<jupyter_text>Control Structure Practice  Exercise 1In the following exercise you will finish writing smallest_positive which is a function that finds the smallest positive number in a list.<jupyter_code>'''
My Solution
'''

import sys

def smallest_positive(in_list):
    # TODO: Define a control structure that finds the smallest positive
    # number in in_list and returns the correct smallest number.
    
    # Variable for holding the smallest positive value, initialized as max value.
    smallest_positive = sys.maxsize
    
    for num in in_list:
        if num > 0 and num < smallest_positive:
            smallest_positive = num
                
    if smallest_positive == sys.maxsize:
        return None
    else:
        return smallest_positive

# Test cases

print(smallest_positive([4, -6, 7, 2, -4, 10]))
# Correct output: 2

print(smallest_positive([.2, 5, 3, -.1, 7, 7, 6]))
# Correct output: 0.2

print(smallest_positive([-6, -9, -7]))
# Correct output: None

print(smallest_positive([]))
# Correct output: None
'''
Solution
'''

def smallest_positive(in_list):
    # TODO: Define a control structure that finds the smallest positive
    # number in in_list and returns the correct smallest number.
    
    # Variable for holding the smallest positive value.
    smallest_positive = None
    
    for num in in_list:
        if num > 0:
            # Update smallest_positive with current num under the following conditions:
            # 1) if smallest_positive has not been updated, update with current num
            # 2) if current num is smaller than the smallest_positive
            if smallest_positive == None or num < smallest_positive:
                smallest_positive = num
                
    return smallest_positive

# Test cases

print(smallest_positive([4, -6, 7, 2, -4, 10]))
# Correct output: 2

print(smallest_positive([.2, 5, 3, -.1, 7, 7, 6]))
# Correct output: 0.2

print(smallest_positive([-6, -9, -7]))
# Correct output: None

print(smallest_positive([]))
# Correct output: None<jupyter_output>2
0.2
None
None<jupyter_text>Exersice 2Now let's assume you are planning on taking some additional courses at your local educational institution, and you have acquired some data about the available courses and when they will be offered. In the following exercise, you will write control structures to process the data and return the semesters when a given course is offered.You will need to complete the function `when_offered(courses, course)`. This function accepts a "courses" data structure and a "course" string. The function should return a list of strings representing the semesters when the input course is offered. See the two test cases below for examples of correct results.Since the `when_offered` function accepts a dictionary data structure, you will find the```pythonfor  in :                      ```construct useful, as this loops through the key values in a dictionary.<jupyter_code>'''
My Perfect Solution
'''
# This exercise uses a data structure that stores Udacity course information.
# The data structure format is:

#    { <semester>: { <class>: { <property>: <value>, ... },
#                                     ... },
#      ... }


courses = {
    'spring2020': { 'cs101': {'name': 'Building a Search Engine',
                           'teacher': 'Dave',
                           'assistant': '<NAME>.'},
                 'cs373': {'name': 'Programming a Robotic Car',
                           'teacher': 'Sebastian',
                           'assistant': 'Andy'}},
    'fall2020': { 'cs101': {'name': 'Building a Search Engine',
                           'teacher': 'Dave',
                           'assistant': 'Sarah'},
                 'cs212': {'name': 'The Design of Computer Programs',
                           'teacher': '<NAME>.',
                           'assistant': 'Andy',
                           'prereq': 'cs101'},
                 'cs253': {'name': 'Web Application Engineering - Building a Blog',
                           'teacher': 'Steve',
                           'prereq': 'cs101'},
                 'cs262': {'name': 'Programming Languages - Building a Web Browser',
                           'teacher': 'Wes',
                           'assistant': '<NAME>.',
                           'prereq': 'cs101'},
                 'cs373': {'name': 'Programming a Robotic Car',
                           'teacher': 'Sebastian'},
                 'cs387': {'name': 'Applied Cryptography',
                           'teacher': 'Dave'}},
    'spring2044': { 'cs001': {'name': 'Building a Quantum Holodeck',
                           'teacher': 'Dorina'},
                        'cs003': {'name': 'Programming a Robotic Robotics Teacher',
                           'teacher': 'Jasper'},
                     }
    }


def when_offered(courses, course):
    # TODO: Fill out the function here.
    
    # List for holding the result list.
    result = []
    
    # Loop through all the keys in courses dictionary.
    for semester in courses:
        # Loop through all the keys in courses[semester] dictionary.
        for course_name in courses[semester]:
            if course_name == course:
                result.append(semester)
    
    # TODO: Return list of semesters here.
    return result



print(when_offered(courses, 'cs101'))
# Correct result: 
# ['fall2020', 'spring2020']

print(when_offered(courses, 'bio893'))
# Correct result: 
# []<jupyter_output>['spring2020', 'fall2020']
[]<jupyter_text>Function and Generator  Python Functions<jupyter_code># Example function 1: return the sum of two numbers.
def sum(a, b):
    return a+b

# Example function 2: return the size of list, and modify the list to now be sorted.
def list_sort(my_list):
    my_list.sort()
    return len(my_list),  my_list<jupyter_output><empty_output><jupyter_text>Python Generators A **generator** in Python is very similar to a function in Python. The only difference is that unlike functions which return a value and exit a process, a generator will pause the process and save its state for the next time. The syntax of a generator in Python is `yield` instead of `return`.A generator is very useful when dealing with a massive amount of data that you don't want to store in memory all at once. It's also very handy in dealing with extremely large or infinite series.In the example shown below, there's an infinite loop, we can see the benefit of a generator that the process of creating even numbers can be paused and proceeded whenever needed.To create the next successive even number we simply call `next()` on the generator object. After `yield` is invoked, everything in the state of the generator function freezes, and the value is returned. When the generator is invoked again with `next()`, it goes back with the state that it stopped at `yield` in the last iteraion.<jupyter_code># Definition of the generator to produce even numbers.
def all_even():
    n = 0
    while True:
        yield n
        n += 2

my_gen = all_even()

# Generate the first 5 even numbers.
for i in range(5):
    print(next(my_gen))

# Now go and do some other processing.
do_something = 4
do_something += 3
print(do_something)

# Now go back to generating more even numbers.
for i in range(100):
    print(next(my_gen))<jupyter_output>0
2
4
6
8
7
10
12
14
16
18
20
22
24
26
28
30
32
34
36
38
40
42
44
46
48
50
52
54
56
58
60
62
64
66
68
70
72
74
76
78
80
82
84
86
88
90
92
94
96
98
100
102
104
106
108
110
112
114
116
118
120
122
124
126
128
130
132
134
136
138
140
142
144
146
148
150
152
154
156
158
160
162
164
166
168
170
172
174
176
178
180
182
184
186
188
190
192
194
196
198
200
202
204
206
208<jupyter_text>Function and Generator Practice  Exercise 1In the following exercise, you will create a generator `fact_gen()` that generates factorials. For a number n, n factorial is denoted by n!, and it is the product of all positive integers less than or equal to n. For example,> $5! = 5 \times 4 \times 3 \times 2 \times 1 = 120$In this exercise, you will define `prod(a, b)` which returns the product of numbers a and b. You will also define `fact_gen()` which yields the next factorial number.<jupyter_code>'''
My Perfect Solution
'''

def prod(a,b):
    # TODO change output to the product of a and b
    output = a * b
    return output

def fact_gen():
    i = 1
    n = i
    while True:
        output = prod(n, i)
        yield output
        # TODO: update i and n
        # Hint: i is a successive integer and n is the previous product
        n = output
        i += 1


# Test block
my_gen = fact_gen()
num = 5
for i in range(num):
    print(next(my_gen))

# Correct result when num = 5:
# 1
# 2
# 6
# 24
# 120<jupyter_output>1
2
6
24
120<jupyter_text>Exercise 2In the next exercise, you will write a function that checks sudoku squares for correctness.Sudoku is a logic puzzle where a game is defined by a partially filled $9 \times 9$ square of digits where each square contains one of the digits $1, 2, 3, 4, 5, 6, 7, 8, 9$. For this question we will generalize and simplify the game.Define a procedure, `check_sudoku`, that takes as input a square list of lists representing an $n \times n$ sudoku puzzle solution and returns the boolean True if the input is a valid sudoku square and returns the boolean False otherwise.A valid sudoku square satisfies these two properties:1. Each column of the square contains each of the whole numbers from 1 to n exactly once.2. Each row of the square contains each of the whole numbers from 1 to n exactly once.You may assume that the input is square and contains at least one row and column.<jupyter_code>'''
My Solution
'''
correct = [[1,2,3],
           [2,3,1],
           [3,1,2]]

incorrect = [[1,2,3,4],
             [2,3,1,3],
             [3,1,2,3],
             [4,4,4,4]]

incorrect2 = [[1,2,3,4],
             [2,3,1,4],
             [4,1,2,3],
             [3,4,1,2]]

incorrect3 = [[1,2,3,4,5],
              [2,3,1,5,6],
              [4,5,2,1,3],
              [3,4,5,2,1],
              [5,6,4,3,2]]

incorrect4 = [['a','b','c'],
              ['b','c','a'],
              ['c','a','b']]

incorrect5 = [ [1, 1.5],
               [1.5, 1]]
               
# Define a function check_sudoku() here:
def check_sudoku(square):
    
    # Dimension of the square.
    n = len(square)
    
    # Loop through all rows in the square
    for row in square:
        # Check if all the consecutive integers exist in current row list.
        for i in range(1, n + 1):
            if i not in row:
                return False
                
    # List to store all the columns.
    cols = []
    
    # Fill in the column list.
    for i in range(n):
        current_col = []
        for row in square:
            current_col.append(row[i])
        
        cols.append(current_col)
    
    # Loop through all the columns.
    for col in cols:
        for i in range(1, n + 1):
            if i not in col:
                return False
                
    return True
    
print(check_sudoku(incorrect))
#>>> False

print(check_sudoku(correct))
#>>> True

print(check_sudoku(incorrect2))
#>>> False

print(check_sudoku(incorrect3))
#>>> False

print(check_sudoku(incorrect4))
#>>> False

print(check_sudoku(incorrect5))
#>>> False
'''
Solution
'''

correct = [[1,2,3],
           [2,3,1],
           [3,1,2]]

incorrect = [[1,2,3,4],
             [2,3,1,3],
             [3,1,2,3],
             [4,4,4,4]]

incorrect2 = [[1,2,3,4],
             [2,3,1,4],
             [4,1,2,3],
             [3,4,1,2]]

incorrect3 = [[1,2,3,4,5],
              [2,3,1,5,6],
              [4,5,2,1,3],
              [3,4,5,2,1],
              [5,6,4,3,2]]

incorrect4 = [['a','b','c'],
              ['b','c','a'],
              ['c','a','b']]

incorrect5 = [ [1, 1.5],
               [1.5, 1]]

# Define a function check_sudoku() here:
def check_sudoku(square):
    # Dimension of the square.
    n = len(square)
    
    # Loop through all the rows.
    for row in square:
        # List of elements to check.
        check_list = list(range(1, n + 1))
        for i in row:
            if i not in check_list:
                return False
            # Remove checked element.
            check_list.remove(i)
    
    for j in range(n):
        # List of elements to check.
        check_list = list(range(1, n + 1))
        for row in square:
            if row[j] not in check_list:
                return False
            check_list.remove(row[j])
    return True
    
print(check_sudoku(incorrect))
#>>> False

print(check_sudoku(correct))
#>>> True

print(check_sudoku(incorrect2))
#>>> False

print(check_sudoku(incorrect3))
#>>> False

print(check_sudoku(incorrect4))
#>>> False

print(check_sudoku(incorrect5))
#>>> False<jupyter_output>False
True
False
False
False
False<jupyter_text>Python Classes  Python Classes OverviewA class is a structure in object-oriented programming that **allows functions and related data to be grouped together**.In a Python class, an important concept is `self`, which is used to reference a class instance's own variables and functions from within the class definition. For example, if we had a class called `Person` and we wanted the class instances to have a variable called age, we could store this information by using `self.age`.Also, if we wanted the class to have a function that would increment the age of the person, we could define a function inside this class called `def birthday(self)`. In order to be a class function, `birthday` needs to include the input variable `self`, as this is used for proper referencing within the class.Another important and commonly used function definition is the class initializer, `def __init__(self)`. The body of the initializer is where instance variable definitions should be added, and the initializer initializes all the variables once an instance of the class is created. Also, any input variables that a class needs to have, such as a name for the person can be passed into initializer function.> `self` must be used when declaring a variable in an `__init__` function so that each instance of the class has its own copy of that variable.  Example of Python ClassBelow is an example of a basic Person class. The class has two variables for name and age, along with three functions for initializing the class, incrementing the person’s age, and getting the person’s name.<jupyter_code>class Person:
    def __init__(self, name, age):
        self.person_name = name
        self.person_age = age

    def birthday(self):
        self.person_age += 1

    def getName(self):
        return self.person_name<jupyter_output><empty_output><jupyter_text>Let’s look at an example for how to create an instance of the `Person` class using the class template above. We can then access that `Person`’s name:<jupyter_code>bob = Person('Bob', 32)
print(bob.getName())
# prints Bob<jupyter_output>Bob<jupyter_text>Currently, we have one function for getting the class’s variable. This is called an **Accessor**. The other function that the class has is actually modifying one of the class’ variables, and that is called a **Mutator**. We can make our `Person` older by calling `birthday()`.<jupyter_code>bob.birthday()
print(bob.person_age)
# prints 33<jupyter_output>33<jupyter_text>The birthday function call successfully increments the age of our `Person`. Also note that we can directly get the age of bob *without using a function call*. This is because the `Person` class variables are defined as **public**, so we can directly access them without a function call. If instead we wanted the `Person`’s age variable to be private to the class, in Python 3 we could put double underscores in front of the variable: `__person_age`. Then we would have to use a function call in order to retrieve it.<jupyter_code>class PrivatePerson:
    def __init__(self, name, age):
        self.person_name = name
        self.__person_age = age

    def birthday(self):
        self.__person_age += 1

    def getName(self):
        return self.person_name
    
    def getAge(self):
        return self.__person_age
incognito = PrivatePerson('Incognito', 50)
incognito.birthday()
print(incognito.__person_age)
# Error.
incognito = PrivatePerson('Incognito', 50)
incognito.birthday()
print(incognito.getAge())
# Prints 51.<jupyter_output>51<jupyter_text>Python Class Practice  Exercise 1Now let's assume that the current month is April, and you want to use a `Person` class to help make use of information about the friends in your contacts list. In particular, you'd like to increment the age of all of your friends with birthdays in April. You would also like to know who they are, along with their current ages, so you can send them birthday cards. Finally, you would also like to figure out which month has the most friends with birthdays, so you can budget for all of the birthday cards you will need to buy.In the following exercise, the `Person` class will be provided for you, and you will be working with a list of instances of the class, representing friends in your contacts. This list is stored in the variable `people`.To complete the exercise, you will need to do two things:Complete the function `get_april_birthdays(people)`. This function should return a dictionary with each name of your friend with an April birthday as a key, and their updated age as the value.Complete the function `get_most_common_month(people)`. This function should return the name of the month with the most number of birthdays among your friends.There is some testing code provided in `test()`, and there are more specific TODO instructions in each of the two functions mentioned.<jupyter_code>'''
My Perfect Solution.
'''

class Person:
    def __init__(self, name, age, month):
        self.name = name
        self.age = age
        self.birthday_month = month
        
    def birthday(self):
        self.age += 1

def create_person_objects(names, ages, months):
    my_data = zip(names, ages, months)
    person_objects = []
    for item in my_data:
        person_objects.append(Person(*item))
    return person_objects

def get_april_birthdays(people):
    # TODO:
    # Increment "age" for all people with birthdays in April.
    # Return a dictionary "april_birthdays" with the names of
    # all people with April birthdays as keys, and their updated ages 
    # as values. See the test below for an example expected output.
    april_birthdays = {}
    
    for person in people:
        if person.birthday_month == 'April':
            person.birthday()
            april_birthdays[person.name] = person.age

    
    # TODO: Modify the return statement 
    return april_birthdays

def get_most_common_month(people):
    # TODO:
    # Use the "months" dictionary to record counts of birthday months
    # for persons in the "people" data.
    # Return the month with the largest number of birthdays.
    months = {'January':0, 'February':0, 'March':0, 'April':0, 'May':0, 
              'June':0, 'July':0, 'August':0, 'September':0, 'October':0,
              'November':0, 'December':0}
    
    most_common_month = 'January'
    
    for person in people:
        months[person.birthday_month] += 1
        
    for month in months:
        if months[month] > months[most_common_month]:
            most_common_month = month
    
    # TODO: Modify the return statement.
    return most_common_month


def test():
    # Here is the data for the test. Assume there is a single most common month.
    names = ['Howard', 'Richard', 'Jules', 'Trula', 'Michael', 'Elizabeth', 'Richard', 'Shirley', 'Mark', 'Brianna', 'Kenneth', 'Gwen', 'William', 'Rosa', 'Denver', 'Shelly', 'Sammy', 'Maryann', 'Kathleen', 'Andrew', 'Joseph', 'Kathleen', 'Lisa', 'Viola', 'George', 'Bonnie', 'Robert', 'William', 'Sabrina', 'John', 'Robert', 'Gil', 'Calvin', 'Robert', 'Dusty', 'Dario', 'Joeann', 'Terry', 'Alan', 'Rosa', 'Jeane', 'James', 'Rachel', 'Tu', 'Chelsea', 'Andrea', 'Ernest', 'Erica', 'Priscilla', 'Carol', 'Michael', 'Dale', 'Arthur', 'Helen', 'James', 'Donna', 'Patricia', 'Betty', 'Patricia', 'Mollie', 'Nicole', 'Ernest', 'Wendy', 'Graciela', 'Teresa', 'Nicole', 'Trang', 'Caleb', 'Robert', 'Paul', 'Nieves', 'Arleen', 'Milton', 'James', 'Lawrence', 'Edward', 'Susan', 'Patricia', 'Tana', 'Jessica', 'Suzanne', 'Darren', 'Arthur', 'Holly', 'Mary', 'Randal', 'John', 'Laura', 'Betty', 'Chelsea', 'Margaret', 'Angel', 'Jeffrey', 'Mary', 'Donald', 'David', 'Roger', 'Evan', 'Danny', 'William']
    ages  = [17, 58, 79, 8, 10, 57, 4, 98, 19, 47, 81, 68, 48, 13, 39, 21, 98, 51, 49, 12, 24, 78, 36, 59, 3, 87, 94, 85, 43, 69, 15, 52, 57, 36, 52, 5, 52, 5, 33, 10, 71, 28, 70, 9, 25, 28, 76, 71, 22, 35, 35, 100, 9, 95, 69, 52, 66, 91, 39, 84, 65, 29, 20, 98, 30, 83, 30, 15, 88, 89, 24, 98, 62, 94, 86, 63, 34, 23, 23, 19, 10, 80, 88, 67, 17, 91, 85, 97, 29, 7, 34, 38, 92, 29, 14, 52, 94, 62, 70, 22]
    months = ['January', 'March', 'January', 'October', 'April', 'February', 'August', 'January', 'June', 'August', 'February', 'May', 'March', 'June', 'February', 'August', 'June', 'March', 'August', 'April', 'April', 'June', 'April', 'June', 'February', 'September', 'March', 'July', 'September', 'December', 'June', 'June', 'August', 'November', 'April', 'November', 'August', 'June', 'January', 'August', 'May', 'March', 'March', 'March', 'May', 'September', 'August', 'April', 'February', 'April', 'May', 'March', 'March', 'January', 'August', 'October', 'February', 'November', 'August', 'June', 'September', 'September', 'January', 'September', 'July', 'July', 'December', 'June', 'April', 'February', 'August', 'September', 'August', 'February', 'April', 'July', 'May', 'November', 'December', 'February', 'August', 'August', 'September', 'December', 'February', 'March', 'June', 'December', 'February', 'May', 'April', 'July', 'March', 'June', 'December', 'March', 'July', 'May', 'September', 'November']
    people = create_person_objects(names, ages, months)

    # Calls to the two functions you have completed.
    print(get_april_birthdays(people))
    print(get_most_common_month(people))



test()
# Expected result:
# {'Michael': 11, 'Erica': 72, 'Carol': 36, 'Lisa': 37, 'Lawrence': 87, 'Joseph': 25, 'Margaret': 35, 'Andrew': 13, 'Dusty': 53, 'Robert': 89}
# August<jupyter_output>{'Michael': 11, 'Andrew': 13, 'Joseph': 25, 'Lisa': 37, 'Dusty': 53, 'Erica': 72, 'Carol': 36, 'Robert': 89, 'Lawrence': 87, 'Margaret': 35}
August
{'fraction_non_alphanumeric': 0.1159563924677899, 'fraction_numerical': 0.03774364056821936, 'mean_word_length': 3.0969379123667737, 'pattern_counts': {'":': 0, '<': 53, '<?xml version=': 0, '>': 90, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 25, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '13', 'dir': 'jupyter-structured-clean-dedup', 'id': '574819', 'license': 'MIT', 'n_tokens_mistral': 8205, 'n_tokens_neox': 7265, 'n_words': 2983, 'path': 'Section1_Introduction/Python Refresher.ipynb', 'repo_name': 'helloimlixin/Data-Structures-and-Algorithms-In-Python', 'size': '24219'}