Part A#

A1#

import math
class Rectangle():
    def __init__(self, side):
        self.height = 2 * side
        self.width = 3 * side
        self.area = self.height * self.width
        self.angles = [90., 90., 90., 90.]
        self.diag1, self.diag2 = 2 * [math.sqrt(self.height ** 2 
                                      + self.width ** 2)]
    def to_parallelogram(self, ang):
        self.angles = [ang, 180. - ang, ang, 180. - ang]
        self.diag1 = math.sqrt(self.height ** 2 + self.width ** 2 
                               + 2 * self.height * self.width * math.cos(ang))
        self.diag2 = math.sqrt(self.height ** 2 + self.width ** 2 
                               - 2 * self.height * self.width * math.cos(ang))
    def __str__(self):
        return f'Quadrilateral with angles = {self.angles}'

In the code snippet above, which answer uses the correct terminology for the different components?

Answer 1:

  • module(s): math, self

  • class(es): Rectangle

  • function(s): sqrt, cos

  • method(s): __init__(), __str__(), diag1, diag2, angles

  • attribute(s): height, width, area

Answer 2:

  • module(s): math

  • class(es): Rectangle

  • function(s): sqrt, cos, area, to_parallelogram()

  • method(s): __init__(), to_parallelogram(), __str__()

  • attribute(s): height, width, area, angles, diag1, diag2

Answer 3:

  • module(s): math

  • class(es): Rectangle

  • function(s): sqrt, cos

  • method(s): __init__(), to_parallelogram(), __str__()

  • attribute(s): height, width, area, angles, diag1, diag2

Answer 4:

  • module(s): math

  • class(es): Rectangle

  • function(s): sqrt, cos, to_parallelogram()

  • method(s): __init__(), __str__()

  • attribute(s): height, width, area, angles, diag1, diag2, self

Correct answer

Answer 3


A2#

import random as rd
import matplotlib.pyplot as plt
from math import sqrt
from turtle import Turtle

Given the code snippet above, are the following statements true or false?

To score a point on this exercise you need to get all 4 right.

  1. sqrt and Turtle are a function and a class from the math and turtle modules: T/F

  2. sqrt and Turtle are functions in the math and turtle modules: T/F

  3. rd and plt are functions in the random and matplotlib.pyplot modules: T/F

  4. rd and plt are import aliases for the random and matplotlib.pyplot modules: T/F

Correct answers

1: true. 2: false. 3: false. 4: true.


A3#

Write a function number_of_occurrences(interesting_words, text) that checks how many times the words in the list interesting_words appear in the string text. The function shall return a dictionary with the words as keys and the number of occurrences as values.

Hint: The call re.findall(r'[a-zA-ZåäöÅÄÖ]+', text) returns a list of all the words in the string text.

See the example code and expected printout below.

# Example code
seasons = ['spring', 'summer', 'autumn', 'winter']
text = """The four seasons are spring, summer, autumn, and winter,
and although various areas of the United States experience drastically
 different weather during these times, all portions of the country
 recognize the seasons; winter in California may bring heat, and winter
  in New York may bring blizzards, but both periods are nevertheless winter."""
word_to_count = number_of_occurrences(seasons, text)
print('The following seasons were mentioned in the text:')
for word, number in word_to_count.items():
    print(f'\t{word} was mentioned {number} time(s)')
# The example code should print the following:
The following seasons were mentioned in the text:
    spring was mentioned 1 time(s)
    summer was mentioned 1 time(s)
    autumn was mentioned 1 time(s)
    winter was mentioned 4 time(s)
Want to check whether your solution is correct? Save it as A3.py. Then create another file in the same folder, copy-paste the code below into it, save it as A3_test.py and run it.
import importlib
import copy
import re
import multiprocessing

def read_py_as_text(filename):
    """Return file contents as str"""
    with open(filename, 'r') as pyfile:
        lines_of_text = pyfile.readlines()
    return lines_of_text

def remove_comments(lines_of_text):
    return [re.sub(r'#.*$', '', line) for line in lines_of_text]

def file_contains_function(filename, function_name):
    """Check if file contains a function call"""
    lines_of_text = read_py_as_text(filename)
    lines_without_comments = remove_comments(lines_of_text)
    text = ''.join(lines_without_comments)
    search_str = function_name + r'\(.*\)'
    list_of_calls = re.findall(search_str, text)
    return list_of_calls != []

def nested_type_sensitive_equals(val1, val2):
    if not val1 == val2:
        return False
    elif not type(val1) == type(val2):
        return False
    elif type(val1) == list or type(val1) == tuple:
        for el1, el2 in zip(val1, val2):
            if not nested_type_sensitive_equals(el1, el2):
                return False
    return True

def error_message_dict(module, correct_output_type, function_name):
    """Return a dict of all basic error messages"""
    msg_dict = {}

    msg_dict['passed'] = """
    WELL DONE! Your solution passed all the tests.
    """

    msg_dict['print_warning'] = """
    WARNING! Your function contains a print statement.
    This is nearly always a bad idea, unless the purpose
    of the function is to print something.
    """

    msg_dict['forbidden_function'] = """
    ERROR! Your function uses the following
    function/method, which is not allowed here: """

    msg_dict['none'] = f"""
    ERROR! Your function returns None.
    This probably means that you forgot a return statement.
    """

    msg_dict['none_plus_print'] = f"""
    ERROR! Your function returns None.
    This probably means that you forgot a return statement.
    Perhaps your function prints the {correct_output_type} instead of returning it?
    """

    msg_dict['wrong_type'] = f"""
    ERROR! Your function does not return a {correct_output_type}.
    Instead, it returns a value of type: """

    msg_dict['correct_type_but_wrong'] = f"""
    ERROR! Your function returns a {correct_output_type},
    but the {correct_output_type} is not quite correct.
    Run the example code and compare your printout to the expected printout.
    """

    msg_dict['hardcoded'] = f"""
    ERROR! Your function works for the examples in the exercise text,
    but not when other values are used as input parameters.
    Ensure that your function works correctly for general inputs.
    """

    msg_dict['no_module'] = f"""
    No module named {module} was found. Check that you saved your
    function in {module}.py and that this test is located
    in the same directory as {module}.py.
    """

    msg_dict['attribute_error'] = f"""
    The module {module} does not contain a function
    named {function_name}. Check for spelling
    mistakes in the function name.
    """

    msg_dict['timeout_error'] = f"""
    Your function did not return within 5 seconds.
    The most likely explanation for this is that you have created an
    infinite loop. Double-check any while loops!
    """

    msg_dict['modifies_input_dict_with_lists'] = f"""
    ERROR! Your function modifies the input dictionary.
    Make sure to:
    1. create a new dictionary instead of modifying the input dictionary
    2. create (sorted) copies of the lists in the original dictionary
    instead of sorting the original lists.
    """

    return msg_dict

def print_inputs_and_output(inputs, output, correct_output, arg_names, function_name):
    if type(output) == str:
        output = "'" + output + "'"

    if type(correct_output) == str:
        correct_output = "'" + correct_output + "'"

    print('    The following call:\n')
    for index, arg in enumerate(inputs):
        if type(arg) == str:
            arg = "'" + arg + "'"
        print(f'    {arg_names[index]} = {arg}')
    arg_string = '(' + ', '.join(arg_names) + ')'
    print(f'    {function_name}{arg_string}')
    print(f'\n    returns: {output}.')
    print(f'\n    The correct value is: {correct_output}.\n')

def print_modified_inputs(inputs, orig_inputs, arg_names, function_name):
    print(f'    Before calling {function_name}:')
    for index, arg in enumerate(orig_inputs):
        if type(arg) == str:
            arg = "'" + arg + "'"
        print(f'    {arg_names[index]}: {arg}')

    print(f'\n    After calling {function_name}:')
    for index, arg in enumerate(inputs):
        if type(arg) == str:
            arg = "'" + arg + "'"
        print(f'    {arg_names[index]}: {arg}')
        print('')

def run_student_fun(module, module_to_import, function_name, inputs):

    # Import student module
    imported_module = importlib.import_module(module)

    # Import any additional python modules in case the student left that code out
    imported_extra_module = importlib.import_module(module_to_import)
    exec(f'imported_module.{module_to_import} = imported_extra_module')

    # Get student function from the imported module
    imported_function = getattr(imported_module, function_name)
    output = imported_function(*inputs)

def run_tests(modules, correct_output_type, function_name, module_to_import,
            visible_inputs, visible_correct_output, hidden_inputs, hidden_correct_output,
            arg_names, forbidden_functions=[], error_msg_if_modifies_input=''):
    """Run visible and hidden tests"""

    function_contains_print = False
    function_contains_forbidden_func = False
    end_of_test_str = '--- End of test ---\n'

    # Loop over modules only needed when developing tests
    for module in modules:

        msg_dict = error_message_dict(module, correct_output_type, function_name)

        print(f'--- Testing {module} ---')

        # Import student module
        try:
            imported_module = importlib.import_module(module)
        except ModuleNotFoundError:
            print(msg_dict['no_module'])
            print(end_of_test_str)
            continue

        # Check for print statements
        if file_contains_function(module + '.py', 'print'):
            print(msg_dict['print_warning'])
            function_contains_print = True

        # Check for forbidden function calls
        for func in forbidden_functions:
            if file_contains_function(module + '.py', func):
                function_contains_forbidden_func = True
                msg = msg_dict['forbidden_function'] + func + '.'
                print(msg)
        if function_contains_forbidden_func:
            print('\n' + end_of_test_str)
            continue

        # Import any additional python modules in case the student left that code out
        imported_extra_module = importlib.import_module(module_to_import)
        exec(f'imported_module.{module_to_import} = imported_extra_module')

        # Get student function from the imported module
        try:
            imported_function = getattr(imported_module, function_name)
        except AttributeError:
            print(msg_dict['attribute_error'])
            print(end_of_test_str)
            continue

        # Loop over all visible tests
        passed_visible = True
        for orig_inputs, correct_output in zip(visible_inputs, visible_correct_output):
            inputs = copy.deepcopy(orig_inputs)
            process_inputs = copy.deepcopy(orig_inputs)

            # Call function
            process_args = (module, module_to_import, function_name, process_inputs)
            process = multiprocessing.Process(target=run_student_fun, args=process_args)
            process.start()
            process.join(5)
            if process.is_alive():
                passed_visible = False
                print(msg_dict['timeout_error'])
                process.terminate()
                process.join()
                break
            output = imported_function(*inputs)

            if not nested_type_sensitive_equals(output, correct_output):
                passed_visible = False

            # Function returns None
            if output is None:
                if function_contains_print:
                    print(msg_dict['none_plus_print'])
                else:
                    print(msg_dict['none'])
                break

            # Function returns the wrong data type (but not None)
            elif not(type(output) == type(correct_output)):
                msg = msg_dict['wrong_type']
                msg += str(type(output)) + '\n'
                print(msg)
                break

            # Function returns correct data type, but wrong
            elif not nested_type_sensitive_equals(output, correct_output):
                msg = msg_dict['correct_type_but_wrong']
                print(msg)
                print_inputs_and_output(inputs, output, correct_output, arg_names, function_name)
                break

            # Function modifies inputs (and is not supposed to do that)
            if inputs != orig_inputs and error_msg_if_modifies_input != '':
                passed_visible = False
                msg = msg_dict[error_msg_if_modifies_input]
                print(msg)
                print_modified_inputs(inputs, orig_inputs, arg_names, function_name)
                break

        # Function reproduces example printouts. Test hidden tests too.
        if passed_visible:

            # Loop over hidden tests
            passed_hidden = True
            for inputs, correct_output in zip(hidden_inputs, hidden_correct_output):

                # Call function
                process_args = (module, module_to_import, function_name, inputs)
                process = multiprocessing.Process(target=run_student_fun, args=process_args)
                process.start()
                process.join(5)
                if process.is_alive():
                    passed_hidden = False
                    print(msg_dict['timeout_error'])
                    process.kill()
                    process.join()
                    break
                output = imported_function(*inputs)

                # Failed a hidden test
                if not nested_type_sensitive_equals(output, correct_output):
                    passed_hidden = False
                    msg = msg_dict['hardcoded']
                    print(msg)
                    print_inputs_and_output(inputs, output, correct_output, arg_names, function_name)
                    break

            if passed_hidden:
                print(msg_dict['passed'])

        print(end_of_test_str)

