CoCalc Public Filesassignment answers / Assignment 3 / Assignment3.ipynb
Author: Alexander Hafner
Views : 77
Compute Environment: Ubuntu 20.04 (Default)

# Assignment 3

## Deadline: 8th Oct - 15:00 - After this time, don't change your notebook. It'll be automatically collected by CoCalc.

Python 3.6 and later have something called f-strings. They are very useful for debugging. To see if we can use them first we check which Python version we are using.

In [1]:
import sys
print(sys.version)

3.8.2 (default, Jul 16 2020, 14:00:26) [GCC 9.3.0]

The syntax is simple. Just put an 'f' in front of your string to turn it into an f-string. In an f-string anything that is inside curly braces will be evaluated as Python code. The value in the brackets will then be replaced by the value of your expression. The cell below shows an example.

In [4]:
import sys
version = sys.version
print("Python version is"":", version)  # what you already know
print("-----------------------------")
print(f"Python version is: {version}")  # using f-strings

Python version is: 3.8.5 (default, Jul 28 2020, 12:59:40) [GCC 9.3.0] ----------------------------- Python version is: 3.8.5 (default, Jul 28 2020, 12:59:40) [GCC 9.3.0]

# Task 1 - for-loop (1 Point)

Write a for-loop that creates a string containing all uneven numbers from 1 to 100 without spaces (you start at 1, so 1 should be included). Store this string in the variable solution1.

HINT: Use range().

In [9]:
solution1 = ''
for i in range(1, 100, 2):
solution1 += str(i)

print(f'The solution is: {solution1}')

The solution is: 13579111315171921232527293133353739414345474951535557596163656769717375777981838587899193959799
In [10]:
solution1 = ""
### BEGIN SOLUTION
for i in range(1, 100, 2):
solution1 += str(i)
### END SOLUTION

In [11]:
from nose.tools import assert_equal, assert_true, assert_false

# Checks correct datatype
assert_true(isinstance(solution1, str), msg="Your solution is not a string.")

# Checks correct beginning
assert_false(solution1[0] == "0", msg="You should start at 1 (one), not 0 (zero).")
assert_true(solution1[0] == "1", msg="You should start at 1 (one).")

# Checks correct solution
assert_equal(solution1, "13579111315171921232527293133353739414345474951535557596163656769717375777981838587899193959799", msg="Your solution does not match the correct solution.")
print("\x1b[6;30;42mSuccess!\x1b[0m")

Success! 

# Task 2 Advanced for-loop and range() (2 Points)

Write a for-loop that creates a string containing the digit sum of every third number from 1 to 1000 without spaces (you start at the number 1). Store this string in the variable solution2.

To calculate the digit sum you sum up all the digits in a number.

An example for the numbers from 1 to 13 is given below:

Number Digit Sum
1 1
4 4
7 7
10 1 + 0 = 1
13 1 + 3 = 4

The solution here is the string "14714". You should not calculate the digit sum of the string containing every third number from 1 to 1000. For every third number, calculate the digit sum, then add it to solution2 as a string.

Don't confuse it with the digital root. The digital sum of 99 is 18, the digital root of 99 is 9. You obtain the digital root by calculating the digital sum until you reach a single digit number.

HINT: Use range().

HINT: Iterate over the digits of each number inside the for-loop with another for-loop.

In [16]:
solution2 = ''

for i in range(1, 1000, 3):
output = 0
for digit in str(i):
output += int(digit)
solution2 += str(output)


In [18]:
solution2 = ""
### BEGIN SOLUTION
for i in range(1, 1000, 3):
# solution as expected by the students
# this should be the easiest way to solve the task for them
out = 0
for digit in str(i):
out += int(digit)
solution2 += str(out)
### END SOLUTION

In [6]:
from nose.tools import assert_equal, assert_true, assert_false

# Checks correct datatype
assert_true(isinstance(solution2, str), msg="Your solution is not a string.")

