Oplossingen#

Dit zijn de antwoorden op de opgaven in het Proeftentamen PGM2 en we nemen je stap voor stap mee in de uitwerkingen.

Belangrijk

Let op, de opgaven kunnen op verschillende manieren worden opgelost, het kan zijn dat jij sommige dingen anders hebt geschreven of andere stappen hebt gevolgd en dat is prima!

Opgave 1 Extensies#

Deze opgave gaat over het recursief controleren van karakters op een specifieke positie.

def check_extension(s, e):
    if s == "":  # of len(s) == 0
        return False

    if e == "":
        return True

    if s[-1] != e[-1]:
        return False
    else:
        return check_extension(s[:-1], e[:-1])


assert check_extension("tentamen.docx", ".exe") is False
assert check_extension("program.exe", ".exe") is True
assert check_extension("wk8ex1.py", ".py") is True

Stap voor stap lopen we na wat hier gebeurt:

  1. Nogmaals, de functie check_extension(s, e) controleert of bestandsnaam s eindigt met extensie e

  2. Er zijn vier situaties die we controleren:

    if s == "":  # Situatie 1
        return False
    

    Als de bestandsnaam leeg is, kan het niet eindigen met de extensie, dus: False (een eerste base case)

    if e == "":  # Situatie 2
        return True
    

    Als de extensie leeg is (we hebben alle letters gecheckt), dan eindigt het bestand met de extensie, dus: True (een tweede base case)

    if s[-1] != e[-1]:  # Situatie 3
        return False
    

    Als de laatste letters niet gelijk zijn, kan het bestand niet eindigen met de extensie, dus: False (een derde base case)

    else:  # Situatie 4
        return check_extension(s[:-1], e[:-1])
    

    Als de laatste letters wél gelijk zijn, controleren we de rest van de strings door de functie opnieuw aan te roepen zonder de laatste letters (de recursieve case)

  3. Voorbeeld met “program.exe” en “.exe”:

    • Eerst vergelijkt het ‘e’ met ‘e’ → gelijk

    • Dan “program.ex” met “.ex”

    • Dan “program.e” met “.e”

    • Dan “program.” met “.”

    • Dan “program” met “” (lege extensie)

    • Dit geeft True terug, dus het bestand eindigt met .exe

Opgave 2 Even op vele manieren#

Deze opgave vraagt jou om met behulp van drie verschillende technieken een lijst samen te stellen.

def only_even_loop(L):
    result = []

    for e in L:
        if e % 2 == 0:
            result += [e]

    return result


def only_even_lc(L):
    return [e for e in L if e % 2 == 0]


def only_even_rec(L):
    if L == []:
        return L

    if L[0] % 2 == 0:
        return [L[0]] + only_even_rec(L[1:])
    else:
        return only_even_rec(L[1:])


assert only_even_loop([0, 1, 2, 3, 4]) == [0, 2, 4]
assert only_even_lc([0, 1, 2, 3, 4]) == [0, 2, 4]
assert only_even_rec([0, 1, 2, 3, 4]) == [0, 2, 4]

De drie verschillende manieren om hetzelfde probleem op te lossen: het vinden van alle even getallen in een lijst.

  1. De lus-manier:

def only_even_loop(L):
    result = []            # We beginnen met een lege lijst
    for e in L:            # We nemen steeds één getal uit de lijst
        if e % 2 == 0:     # Als het getal even is
            result += [e]  # stoppen we het in onze nieuwe lijst
    return result          # Als we klaar zijn geven we de nieuwe lijst terug

Dit is zoals het sorteren van knikkers: je pakt er één, kijkt of die even is, en als dat zo is stop je hem in een nieuwe zak. Dit doe je tot alle knikkers op zijn.

  1. De list comprehension-manier:

def only_even_lc(L):
    return [e for e in L if e % 2 == 0]

Dit is alsof je tegen Python zegt: “Maak een nieuwe lijst voor mij. Kijk naar alle getallen in L, en als een getal even is, stop het dan in de nieuwe lijst”. Het is eigenlijk precies hetzelfde als de lus-manier, maar dan in één regel geschreven.

  1. De recursieve manier:

def only_even_rec(L):
    if L == []:        # Als de lijst leeg is
        return L       # zijn we klaar
    
    if L[0] % 2 == 0:  # Is het eerste getal even?
        return [L[0]] + only_even_rec(L[1:])  # Ja: bewaar het en kijk naar de rest
    else:
        return only_even_rec(L[1:])           # Nee: kijk alleen naar de rest

Dit is als een stapel kaarten: je kijkt naar de bovenste kaart, beslist of die in je nieuwe stapel moet, en geeft de rest van de stapel door aan een vriend die precies hetzelfde doet. Jullie blijven dit doen tot de stapel op is.

Het verschil zit vooral in hoe je het probleem aanpakt:

  • Bij de lus ga je stap voor stap door de getallen heen, zoals wanneer je zelf knikkers sorteert

  • Bij list comprehension vertel je Python in één keer wat je wilt, zoals een bestelling opgeven

  • Bij recursie deel je het werk op in kleine stapjes en lost elke stap een klein deel op, zoals bij het doorgeven van de kaarten

Ze doen allemaal precies hetzelfde, maar op een andere manier. Het is net als verschillende routes naar school: je kunt fietsen, lopen of de bus nemen - je komt uiteindelijk op dezelfde plek uit!

Opgave 3 Bonus#

Deze opgave vraagt jou een list comprehension toe te passen met een conditie. Wat deze opgave lastig maakt is dat het gaat om een lijst van lijsten die moet worden samengesteld.

L = [
    ["0308230", 7.6, True],
    ["8273927", 5.1, False],
    ["8234987", 6.4, False],
    ["2368612", 5.9, True],
    ["9731827", 3.2, False],
]

HW = [[e[0], e[1] + 0.5] for e in L if e[2] is True]

assert HW == [["0308230", 8.1], ["2368612", 6.4]]

Laten we de regel

HW = [[e[0], e[1] + 0.5] for e in L if e[2] is True]

stap voor stap doornemen:

  1. for e in L:

    • We nemen één voor één elke student uit de lijst L

    • e wordt dan bijvoorbeeld ["0308230", 7.6, True]

  2. if e[2] == True:

    • We kijken naar het derde element (positie 2) van de studentgegevens

    • Dit vertelt ons of het huiswerk is gemaakt

    • Alleen als dit True is, nemen we deze student mee

  3. [e[0], e[1] + 0.5]:

    • Voor elke student die zijn huiswerk heeft gemaakt, maken we een nieuwe lijst met:

      • e[0]: Het studentnummer (eerste element)

      • e[1] + 0.5: Het cijfer (tweede element) plus 0.5 punt bonus

  4. De vierkante haken [ ] om de hele expressie maken er een list comprehension van.

Opgave 4 Nim#

Deze opgave vraagt jou om methoden in een klasse verder uit te werken. Het gebruik van de klasse pas je toe in een lus.

from random import choice


class Nim:
    def __init__(self, number_of_sticks):
        self.sticks = number_of_sticks

    def player_takes(self, player_turn):
        # verminder het aantal stokjes met de hoeveelheid die de speler pakt
        self.sticks -= player_turn

    def AI_turn(self):
        if self.sticks <= 3:
            # als er 3 of minder stokjes zijn, pak ze allemaal
            return self.sticks

        # bereken hoeveel stokjes we moeten nemen om een meervoud van 4 over te houden
        remainder = self.sticks % 4
        if remainder == 0:
            # als het al een meervoud van 4 is, kies random
            return choice([1, 2, 3])
        else:
            # pak precies genoeg om een meervoud van 4 over te houden
            return remainder

    def game_over(self):
        # het spel is voorbij als er geen stokjes meer zijn
        return self.sticks <= 0  # geeft True of False terug


game = Nim(16)

while True:
    print("Aantal stokjes op tafel: ", game.sticks)

    player_turn = int(input("Hoeveel stokjes pak je? "))

    if player_turn not in [1, 2, 3]:
        print("Gekozen aantal is niet toegestaan ...")
        continue

    # de beurt van de speler
    game.player_takes(player_turn)

    if game.game_over():
        print("Jij wint")
        break

    # de beurt van de computer
    ai_takes = game.AI_turn()
    game.player_takes(ai_takes)
    print(f"Computer pakt {ai_takes} stokje(s)")

    if game.game_over():
        print("Computer wint")
        break