if __name__ == '__main__':

    #--- Excercise details ---
    modules = ['A3']
    function_name = 'number_of_occurrences'
    correct_output_type = 'dictionary'
    module_to_import = 're'
    arg_names = ['interesting_words', 'text']
    #-----------------------------------

    #--- Lists of inputs and correct outputs for visible tests ---
    seasons = ['spring', 'summer', 'autumn', 'winter']
    text = """The four seasons are spring, summer, autumn, and winter,
    and although various areas of the United States experience drastically
     different weather during these times, all portions of the country
     recognize the seasons; winter in California may bring heat, and winter
      in New York may bring blizzards, but both periods are nevertheless winter."""
    visible_inputs = [[seasons, text]]
    visible_correct_output = [{'spring': 1, 'summer': 1, 'autumn': 1, 'winter': 4}]
    #-----------------------------------

    #--- Lists of inputs and correct outputs for hidden tests ---
    interesting_words = ['how', 'you']
    text = 'Hi, how are you? Are you okay?'
    hidden_inputs = [[interesting_words, text]]
    hidden_correct_output = [{'how': 1, 'you': 2}]
    #-----------------------------------

    run_tests(modules, correct_output_type, function_name, module_to_import,
                visible_inputs, visible_correct_output, hidden_inputs, hidden_correct_output,
                arg_names)

If you do use any of the testing scripts on this page, we would greatly appreciate if you could provide some feedback by filling out this 1-minute anonymous survey.

Example solution
import re
def number_of_occurrences(interesting_words, text):
    word2count = {}
    words = re.findall(r'[a-zA-ZåäöÅÄÖ]+', text)
    for word in words:
        word = word.lower()
        if word in interesting_words:
            if word in word2count:
                word2count[word] += 1
            else:
                word2count[word] = 1
    return word2count
Common mistakes
  • print instead of return

  • Counts all words, not just the ones in interesting_words.


A4#

Write a function bounded_squares(n) that takes an integer \( n > 0\) as input and returns a list of all positive integers \(p\) that satisfy \(n^2 < p^2 < 3n^2\).

See the example code and expected printout below.

# Example code
print(bounded_squares(1))
print(bounded_squares(2))
print(bounded_squares(5))
# The example code should print the following:
[]
[3]
[6, 7, 8]
Want to check whether your solution is correct? Save it as A4.py. Then create another file in the same folder, copy-paste the code below into it, save it as A4_test.py and run it.
import importlib
import copy
import re
import multiprocessing

def read_py_as_text(filename):
    """Return file contents as str"""
    with open(filename, 'r') as pyfile:
        lines_of_text = pyfile.readlines()
    return lines_of_text

def remove_comments(lines_of_text):
    return [re.sub(r'#.*$', '', line) for line in lines_of_text]

def file_contains_function(filename, function_name):
    """Check if file contains a function call"""
    lines_of_text = read_py_as_text(filename)
    lines_without_comments = remove_comments(lines_of_text)
    text = ''.join(lines_without_comments)
    search_str = function_name + r'\(.*\)'
    list_of_calls = re.findall(search_str, text)
    return list_of_calls != []

def nested_type_sensitive_equals(val1, val2):
    if not val1 == val2:
        return False
    elif not type(val1) == type(val2):
        return False
    elif type(val1) == list or type(val1) == tuple:
        for el1, el2 in zip(val1, val2):
            if not nested_type_sensitive_equals(el1, el2):
                return False
    return True

def error_message_dict(module, correct_output_type, function_name):
    """Return a dict of all basic error messages"""
    msg_dict = {}

    msg_dict['passed'] = """
    WELL DONE! Your solution passed all the tests.
    """

    msg_dict['print_warning'] = """
    WARNING! Your function contains a print statement.
    This is nearly always a bad idea, unless the purpose
    of the function is to print something.
    """

    msg_dict['forbidden_function'] = """
    ERROR! Your function uses the following
    function/method, which is not allowed here: """

    msg_dict['none'] = f"""
    ERROR! Your function returns None.
    This probably means that you forgot a return statement.
    """

    msg_dict['none_plus_print'] = f"""
    ERROR! Your function returns None.
    This probably means that you forgot a return statement.
    Perhaps your function prints the {correct_output_type} instead of returning it?
    """

    msg_dict['wrong_type'] = f"""
    ERROR! Your function does not return a {correct_output_type}.
    Instead, it returns a value of type: """

    msg_dict['correct_type_but_wrong'] = f"""
    ERROR! Your function returns a {correct_output_type},
    but the {correct_output_type} is not quite correct.
    Run the example code and compare your printout to the expected printout.
    """

    msg_dict['hardcoded'] = f"""
    ERROR! Your function works for the examples in the exercise text,
    but not when other values are used as input parameters.
    Ensure that your function works correctly for general inputs.
    """

    msg_dict['no_module'] = f"""
    No module named {module} was found. Check that you saved your
    function in {module}.py and that this test is located
    in the same directory as {module}.py.
    """

    msg_dict['attribute_error'] = f"""
    The module {module} does not contain a function
    named {function_name}. Check for spelling
    mistakes in the function name.
    """

    msg_dict['timeout_error'] = f"""
    Your function did not return within 5 seconds.
    The most likely explanation for this is that you have created an
    infinite loop. Double-check any while loops!
    """

    msg_dict['modifies_input_dict_with_lists'] = f"""
    ERROR! Your function modifies the input dictionary.
    Make sure to:
    1. create a new dictionary instead of modifying the input dictionary
    2. create (sorted) copies of the lists in the original dictionary
    instead of sorting the original lists.
    """

    return msg_dict

def print_inputs_and_output(inputs, output, correct_output, arg_names, function_name):
    if type(output) == str:
        output = "'" + output + "'"

    if type(correct_output) == str:
        correct_output = "'" + correct_output + "'"

    print('    The following call:\n')
    for index, arg in enumerate(inputs):
        if type(arg) == str:
            arg = "'" + arg + "'"
        print(f'    {arg_names[index]} = {arg}')
    arg_string = '(' + ', '.join(arg_names) + ')'
    print(f'    {function_name}{arg_string}')
    print(f'\n    returns: {output}.')
    print(f'\n    The correct value is: {correct_output}.\n')

def print_modified_inputs(inputs, orig_inputs, arg_names, function_name):
    print(f'    Before calling {function_name}:')
    for index, arg in enumerate(orig_inputs):
        if type(arg) == str:
            arg = "'" + arg + "'"
        print(f'    {arg_names[index]}: {arg}')

    print(f'\n    After calling {function_name}:')
    for index, arg in enumerate(inputs):
        if type(arg) == str:
            arg = "'" + arg + "'"
        print(f'    {arg_names[index]}: {arg}')
        print('')

def run_student_fun(module, module_to_import, function_name, inputs):

    # Import student module
    imported_module = importlib.import_module(module)

    # Import any additional python modules in case the student left that code out
    imported_extra_module = importlib.import_module(module_to_import)
    exec(f'imported_module.{module_to_import} = imported_extra_module')

    # Get student function from the imported module
    imported_function = getattr(imported_module, function_name)
    output = imported_function(*inputs)

