# Functions¶

At any part of a Python code you can define a function with the def keyword. Mind the code block (identation) and colon.

In [1]:
def function_name(n):
pass

function_name(5)


## A function and a calling a function¶

The following is a function definition, a recipe which tells what to do if the function is called.

In [2]:
def square(l):
new_l = []
for i in l:
new_l.append(i*i)
return new_l


After the def keyword there is the name of the function (how we would like to call it). After that the parameter(s), comma separated, in parenthesis. After the colon there is the function body, that part is executed when the function is called.

Inside the function body there is a return keyword which tells the function to stop and the result of the function will be the value after the word return.

To call a function write its name and the necessary parameters in a parenthesis (in the example: a single parameter which is a list):

In [3]:
square([4, 3, 5])

Out[3]:
[16, 9, 25]

One can call the function to operate on a previously defined variable.

In [4]:
numbers = [5, 1, 8]
squared_numbers = square(numbers)
print(numbers, squared_numbers)

[5, 1, 8] [25, 1, 64]


If the function is correct then the result is the list of squared numbers and nothing unexpected happens, but look what happens in the following solution.

In [5]:
def square2(l):
i = 0
while i < len(l):
l[i] = l[i] ** 2
i += 1
return l

numbers = [5, 1, 8]
squared_numbers = square2(numbers)
print(numbers, squared_numbers)

[25, 1, 64] [25, 1, 64]


The function modified its parameter, the original list, and also returned the modified list.

We will detail on this issue later in the semester, but for now: write functions which do not modify their parameters.

## Multivariate functions¶

Parameters are listed in a comma separated list.

In [6]:
def dot_product(v1, v2):
result = 0
for i in range(len(v1)):
result += v1[i] * v2[i]
return result

In [7]:
dot_product([2, 3, 5], [1, 5, 2])

Out[7]:
27

Parameters can be any objects with any type. A function can have any given number of parameters, even 0, but nevertheless the parenthesis is mandatory.

In [8]:
def message():
return "Good morning!"

In [9]:
message()

Out[9]:
'Good morning!'

## Function and method¶

A function (at first glance) is called in this way:

• name of the function
• parenthesis
• parameters

A method looks like this:

• an object
• a dot
• name of the method
• parenthesis
In [11]:
l = [5, 2, 4]
l.sort()
print(l)

[2, 4, 5]


The sort is a built in method of lists (a number itself cannot be sorted). For example there is a similar function called sorted:

In [13]:
l = [5, 2, 4]
new_l = sorted(l)
print(l, new_l)

[5, 2, 4] [2, 4, 5]


The function sorted does not modify the list itself, but returns a new, sorted list.

This is a common practice with methods and functions, but not a law.

• the function does not modify its parameters but returns a value
• a method does modify its parameter, but rarely returns a value (different from None)

Later we will learn how to write methods.

## Using return¶

The command return exits the function without processing any further parts of the function body. One can take advantage of this behaviour:

In [14]:
def is_a_prime(n):
divisor = 2
while divisor**2 <= n:
if n % divisor == 0:
return False
divisor += 1
return True

In [15]:
print(is_a_prime(4))
print(is_a_prime(23))

False
True


If the function arrives at a true divisor then returns immediately because there is no need to look further. The True value is returned only if neither of the numbers were true divisors.

Remark: break exits a loop (only a loop and only one of them), and return exits a function (only a function and only one of them, but breaks any loop in that function).

### None¶

One can return None to represent "no value is returned".

If a function has no return command then the result will be None. For example is you forgot to write return or you wrote it to the wrong place.

Remark: the .sort() method results None, but it sorts the list as a side-effect.

In [16]:
l = [3, 2, 1, 4]
l2 = l.sort()
print(l2, l)

None [1, 2, 3, 4]


## Function called inside a function¶

You can call any function (once defined) inside other functions as well. It is not only possible but encouraged!

Best if you write no more than 4-5 line functions and put those functions together to solve bigger problem. This way you functions will be shorter and harder to make mistakes. The goal and the mechanism of the function should be clear by reading its name and its parameters.

An example:

