<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Writing Functions
This lecture discusses the mechanics of writing functions and how to encapsulate scripts as functions.
Step1:  Copying and pasting code can introduce bugs
Step2:  Why write functions?
Easily reuse code (without introducing bugs)
Easy testing of components
    <ul>
    <li>Later in the course we will learn about writing unit tests. You will create a set of input values for a function representing potential scenarios, and will test that the function is generating the expected output.
    </ul>
Better readability
    <ul>
    <li>Functions encapsulate your code into components with meaningful names. You can get a high-level view of what the code is doing, then dive into the function definitions if you need more detail.       
    </ul>
A function should have one task
Functions should usually be pretty short.  
It's good to think about functions as trying to do one single thing.
Step4:  Mechanics of Writing a Function
Function definition line - How python knows that this is a function
Function body - Code that does the computation of the function
Arguments - the values passed to a function
Formal parameters - the values accepted by the function
    (the arguments become the formal parameters once they are inside the function)
Return values - value returned to the caller
If you are familiar with other languages like Java, you may have needed to declare the types of the parameters and return value. This is not necessary in Python.
Step5:  Variable names and scope
In Python, variables have a scope (a context in which they are valid).  
Variables created in a function cannot be referenced outside of a function
Step6:  If you modify an object (like a list or a dataframe) inside of a function, the modifications will affect its value outside of the function
Step7:  Exercise
Step8:  Once you've identified the bug in the above code, take the code and turn it into a function that takes a number as input and returns True if the number is prime and False if it is not.
See if you can find any ways to make the code more efficient.
Step9:  Test cases
What are some numbers that we might use as tests to see if our function is working right?  
Think of different classes of numbers that might have different behavior, like even vs. odd numbers, and make 
sure you are including examples from each class.
Also try to think of some "edge cases" that might cause strange behavior.
Think of some good test cases for your function and see if you get the results you expect
Step10:  Exercise - Write a Function
Write a function that finds two factors of a provided number if it is not prime.
What did you name the function?
What are the formal arguments? What did you name them?
What value(s) are returned?
Crafting a Function
Crafting refers to what you want a function to do. 
- What are the arguments?
- Should it refer to names outside the function definition?
- Should it return a value?
Problem
<ASSISTANT_TASK:>
Python Code:
# Example: We're going to use Pandas dataframes to create a gradebook for this course
import pandas as pd
# Student Rosters:
section_1_students = ['Hao', 'Jennifer', 'Alex']
section_2_students = ['Christa', 'Troy', 'Xin']
# Gradebook columns:
columns = ['raw_grade', 'did_extra_credit', 'final_grade']
# Let's create two dataframes, one for each class section
section_1_gradebook = pd.DataFrame(index=section_1_students, columns=columns)
section_2_gradebook = pd.DataFrame(index=section_2_students, columns=columns)
print("Section 1 Gradebook:")
print(section_1_gradebook)
print("\n") # prints a newline character to put an empty line
print("Section 2 Gradebook:")
print(section_2_gradebook)
# Now let's add some data
# (in real life we might load this from a CSV or other file)
section_1_gradebook.loc['Hao']['raw_grade'] = 80
section_1_gradebook.loc['Hao']['did_extra_credit'] = True # python supports boolean (True/False) values
section_1_gradebook.loc['Jennifer']['raw_grade'] = 98
section_1_gradebook.loc['Jennifer']['did_extra_credit'] = False
section_1_gradebook.loc['Alex']['raw_grade'] = 85
section_1_gradebook.loc['Alex']['did_extra_credit'] = True
section_2_gradebook.loc['Christa']['raw_grade'] = 91
section_2_gradebook.loc['Christa']['did_extra_credit'] = False
section_2_gradebook.loc['Troy']['raw_grade'] = 89
section_2_gradebook.loc['Troy']['did_extra_credit'] = True
section_2_gradebook.loc['Xin']['raw_grade'] = 89
section_2_gradebook.loc['Xin']['did_extra_credit'] = False
                            
print("Section 1 Gradebook:")
print(section_1_gradebook)
print("\n") # prints a newline character to put an empty line
print("Section 2 Gradebook:")
print(section_2_gradebook)
# Let's give everyone who did Extra Credit 5 additional points
# But we'll put a max of 100 points
# We'll do section 1 first:
# Now let's print out the results:
print("Section 1 Final Grades:")
print(section_1_gradebook)
# Now let's do Section 2:
        