def run_tests(modules, correct_output_type, function_name, module_to_import,
            visible_inputs, visible_correct_output, hidden_inputs, hidden_correct_output,
            arg_names, forbidden_functions=[], error_msg_if_modifies_input=''):
    """Run visible and hidden tests"""

    function_contains_print = False
    function_contains_forbidden_func = False
    end_of_test_str = '--- End of test ---\n'

    # Loop over modules only needed when developing tests
    for module in modules:

        msg_dict = error_message_dict(module, correct_output_type, function_name)

        print(f'--- Testing {module} ---')

        # Import student module
        try:
            imported_module = importlib.import_module(module)
        except ModuleNotFoundError:
            print(msg_dict['no_module'])
            print(end_of_test_str)
            continue

        # Check for print statements
        if file_contains_function(module + '.py', 'print'):
            print(msg_dict['print_warning'])
            function_contains_print = True

        # Check for forbidden function calls
        for func in forbidden_functions:
            if file_contains_function(module + '.py', func):
                function_contains_forbidden_func = True
                msg = msg_dict['forbidden_function'] + func + '.'
                print(msg)
        if function_contains_forbidden_func:
            print('\n' + end_of_test_str)
            continue

        # Import any additional python modules in case the student left that code out
        imported_extra_module = importlib.import_module(module_to_import)
        exec(f'imported_module.{module_to_import} = imported_extra_module')

        # Get student function from the imported module
        try:
            imported_function = getattr(imported_module, function_name)
        except AttributeError:
            print(msg_dict['attribute_error'])
            print(end_of_test_str)
            continue

        # Loop over all visible tests
        passed_visible = True
        for orig_inputs, correct_output in zip(visible_inputs, visible_correct_output):
            inputs = copy.deepcopy(orig_inputs)
            process_inputs = copy.deepcopy(orig_inputs)

            # Call function
            process_args = (module, module_to_import, function_name, process_inputs)
            process = multiprocessing.Process(target=run_student_fun, args=process_args)
            process.start()
            process.join(5)
            if process.is_alive():
                passed_visible = False
                print(msg_dict['timeout_error'])
                process.terminate()
                process.join()
                break
            output = imported_function(*inputs)

            if not nested_type_sensitive_equals(output, correct_output):
                passed_visible = False

            # Function returns None
            if output is None:
                if function_contains_print:
                    print(msg_dict['none_plus_print'])
                else:
                    print(msg_dict['none'])
                break

            # Function returns the wrong data type (but not None)
            elif not(type(output) == type(correct_output)):
                msg = msg_dict['wrong_type']
                msg += str(type(output)) + '\n'
                print(msg)
                break

            # Function returns correct data type, but wrong
            elif not nested_type_sensitive_equals(output, correct_output):
                msg = msg_dict['correct_type_but_wrong']
                print(msg)
                print_inputs_and_output(inputs, output, correct_output, arg_names, function_name)
                break

            # Function modifies inputs (and is not supposed to do that)
            if inputs != orig_inputs and error_msg_if_modifies_input != '':
                passed_visible = False
                msg = msg_dict[error_msg_if_modifies_input]
                print(msg)
                print_modified_inputs(inputs, orig_inputs, arg_names, function_name)
                break

        # Function reproduces example printouts. Test hidden tests too.
        if passed_visible:

            # Loop over hidden tests
            passed_hidden = True
            for inputs, correct_output in zip(hidden_inputs, hidden_correct_output):

                # Call function
                process_args = (module, module_to_import, function_name, inputs)
                process = multiprocessing.Process(target=run_student_fun, args=process_args)
                process.start()
                process.join(5)
                if process.is_alive():
                    passed_hidden = False
                    print(msg_dict['timeout_error'])
                    process.kill()
                    process.join()
                    break
                output = imported_function(*inputs)

                # Failed a hidden test
                if not nested_type_sensitive_equals(output, correct_output):
                    passed_hidden = False
                    msg = msg_dict['hardcoded']
                    print(msg)
                    print_inputs_and_output(inputs, output, correct_output, arg_names, function_name)
                    break

            if passed_hidden:
                print(msg_dict['passed'])

        print(end_of_test_str)

if __name__ == '__main__':

    #--- Excercise details ---
    modules = ['A4']
    function_name = 'bounded_squares'
    correct_output_type = 'list'
    module_to_import = 'math'
    arg_names = ['n']
    #-----------------------------------

    def bounded_squares_correct(n):
        outlist = []
        p = n + 1
        while p**2 < 3*n**2:
            outlist.append(p)
            p += 1
        return outlist

    #--- Lists of inputs and correct outputs for visible tests ---
    visible_inputs = [[1], [2], [5]]
    visible_correct_output = [[], [3], [6, 7, 8]]
    #-----------------------------------

    #--- Lists of inputs and correct outputs for hidden tests ---
    hidden_inputs = [[101], [1001]]
    hidden_correct_output = [bounded_squares_correct(*n) for n in hidden_inputs]
    #-----------------------------------

    run_tests(modules, correct_output_type, function_name, module_to_import,
                visible_inputs, visible_correct_output, hidden_inputs, hidden_correct_output,
                arg_names)

If you do use any of the testing scripts on this page, we would greatly appreciate if you could provide some feedback by filling out this 1-minute anonymous survey.

Example solution
# Alternative 1: using a while-loop
def bounded_squares(n):
    outlist = []
    p = n + 1
    while p**2 < 3*n**2:
        outlist.append(p)
        p += 1
    return outlist

# Alternative 2: first determine the smallest and largest possible values of p
def bounded_squares(n):
    p_min = n+1
    p_max = int(3**0.5 * n)
    return [p for p in range(p_min, p_max + 1)]
Common mistakes
  • print instead of return

  • Hard-coded upper limit for p, e.g. p=10 or p=100


A5#

Consider the code below:

class Animal:
    """Representation of an animal.
    self.species: string specifying what animal.
    Examples: 'cat', 'dog', 'elephant'
    self.position: tuple of x- and y-coordinates
    Examples: (0, 0), (1, 0), (-4, 9)
    """
    def __init__(self, species='cat', position=(0,0)):
        self.species = species
        self.position = position
 
    def move_to(self, new_position):
        self.position = new_position
 
 
dog = Animal('dog', (0, 1))
cat = Animal('cat', (3, 4))
print(dog)
print(cat)
cat.move_to((-1, 4))
print(cat)

When this code is executed, it produces the cryptic output in Printout 1:

# Printout 1 (caused by the example code above)
<__main__.Animal object at 0x7f810813caf0>
<__main__.Animal object at 0x7f8108131be0>
<__main__.Animal object at 0x7f8108131be0>

Add a method to the class Animal such that the same print-statements produce the desired output in Printout 2:

# Printout 2 (desired printout)
dog at position (0, 1)
cat at position (3, 4)
cat at position (-1, 4)

Note that you should only add one method to the class. You are NOT allowed to change anything else in the code.

Example solution
def __str__(self):
    return f'{self.species} at position {self.position}'

A6#

Write a function min_max_list() that takes a list of integers as input and returns a tuple containing the list’s minimum and maximum values. You are NOT allowed to use any of the following:

  • the built-in functions sorted(), min() and max(),

  • the list method sort() or the function sorted()

  • classes or functions from the collections or numpy modules.

The following statements:

# Example code
print(min_max_list([7, -90, 8, 34, 7, -56, 89]))
print(min_max_list([21, 17, 27, 4, 11, 15, 8, 13]))

should produce the printout

(-90, 89)
(4, 27)

Hint: You may use the following algorithm to compute the list’s maximum value:

  1. Assign the first value in the list to a variable, which we here call x_max.

  2. For the next value, x, in the list, if x is greater than x_max, then set x_max = x.

  3. Repeat step 2 for all remaining values in the list.

Once step 3 has been completed, the list’s maximum value should be stored in the variable x_max. The minimum value can be computed in similar fashion.

Want to check whether your solution is correct? Save it as A6.py. Then create another file in the same folder, copy-paste the code below into it, save it as A6_test.py and run it.
import importlib
import copy
import re
import multiprocessing

def read_py_as_text(filename):
    """Return file contents as str"""
    with open(filename, 'r') as pyfile:
        lines_of_text = pyfile.readlines()
    return lines_of_text

def remove_comments(lines_of_text):
    return [re.sub(r'#.*$', '', line) for line in lines_of_text]

def file_contains_function(filename, function_name):
    """Check if file contains a function call"""
    lines_of_text = read_py_as_text(filename)
    lines_without_comments = remove_comments(lines_of_text)
    text = ''.join(lines_without_comments)
    search_str = function_name + r'\(.*\)'
    list_of_calls = re.findall(search_str, text)
    return list_of_calls != []

def nested_type_sensitive_equals(val1, val2):
    if not val1 == val2:
        return False
    elif not type(val1) == type(val2):
        return False
    elif type(val1) == list or type(val1) == tuple:
        for el1, el2 in zip(val1, val2):
            if not nested_type_sensitive_equals(el1, el2):
                return False
    return True

def error_message_dict(module, correct_output_type, function_name):
    """Return a dict of all basic error messages"""
    msg_dict = {}

    msg_dict['passed'] = """
    WELL DONE! Your solution passed all the tests.
    """

    msg_dict['print_warning'] = """
    WARNING! Your function contains a print statement.
    This is nearly always a bad idea, unless the purpose
    of the function is to print something.
    """

    msg_dict['forbidden_function'] = """
    ERROR! Your function uses the following
    function/method, which is not allowed here: """

    msg_dict['none'] = f"""
    ERROR! Your function returns None.
    This probably means that you forgot a return statement.
    """

    msg_dict['none_plus_print'] = f"""
    ERROR! Your function returns None.
    This probably means that you forgot a return statement.
    Perhaps your function prints the {correct_output_type} instead of returning it?
    """

    msg_dict['wrong_type'] = f"""
    ERROR! Your function does not return a {correct_output_type}.
    Instead, it returns a value of type: """

    msg_dict['correct_type_but_wrong'] = f"""
    ERROR! Your function returns a {correct_output_type},
    but the {correct_output_type} is not quite correct.
    Run the example code and compare your printout to the expected printout.
    """

    msg_dict['hardcoded'] = f"""
    ERROR! Your function works for the examples in the exercise text,
    but not when other values are used as input parameters.
    Ensure that your function works correctly for general inputs.
    """

    msg_dict['no_module'] = f"""
    No module named {module} was found. Check that you saved your
    function in {module}.py and that this test is located
    in the same directory as {module}.py.
    """

    msg_dict['attribute_error'] = f"""
    The module {module} does not contain a function
    named {function_name}. Check for spelling
    mistakes in the function name.
    """

    msg_dict['timeout_error'] = f"""
    Your function did not return within 5 seconds.
    The most likely explanation for this is that you have created an
    infinite loop. Double-check any while loops!
    """

    msg_dict['modifies_input_dict_with_lists'] = f"""
    ERROR! Your function modifies the input dictionary.
    Make sure to:
    1. create a new dictionary instead of modifying the input dictionary
    2. create (sorted) copies of the lists in the original dictionary
    instead of sorting the original lists.
    """

    return msg_dict

def print_inputs_and_output(inputs, output, correct_output, arg_names, function_name):
    if type(output) == str:
        output = "'" + output + "'"

    if type(correct_output) == str:
        correct_output = "'" + correct_output + "'"

    print('    The following call:\n')
    for index, arg in enumerate(inputs):
        if type(arg) == str:
            arg = "'" + arg + "'"
        print(f'    {arg_names[index]} = {arg}')
    arg_string = '(' + ', '.join(arg_names) + ')'
    print(f'    {function_name}{arg_string}')
    print(f'\n    returns: {output}.')
    print(f'\n    The correct value is: {correct_output}.\n')