Exercise: Write a function which has one parameter, a list, and finds the smallest and greatest elements in the list. The output is a list with replace the extremal values with a 0 value!

1. break down to easier subtasks
3. put those together in the final solution

• finding minimum in a list
• finding maximum in a list
• erasing given elements of a list

Lets solve these

In [17]:
def minimum(l):
min_elem = float("inf")
for e in l:
if e < min_elem:
min_elem = e
return min_elem

def maximum(l):
max_elem = -float("inf")
for e in l:
if e > max_elem:
max_elem = e
return max_elem

def erase(l, elem):
newl = l[:]     # copy l (sublist from the beginning to the end)
for i in range(len(newl)):
if newl[i] == elem:
newl[i] = 0
return newl


Now you have everything to write the main function:

In [18]:
def min_max_erase(l):
minelem = minimum(l)
maxelem = maximum(l)
newl = erase(l, minelem)
newl = erase(newl, maxelem)
return newl

In [19]:
min_max_erase([2, 3, 1, 4, 6, 2, 9, 3, 1, 3, 1, 9, 3, 9])

Out[19]:
[2, 3, 0, 4, 6, 2, 0, 3, 0, 3, 0, 0, 3, 0]
In [20]:
min_max_erase([])

Out[20]:
[]
In [21]:
min_max_erase([1, 1, 1, 2])

Out[21]:
[0, 0, 0, 0]
In [22]:
min_max_erase([1,1])

Out[22]:
[0, 0]

A shorter solution for the last part (last three lines of the main function):

return erase(erase(l, minelem), maxelem)

You can solve this in one big function:

In [23]:
def min_max_erase2(l):
min_elem = float("inf")
for e in l:
if e < min_elem:
min_elem = e

max_elem = -float("inf")
for e in l:
if e > max_elem:
max_elem = e

newl = l[:]     # copy l (sublist from the beginning to the end)
for i in range(len(newl)):
if newl[i] == min_elem:
newl[i] = 0

for i in range(len(newl)):
if newl[i] == max_elem:
newl[i] = 0

return newl

In [24]:
min_max_erase2([2, 3, 1, 4, 6, 2, 9, 3, 1, 3, 1, 9, 3, 9])

Out[24]:
[2, 3, 0, 4, 6, 2, 0, 3, 0, 3, 0, 0, 3, 0]

The first solution is

• easier to modify,
• easier to fix.
• And you can use its components in further tasks.

The second solution works, too.

# Nested loops, more complex algorithms¶

## Sorting¶

Write a function which sorts a given list (a single parameter) but write it on your own, without using the builtin sort method or the sorted function!

A simple solution is the so-called bubble sort. See a musical and a dance performance of the algorithm.

In [25]:
def bubble(l):
newl = l[:]
for i in range(len(newl) - 1):
for j in range(len(newl) - i - 1):
if newl[j] > newl[j + 1]:
newl[j], newl[j+1] = newl[j+1], newl[j]
# temp = newl[j]; newl[j] = newl[j + 1]; newl[j + 1] = temp
return newl

In [26]:
bubble([2, 3, 1, 4, 6, 2, 9, 3, 1, 3, 1, 9, 3, 9])

Out[26]:
[1, 1, 1, 2, 2, 3, 3, 3, 3, 4, 6, 9, 9, 9]
In [27]:
bubble(list(range(10, 0, -1)))

Out[27]:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Lets print the actual state during the algorithm:

In [28]:
def bubble_print(l):
newl = l[:]
for i in range(len(newl) - 1):
for j in range(len(newl) - i - 1):
print(newl)                      # print here
if newl[j] > newl[j + 1]:
newl[j], newl[j+1] = newl[j+1], newl[j]
return newl

In [29]:
bubble_print(list(range(10, 0, -1)))

