Monday, September 15th, 2025¶
Last time we added code comments to our is_prime
to explain how the code works. We also discussed using Markdown cells above code cells to explain what the code will do and how it will work.
Exercise: Using Markdown and LaTeX, explain what the is_prime
function does and how it works.
from math import sqrt
def is_prime(n): # Define a function is_prime
n_is_prime = True # Define a Boolean that is True when n is
# prime, false otherwise. We will assume that
# n is prime unless we can show otherwise
for d in range(2,int(sqrt(n))+1): # Loop through possible divisors d
if n % d == 0: # Check if n is divisble by d
n_is_prime = False # If so, then n is not prime...
break # ... and we can stop looking for divisors
if n_is_prime: # If no divisor was found, then n must be prime
return True
else: # Otherwise, a divisor was found and n is not prime
return False
from math import sqrt
def is_prime(n): # Define a function is_prime
for d in range(2,int(sqrt(n))+1): # Loop through possible divisors d
if n % d == 0: # Check if n is divisble by d
return False
return True
Project 1¶
Modular arithmetic and congruences¶
See Project 1 page for an introduction to modular arithmetic and congruences.
From the project page, we say that a number $n$ is "prime-like" if
$$a^n \equiv a\pmod{n}$$ for all integers $0 \leq a < n$.
Exercise: Write a function is_prime_like
that takes in an integer n
and returns True
if n
is prime-like and False
if not.
def is_prime_like(n):
for a in range(n):
if a**n % n != a % n:
return False
return True
Note: See project page for details about the operation a**n % n
. This turns out to be much too slow for our needs.
help(pow)
Help on built-in function pow in module builtins: pow(base, exp, mod=None) Equivalent to base**exp with 2 arguments or base**exp % mod with 3 arguments Some types, such as ints, are able to use a more efficient algorithm when invoked using the three argument form.
is_prime_like(3)
True
is_prime_like(4)
False
is_prime_like(561)
True
is_prime(561)
False
Exercise: Write a function is_false_prime(n)
that returns True
if n
is a false prime and False
otherwise.
def is_false_prime(n):
if is_prime_like(n) and not is_prime(n):
return True
else:
return False
Note: The is_false_prime
function above has a critical problem that will us from finding 20
false primes in a reasonable amount of time.
is_false_prime(561)
True
is_false_prime(29)
False
is_false_prime(4)
False
Exercise: Write a function that takes in an integer n
and returns a list of prime numbers that divide n
.
Exercise: Modify the function from the previous exercise to return the primary decomposition of n
. That is, modify the code to account for the multiplicity of each prime divisor.
So far, we've discussed defining functions that take in some number of input variables. For example, we defined the is_power
function that took in two inputs, namely n
and power
.
def is_power(n,power):
root_n = n**(1/power)
if round(root_n)**power == n:
return True
else:
return False
When we call a function, we can simply enter values in the corresponding order to map them to the input variables. For example, calling is_power(16,4)
will execute the function by mapping n=16
and power=4
. Sometimes, it is useful to explicitly assign values to variables. This can be done using keyword arguments. To use a keyword argument, we plug a mapping <variable name> = <value>
into the function.
For example, is_power(n=16, power=4)
will explicitly map n=16
and power=4
and execute the function's code.
is_power(n=16, power=4)
True
With keyword arguments, we do not need to enter the values in the same order that was used to define the function. That is, is_power(power=4,n=16)
will give the same result, whereas is_power(4,16)
would map n=4
and power=16
.
is_power(power=4, n=16)
True
For a more illustrative toy example:
def f(a,b,c):
print(a + b + c)
f('Hello','Goodbye','!')
HelloGoodbye!
f(c='Hello',a='Goodbye',b='!')
Goodbye!Hello
When calling a function, inputs that we've not explicitly labeled as keyword arguments are called positional arguments. All positional arguments must come before any keyword arguments, and their ordering determines the mapping to the input variable names.
f('Hello', c='Goodbye', b='!')
Hello!Goodbye
f(c='Hello', 'Goodbye', '!')
Cell In[34], line 1 f(c='Hello', 'Goodbye', '!') ^ SyntaxError: positional argument follows keyword argument
Default arguments¶
It often happens that we write a function that takes in an input variable that has a natural or typically useful value. For example, in our second project, we will want to be able to tell if a given integer is a square. That is, we will want to use the is_power
function with power=2
.
In situations like this, we can assign a default value to the input variable power
when we define the function. To do so, we use the syntax def <some function>(<some variable>=<default value>):
. Let's modify the is_power
function to default to power=2
:
def is_power(n,power=2):
root_n = n**(1/power)
if round(root_n)**power == n:
return True
else:
return False
When a function is defined with default arguments, we can override them by supplying our own input value, or skip that input value and use the default.
is_power(9)
True
is_power(16)
True
is_power(5)
False
is_power(9,3)
False
is_power(27,3)
True
Note: When defining a function that takes in several variables, any variables that have default values assigned must come after all variables that do not.
def f(a='Hello', b='Goodbye', c='!'):
print(a + b + c)
f()
HelloGoodbye!
f('MTH337')
MTH337Goodbye!
f(c='MTH337')
HelloGoodbyeMTH337