def print_modified_inputs(inputs, orig_inputs, arg_names, function_name):
    print(f'    Before calling {function_name}:')
    for index, arg in enumerate(orig_inputs):
        if type(arg) == str:
            arg = "'" + arg + "'"
        print(f'    {arg_names[index]}: {arg}')

    print(f'\n    After calling {function_name}:')
    for index, arg in enumerate(inputs):
        if type(arg) == str:
            arg = "'" + arg + "'"
        print(f'    {arg_names[index]}: {arg}')
        print('')

def run_student_fun(module, module_to_import, function_name, inputs):

    # Import student module
    imported_module = importlib.import_module(module)

    # Import any additional python modules in case the student left that code out
    imported_extra_module = importlib.import_module(module_to_import)
    exec(f'imported_module.{module_to_import} = imported_extra_module')

    # Get student function from the imported module
    imported_function = getattr(imported_module, function_name)
    output = imported_function(*inputs)

def run_tests(modules, correct_output_type, function_name, module_to_import,
            visible_inputs, visible_correct_output, hidden_inputs, hidden_correct_output,
            arg_names, forbidden_functions=[], error_msg_if_modifies_input=''):
    """Run visible and hidden tests"""

    function_contains_print = False
    function_contains_forbidden_func = False
    end_of_test_str = '--- End of test ---\n'

    # Loop over modules only needed when developing tests
    for module in modules:

        msg_dict = error_message_dict(module, correct_output_type, function_name)

        print(f'--- Testing {module} ---')

        # Import student module
        try:
            imported_module = importlib.import_module(module)
        except ModuleNotFoundError:
            print(msg_dict['no_module'])
            print(end_of_test_str)
            continue

        # Check for print statements
        if file_contains_function(module + '.py', 'print'):
            print(msg_dict['print_warning'])
            function_contains_print = True

        # Check for forbidden function calls
        for func in forbidden_functions:
            if file_contains_function(module + '.py', func):
                function_contains_forbidden_func = True
                msg = msg_dict['forbidden_function'] + func + '.'
                print(msg)
        if function_contains_forbidden_func:
            print('\n' + end_of_test_str)
            continue

        # Import any additional python modules in case the student left that code out
        imported_extra_module = importlib.import_module(module_to_import)
        exec(f'imported_module.{module_to_import} = imported_extra_module')

        # Get student function from the imported module
        try:
            imported_function = getattr(imported_module, function_name)
        except AttributeError:
            print(msg_dict['attribute_error'])
            print(end_of_test_str)
            continue

        # Loop over all visible tests
        passed_visible = True
        for orig_inputs, correct_output in zip(visible_inputs, visible_correct_output):
            inputs = copy.deepcopy(orig_inputs)
            process_inputs = copy.deepcopy(orig_inputs)

            # Call function
            process_args = (module, module_to_import, function_name, process_inputs)
            process = multiprocessing.Process(target=run_student_fun, args=process_args)
            process.start()
            process.join(5)
            if process.is_alive():
                passed_visible = False
                print(msg_dict['timeout_error'])
                process.terminate()
                process.join()
                break
            output = imported_function(*inputs)

            if not nested_type_sensitive_equals(output, correct_output):
                passed_visible = False

            # Function returns None
            if output is None:
                if function_contains_print:
                    print(msg_dict['none_plus_print'])
                else:
                    print(msg_dict['none'])
                break

            # Function returns the wrong data type (but not None)
            elif not(type(output) == type(correct_output)):
                msg = msg_dict['wrong_type']
                msg += str(type(output)) + '\n'
                print(msg)
                break

            # Function returns correct data type, but wrong
            elif not nested_type_sensitive_equals(output, correct_output):
                msg = msg_dict['correct_type_but_wrong']
                print(msg)
                print_inputs_and_output(inputs, output, correct_output, arg_names, function_name)
                break

            # Function modifies inputs (and is not supposed to do that)
            if inputs != orig_inputs and error_msg_if_modifies_input != '':
                passed_visible = False
                msg = msg_dict[error_msg_if_modifies_input]
                print(msg)
                print_modified_inputs(inputs, orig_inputs, arg_names, function_name)
                break

        # Function reproduces example printouts. Test hidden tests too.
        if passed_visible:

            # Loop over hidden tests
            passed_hidden = True
            for inputs, correct_output in zip(hidden_inputs, hidden_correct_output):

                # Call function
                process_args = (module, module_to_import, function_name, inputs)
                process = multiprocessing.Process(target=run_student_fun, args=process_args)
                process.start()
                process.join(5)
                if process.is_alive():
                    passed_hidden = False
                    print(msg_dict['timeout_error'])
                    process.kill()
                    process.join()
                    break
                output = imported_function(*inputs)

                # Failed a hidden test
                if not nested_type_sensitive_equals(output, correct_output):
                    passed_hidden = False
                    msg = msg_dict['hardcoded']
                    print(msg)
                    print_inputs_and_output(inputs, output, correct_output, arg_names, function_name)
                    break

            if passed_hidden:
                print(msg_dict['passed'])

        print(end_of_test_str)

if __name__ == '__main__':

    #--- Excercise details ---
    modules = ['A6']
    function_name = 'min_max_list'
    correct_output_type = 'tuple'
    module_to_import = 'math'
    arg_names = ['lst']
    forbidden_functions = ['sort', 'sorted', 'min', 'max']
    #-----------------------------------

    #--- Lists of inputs and correct outputs for visible tests ---
    visible_inputs = [[[7, -90, 8, 34, 7, -56, 89]], [[21, 17, 27, 4, 11, 15, 8, 13]]]
    visible_correct_output = [(-90, 89), (4, 27)]
    #-----------------------------------

    #--- Lists of inputs and correct outputs for hidden tests ---
    hidden_inputs = [[[22, 17, 21, -3, -1, 15, -2, 13]]]
    hidden_correct_output = [(-3, 22)]
    #-----------------------------------

    run_tests(modules, correct_output_type, function_name, module_to_import,
                visible_inputs, visible_correct_output, hidden_inputs, hidden_correct_output,
                arg_names, forbidden_functions)

If you do use any of the testing scripts on this page, we would greatly appreciate if you could provide some feedback by filling out this 1-minute anonymous survey.

Example solution
def min_max_list(inlist):
    vmin = inlist[0]
    vmax = inlist[0]
    for val in inlist:
        if val < vmin:
            vmin = val
        if val > vmax:
            vmax = val
    return vmin, vmax
Common mistakes
  • Returns the wrong data type, e.g. a string or a list instead of a tuple

  • print instead of return


A7#

Consider the Fibonacci sequence: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55,…, where every number in the sequence (except the first two) equals the sum of the two preceding numbers. Write a function fibonacci_numbers(limit) that returns a list of all the Fibonacci numbers that are less than or equal to limit.

The following code:

print(fibonacci_numbers(4))
print(fibonacci_numbers(13))

should produce the printout

[0, 1, 1, 2, 3]
[0, 1, 1, 2, 3, 5, 8, 13]

Note that your function must work for arbitrarily large values of the limit parameter.

Want to check whether your solution is correct? Save it as A7.py. Then create another file in the same folder, copy-paste the code below into it, save it as A7_test.py and run it.
import importlib
import copy
import re
import multiprocessing

def read_py_as_text(filename):
    """Return file contents as str"""
    with open(filename, 'r') as pyfile:
        lines_of_text = pyfile.readlines()
    return lines_of_text

def remove_comments(lines_of_text):
    return [re.sub(r'#.*$', '', line) for line in lines_of_text]

def file_contains_function(filename, function_name):
    """Check if file contains a function call"""
    lines_of_text = read_py_as_text(filename)
    lines_without_comments = remove_comments(lines_of_text)
    text = ''.join(lines_without_comments)
    search_str = function_name + r'\(.*\)'
    list_of_calls = re.findall(search_str, text)
    return list_of_calls != []

def nested_type_sensitive_equals(val1, val2):
    if not val1 == val2:
        return False
    elif not type(val1) == type(val2):
        return False
    elif type(val1) == list or type(val1) == tuple:
        for el1, el2 in zip(val1, val2):
            if not nested_type_sensitive_equals(el1, el2):
                return False
    return True

def error_message_dict(module, correct_output_type, function_name):
    """Return a dict of all basic error messages"""
    msg_dict = {}

    msg_dict['passed'] = """
    WELL DONE! Your solution passed all the tests.
    """

    msg_dict['print_warning'] = """
    WARNING! Your function contains a print statement.
    This is nearly always a bad idea, unless the purpose
    of the function is to print something.
    """

    msg_dict['forbidden_function'] = """
    ERROR! Your function uses the following
    function/method, which is not allowed here: """

    msg_dict['none'] = f"""
    ERROR! Your function returns None.
    This probably means that you forgot a return statement.
    """

    msg_dict['none_plus_print'] = f"""
    ERROR! Your function returns None.
    This probably means that you forgot a return statement.
    Perhaps your function prints the {correct_output_type} instead of returning it?
    """

    msg_dict['wrong_type'] = f"""
    ERROR! Your function does not return a {correct_output_type}.
    Instead, it returns a value of type: """

    msg_dict['correct_type_but_wrong'] = f"""
    ERROR! Your function returns a {correct_output_type},
    but the {correct_output_type} is not quite correct.
    Run the example code and compare your printout to the expected printout.
    """

    msg_dict['hardcoded'] = f"""
    ERROR! Your function works for the examples in the exercise text,
    but not when other values are used as input parameters.
    Ensure that your function works correctly for general inputs.
    """

    msg_dict['no_module'] = f"""
    No module named {module} was found. Check that you saved your
    function in {module}.py and that this test is located
    in the same directory as {module}.py.
    """

    msg_dict['attribute_error'] = f"""
    The module {module} does not contain a function
    named {function_name}. Check for spelling
    mistakes in the function name.
    """

    msg_dict['timeout_error'] = f"""
    Your function did not return within 5 seconds.
    The most likely explanation for this is that you have created an
    infinite loop. Double-check any while loops!
    """

    msg_dict['modifies_input_dict_with_lists'] = f"""
    ERROR! Your function modifies the input dictionary.
    Make sure to:
    1. create a new dictionary instead of modifying the input dictionary
    2. create (sorted) copies of the lists in the original dictionary
    instead of sorting the original lists.
    """

    return msg_dict

