Extra¶
Opdracht: Extra
Opdracht 1¶
a. Schrijf de functie lines(height, space) die twee verticale #-lijnen tekent van height regels hoog, met space spaties ertussen.
def lines(height, space):
"""Tekent twee verticale #-lijnen van height regels met space spaties ertussen"""
if height <= 0:
return
row = "#" + space * " " + "#"
print((row + "\n") * (height - 1) + row)
assert lines(3, 3) is None
assert lines(0, 4) is None
lines(2, 5)
# #
# #
# #
# #
# #
Eén regel van de tekening is altijd hetzelfde: een #, een aantal spaties, en nog een #. Die zet je één keer klaar in row.
Daarna hoef je hem alleen nog height keer af te drukken, en dat kan zonder lus omdat je een string mag vermenigvuldigen. De laatste regel krijgt geen eigen \n mee, want print zet er zelf al een achter.
De controle op height is nodig omdat print_square hieronder ook lines(0, 0) aanroept, en dan hoort er niets te gebeuren.
De twee assertions leggen vast wat er terugkomt en niet wat er getekend wordt: een functie die alleen afdrukt geeft None terug. Naar de tekening moet je kijken, en daarom staat de aanroep lines(2, 5) eronder.
Je ziet hieronder vijf regels verschijnen en niet twee: ook een assertion roept de functie aan, en lines(3, 3) tekent dus gewoon zijn drie regels voordat de vergelijking met None aan de beurt is.
b. Schrijf de functie print_square(size) die een vierkant van # met zijden van size tekens tekent. Maak gebruik van de functie lines.
def print_square(size):
"""Tekent een vierkant van # met zijden van size tekens"""
if size <= 0:
return
if size == 1:
print("#")
return
print(size * "#")
lines(size - 2, size - 2)
print(size * "#")
assert print_square(3) is None
assert print_square(1) is None
print_square(5)
print_square(2)
###
# #
###
#
#####
# #
# #
# #
#####
##
##
Het verschil met lines is dat print_square begint en eindigt met een volle regel size * "#". Daartussen doet lines het werk.
Dat levert twee keer size - 2 op: twee regels minder hoog, omdat de bovenste en de onderste rand al zijn afgedrukt, en twee tekens minder breed, omdat lines links en rechts zelf een # zet.
De twee randgevallen verdienen elk een eigen regel. Bij size 1 vallen de bovenste en de onderste rand samen, dus één # volstaat; zonder die controle zou er een tweede regel onder verschijnen. Bij size 2 is er niets tussen de randen, en dat lost lines zelf al op doordat lines(0, 0) niets tekent.
Ook hier tekenen de assertions mee: de vier vierkanten hieronder zijn die van 3, 1, 5 en 2 tekens, in die volgorde.
Opdracht 2¶
Schrijf de functie is_valid_date(day, month, year) die True teruggeeft als de drie getallen samen een bestaande datum vormen.
De opgave vroeg om de opdeling zelf te maken. Deze uitwerking gebruikt drie functies, en de knip zit op de plekken waar de vraag verandert: eerst is dit jaar een schrikkeljaar, dan hoeveel dagen heeft deze maand, en pas dan bestaat deze datum. Elk van de drie is in één zin uit te leggen en apart te testen. Een andere verdeling kan ook goed zijn, zolang elke functie één ding doet.
def is_leap_year(year):
"""Geeft True als year een schrikkeljaar is"""
if year % 400 == 0:
return True
if year % 100 == 0:
return False
return year % 4 == 0
assert is_leap_year(2024) is True
assert is_leap_year(2023) is False
assert is_leap_year(2000) is True
assert is_leap_year(1900) is False
De drie treden uit de opgave staan hier in omgekeerde volgorde, en dat is geen toeval: de scherpste uitzondering moet als eerste aan de beurt komen. Zou je met year % 4 beginnen, dan gaf 1900 al True en kwam de uitzondering nooit meer aan bod.
1900 en 2000 zijn de twee randgevallen, en het zijn precies de twee jaren uit de opgave.
def days_in_month(month, year):
"""Geeft het aantal dagen van month in year"""
if month == 2:
if is_leap_year(year):
return 29
return 28
if month == 4 or month == 6 or month == 9 or month == 11:
return 30
return 31
assert days_in_month(1, 2025) == 31
assert days_in_month(4, 2025) == 30
assert days_in_month(2, 2024) == 29
assert days_in_month(2, 1900) == 28
Februari is de enige maand die het jaartal nodig heeft, en die staat daarom vooraan. De vier maanden van dertig dagen rijg je met or aan elkaar; alle overige maanden hebben er 31, en die hoef je dus niet meer op te noemen.
Let op dat deze functie niets zegt over een maandnummer buiten 1 tot en met 12. days_in_month(13, 2025) geeft gewoon 31 terug. Dat is geen fout maar een afspraak: die controle hoort in de functie hieronder, want dáár gaat het over de vraag of de datum bestaat.
def is_valid_date(day, month, year):
"""Geeft True als day, month en year samen een bestaande datum vormen"""
if month < 1 or month > 12:
return False
return day >= 1 and day <= days_in_month(month, year)
assert is_valid_date(29, 2, 2000) is True
assert is_valid_date(29, 2, 1900) is False
assert is_valid_date(29, 2, 2024) is True
assert is_valid_date(29, 2, 2023) is False
assert is_valid_date(31, 4, 2024) is False
assert is_valid_date(30, 4, 2024) is True
assert is_valid_date(31, 12, 2025) is True
assert is_valid_date(0, 1, 2024) is False
assert is_valid_date(1, 13, 2024) is False
De maandcontrole staat vooraan, en net als bij de elfproef is dat geen kwestie van smaak: days_in_month gaat uit van een maandnummer dat bestaat, dus die vraag moet eerder beantwoord zijn.
Daarna is er nog één regel over. day >= 1 sluit dag 0 en negatieve dagen uit, en de vergelijking met days_in_month doet de rest. Die vergelijking levert zelf al True of False op, dus er hoeft geen if omheen.
Drie functies, en de vraag uit de opgave staat er in één regel: bestaat deze dag in deze maand van dit jaar.