Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupport News AboutSign UpSign In
| Download
Project: CSCI 195
Views: 5930
Image: ubuntu2004
Kernel: Python 3 (system-wide)

Introduction to functions

A function allows us to write generic code that can operate on different argument values. The syntax looks like this

def function_name(arg1, arg2, ..., argn): # A series of statements that help us compute the value of the function for the given arguments # The return keyword tells Python the value that has been computed by the function, and terminates # execution of the function when it is encountered return value

Example function -- quadratic formula

We know from mathematics that if we have a mathematical function f(x)=ax2+bx+cf(x) = ax^2 + bx + c, that the function in general has 2 roots, which are values of xx for which f(x)=0f(x) = 0. These roots are defined as:

ParseError: KaTeX parse error: {align} can be used only in display mode.

First, let's define the appropriate function signature for a function named quadratic. For right now, we'll just have it return a value of 0.

def quadratic(a, b, c): # Compute the discriminant of the function # If the discriminant is non zero, there are 2 different roots # Otherwise if the discriminant is 0, there is 1 unique root # Otherwise, there are no real roots return 0

Now let's compute the value of the discriminant inside the function. The discriminant is just the value of b24acb^2-4ac. We'll still return 0 at this point.

def quadratic(a, b, c): # Compute the discriminant of the function discriminant = b*b - 4*a*c # If the discriminant is non zero, there are 2 different roots # Otherwise if the discriminant is 0, there is 1 unique root # Otherwise, there are no real roots return 0

It's important to note that the variable named discriminant inside the quadratic function isn't available outside of that function. It's called a local variable.

print(discriminant)
--------------------------------------------------------------------------- NameError Traceback (most recent call last) /tmp/ipykernel_1022/3314607954.py in <module> ----> 1 print(discriminant) NameError: name 'discriminant' is not defined

Now let's create local variables within quadratic named numerator and denominator that compute their respective values.

def quadratic(a, b, c): # Compute the discriminant of the function discriminant = b*b - 4*a*c # If discriminant is less than 0, there are no real roots if discriminant < 0: return [] denominator = 2*a # If the discriminant is non zero, there are 2 different roots # Otherwise if the discriminant is 0, there is 1 unique root return 0

Now, we'll return a list containing the two roots of the equation, rather than the "hard-coded" value of 0.

from math import sqrt def quadratic(a, b, c): # Compute the discriminant of the function discriminant = b*b - 4*a*c # If discriminant is less than 0, there are no real roots if discriminant < 0: return [] denominator = 2*a # If the discriminant is non zero, there are 2 different roots if discriminant > 0: return [ (-b + sqrt(discriminant))/denominator, (-b - sqrt(discriminant))/denominator] # Otherwise if the discriminant is 0, there is 1 unique root return 0
print(quadratic(1,3,1))
[-0.3819660112501051, -2.618033988749895]

Test the function by solving (x2)(x+4)=0(x-2)*(x+4) = 0, which can be written x2+2x8=0x^2+2x-8=0

print(quadratic(1,2,-8))
[2.0, -4.0]

If the value of discriminant is 0, there is only one root, so add some code in to test for this condition, and return a list with a single element if true.

def quadratic(a, b, c): # Compute the discriminant of the function discriminant = b*b - 4*a*c # If discriminant is less than 0, there are no real roots if discriminant < 0: return [] denominator = 2*a # If the discriminant is non zero, there are 2 different roots if discriminant > 0: return [ (-b + sqrt(discriminant))/denominator, (-b - sqrt(discriminant))/denominator] # Otherwise if the discriminant is 0, there is 1 unique root return [ (-b + sqrt(discriminant))/denominator] return 0

Test this for the quadratic equation 2x2+4x+2=02x^2 + 4x + 2 = 0. The only solution to this should be x=1x = -1.

print(quadratic(2, 4, 2))
[-1.0]

Finally, when discriminant < 0 there are no real roots to the equation. For this, we should return an empty list.

And test it for 2x2+x+1=02x^2+x+1=0

Equations for driving time saved

First, let's define and implement a function named drive_time_in_minutes that computes the time it takes to drive distance at a speed of speed_in_mph.

def drive_time_minutes(distance, speed_in_mph): return distance / speed_in_mph * 60

And test it for the 35 miles to the GR airport from Holland at a rate of 70 mph.

time_to_airport = drive_time_minutes(35, 70) print(f"It will take {time_to_airport} minutes to get to the Grand Rapids Airport")
It will take 30.0 minutes to get to the Grand Rapids Airport

Since the speed limit is often 70 mph, let's make that the default value for the function.

def drive_time_minutes(distance, speed_in_mph=70): return distance / speed_in_mph * 60

Now we can call it like this:

time_to_airport = drive_time_minutes(35) print(f"It will take {time_to_airport} minutes to get to the Grand Rapids Airport")
It will take 30.0 minutes to get to the Grand Rapids Airport

Now define the function time_saved_by_speeding which takes arguments distance, speed_limit, and amount_over_limit. The implementation can make use of the drive_time_minutes function to compute the times at the two different speeds.

def time_saved_by_speeding(distance, speed_limit, amount_over_limit): original_time = drive_time_minutes(distance, speed_limit) faster_time = drive_time_minutes(distance, speed_limit+amount_over_limit) return original_time - faster_time

Test it out to see what we get.

time_saved=time_saved_by_speeding(35, 70, 10) print(f"We can save {time_saved:.2f} minutes by going 10 miles over the speed limit to GR")
We can save 3.75 minutes by going 10 miles over the speed limit to GR

What happens if we try to define a version of time_saved_by_speeding that has a default value for the speed_limit argument?

def time_saved_by_speeding(distance, speed_limit=70, amount_over_limit=7.5): original_time = drive_time_minutes(distance, speed_limit) faster_time = drive_time_minutes(distance, speed_limit+amount_over_limit) return original_time - faster_time

Why does this happen? Suppose we called time_saved_by_speeding(35, 60). Did we intend:

  • The value of speed_limit to be 60, and we forgot a value for amount_over_limit?

  • The value of speed_limit to be its default of 70, and amount_over_limit to be 60?

There's no way for the Python interpreter to know. The rule is that all arguments that don't have default values must come before any with default arguments.

print(time_saved_by_speeding(35,amount_over_limit=60, speed_limit=55))
19.92094861660079

How fast should we drive?

Finally, the equation to tell us how fast to drive if we want to save a certain amount of time when traveling a specific distance.

def speed_needed_to_save_time(distance, original_speed, desired_time_savings): numerator = original_speed * desired_time_savings * original_speed / (distance * 60) denominator = 1 - (original_speed * desired_time_savings)/(distance*60) increase_in_speed = numerator / denominator return original_speed + increase_in_speed
new_speed = speed_needed_to_save_time(35, 70, 10) print(f"To save 10 minutes versus driving 70, we must drive {new_speed} MPH")
To save 10 minutes versus driving 70, we must drive 105.0 MPH