def print_inputs_and_output(inputs, output, correct_output, arg_names, function_name):
    if type(output) == str:
        output = "'" + output + "'"

    if type(correct_output) == str:
        correct_output = "'" + correct_output + "'"

    print('    The following call:\n')
    for index, arg in enumerate(inputs):
        if type(arg) == str:
            arg = "'" + arg + "'"
        print(f'    {arg_names[index]} = {arg}')
    arg_string = '(' + ', '.join(arg_names) + ')'
    print(f'    {function_name}{arg_string}')
    print(f'\n    returns: {output}.')
    print(f'\n    The correct value is: {correct_output}.\n')

def print_modified_inputs(inputs, orig_inputs, arg_names, function_name):
    print(f'    Before calling {function_name}:')
    for index, arg in enumerate(orig_inputs):
        if type(arg) == str:
            arg = "'" + arg + "'"
        print(f'    {arg_names[index]}: {arg}')

    print(f'\n    After calling {function_name}:')
    for index, arg in enumerate(inputs):
        if type(arg) == str:
            arg = "'" + arg + "'"
        print(f'    {arg_names[index]}: {arg}')
        print('')

def run_student_fun(module, module_to_import, function_name, inputs):

    # Import student module
    imported_module = importlib.import_module(module)

    # Import any additional python modules in case the student left that code out
    imported_extra_module = importlib.import_module(module_to_import)
    exec(f'imported_module.{module_to_import} = imported_extra_module')

    # Get student function from the imported module
    imported_function = getattr(imported_module, function_name)
    output = imported_function(*inputs)

def run_tests(modules, correct_output_type, function_name, module_to_import,
            visible_inputs, visible_correct_output, hidden_inputs, hidden_correct_output,
            arg_names, forbidden_functions=[], error_msg_if_modifies_input=''):
    """Run visible and hidden tests"""

    function_contains_print = False
    function_contains_forbidden_func = False
    end_of_test_str = '--- End of test ---\n'

    # Loop over modules only needed when developing tests
    for module in modules:

        msg_dict = error_message_dict(module, correct_output_type, function_name)

        print(f'--- Testing {module} ---')

        # Import student module
        try:
            imported_module = importlib.import_module(module)
        except ModuleNotFoundError:
            print(msg_dict['no_module'])
            print(end_of_test_str)
            continue

        # Check for print statements
        if file_contains_function(module + '.py', 'print'):
            print(msg_dict['print_warning'])
            function_contains_print = True

        # Check for forbidden function calls
        for func in forbidden_functions:
            if file_contains_function(module + '.py', func):
                function_contains_forbidden_func = True
                msg = msg_dict['forbidden_function'] + func + '.'
                print(msg)
        if function_contains_forbidden_func:
            print('\n' + end_of_test_str)
            continue

        # Import any additional python modules in case the student left that code out
        imported_extra_module = importlib.import_module(module_to_import)
        exec(f'imported_module.{module_to_import} = imported_extra_module')

        # Get student function from the imported module
        try:
            imported_function = getattr(imported_module, function_name)
        except AttributeError:
            print(msg_dict['attribute_error'])
            print(end_of_test_str)
            continue

        # Loop over all visible tests
        passed_visible = True
        for orig_inputs, correct_output in zip(visible_inputs, visible_correct_output):
            inputs = copy.deepcopy(orig_inputs)
            process_inputs = copy.deepcopy(orig_inputs)

            # Call function
            process_args = (module, module_to_import, function_name, process_inputs)
            process = multiprocessing.Process(target=run_student_fun, args=process_args)
            process.start()
            process.join(5)
            if process.is_alive():
                passed_visible = False
                print(msg_dict['timeout_error'])
                process.terminate()
                process.join()
                break
            output = imported_function(*inputs)

            if not nested_type_sensitive_equals(output, correct_output):
                passed_visible = False

            # Function returns None
            if output is None:
                if function_contains_print:
                    print(msg_dict['none_plus_print'])
                else:
                    print(msg_dict['none'])
                break

            # Function returns the wrong data type (but not None)
            elif not(type(output) == type(correct_output)):
                msg = msg_dict['wrong_type']
                msg += str(type(output)) + '\n'
                print(msg)
                break

            # Function returns correct data type, but wrong
            elif not nested_type_sensitive_equals(output, correct_output):
                msg = msg_dict['correct_type_but_wrong']
                print(msg)
                print_inputs_and_output(inputs, output, correct_output, arg_names, function_name)
                break

            # Function modifies inputs (and is not supposed to do that)
            if inputs != orig_inputs and error_msg_if_modifies_input != '':
                passed_visible = False
                msg = msg_dict[error_msg_if_modifies_input]
                print(msg)
                print_modified_inputs(inputs, orig_inputs, arg_names, function_name)
                break

        # Function reproduces example printouts. Test hidden tests too.
        if passed_visible:

            # Loop over hidden tests
            passed_hidden = True
            for inputs, correct_output in zip(hidden_inputs, hidden_correct_output):

                # Call function
                process_args = (module, module_to_import, function_name, inputs)
                process = multiprocessing.Process(target=run_student_fun, args=process_args)
                process.start()
                process.join(5)
                if process.is_alive():
                    passed_hidden = False
                    print(msg_dict['timeout_error'])
                    process.kill()
                    process.join()
                    break
                output = imported_function(*inputs)

                # Failed a hidden test
                if not nested_type_sensitive_equals(output, correct_output):
                    passed_hidden = False
                    msg = msg_dict['hardcoded']
                    print(msg)
                    print_inputs_and_output(inputs, output, correct_output, arg_names, function_name)
                    break

            if passed_hidden:
                print(msg_dict['passed'])

        print(end_of_test_str)

if __name__ == '__main__':

    #--- Excercise details ---
    modules = ['A7']
    function_name = 'fibonacci_numbers'
    correct_output_type = 'list'
    module_to_import = 'math'
    arg_names = ['limit']
    forbidden_functions = []
    #-----------------------------------

    #--- Lists of inputs and correct outputs for visible tests ---
    visible_inputs = [[4], [13]]
    visible_correct_output = [[0, 1, 1, 2, 3], [0, 1, 1, 2, 3, 5, 8, 13]]
    #-----------------------------------

    #--- Lists of inputs and correct outputs for hidden tests ---
    hidden_inputs = [[1], [2], [3], [30], [1001]]
    hidden_correct_output = [[0, 1, 1], [0, 1, 1, 2], [0, 1, 1, 2, 3],
    [0, 1, 1, 2, 3, 5, 8, 13, 21],
    [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987]]
    #-----------------------------------

    run_tests(modules, correct_output_type, function_name, module_to_import,
                visible_inputs, visible_correct_output, hidden_inputs, hidden_correct_output,
                arg_names, forbidden_functions)

If you do use any of the testing scripts on this page, we would greatly appreciate if you could provide some feedback by filling out this 1-minute anonymous survey.

Example solution
def fibonacci_numbers(limit):
    numbers = [0]
    next_number = 1
    while next_number <= limit:
        numbers.append(next_number)
        next_number = numbers[-2] + numbers[-1]
    return numbers
Common mistakes
  • Hard-coded solutions that only work for small values of limit, e.g. limit < 100

  • Implicitly assumes that the return list will contain at most limit numbers, which is not true if limit <= 3

  • print instead of return


A8#

Write a function sorted_dictionary(input_dict) that takes a dictionary whose values are lists of integers as parameter and returns another dictionary in which the lists are sorted. The output dictionary should have the same keys as the input. The input dictionary should remain unchanged after the function call.

The following code:

d = {'a1': [21, 17, 22, 3], 'a2': [11, 15, 8, 13], 'a3': [7, 13, 2, 11]} 
d_sorted = sorted_dictionary(d) 
print('Original: ', d) 
print('Sorted:   ', d_sorted)

should produce the printout

Original: {'a1': [21, 17, 22, 3], 'a2': [11, 15, 8, 13], 'a3': [7, 13, 2, 11]} 
Sorted:   {'a1': [3, 17, 21, 22], 'a2': [8, 11, 13, 15], 'a3': [2, 7, 11, 13]}

Note that your function should work for dictionaries with other keys and lists than in the example.

Want to check whether your solution is correct? Save it as A8.py. Then create another file in the same folder, copy-paste the code below into it, save it as A8_test.py and run it.
import importlib
import copy
import re
import multiprocessing

def read_py_as_text(filename):
    """Return file contents as str"""
    with open(filename, 'r') as pyfile:
        lines_of_text = pyfile.readlines()
    return lines_of_text

def remove_comments(lines_of_text):
    return [re.sub(r'#.*$', '', line) for line in lines_of_text]

def file_contains_function(filename, function_name):
    """Check if file contains a function call"""
    lines_of_text = read_py_as_text(filename)
    lines_without_comments = remove_comments(lines_of_text)
    text = ''.join(lines_without_comments)
    search_str = function_name + r'\(.*\)'
    list_of_calls = re.findall(search_str, text)
    return list_of_calls != []

def nested_type_sensitive_equals(val1, val2):
    if not val1 == val2:
        return False
    elif not type(val1) == type(val2):
        return False
    elif type(val1) == list or type(val1) == tuple:
        for el1, el2 in zip(val1, val2):
            if not nested_type_sensitive_equals(el1, el2):
                return False
    return True

def error_message_dict(module, correct_output_type, function_name):
    """Return a dict of all basic error messages"""
    msg_dict = {}

    msg_dict['passed'] = """
    WELL DONE! Your solution passed all the tests.
    """

    msg_dict['print_warning'] = """
    WARNING! Your function contains a print statement.
    This is nearly always a bad idea, unless the purpose
    of the function is to print something.
    """

    msg_dict['forbidden_function'] = """
    ERROR! Your function uses the following
    function/method, which is not allowed here: """

    msg_dict['none'] = f"""
    ERROR! Your function returns None.
    This probably means that you forgot a return statement.
    """

    msg_dict['none_plus_print'] = f"""
    ERROR! Your function returns None.
    This probably means that you forgot a return statement.
    Perhaps your function prints the {correct_output_type} instead of returning it?
    """

    msg_dict['wrong_type'] = f"""
    ERROR! Your function does not return a {correct_output_type}.
    Instead, it returns a value of type: """

    msg_dict['correct_type_but_wrong'] = f"""
    ERROR! Your function returns a {correct_output_type},
    but the {correct_output_type} is not quite correct.
    Run the example code and compare your printout to the expected printout.
    """

    msg_dict['hardcoded'] = f"""
    ERROR! Your function works for the examples in the exercise text,
    but not when other values are used as input parameters.
    Ensure that your function works correctly for general inputs.
    """

    msg_dict['no_module'] = f"""
    No module named {module} was found. Check that you saved your
    function in {module}.py and that this test is located
    in the same directory as {module}.py.
    """

    msg_dict['attribute_error'] = f"""
    The module {module} does not contain a function
    named {function_name}. Check for spelling
    mistakes in the function name.
    """

    msg_dict['timeout_error'] = f"""
    Your function did not return within 5 seconds.
    The most likely explanation for this is that you have created an
    infinite loop. Double-check any while loops!
    """

    msg_dict['modifies_input_dict_with_lists'] = f"""
    ERROR! Your function modifies the input dictionary.
    Make sure to:
    1. create a new dictionary instead of modifying the input dictionary
    2. create (sorted) copies of the lists in the original dictionary
    instead of sorting the original lists.
    """

    return msg_dict