De uitleg, stap voor stap:

  1. De functie player_takes:

  • De student moet een functie maken die een parameter player_turn accepteert

  • Deze functie moet het aantal lucifers verminderen met het aantal dat de speler pakt

  • Dit doe je door self.sticks -= player_turn te schrijven

  • Dit is een korte maar belangrijke functie die de basis vormt van het spel

  1. De functie AI_turn:

  • Deze functie moet bepalen hoeveel lucifers de AI pakt

  • Bij 3 of minder lucifers: pak alles wat er ligt (return self.sticks)

  • Anders: bereken met % hoeveel er over zijn na deling door 4

  • Als het al deelbaar is door 4: kies willekeurig met choice([1, 2, 3])

  • Anders: pak precies genoeg om weer op een meervoud van 4 uit te komen

  1. De functie game_over:

  • Deze functie moet controleren of het spel voorbij is

  • Dit doe je door te kijken of er nog lucifers over zijn

  • De functie moet True teruggeven als er geen lucifers meer zijn (self.sticks <= 0)

  • Anders geeft de functie False terug

  • Dit kan in één regel: return self.sticks <= 0

  1. De hoofdlus afmaken:

  • Onder het commentaar moet de beurt van de computer worden toegevoegd

  • Dit bestaat uit drie stappen:

    1. Vraag hoeveel lucifers de AI wil pakken: ai_takes = game.AI_turn()

    2. Voer deze zet uit: game.player_takes(ai_takes)

    3. Toon wat de computer heeft gepakt

  • Daarna moet je controleren of de computer heeft gewonnen

Deze opdeling in subvragen helpt de student om het probleem in kleinere, behapbare stukken op te delen. Elke subvraag bouwt voort op de vorige en samen vormen ze een werkend spel.

Opgave 5 Alfabetisch#

Deze opgave vraagt jou om recursief te bepalen of een eerstvolgend karakter later in het alfabet voorkomt.

def alfabet_word(s):
    if len(s) == 1:
        return True

    if s[0] > s[1]:
        return False
    else:
        return alfabet_word(s[1:])


assert alfabet_word("b") is True
assert alfabet_word("bel") is True
assert alfabet_word("adem") is True
assert alfabet_word("python") is False

Stap voor stap:

  1. Eerst kijken we naar de functie definitie:

def alfabet_word(s):

Deze functie accepteert één parameter s, wat een string (woord) is dat we gaan controleren.

  1. De eerste controle in de functie is:

if len(s) == 1:
    return True

Dit is wat we noemen een “base case” (of basisgeval) voor recursie. Als het woord maar 1 letter lang is, dan staan de letters automatisch in alfabetische volgorde (er is immers maar één letter). Daarom geven we True terug.

  1. De volgende controle is:

if s[0] > s[1]:
    return False

Hier vergelijken we de eerste letter (s[0]) met de tweede letter (s[1]). In Python kunnen we letters direct vergelijken - als de eerste letter later in het alfabet komt dan de tweede letter, dan is de volgorde niet correct en geven we False terug. Dit is ook een base case, want in dit geval zijn we klaar en hebben niets meer door te geven.

  1. Als de letters wel in de juiste volgorde staan, gaan we verder met:

else:
    return alfabet_word(s[1:])

Dit is het recursieve deel. We roepen dezelfde functie opnieuw aan, maar nu met een kleiner deel van het woord: s[1:] betekent “alle letters vanaf positie 1” (dus zonder de eerste letter).

Laten we dit bekijken met een voorbeeld voor het woord “bel”:

  1. Eerste aanroep met “bel”:

    • Is lengte 1? Nee (lengte is 3)

    • Is ‘b’ > ‘e’? Nee (‘b’ komt eerder in alfabet)

    • Roep functie aan met “el”

  2. Tweede aanroep met “el”:

    • Is lengte 1? Nee (lengte is 2)

    • Is ‘e’ > ‘l’? Nee (‘e’ komt eerder in alfabet)

    • Roep functie aan met “l”

  3. Derde aanroep met “l”:

    • Is lengte 1? Ja!

    • Return True