# Now let's print out the results:        
print("Section 2 Final Grades:")
print(section_2_gradebook)
Explanation: Writing Functions
This lecture discusses the mechanics of writing functions and how to encapsulate scripts as functions.
End of explanation
# Let's put our extra credit code in a function!
Explanation: Copying and pasting code can introduce bugs:
You might forget to change a variable name.  
If you later make a change (like making extra credit worth 10 points instead of 5), you need to remember to change it in multiple places.
If we put the code in a function, we can avoid these problems!
End of explanation
# Let's decompose our other function into two functions that each do one thing
extra_credit_value = 10
add_final_grades_for_section(section_1_gradebook, extra_credit_value)
add_final_grades_for_section(section_2_gradebook, extra_credit_value)
Explanation: Why write functions?
Easily reuse code (without introducing bugs)
Easy testing of components
    <ul>
    <li>Later in the course we will learn about writing unit tests. You will create a set of input values for a function representing potential scenarios, and will test that the function is generating the expected output.
    </ul>
Better readability
    <ul>
    <li>Functions encapsulate your code into components with meaningful names. You can get a high-level view of what the code is doing, then dive into the function definitions if you need more detail.       
    </ul>
A function should have one task
Functions should usually be pretty short.  
It's good to think about functions as trying to do one single thing.
End of explanation
def example_addition_function(num_1, num_2):
    
    This function adds two numbers.
    
    example_addition_function is the function name
    Parameters:
        num_1: This is the first formal parameter
        num_2: This is the second formal parameter
    Returns:
        sum of num_1 and num_2
    
        
    added_value = num_1 + num_2
    return added_value
arg_1 = 5
arg_2 = 10
result_value = example_addition_function(arg_1, arg_2) # arg_1 and arg_2 are the arguments to the function
    
Explanation: Mechanics of Writing a Function
Function definition line - How python knows that this is a function
Function body - Code that does the computation of the function
Arguments - the values passed to a function
Formal parameters - the values accepted by the function
    (the arguments become the formal parameters once they are inside the function)
Return values - value returned to the caller
If you are familiar with other languages like Java, you may have needed to declare the types of the parameters and return value. This is not necessary in Python.
End of explanation
def print_message(message):
    message_to_print = "Here is your message: " + message
    print(message_to_print)
    
my_message = "Hello, class!"
print_message(my_message)
#print(message_to_print) # this will cause an error. This variable only exists within the function.
Explanation: Variable names and scope
In Python, variables have a scope (a context in which they are valid).  
Variables created in a function cannot be referenced outside of a function
End of explanation
def add_name_to_list(name_list, new_name):
    name_list.append(new_name)
    
teachers = ["Bernease", "Dave", "Joe"]
print(teachers)
add_name_to_list(teachers, "Colin")
print(teachers)
Explanation: If you modify an object (like a list or a dataframe) inside of a function, the modifications will affect its value outside of the function
End of explanation
# Determine if num is prime
# This code has a bug. What is it?
# Also, the efficiency of the code can be improved. How?
num = 3
is_prime = True
for integer in range(1, num):
    if num % integer == 0: 
        # The "==" operator checks for equality and returns True or False. 
        # Note the difference between "==" and "=", which assigns a value to a variable.
        #
        # The "%" operator calculates the remainder of a division operation
        # if the remainder is zero, integer is a divisor of num, so num is not prime
        print("Not prime!")
        is_prime = False
if is_prime:
    print("Is prime!")
Explanation: Exercise: Write a function to determine if a number is prime
Below is some code that checks if a number is prime. The code has a bug in it!
End of explanation
# Write a function that takes a number as input and determines if it is prime.
# If it is prime, return True
# Otherwise, return False
Explanation: Once you've identified the bug in the above code, take the code and turn it into a function that takes a number as input and returns True if the number is prime and False if it is not.
See if you can find any ways to make the code more efficient.
End of explanation
# Check your function on several test cases to make sure you are getting the correct output:
Explanation: Test cases
What are some numbers that we might use as tests to see if our function is working right?  
Think of different classes of numbers that might have different behavior, like even vs. odd numbers, and make 
sure you are including examples from each class.
Also try to think of some "edge cases" that might cause strange behavior.
Think of some good test cases for your function and see if you get the results you expect:
End of explanation
# Function header: name, arguments
# Function logic
# return something
Explanation: Exercise - Write a Function
Write a function that finds two factors of a provided number if it is not prime.
What did you name the function?
What are the formal arguments? What did you name them?
What value(s) are returned?
Crafting a Function
Crafting refers to what you want a function to do. 
- What are the arguments?
- Should it refer to names outside the function definition?
- Should it return a value?
Problem: Write a function that finds all of the prime numbers less than a given value.
End of explanation
<END_TASK>