def print_inputs_and_output(inputs, output, correct_output, arg_names, function_name):
    if type(output) == str:
        output = "'" + output + "'"

    if type(correct_output) == str:
        correct_output = "'" + correct_output + "'"

    print('    The following call:\n')
    for index, arg in enumerate(inputs):
        if type(arg) == str:
            arg = "'" + arg + "'"
        print(f'    {arg_names[index]} = {arg}')
    arg_string = '(' + ', '.join(arg_names) + ')'
    print(f'    {function_name}{arg_string}')
    print(f'\n    returns: {output}.')
    print(f'\n    The correct value is: {correct_output}.\n')

def print_modified_inputs(inputs, orig_inputs, arg_names, function_name):
    print(f'    Before calling {function_name}:')
    for index, arg in enumerate(orig_inputs):
        if type(arg) == str:
            arg = "'" + arg + "'"
        print(f'    {arg_names[index]}: {arg}')

    print(f'\n    After calling {function_name}:')
    for index, arg in enumerate(inputs):
        if type(arg) == str:
            arg = "'" + arg + "'"
        print(f'    {arg_names[index]}: {arg}')
        print('')

def run_student_fun(module, module_to_import, function_name, inputs):

    # Import student module
    imported_module = importlib.import_module(module)

    # Import any additional python modules in case the student left that code out
    imported_extra_module = importlib.import_module(module_to_import)
    exec(f'imported_module.{module_to_import} = imported_extra_module')

    # Get student function from the imported module
    imported_function = getattr(imported_module, function_name)
    output = imported_function(*inputs)

def run_tests(modules, correct_output_type, function_name, module_to_import,
            visible_inputs, visible_correct_output, hidden_inputs, hidden_correct_output,
            arg_names, forbidden_functions=[], error_msg_if_modifies_input=''):
    """Run visible and hidden tests"""

    function_contains_print = False
    function_contains_forbidden_func = False
    end_of_test_str = '--- End of test ---\n'

    # Loop over modules only needed when developing tests
    for module in modules:

        msg_dict = error_message_dict(module, correct_output_type, function_name)

        print(f'--- Testing {module} ---')

        # Import student module
        try:
            imported_module = importlib.import_module(module)
        except ModuleNotFoundError:
            print(msg_dict['no_module'])
            print(end_of_test_str)
            continue

        # Check for print statements
        if file_contains_function(module + '.py', 'print'):
            print(msg_dict['print_warning'])
            function_contains_print = True

        # Check for forbidden function calls
        for func in forbidden_functions:
            if file_contains_function(module + '.py', func):
                function_contains_forbidden_func = True
                msg = msg_dict['forbidden_function'] + func + '.'
                print(msg)
        if function_contains_forbidden_func:
            print('\n' + end_of_test_str)
            continue

        # Import any additional python modules in case the student left that code out
        imported_extra_module = importlib.import_module(module_to_import)
        exec(f'imported_module.{module_to_import} = imported_extra_module')

        # Get student function from the imported module
        try:
            imported_function = getattr(imported_module, function_name)
        except AttributeError:
            print(msg_dict['attribute_error'])
            print(end_of_test_str)
            continue

        # Loop over all visible tests
        passed_visible = True
        for orig_inputs, correct_output in zip(visible_inputs, visible_correct_output):
            inputs = copy.deepcopy(orig_inputs)
            process_inputs = copy.deepcopy(orig_inputs)

            # Call function
            process_args = (module, module_to_import, function_name, process_inputs)
            process = multiprocessing.Process(target=run_student_fun, args=process_args)
            process.start()
            process.join(5)
            if process.is_alive():
                passed_visible = False
                print(msg_dict['timeout_error'])
                process.terminate()
                process.join()
                break
            output = imported_function(*inputs)

            if not nested_type_sensitive_equals(output, correct_output):
                passed_visible = False

            # Function returns None
            if output is None:
                if function_contains_print:
                    print(msg_dict['none_plus_print'])
                else:
                    print(msg_dict['none'])
                break

            # Function returns the wrong data type (but not None)
            elif not(type(output) == type(correct_output)):
                msg = msg_dict['wrong_type']
                msg += str(type(output)) + '\n'
                print(msg)
                break

            # Function returns correct data type, but wrong
            elif not nested_type_sensitive_equals(output, correct_output):
                msg = msg_dict['correct_type_but_wrong']
                print(msg)
                print_inputs_and_output(inputs, output, correct_output, arg_names, function_name)
                break

            # Function modifies inputs (and is not supposed to do that)
            if inputs != orig_inputs and error_msg_if_modifies_input != '':
                passed_visible = False
                msg = msg_dict[error_msg_if_modifies_input]
                print(msg)
                print_modified_inputs(inputs, orig_inputs, arg_names, function_name)
                break

        # Function reproduces example printouts. Test hidden tests too.
        if passed_visible:

            # Loop over hidden tests
            passed_hidden = True
            for inputs, correct_output in zip(hidden_inputs, hidden_correct_output):

                # Call function
                process_args = (module, module_to_import, function_name, inputs)
                process = multiprocessing.Process(target=run_student_fun, args=process_args)
                process.start()
                process.join(5)
                if process.is_alive():
                    passed_hidden = False
                    print(msg_dict['timeout_error'])
                    process.kill()
                    process.join()
                    break
                output = imported_function(*inputs)

                # Failed a hidden test
                if not nested_type_sensitive_equals(output, correct_output):
                    passed_hidden = False
                    msg = msg_dict['hardcoded']
                    print(msg)
                    print_inputs_and_output(inputs, output, correct_output, arg_names, function_name)
                    break

            if passed_hidden:
                print(msg_dict['passed'])

        print(end_of_test_str)

if __name__ == '__main__':

    #--- Excercise details ---
    modules = ['A8']
    function_name = 'sorted_dictionary'
    correct_output_type = 'dictionary'
    module_to_import = 'math'
    arg_names = ['d']
    forbidden_functions = []
    err_msg_mod = 'modifies_input_dict_with_lists'
    #-----------------------------------

    #--- Lists of inputs and correct outputs for visible tests ---
    d = {'a1': [21, 17, 22, 3], 'a2': [11, 15, 8, 13], 'a3': [7, 13, 2, 11]}
    d_sorted = {'a1': [3, 17, 21, 22], 'a2': [8, 11, 13, 15], 'a3': [2, 7, 11, 13]}
    visible_inputs = [[d]]
    visible_correct_output = [d_sorted]
    #-----------------------------------

    #--- Lists of inputs and correct outputs for hidden tests ---
    d2 = {'b1': [7, -90, 8], 'b2': [ 34, 7, -56, 89]}
    d2_sorted = {'b1': [-90, 7, 8], 'b2': [-56, 7, 34, 89]}
    hidden_inputs = [[d2]]
    hidden_correct_output = [d2_sorted]
    #-----------------------------------

    run_tests(modules, correct_output_type, function_name, module_to_import,
                visible_inputs, visible_correct_output, hidden_inputs, hidden_correct_output,
                arg_names, forbidden_functions, error_msg_if_modifies_input=err_msg_mod)

If you do use any of the testing scripts on this page, we would greatly appreciate if you could provide some feedback by filling out this 1-minute anonymous survey.

Example solution
def sorted_dictionary(input_dict):
    sorted_dict = {}
    for key, value in input_dict.items():
        sorted_dict[key] = sorted(value)
    return sorted_dict
Common mistakes
  • Incorrect use of global variables

  • Using the list method sort() to sort the existing lists in place (as opposed to creating new lists) and therefore sorting the original dictionary too.

  • print instead of return


A9#