Het True resultaat wordt nu “teruggeborreld” door alle aanroepen heen, waardoor we uiteindelijk True krijgen voor het hele woord “bel”.

Als we dit zouden doen met “test”:

  1. Eerste aanroep met “test”:

    • Is lengte 1? Nee

    • Is ‘t’ > ‘e’? Ja! (‘t’ komt later in alfabet)

    • Return False direct

Dit verklaart ook waarom de assertions correct zijn:

assert alfabet_word("b") is True        # één letter is altijd True
assert alfabet_word("bel") is True      # b->e->l gaat omhoog in alfabet
assert alfabet_word("adem") is True     # a->d->e->m gaat omhoog in alfabet
assert alfabet_word("python") is False  # p->y gaat omhoog, maar y->t gaat omlaag

Opgave 6 Moeilijke woorden#

Deze opgave vraagt jou om in een paar stappen gegevens te reduceren (tekst naar zinnen en zinnen naar woorden) en vervolgens op basis daarvan met een eenvoudige formule een resultaat te berekenen. We kiezen hier voor een enkele methode (geen hulpfunctie) en list comprehension omdat we hier veel moeten werken met lijsten (lijsten met zinnen en lijsten met woorden). Let ook op dat we ook geneste lussen binnen een list comprehensions gebruiken (bijvoorbeeld bij all_words). Andere oplossingen zijn natuurlijk ook mogelijk en daar kijken we later naar.

def complexity_score(text):
    # Split text in to sentences
    sentences = text.split(".")[:-1]

    # Split sentences in to words
    all_words = [word for sentence in sentences for word in sentence.split()]

    # Count complex sentences
    complex_sentences = [
        sentence for sentence in sentences if len(sentence.split()) > 15
    ]

    # Count complex words
    complex_words = [word for word in all_words if len(word) > 10]

    # Calculate score
    total_elements = len(sentences) + len(all_words)
    total_complex = len(complex_words) + len(complex_sentences)

    # See if we have any elements to avoid division by 0
    if total_elements > 0:
        return (total_complex / total_elements) * 100
    else:
        return 0.0
assert round(complexity_score("Dit is een korte zin."), 1) == 0.0
# Uitleg:
# Woorden (5): Dit(1) is(2) een(3) korte(4) zin(5)
# Zinnen (1): 1 zin van 5 woorden (< 15, dus niet complex)
# Score: 0 complexe elementen / 6 totale elementen = 0.0%

assert (
    round(
        complexity_score(
            "Dit is een gecompliceerde zin met veel verschillende woorden erin verstopt."
        ),
        1,
    )
    == 16.7
)
# Uitleg:
# Woorden (11): Dit(1) is(2) een(3) gecompliceerde(4) zin(5) met(6) veel(7)
#               verschillende(8) woorden(9) erin(10) verstopt(11)
# Zinnen (1): 1 zin van 11 woorden (< 15, dus niet complex)
# Complex: gecompliceerde(13 letters), verschillende(13 letters)
# Score: 2 complexe woorden / 12 totale elementen ≈ 16.7%

assert (
    round(complexity_score("De intelligentie van computerprogramma's neemt toe."), 1)
    == 28.6
)
# Uitleg:
# Woorden (6): De(1) intelligentie(2) van(3) computerprogramma's(4)
#              neemt(5) toe(6)
# Zinnen (1): 1 zin van 6 woorden (< 15, dus niet complex)
# Complex: intelligentie(12 letters), computerprogramma's(16 letters)
# Score: 2 complexe woorden / 7 totale elementen ≈ 28.6%