# Checks correct beginning
assert_false(solution2[0] == "0", msg="You should start at 1 (one), not 0 (zero).")
assert_true(solution2[0] == "1", msg="You should start at 1 (one).")

# Checks correct solution
assert_equal(solution2, "147147104710471047101371013710137101316101316101316147104710471047101371013710137101316101316101316101316194710471047101371013710137101316101316101316101316191316194710471013710137101371013161013161013161013161913161913161947101371013710137101316101316101316101316191316191316191316192271013710137101316101316101316101316191316191316191316192216192271013710131610131610131610131619131619131619131619221619221619227101316101316101316101316191316191316191316192216192216192216192225101316101316101316191316191316191316192216192216192216192225192225101316101316191316191316191316192216192216192216192225192225192225", msg="Your solution does not match the correct solution. Did you store your solution in the variable 'solution1' instead of 'solution2'? You should use 'solution2' for this task.")
print("\x1b[6;30;42mSuccess!\x1b[0m")

Success! 

# Task 3 - prime checking (2 Points):

Write a function is_prime that takes a positive integer as argument and returns True if the integer is a prime number and False if it is not.

HINT: To check if a number is dividable by another number you can use modulo (%).

HINT: Don't overcomplicate things: Check if the number your function gets as an argument is dividable by another number (other than 1 and itself) and if it is not then you have found a prime number.

In [7]:
### BEGIN SOLUTION
import math
def is_prime(num):
if num == 2:
return True
if num < 2 or num % 2 == 0:
return False
# can be solved without the sqrt, just makes it faster
for n in range(3, int(math.sqrt(num)) + 1, 2):
if num % n == 0:
return False
return True
### END SOLUTION

In [8]:
from nose.tools import assert_equal, assert_true

assert_true("is_prime" in locals(), msg="The function 'is_prime' does not exist. Did you missspell the name?")
assert_true(is_prime(2), msg="2 is a prime number.")
print("\x1b[6;30;42mSuccess!\x1b[0m")

Success! 
In [9]:
from nose.tools import assert_equal, assert_true

assert_true(is_prime(2029))
assert_true(is_prime(82261))
assert_true(is_prime(181361))
assert_true(is_prime(226169))

assert_false(is_prime(1))
assert_false(is_prime(4))
assert_false(is_prime(42))
assert_false(is_prime(43249329423842390572358290))
print("\x1b[6;30;42mSuccess!\x1b[0m")

Success! 

# Task 4 - function with default parameters (1 Point)

Define a function sum_numbers that takes three arguments: start, end and step. It should sum up all numbers between start (inclusive) and end (exclusive), with a step size of step and return this sum.

Example: start = 1, end = 10, step = 2 should sum up every second number between 1 and 10.

1 + 3 + 5 + 7 + 9 = 25

The parameters should be optional with default values as in the example above.

HINT: You can use sum() to sum up all values in an iterable (such as a list, or a tuple, ...).

In [10]:
### BEGIN SOLUTION
def sum_numbers(start=1, end=10, step=2):
return sum(range(start, end, step))
### END SOLUTION

In [21]:
a = [1, 2, 3, 4, 5]
a

[1, 2, 3, 4, 5]
In [22]:
sum(a)

15
In [23]:
sum(range(4))

6
In [11]:
from nose.tools import assert_false
assert_false(sum_numbers() is None, msg="Your function returns None. Did you forget a 'return' statement?")
print("\x1b[6;30;42mSuccess!\x1b[0m")

Success! 
In [12]:
from nose.tools import assert_true, assert_equal
from inspect import signature
s = signature(sum_numbers)
assert_true("start" in s.parameters, msg="The 'start' parameter is missing.")
assert_true("end" in s.parameters, msg="The 'end' parameter is missing.")
assert_true("step" in s.parameters, msg="The 'step' parameter is missing.")

assert_equal(s.parameters["start"].default, 1, msg="The default value of the 'start' parameter is wrong, it should be 1.")
assert_equal(s.parameters["end"].default, 10, msg="The default value of the 'end' parameter is wrong, it should be 10.")
assert_equal(s.parameters["step"].default, 2, msg="The default value of the 'step' parameter is wrong, it should be 2.")
print("\x1b[6;30;42mSuccess!\x1b[0m")