Write a function alternative_splicing(seq) that ”splices” an input list seq and returns a tuple of 3 lists such that every third element in `seq is written in the same output list. That is:

  • The 1st, 4th, 7th, etc. elements of seq are written in the first output list,

  • The 2nd, 5th, 8th, etc. elements of seq are written in the second output list,

  • The 3rd, 6th, 9th, etc. elements of seq are written in the third output list.

The following code:

u = [10, 9, -2, 16, 18, -13, 12, 18, -14]
v = [-14, -20, -12, 3, -20, 7, -6, 13]
print(alternative_splicing(u))
print(alternative_splicing(v))

should produce the printout

([10, 16, 12], [9, 18, 18], [-2, -13, -14])
([-14, 3, -6], [-20, -20, 13], [-12, 7])

Note that the three output lists will be of different lengths if the number of elements in seq is not a multiple of 3 (see the example with the list v above).

Want to check whether your solution is correct? Save it as A9.py. Then create another file in the same folder, copy-paste the code below into it, save it as A9_test.py and run it.
import importlib
import copy
import re
import multiprocessing

def read_py_as_text(filename):
    """Return file contents as str"""
    with open(filename, 'r') as pyfile:
        lines_of_text = pyfile.readlines()
    return lines_of_text

def remove_comments(lines_of_text):
    return [re.sub(r'#.*$', '', line) for line in lines_of_text]

def file_contains_function(filename, function_name):
    """Check if file contains a function call"""
    lines_of_text = read_py_as_text(filename)
    lines_without_comments = remove_comments(lines_of_text)
    text = ''.join(lines_without_comments)
    search_str = function_name + r'\(.*\)'
    list_of_calls = re.findall(search_str, text)
    return list_of_calls != []

def nested_type_sensitive_equals(val1, val2):
    if not val1 == val2:
        return False
    elif not type(val1) == type(val2):
        return False
    elif type(val1) == list or type(val1) == tuple:
        for el1, el2 in zip(val1, val2):
            if not nested_type_sensitive_equals(el1, el2):
                return False
    return True

def error_message_dict(module, correct_output_type, function_name):
    """Return a dict of all basic error messages"""
    msg_dict = {}

    msg_dict['passed'] = """
    WELL DONE! Your solution passed all the tests.
    """

    msg_dict['print_warning'] = """
    WARNING! Your function contains a print statement.
    This is nearly always a bad idea, unless the purpose
    of the function is to print something.
    """

    msg_dict['forbidden_function'] = """
    ERROR! Your function uses the following
    function/method, which is not allowed here: """

    msg_dict['none'] = f"""
    ERROR! Your function returns None.
    This probably means that you forgot a return statement.
    """

    msg_dict['none_plus_print'] = f"""
    ERROR! Your function returns None.
    This probably means that you forgot a return statement.
    Perhaps your function prints the {correct_output_type} instead of returning it?
    """

    msg_dict['wrong_type'] = f"""
    ERROR! Your function does not return a {correct_output_type}.
    Instead, it returns a value of type: """

    msg_dict['correct_type_but_wrong'] = f"""
    ERROR! Your function returns a {correct_output_type},
    but the {correct_output_type} is not quite correct.
    Run the example code and compare your printout to the expected printout.
    """

    msg_dict['hardcoded'] = f"""
    ERROR! Your function works for the examples in the exercise text,
    but not when other values are used as input parameters.
    Ensure that your function works correctly for general inputs.
    """

    msg_dict['no_module'] = f"""
    No module named {module} was found. Check that you saved your
    function in {module}.py and that this test is located
    in the same directory as {module}.py.
    """

    msg_dict['attribute_error'] = f"""
    The module {module} does not contain a function
    named {function_name}. Check for spelling
    mistakes in the function name.
    """

    msg_dict['timeout_error'] = f"""
    Your function did not return within 5 seconds.
    The most likely explanation for this is that you have created an
    infinite loop. Double-check any while loops!
    """

    msg_dict['modifies_input_dict_with_lists'] = f"""
    ERROR! Your function modifies the input dictionary.
    Make sure to:
    1. create a new dictionary instead of modifying the input dictionary
    2. create (sorted) copies of the lists in the original dictionary
    instead of sorting the original lists.
    """

    return msg_dict

def print_inputs_and_output(inputs, output, correct_output, arg_names, function_name):
    if type(output) == str:
        output = "'" + output + "'"

    if type(correct_output) == str:
        correct_output = "'" + correct_output + "'"

    print('    The following call:\n')
    for index, arg in enumerate(inputs):
        if type(arg) == str:
            arg = "'" + arg + "'"
        print(f'    {arg_names[index]} = {arg}')
    arg_string = '(' + ', '.join(arg_names) + ')'
    print(f'    {function_name}{arg_string}')
    print(f'\n    returns: {output}.')
    print(f'\n    The correct value is: {correct_output}.\n')

def print_modified_inputs(inputs, orig_inputs, arg_names, function_name):
    print(f'    Before calling {function_name}:')
    for index, arg in enumerate(orig_inputs):
        if type(arg) == str:
            arg = "'" + arg + "'"
        print(f'    {arg_names[index]}: {arg}')

    print(f'\n    After calling {function_name}:')
    for index, arg in enumerate(inputs):
        if type(arg) == str:
            arg = "'" + arg + "'"
        print(f'    {arg_names[index]}: {arg}')
        print('')

def run_student_fun(module, module_to_import, function_name, inputs):

    # Import student module
    imported_module = importlib.import_module(module)

    # Import any additional python modules in case the student left that code out
    imported_extra_module = importlib.import_module(module_to_import)
    exec(f'imported_module.{module_to_import} = imported_extra_module')

    # Get student function from the imported module
    imported_function = getattr(imported_module, function_name)
    output = imported_function(*inputs)

def run_tests(modules, correct_output_type, function_name, module_to_import,
            visible_inputs, visible_correct_output, hidden_inputs, hidden_correct_output,
            arg_names, forbidden_functions=[], error_msg_if_modifies_input=''):
    """Run visible and hidden tests"""

    function_contains_print = False
    function_contains_forbidden_func = False
    end_of_test_str = '--- End of test ---\n'

    # Loop over modules only needed when developing tests
    for module in modules:

        msg_dict = error_message_dict(module, correct_output_type, function_name)

        print(f'--- Testing {module} ---')

        # Import student module
        try:
            imported_module = importlib.import_module(module)
        except ModuleNotFoundError:
            print(msg_dict['no_module'])
            print(end_of_test_str)
            continue

        # Check for print statements
        if file_contains_function(module + '.py', 'print'):
            print(msg_dict['print_warning'])
            function_contains_print = True

        # Check for forbidden function calls
        for func in forbidden_functions:
            if file_contains_function(module + '.py', func):
                function_contains_forbidden_func = True
                msg = msg_dict['forbidden_function'] + func + '.'
                print(msg)
        if function_contains_forbidden_func:
            print('\n' + end_of_test_str)
            continue

        # Import any additional python modules in case the student left that code out
        imported_extra_module = importlib.import_module(module_to_import)
        exec(f'imported_module.{module_to_import} = imported_extra_module')

        # Get student function from the imported module
        try:
            imported_function = getattr(imported_module, function_name)
        except AttributeError:
            print(msg_dict['attribute_error'])
            print(end_of_test_str)
            continue

        # Loop over all visible tests
        passed_visible = True
        for orig_inputs, correct_output in zip(visible_inputs, visible_correct_output):
            inputs = copy.deepcopy(orig_inputs)
            process_inputs = copy.deepcopy(orig_inputs)

            # Call function
            process_args = (module, module_to_import, function_name, process_inputs)
            process = multiprocessing.Process(target=run_student_fun, args=process_args)
            process.start()
            process.join(5)
            if process.is_alive():
                passed_visible = False
                print(msg_dict['timeout_error'])
                process.terminate()
                process.join()
                break
            output = imported_function(*inputs)

            if not nested_type_sensitive_equals(output, correct_output):
                passed_visible = False

            # Function returns None
            if output is None:
                if function_contains_print:
                    print(msg_dict['none_plus_print'])
                else:
                    print(msg_dict['none'])
                break

            # Function returns the wrong data type (but not None)
            elif not(type(output) == type(correct_output)):
                msg = msg_dict['wrong_type']
                msg += str(type(output)) + '\n'
                print(msg)
                break

            # Function returns correct data type, but wrong
            elif not nested_type_sensitive_equals(output, correct_output):
                msg = msg_dict['correct_type_but_wrong']
                print(msg)
                print_inputs_and_output(inputs, output, correct_output, arg_names, function_name)
                break

            # Function modifies inputs (and is not supposed to do that)
            if inputs != orig_inputs and error_msg_if_modifies_input != '':
                passed_visible = False
                msg = msg_dict[error_msg_if_modifies_input]
                print(msg)
                print_modified_inputs(inputs, orig_inputs, arg_names, function_name)
                break

        # Function reproduces example printouts. Test hidden tests too.
        if passed_visible:

            # Loop over hidden tests
            passed_hidden = True
            for inputs, correct_output in zip(hidden_inputs, hidden_correct_output):

                # Call function
                process_args = (module, module_to_import, function_name, inputs)
                process = multiprocessing.Process(target=run_student_fun, args=process_args)
                process.start()
                process.join(5)
                if process.is_alive():
                    passed_hidden = False
                    print(msg_dict['timeout_error'])
                    process.kill()
                    process.join()
                    break
                output = imported_function(*inputs)

                # Failed a hidden test
                if not nested_type_sensitive_equals(output, correct_output):
                    passed_hidden = False
                    msg = msg_dict['hardcoded']
                    print(msg)
                    print_inputs_and_output(inputs, output, correct_output, arg_names, function_name)
                    break

            if passed_hidden:
                print(msg_dict['passed'])

        print(end_of_test_str)

if __name__ == '__main__':

    #--- Excercise details ---
    modules = ['A9']
    function_name = 'alternative_splicing'
    correct_output_type = 'tuple'
    module_to_import = 'math'
    arg_names = ['u']
    forbidden_functions = []
    #-----------------------------------

    #--- Lists of inputs and correct outputs for visible tests ---
    visible_inputs = [[[10, 9, -2, 16, 18, -13, 12, 18, -14]], [[-14, -20, -12, 3, -20, 7, -6, 13]]]
    visible_correct_output = [([10, 16, 12], [9, 18, 18], [-2, -13, -14]), ([-14, 3, -6], [-20, -20, 13], [-12, 7])]
    #-----------------------------------

    #--- Lists of inputs and correct outputs for hidden tests ---
    w = list(range(11, 11+13))
    hidden_inputs = [[w]]
    hidden_correct_output = [([11, 14, 17, 20, 23], [12, 15, 18, 21], [13, 16, 19, 22])]
    #-----------------------------------

    run_tests(modules, correct_output_type, function_name, module_to_import,
                visible_inputs, visible_correct_output, hidden_inputs, hidden_correct_output,
                arg_names, forbidden_functions)

If you do use any of the testing scripts on this page, we would greatly appreciate if you could provide some feedback by filling out this 1-minute anonymous survey.

Example solutions
# Alternative 1: using slicing
def alternative_splicing(seq):
    lst1 = seq[::3]
    lst2 = seq[1::3]
    lst3 = seq[2::3]
    return lst1, lst2, lst3

# Alternative 2: using a for loop and the modulo operator
def alternative_splicing(seq):
    lst1 = []
    lst2 = []
    lst3 = []
    for i in range(len(seq)):
        if i % 3 == 0:
            lst1.append(seq[i])
        elif i % 3 == 1:
            lst2.append(seq[i])
        else:
            lst3.append(seq[i])
    return lst1, lst2, lst3
Common mistakes
  • Hard-coded solutions that only work for lists with 8 or 9 elements

  • Returning the wrong data type, e.g. a string or a list instead of a tuple.

  • print instead of return


A10#

Write a function dna_to_rna(dna) that transcribes a string representing a DNA sequence to RNA and returns the RNA string. You can assume that the DNA string only contains the characters ’A’, ’C’, ’G’ and ’T’. For simplicity, assume that the Thymine (T) base is always transcribed to Uracil (U) while the other bases (A, C, G) remain unchanged.

The following code:

dna = 'GTCCCTAGGGGACTCACAATTGAAGTGGCA'
rna = dna_to_rna(dna)
print('DNA: ', dna)
print('RNA: ', rna)

should produce the printout

DNA:  GTCCCTAGGGGACTCACAATTGAAGTGGCA
RNA:  GUCCCUAGGGGACUCACAAUUGAAGUGGCA
Want to check whether your solution is correct? Save it as A10.py. Then create another file in the same folder, copy-paste the code below into it, save it as A10_test.py and run it.
import importlib
import copy
import re
import multiprocessing

def read_py_as_text(filename):
    """Return file contents as str"""
    with open(filename, 'r') as pyfile:
        lines_of_text = pyfile.readlines()
    return lines_of_text

def remove_comments(lines_of_text):
    return [re.sub(r'#.*$', '', line) for line in lines_of_text]

def file_contains_function(filename, function_name):
    """Check if file contains a function call"""
    lines_of_text = read_py_as_text(filename)
    lines_without_comments = remove_comments(lines_of_text)
    text = ''.join(lines_without_comments)
    search_str = function_name + r'\(.*\)'
    list_of_calls = re.findall(search_str, text)
    return list_of_calls != []

def nested_type_sensitive_equals(val1, val2):
    if not val1 == val2:
        return False
    elif not type(val1) == type(val2):
        return False
    elif type(val1) == list or type(val1) == tuple:
        for el1, el2 in zip(val1, val2):
            if not nested_type_sensitive_equals(el1, el2):
                return False
    return True

def error_message_dict(module, correct_output_type, function_name):
    """Return a dict of all basic error messages"""
    msg_dict = {}

    msg_dict['passed'] = """
    WELL DONE! Your solution passed all the tests.
    """

    msg_dict['print_warning'] = """
    WARNING! Your function contains a print statement.
    This is nearly always a bad idea, unless the purpose
    of the function is to print something.
    """

    msg_dict['forbidden_function'] = """
    ERROR! Your function uses the following
    function/method, which is not allowed here: """

    msg_dict['none'] = f"""
    ERROR! Your function returns None.
    This probably means that you forgot a return statement.
    """

    msg_dict['none_plus_print'] = f"""
    ERROR! Your function returns None.
    This probably means that you forgot a return statement.
    Perhaps your function prints the {correct_output_type} instead of returning it?
    """

    msg_dict['wrong_type'] = f"""
    ERROR! Your function does not return a {correct_output_type}.
    Instead, it returns a value of type: """

    msg_dict['correct_type_but_wrong'] = f"""
    ERROR! Your function returns a {correct_output_type},
    but the {correct_output_type} is not quite correct.
    Run the example code and compare your printout to the expected printout.
    """

    msg_dict['hardcoded'] = f"""
    ERROR! Your function works for the examples in the exercise text,
    but not when other values are used as input parameters.
    Ensure that your function works correctly for general inputs.
    """

    msg_dict['no_module'] = f"""
    No module named {module} was found. Check that you saved your
    function in {module}.py and that this test is located
    in the same directory as {module}.py.
    """

    msg_dict['attribute_error'] = f"""
    The module {module} does not contain a function
    named {function_name}. Check for spelling
    mistakes in the function name.
    """

    msg_dict['timeout_error'] = f"""
    Your function did not return within 5 seconds.
    The most likely explanation for this is that you have created an
    infinite loop. Double-check any while loops!
    """

    msg_dict['modifies_input_dict_with_lists'] = f"""
    ERROR! Your function modifies the input dictionary.
    Make sure to:
    1. create a new dictionary instead of modifying the input dictionary
    2. create (sorted) copies of the lists in the original dictionary
    instead of sorting the original lists.
    """

    return msg_dict

def print_inputs_and_output(inputs, output, correct_output, arg_names, function_name):
    if type(output) == str:
        output = "'" + output + "'"

    if type(correct_output) == str:
        correct_output = "'" + correct_output + "'"

    print('    The following call:\n')
    for index, arg in enumerate(inputs):
        if type(arg) == str:
            arg = "'" + arg + "'"
        print(f'    {arg_names[index]} = {arg}')
    arg_string = '(' + ', '.join(arg_names) + ')'
    print(f'    {function_name}{arg_string}')
    print(f'\n    returns: {output}.')
    print(f'\n    The correct value is: {correct_output}.\n')

def print_modified_inputs(inputs, orig_inputs, arg_names, function_name):
    print(f'    Before calling {function_name}:')
    for index, arg in enumerate(orig_inputs):
        if type(arg) == str:
            arg = "'" + arg + "'"
        print(f'    {arg_names[index]}: {arg}')

    print(f'\n    After calling {function_name}:')
    for index, arg in enumerate(inputs):
        if type(arg) == str:
            arg = "'" + arg + "'"
        print(f'    {arg_names[index]}: {arg}')
        print('')

def run_student_fun(module, module_to_import, function_name, inputs):

    # Import student module
    imported_module = importlib.import_module(module)

    # Import any additional python modules in case the student left that code out
    imported_extra_module = importlib.import_module(module_to_import)
    exec(f'imported_module.{module_to_import} = imported_extra_module')

    # Get student function from the imported module
    imported_function = getattr(imported_module, function_name)
    output = imported_function(*inputs)

def run_tests(modules, correct_output_type, function_name, module_to_import,
            visible_inputs, visible_correct_output, hidden_inputs, hidden_correct_output,
            arg_names, forbidden_functions=[], error_msg_if_modifies_input=''):
    """Run visible and hidden tests"""

    function_contains_print = False
    function_contains_forbidden_func = False
    end_of_test_str = '--- End of test ---\n'

    # Loop over modules only needed when developing tests
    for module in modules:

        msg_dict = error_message_dict(module, correct_output_type, function_name)

        print(f'--- Testing {module} ---')

        # Import student module
        try:
            imported_module = importlib.import_module(module)
        except ModuleNotFoundError:
            print(msg_dict['no_module'])
            print(end_of_test_str)
            continue

        # Check for print statements
        if file_contains_function(module + '.py', 'print'):
            print(msg_dict['print_warning'])
            function_contains_print = True

        # Check for forbidden function calls
        for func in forbidden_functions:
            if file_contains_function(module + '.py', func):
                function_contains_forbidden_func = True
                msg = msg_dict['forbidden_function'] + func + '.'
                print(msg)
        if function_contains_forbidden_func:
            print('\n' + end_of_test_str)
            continue

        # Import any additional python modules in case the student left that code out
        imported_extra_module = importlib.import_module(module_to_import)
        exec(f'imported_module.{module_to_import} = imported_extra_module')

        # Get student function from the imported module
        try:
            imported_function = getattr(imported_module, function_name)
        except AttributeError:
            print(msg_dict['attribute_error'])
            print(end_of_test_str)
            continue

        # Loop over all visible tests
        passed_visible = True
        for orig_inputs, correct_output in zip(visible_inputs, visible_correct_output):
            inputs = copy.deepcopy(orig_inputs)
            process_inputs = copy.deepcopy(orig_inputs)

            # Call function
            process_args = (module, module_to_import, function_name, process_inputs)
            process = multiprocessing.Process(target=run_student_fun, args=process_args)
            process.start()
            process.join(5)
            if process.is_alive():
                passed_visible = False
                print(msg_dict['timeout_error'])
                process.terminate()
                process.join()
                break
            output = imported_function(*inputs)

            if not nested_type_sensitive_equals(output, correct_output):
                passed_visible = False

            # Function returns None
            if output is None:
                if function_contains_print:
                    print(msg_dict['none_plus_print'])
                else:
                    print(msg_dict['none'])
                break

            # Function returns the wrong data type (but not None)
            elif not(type(output) == type(correct_output)):
                msg = msg_dict['wrong_type']
                msg += str(type(output)) + '\n'
                print(msg)
                break

            # Function returns correct data type, but wrong
            elif not nested_type_sensitive_equals(output, correct_output):
                msg = msg_dict['correct_type_but_wrong']
                print(msg)
                print_inputs_and_output(inputs, output, correct_output, arg_names, function_name)
                break

            # Function modifies inputs (and is not supposed to do that)
            if inputs != orig_inputs and error_msg_if_modifies_input != '':
                passed_visible = False
                msg = msg_dict[error_msg_if_modifies_input]
                print(msg)
                print_modified_inputs(inputs, orig_inputs, arg_names, function_name)
                break

        # Function reproduces example printouts. Test hidden tests too.
        if passed_visible:

            # Loop over hidden tests
            passed_hidden = True
            for inputs, correct_output in zip(hidden_inputs, hidden_correct_output):

                # Call function
                process_args = (module, module_to_import, function_name, inputs)
                process = multiprocessing.Process(target=run_student_fun, args=process_args)
                process.start()
                process.join(5)
                if process.is_alive():
                    passed_hidden = False
                    print(msg_dict['timeout_error'])
                    process.kill()
                    process.join()
                    break
                output = imported_function(*inputs)

                # Failed a hidden test
                if not nested_type_sensitive_equals(output, correct_output):
                    passed_hidden = False
                    msg = msg_dict['hardcoded']
                    print(msg)
                    print_inputs_and_output(inputs, output, correct_output, arg_names, function_name)
                    break

            if passed_hidden:
                print(msg_dict['passed'])

        print(end_of_test_str)

if __name__ == '__main__':

    #--- Excercise details ---
    modules = ['A10']
    function_name = 'dna_to_rna'
    correct_output_type = 'string'
    module_to_import = 'math'
    arg_names = ['dna']
    forbidden_functions = []
    #-----------------------------------

    #--- Lists of inputs and correct outputs for visible tests ---
    visible_inputs = [['GTCCCTAGGGGACTCACAATTGAAGTGGCA']]
    visible_correct_output = ['GUCCCUAGGGGACUCACAAUUGAAGUGGCA']
    #-----------------------------------

    #--- Lists of inputs and correct outputs for hidden tests ---
    hidden_inputs = [['ACAATTGATG']]
    hidden_correct_output = ['ACAAUUGAUG']
    #-----------------------------------

    run_tests(modules, correct_output_type, function_name, module_to_import,
                visible_inputs, visible_correct_output, hidden_inputs, hidden_correct_output,
                arg_names, forbidden_functions)

If you do use any of the testing scripts on this page, we would greatly appreciate if you could provide some feedback by filling out this 1-minute anonymous survey.

Example solutions
# Alternative 1: looping over all characters
def dna_to_rna(dna):
    rna = ''
    for char in dna:
        if char == 'T':
            rna += 'U'
        else:
            rna += char
    return rna

# Alternative 2: using the string method replace
def dna_to_rna(dna):
    rna = dna.replace('T', 'U')
    return rna
Common mistakes
  • Incorrect use of global variables

  • print instead of return