assert (
    round(
        complexity_score(
            "Programmeren is leuk. Dit is een zeer lange zin die meer dan vijftien woorden bevat en daarom als complex wordt beschouwd bij deze analyse. Nog een zin."
        ),
        1,
    )
    == 6.7
)
# Uitleg:
# Zin 1 (3): Programmeren(1) is(2) leuk(3)
# Zin 2 (21): Dit(1) is(2) een(3) zeer(4) lange(5) zin(6) die(7) meer(8) dan(9)
#             vijftien(10) woorden(11) bevat(12) en(13) daarom(14) als(15)
#             complex(16) wordt(17) beschouwd(18) bij(19) deze(20) analyse(21)
# Zin 3 (3): Nog(1) een(2) zin(3)
# Complex: programmeren(11 letters), en zin 2 (21 woorden > 15)
# Score: 2 complexe elementen / 30 totale elementen ≈ 6.7%

De oplossing stap voor stap uitgelegd:

  1. Eerst kijken we naar de functiedefinitie (ook wel de signatuur genoemd):

def complexity_score(text):

Dit maakt een functie die één parameter accepteert: de tekst die we willen analyseren.

  1. Het splitsen van de tekst in zinnen:

sentences = text.split(".")[:-1]

Dit is zoals het je is verteld in de opgave, deel een tekst op in zinnen op basis van punten. Wat na de laatste punt komt zal split() beschouwen als een lege zin en dit is vervelend, vandaar het gebruik van list slicing om met [:-1] alle zinnen behalve de laatste (lege) zin over te houden.

Als je tekst hebt zoals “Hallo. Doei.” krijg je een lijst met twee zinnen: [“Hallo”, “ Doei”].

  1. Het splitsen van alle zinnen in woorden:

all_words = [word for sentence in sentences for word in sentence.split()]

Dit is een geneste list comprehension die twee dingen doet:

  • Voor elke zin (for sentence in sentences)

  • Split die zin in woorden (for word in sentence.split()) Het resultaat is één grote lijst met alle woorden uit alle zinnen.

  1. Het vinden van complexe zinnen:

complex_sentences = [sentence for sentence in sentences if len(sentence.split()) > 15]

Deze list comprehension vindt alle zinnen met meer dan 15 woorden:

  • Voor elke zin (for sentence in sentences)

  • Tel het aantal woorden (len(sentence.split()))

  • Neem de zin alleen op als het meer dan 15 woorden heeft (if len(sentence.split()) > 15)

  1. Het vinden van complexe woorden:

complex_words = [word for word in all_words if len(word) > 10]

Deze list comprehension vindt alle woorden die langer zijn dan 10 letters:

  • Voor elk woord in onze lijst met alle woorden (for word in all_words)

  • Tel de letters (len(word))

  • Neem het woord alleen op als het meer dan 10 letters heeft (if len(word) > 10)

  1. De score berekenen:

total_elements = len(sentences) + len(all_words)
total_complex = len(complex_words) + len(complex_sentences)

We tellen:

  • Totaal aantal elementen = aantal zinnen + aantal woorden

  • Totaal aantal complexe elementen = aantal complexe woorden + aantal complexe zinnen

  1. De uiteindelijke berekening:

if total_elements > 0:
    return (total_complex / total_elements) * 100
else:
    return 0.0

Als er elementen zijn (om delen door nul te voorkomen):

  • Deel het aantal complexe elementen door het totaal aantal elementen

  • Vermenigvuldig met 100 om een percentage te krijgen

Meerdere oplossingen zijn mogelijk, bijvoorbeeld met gewone lussen:

def complexity_score(text):
    # Split text into sentences
    sentences = []
    for sentence in text.split(".")[:-1]:
        # Only add non-empty sentences
        if sentence:
            sentences.append(sentence)
    
    # Get all words from all sentences
    all_words = []
    for sentence in sentences:
        words = sentence.split()
        for word in words:
            all_words.append(word)
    
    # Find complex sentences (more than 15 words)
    complex_sentences = []
    for sentence in sentences:
        words_in_sentence = sentence.split()
        if len(words_in_sentence) > 15:
            complex_sentences.append(sentence)
    
    # Find complex words (more than 10 letters)
    complex_words = []
    for word in all_words:
        if len(word) > 10:
            complex_words.append(word)
    
    # Calculate total elements and complex elements
    total_elements = len(sentences) + len(all_words)
    total_complex = len(complex_words) + len(complex_sentences)
    
    # Calculate and return score
    if total_elements > 0:
        return (total_complex / total_elements) * 100
    else:
        return 0.0