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:
Nogmaals, de functie
check_extension(s, e)
controleert of bestandsnaams
eindigt met extensiee
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)
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.
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.
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.
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:
for e in L
:We nemen één voor één elke student uit de lijst
L
e
wordt dan bijvoorbeeld["0308230", 7.6, True]
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
[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
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:
De functie
player_takes
:
De student moet een functie maken die een parameter
player_turn
accepteertDeze functie moet het aantal lucifers verminderen met het aantal dat de speler pakt
Dit doe je door
self.sticks -= player_turn
te schrijvenDit is een korte maar belangrijke functie die de basis vormt van het spel
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 4Als 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
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
terugDit kan in één regel:
return self.sticks <= 0
De hoofdlus afmaken:
Onder het commentaar moet de beurt van de computer worden toegevoegd
Dit bestaat uit drie stappen:
Vraag hoeveel lucifers de AI wil pakken:
ai_takes = game.AI_turn()
Voer deze zet uit:
game.player_takes(ai_takes)
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:
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.
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.
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.
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”:
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”
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”
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”:
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:
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.
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”].
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.
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
)
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
)
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
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