Success! 
In [13]:
from nose.tools import assert_equal, assert_false

assert_equal(sum_numbers(1, 10, 2), 25, msg="The order of your function parameters seems to be wrong.")
assert_equal(sum_numbers(2, 10, 2), 20, msg="Your function returns an incorrect result when called with a different 'start' value. Are you using the function arguments in your calculation?")
assert_equal(sum_numbers(1, 100, 2), 2500, msg="Your function returns an incorrect result when called with a different 'end' value. Are you using the function arguments in your calculation?")
assert_equal(sum_numbers(1, 10, 3), 12, msg="Your function returns an incorrect result when called with a different 'step' value. Are you using the function arguments in your calculation?")
assert_equal(sum_numbers(), 25, msg="Incorrect result when using only the default values. Did you forget to define the default values?")

assert_equal(sum_numbers(42, 92832, 17), 253488665, msg="Your function returns an incorrect result.")
print("\x1b[6;30;42mSuccess!\x1b[0m")

Success! 

# Task 5 - FizzBuzz (5 Points)

Write a function fizzbuzz with one argument limit. Your function should return a list with limit + 1 items (so if limit = 10 your list should have length 11 - remember that lists start counting at 0!) where for each index the value in the list should be "Fizz" if the index is a multiple of three, "Buzz" if the index is a multiple of five and in case it is a multiple of both three and five it should contain "FizzBuzz", in all other cases it should be the index itself.

Example:

fizzbuzz(15) should return a list with 16 elements (this means it goes up to lst[15]).

• 2 is not a multiple of three or five, so lst[2] == 2 (the integer 2, not the string '2')
• 3 is a multiple of three, so lst[3] == "Fizz"
• 15 is a multiple of three and five, so lst[15] == "FizzBuzz"

HINT: Be careful when using if and elif, as a number can be a multiple of both three and five!

HINT: Don't forget to handle the case when a number is not a multiple of three or five.

In [24]:
### BEGIN SOLUTION
def fizzbuzz(limit):
lst = [""] * (limit + 1)
for i in range(limit + 1):
flag = False
if i % 3 == 0:
lst[i] += "Fizz"
flag = True
if i % 5 == 0: #deshalb verwenden wir hier if anstatt elif --> dieses if kann auch True sein, wenn erstes if schon True ist, wenn man elif verwenden würde, dann hört es beim ersten if true schon auf
lst[i] += "Buzz"
flag = True
if not flag:
lst[i] = i
return lst

fizzbuzz(15)
### END SOLUTION

['FizzBuzz', 1, 2, 'Fizz', 4, 'Buzz', 'Fizz', 7, 8, 'Fizz', 'Buzz', 11, 'Fizz', 13, 14, 'FizzBuzz']
In [15]:
# These tests are worth 1 point
from inspect import signature
from nose.tools import assert_true
assert_true("fizzbuzz" in locals(), msg="The function 'fizzbuzz' does not exist. Did you name your function correctly?")
assert_true("limit" in signature(fizzbuzz).parameters, msg="The function 'fizzbuzz' does not have a parameter called 'limit'. Did you name it correctly?")
assert_true(fizzbuzz(5) is not None, msg="The function 'fizzbuzz' does not return any value (it returns None!). Did you forget the 'return' statement?")
assert_true(isinstance(fizzbuzz(5), list), msg="The function 'fizzbuzz' does not return a list.")
print("\x1b[6;30;42mSuccess!\x1b[0m")

Success! 
In [16]:
# These tests are worth 1 point
from nose.tools import assert_true
assert_true(len(fizzbuzz(5)) == 6, msg="The list your 'fizzbuzz' function returns does not have the correct length. The length should be equal to 'limit + 1'.")
assert_true(len(fizzbuzz(100)) == 101, msg="The list your 'fizzbuzz' function returns does not have the correct length. The length should be equal to 'limit + 1'.")
print("\x1b[6;30;42mSuccess!\x1b[0m")