[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
[9, 10, 8, 7, 6, 5, 4, 3, 2, 1]
[9, 8, 10, 7, 6, 5, 4, 3, 2, 1]
[9, 8, 7, 10, 6, 5, 4, 3, 2, 1]
[9, 8, 7, 6, 10, 5, 4, 3, 2, 1]
[9, 8, 7, 6, 5, 10, 4, 3, 2, 1]
[9, 8, 7, 6, 5, 4, 10, 3, 2, 1]
[9, 8, 7, 6, 5, 4, 3, 10, 2, 1]
[9, 8, 7, 6, 5, 4, 3, 2, 10, 1]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 10]
[8, 9, 7, 6, 5, 4, 3, 2, 1, 10]
[8, 7, 9, 6, 5, 4, 3, 2, 1, 10]
[8, 7, 6, 9, 5, 4, 3, 2, 1, 10]
[8, 7, 6, 5, 9, 4, 3, 2, 1, 10]
[8, 7, 6, 5, 4, 9, 3, 2, 1, 10]
[8, 7, 6, 5, 4, 3, 9, 2, 1, 10]
[8, 7, 6, 5, 4, 3, 2, 9, 1, 10]
[8, 7, 6, 5, 4, 3, 2, 1, 9, 10]
[7, 8, 6, 5, 4, 3, 2, 1, 9, 10]
[7, 6, 8, 5, 4, 3, 2, 1, 9, 10]
[7, 6, 5, 8, 4, 3, 2, 1, 9, 10]
[7, 6, 5, 4, 8, 3, 2, 1, 9, 10]
[7, 6, 5, 4, 3, 8, 2, 1, 9, 10]
[7, 6, 5, 4, 3, 2, 8, 1, 9, 10]
[7, 6, 5, 4, 3, 2, 1, 8, 9, 10]
[6, 7, 5, 4, 3, 2, 1, 8, 9, 10]
[6, 5, 7, 4, 3, 2, 1, 8, 9, 10]
[6, 5, 4, 7, 3, 2, 1, 8, 9, 10]
[6, 5, 4, 3, 7, 2, 1, 8, 9, 10]
[6, 5, 4, 3, 2, 7, 1, 8, 9, 10]
[6, 5, 4, 3, 2, 1, 7, 8, 9, 10]
[5, 6, 4, 3, 2, 1, 7, 8, 9, 10]
[5, 4, 6, 3, 2, 1, 7, 8, 9, 10]
[5, 4, 3, 6, 2, 1, 7, 8, 9, 10]
[5, 4, 3, 2, 6, 1, 7, 8, 9, 10]
[5, 4, 3, 2, 1, 6, 7, 8, 9, 10]
[4, 5, 3, 2, 1, 6, 7, 8, 9, 10]
[4, 3, 5, 2, 1, 6, 7, 8, 9, 10]
[4, 3, 2, 5, 1, 6, 7, 8, 9, 10]
[4, 3, 2, 1, 5, 6, 7, 8, 9, 10]
[3, 4, 2, 1, 5, 6, 7, 8, 9, 10]
[3, 2, 4, 1, 5, 6, 7, 8, 9, 10]
[3, 2, 1, 4, 5, 6, 7, 8, 9, 10]
[2, 3, 1, 4, 5, 6, 7, 8, 9, 10]
[2, 1, 3, 4, 5, 6, 7, 8, 9, 10]

Out[29]:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

There are more sophisticated (and faster) algorithms, you will learn those in the Theory of Algorithms class.

## Min sort algorithm¶

1. Find the minimum, put that in the first place.
2. find the minimum out of the remaining elements (from 2 to the end)
3. put that element into the 2nd place
4. find the minimum out of the remaining elements (from 3 to the end)
5. put that element into the 3rd place
6. ...

First subtask is finding a minimum.

In [30]:
def armin(l):
min_place = 0
min_elem = l[0]
for i in range(len(l)):
if l[i] < min_elem:
min_elem = l[i]
min_place = i
return min_place

In [31]:
armin([3, 2, 100, -1, 1])

Out[31]:
3

In [32]:
def sort_min(l):
newl = l[:]
for i in range(len(newl)-1):
j = armin(newl[i:])
newl[i], newl[i + j] = newl[i + j], newl[i]
return newl

In [33]:
sort_min([3, 2, 100, -1, 1])

Out[33]:
[-1, 1, 2, 3, 100]
In [ ]: