2022-10-21, Del 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}'

Betrakta koden ovan. Vilket alternativ använder korrekt terminologi för de olika komponenterna i koden?

Alternativ 1:

  • moduler: math, self

  • klasser: Rectangle

  • funktioner: sqrt, cos

  • metoder: __init__(), __str__(), diag1, diag2, angles

  • attribut: height, width, area

Alternativ 2:

  • moduler: math

  • klasser: Rectangle

  • funktioner: sqrt, cos, area, to_parallelogram()

  • metoder: __init__(), to_parallelogram(), __str__()

  • attribut: height, width, area, angles, diag1, diag2

Alternativ 3:

  • moduler: math

  • klasser: Rectangle

  • funktioner: sqrt, cos

  • metoder: __init__(), to_parallelogram(), __str__()

  • attribut: height, width, area, angles, diag1, diag2

Alternativ 4:

  • moduler: math

  • klasser: Rectangle

  • funktioner: sqrt, cos, to_parallelogram()

  • metoder: __init__(), __str__()

  • attribut: height, width, area, angles, diag1, diag2, self

Rätt svar

Alternativ 3


A2#

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

Betrakta koden ovanför. För varje påstående nedanför, avgör om det är sant eller falskt.

  1. sqrt och Turtle är en funktion respektive en klass från modulerna math respektive turtle.

  2. sqrt och Turtle är funktioner från modulerna math respektive turtle.

  3. rd och plt är funktioner från modulerna random respektive matplotlib.pyplot.

  4. rd och plt är import-alias till modulerna random respektive matplotlib.pyplot

Du måste svara rätt på alla 4 påståenden för att få 1 poäng på den här uppgiften.

Rätt svar

1: sant. 2: falskt. 3: falskt. 4: sant.


A3#

Skriv en funktion number_of_occurrences(interesting_words, text) som undersöker hur många gånger orden i listan interesting_words förekommer i strängen text. Funktionen ska returnera ett lexikon med orden som nycklar och antalet förekomster som värden.

Tips: Anropet re.findall(r'[a-zA-ZåäöÅÄÖ]+', text) returnerar en lista med alla ord i strängen text.

Följande kod:

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)')

ska ge utskriften

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)
Vill du testa om din lösning är korrekt? Spara din lösning i A3.py. Skapa sedan en ny fil i samma mapp, kopiera in koden nedan, spara filen som t.ex. A3_test.py och kör den.
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)
Lösningsförslag
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
Vanliga fel
  • print istället för return

  • Tar med alla ord, inte bara de i interesting words.


A4#

Skriv en funktion bounded_squares(n) som tar ett heltal \( n > 0\) som parameter och returnerar en lista med alla positiva heltal \(p\) som uppfyller \(n^2 < p^2 < 3n^2\).

Följande kod:

print(bounded_squares(1))
print(bounded_squares(2))
print(bounded_squares(5))

ska ge utskriften

[]
[3]
[6, 7, 8]
Vill du testa om din lösning är korrekt? Spara din lösning i A4.py. Skapa sedan en ny fil i samma mapp, kopiera in koden nedan, spara filen som t.ex. A4_test.py och kör den.
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)
Lösningsförslag
# Förslag 1: med while-loop
def bounded_squares(n):
    outlist = []
    p = n + 1
    while p**2 < 3*n**2:
        outlist.append(p)
        p += 1
    return outlist

# Förslag 2: börjar med att bestämma minsta och största p som kan vara med. 
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)]
Vanliga fel
  • print istället för return

  • Hårdkodad övre gräns för p, t.ex. p=10 eller p=100


A5#

Betrakta följande kod.

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)

När denna kod körs erhålls följande kryptiska utskrift:

<__main__.Animal object at 0x7f810813caf0>
<__main__.Animal object at 0x7f8108131be0>
<__main__.Animal object at 0x7f8108131be0>

Lägg till en metod i klassen Animal så att samma print-satser istället producerar följande utskrift:

dog at position (0, 1)
cat at position (3, 4)
cat at position (-1, 4)

Notera att du endast får lägga till en metod i klassen. Du får INTE göra några andra ändringar i koden.

Lösningsförslag
def __str__(self):
    return f'{self.species} at position {self.position}'

A6#

Skriv en funktion min_max_list() som tar en lista med heltal som parameter och returnerar en tupel med listans minsta och största värden. Du får INTE använda något av följande:

  • de inbyggda funktionerna sorted(), min() och max(),

  • list-metoden sort(),

  • klasser eller funktioner från modulerna collections eller numpy.

Följande kod:

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

ska ge utskriften

(-90, 89)
(4, 27)

Tips: Du kan använda följande algoritm för att ta fram listans största tal:

  1. Tilldela listans första tal till en variabel, som vi här kallar x_max.

  2. För nästa tal, x, i listan, om x är större än x_max, sätt x_max = x.

  3. Upprepa steg 2 för resterande tal i listan.

När du är klar med steg 3 bör listans största värde finnas sparat i variabeln x_max. Du kan ta fram listans minsta värde på liknande sätt.

Vill du testa om din lösning är korrekt? Spara din lösning i A6.py. Skapa sedan en ny fil i samma mapp, kopiera in koden nedan, spara filen som t.ex. A6_test.py och kör den.
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)
Lösningsförslag
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
Vanliga fel
  • Returnerar fel datatyp, t.ex. en sträng eller en lista istället för en tupel

  • print istället för return


A7#

Betrakta Fibonacci-talen: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, …, där varje tal i följden (förutom de två första) är summan av de två föregående. Skriv en funktion fibonacci_numbers(limit) som returnerar en lista med alla Fibonacci-tal som är mindre än eller lika med limit.

Följande kod:

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

ska ge utskriften

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

Observera att din funktion måste fungera för ett godtyckligt stort värde på parametern limit.

Vill du testa om din lösning är korrekt? Spara din lösning i A7.py. Skapa sedan en ny fil i samma mapp, kopiera in koden nedan, spara filen som t.ex. A7_test.py och kör den.
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)
Lösningsförslag
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
Vanliga fel
  • Hårdkodade lösningar som bara fungerar för t.ex. limit < 100.

  • Antar att listan kommer innehålla som mest limit antal tal, vilket inte är sant för limit <= 3

  • print istället för return


A8#

Skriv en funktion sorted_dictionary(input_dict) som tar ett lexikon, vars värden är listor med heltal, som parameter och returnerar ett nytt lexikon där listorna är sorterade. Det returnerade lexikonet ska ha samma nycklar som det ursprungliga lexikonet. Det ursprungliga lexikonet ska vara oförändrat efter funktionsanropet.

Följande kod:

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)

ska ge utskriften

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]}

Notera att din funktion ska fungera för lexikon med andra nycklar och listor än i exemplet.

Vill du testa om din lösning är korrekt? Spara din lösning i A8.py. Skapa sedan en ny fil i samma mapp, kopiera in koden nedan, spara filen som t.ex. A8_test.py och kör den.
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)
Lösningsförslag
def sorted_dictionary(input_dict):
    sorted_dict = {}
    for key, value in input_dict.items():
        sorted_dict[key] = sorted(value)
    return sorted_dict
Vanliga fel
  • Felaktigt användande av globala variabler

  • Använder list-metoden sort() för att sortera de befintliga listorna (istället för att skapa nya listor) och sorterar därmed även i det ursprungliga lexikonet

  • print istället för return


A9#

Skriv en funktion alternative_splicing(seq) som delar upp en lista seq på ett speciellt sätt. Funktionen ska returnera en tupel med 3 listor, där varje lista innehåller vart tredje element i seq. Närmare bestämt:

  • Den första returnerade listan innehåller det 1:a, 4:e, 7:e, osv., elementen i seq,

  • Den andra returnerade listan innehåller det 2:a, 5:e, 8:e, osv., elementen i seq,

  • Den tredje returnerade listan innehåller det 3:e, 6:e, 9:e, osv., elementen i seq.

Följande kod:

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))

ska ge utskriften

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

Notera att de tre returnerade listorna inte blir lika långa om antalet element i seq inte är delbart med 3 (se körexemplet med listan v ovan).

Vill du testa om din lösning är korrekt? Spara din lösning i A9.py. Skapa sedan en ny fil i samma mapp, kopiera in koden nedan, spara filen som t.ex. A9_test.py och kör den.
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)
Lösningsförslag
# Förslag 1: med list-skivning
def alternative_splicing(seq):
    lst1 = seq[::3]
    lst2 = seq[1::3]
    lst3 = seq[2::3]
    return lst1, lst2, lst3

# Förslag 2: med loop och modulo-operatorn
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
Vanliga fel
  • Hårdkodade lösningar som bara fungerar för listor med 8 eller 9 element

  • Returnerar fel datatyp, t.ex. en sträng eller en lista istället för en tupel

  • print istället för return


A10#

Skriv en funktion dna_to_rna(dna) som transkriberar en sträng som representerar en DNA-sekvens till RNA och returnerar RNA-strängen. Du kan anta att DNA-strängen endast innehåller tecknen ’A’, ’C’, ’G’ och ’T’. För enkelhets skull antar vi också att basen tymin (T) alltid transkriberas till uracil (U) medan de andra baserna (A, C, G) inte ändras.

Följande kod:

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

ska ge utskriften

DNA:  GTCCCTAGGGGACTCACAATTGAAGTGGCA
RNA:  GUCCCUAGGGGACUCACAAUUGAAGUGGCA
Vill du testa om din lösning är korrekt? Spara din lösning i A10.py. Skapa sedan en ny fil i samma mapp, kopiera in koden nedan, spara filen som t.ex. A10_test.py och kör den.
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)
Lösningsförslag
# Förslag 1: med loop över alla tecken
def dna_to_rna(dna):
    rna = ''
    for char in dna:
        if char == 'T':
            rna += 'U'
        else:
            rna += char
    return rna

# Förslag 2: med sträng-metoden replace
def dna_to_rna(dna):
    rna = dna.replace('T', 'U')
    return rna
Vanliga fel
  • Felaktigt användande av globala variabler

  • print istället för return