Success! 
In [17]:
# These tests are worth 1 point
from nose.tools import assert_true
assert_true(isinstance(fizzbuzz(5)[1], int), msg="The list that your 'fizzbuzz' function returns should have integers as values for indices where it does not have 'Fizz', 'Buzz' or 'FizzBuzz'. Did you convert the value to string instead of keeping it as integer maybe? For this example when doing 'lst[1] == 1' should be True.")
print("\x1b[6;30;42mSuccess!\x1b[0m")

Success! 
In [18]:
# These tests are worth 2 points
from nose.tools import assert_true
result = fizzbuzz(100)
assert_true(result[0] == "FizzBuzz", msg="The value for '0' should be 'FizzBuzz'. '0' (zero) is divisible by both 3 and 5 (in Python at least).")
assert_true(result[3] == "Fizz", msg="The value for '3' should be 'Fizz'.")
assert_true(result[5] == "Buzz", msg="The value for '5' should be 'Buzz'.")
assert_true(result[15] == "FizzBuzz", msg="The value for '15' should be 'FizzBuzz'.")
assert_true(result[90] == "FizzBuzz", msg="The value for '90' should be 'FizzBuzz'.")
assert_true(result[93] == "Fizz", msg="The value for '93' should be 'FizzBuzz'.")
assert_true(result[95] == "Buzz", msg="The value for '95' should be 'Buzz'.")
assert_true(result[97] == 97, msg="The value for '97' should be '97' (integer, not string).")
print("\x1b[6;30;42mSuccess!\x1b[0m")

Success! 

# Task 6 - function interaction (3 Point)

Write a function unique_elements that has one argument elems and returns a sorted list containing the the elements from elems without duplicates. If a value appears multiple times in elems you should return a list that contains only one copy of it. In other words: Remove the duplicates from elems.

Write a second function tuple_firsts that has one argument tpls. This argument will be a list of tuples. Your function should then call your unique_elements functions with a list that contains only the first elements from tpls and returns the value that is returned by unique_elements.

For the list of elements given above in the get_testing_elements() function this means that:

• your tuple_firsts function is called with the list of tuples from _elements()
• inside your tuple_firsts function you extract the first values and store them in a list: ["django", "django", "pandas", "pandas", "NumPy"]
• you call your unique_elements function with the list from the previous step
• unique_elements returns a (sorted) list containing the values ["django", "pandas", "NumPy"]
• your tuple_firsts functions returns the value that unique_elements returns when you call it

HINT: Start with the unique_elements function! Then, when you think you have implemented the function correctly, try calling it with lists that contain duplicates and check if it works!

HINT: For the tuple_firsts function first find a way to get the first element from a list of tuples. Again, try calling your function and check if it works by using print() statements. Once you are sure this works you can call your unique_elements function from inside your tuple_firsts function!

In [19]:
def get_testing_elements():
"""
This function is here so you can use it for testing while minimizing the risk that you overwrite the values.
Call this function and store it in a variable and then you can use that variable to test your functions.
"""
return  [
("django", "Python framework"),
("django", "(Unchained), movie"),
("pandas", "Cute animal 🐼"),
("pandas", "Python library"),
("NumPy", "Python library")
]

In [27]:
### BEGIN SOLUTION
def unique_elements(elems):
return sorted(list(set(elems))) #set for duplicate elimination

def tuple_firsts(tpls):
elems = []
for elem in tpls:
elems.append(elem[0])
unique = unique_elements(elems) #returns sorted list
return unique
### END SOLUTION

In [22]:
unique_elements([1, 2, 3, 3, 4, 4, 5, 69]) #eliminates one time 3 and one time 4

[1, 2, 3, 4, 5, 69]
In [26]:
tuple_firsts([(1, 2, 3), (34, 2, 3)]) #gets the first elements from a list of tuples


[1, 34]
from nose.tools import assert_equal

Success!