Basis¶
Het burgerservicenummer¶
Opdracht: Het burgerservicenummer
from string import digits
def is_digit(c):
"""Geeft True als het teken een cijfer is
:param c: het te controleren teken
:type c: str
:rtype: bool
"""
return c in digits
assert is_digit("7") is True
assert is_digit("0") is True
assert is_digit("x") is False
digits uit de module string is niets anders dan de string "0123456789". De
operator in kijkt of een teken daarin voorkomt, en geeft dus meteen een True
of False terug.
Dit is ook de reden dat je een variabele nooit string moet noemen: dan kun je
de module niet meer importeren.
def digit_at(bsn, i):
"""Geeft het cijfer op positie i terug als getal
:param bsn: het nummer als string
:type bsn: str
:param i: de positie, waarbij 0 het eerste cijfer is
:type i: int
:rtype: int
"""
return int(bsn[i])
assert digit_at("999000032", 0) == 9
assert digit_at("999000032", 7) == 3
assert digit_at("999000032", 8) == 2
Eén regel, maar hij geeft de bewerking een naam. Dat scheelt hieronder negen keer
int(bsn[...]) schrijven, en als er ooit iets aan die omzetting verandert hoeft
het maar op één plek.
def weighted_sum(bsn):
"""Geeft de som van de cijfers maal het gewicht van hun positie
:param bsn: het nummer als string van negen cijfers
:type bsn: str
:rtype: int
"""
return (
digit_at(bsn, 0) * 9
+ digit_at(bsn, 1) * 8
+ digit_at(bsn, 2) * 7
+ digit_at(bsn, 3) * 6
+ digit_at(bsn, 4) * 5
+ digit_at(bsn, 5) * 4
+ digit_at(bsn, 6) * 3
+ digit_at(bsn, 7) * 2
+ digit_at(bsn, 8) * -1
)
assert weighted_sum("999000032") == 220
assert weighted_sum("999000023") == 217
assert weighted_sum("123456789") == 147
Negen termen, onder elkaar. Dat oogt log, en dat is het ook, maar het is wel te lezen: elke regel is een positie en de gewichten lopen netjes af. Zet je alles op één regel, dan is een fout in een gewicht bijna niet te zien.
def has_nine_digits(bsn):
"""Geeft True als het nummer uit precies negen cijfers bestaat
:param bsn: het te controleren nummer
:type bsn: str
:rtype: bool
"""
return (
len(bsn) == 9
and is_digit(bsn[0])
and is_digit(bsn[1])
and is_digit(bsn[2])
and is_digit(bsn[3])
and is_digit(bsn[4])
and is_digit(bsn[5])
and is_digit(bsn[6])
and is_digit(bsn[7])
and is_digit(bsn[8])
)
assert has_nine_digits("999000032") is True
assert has_nine_digits("99900003") is False
assert has_nine_digits("99900003x") is False
Beide voorwaarden zijn nodig. Alleen op lengte controleren laat "99900003x"
door, en alleen op cijfers controleren laat een nummer van acht cijfers door.
De lengtecontrole staat vooraan en dat is geen kwestie van smaak: staat hij
achteraan, dan wordt bsn[8] opgevraagd op een string die maar acht tekens
heeft. Python stopt dan met een foutmelding in plaats van False terug te geven.
def is_valid(bsn):
"""Geeft True als het nummer negen cijfers heeft en de elfproef doorstaat
:param bsn: het te controleren nummer
:type bsn: str
:rtype: bool
"""
if not has_nine_digits(bsn):
return False
return weighted_sum(bsn) % 11 == 0
assert is_valid("999000032") is True
assert is_valid("999999990") is True
assert is_valid("999000023") is False
assert is_valid("123456789") is False
assert is_valid("99900003") is False
Om dezelfde reden staat de vormcontrole hier vooraan en geeft hij meteen False
terug: weighted_sum leest negen posities, dus op een korter nummer loopt hij
vast.
def check(bsn):
"""Geeft een omschrijving van de uitkomst van de controle
:param bsn: het te controleren nummer
:type bsn: str
:rtype: str
"""
if not has_nine_digits(bsn):
return "geen negen cijfers"
if not is_valid(bsn):
return "mislukt op de elfproef"
return "geldig"
assert check("999000032") == "geldig"
assert check("99900003") == "geen negen cijfers"
assert check("999000023") == "mislukt op de elfproef"
Deze functie beslist niets zelf; hij vertaalt alleen wat de andere functies al vaststellen. Daardoor staat de rekenregel op precies één plek, en verandert er verder niets als die regel ooit wordt aangepast.
Dat is wat het opdelen in zes functies oplevert. Elke functie is in één zin uit te leggen en apart te testen, en de laatste maakt er een antwoord van.
Opdracht 1¶
Schrijf de functie tpl(x) die een getal als argument accepteert en drie keer de waarde van dat argument teruggeeft.
def tpl(x):
"""Geeft driemaal het argument terug
:param x: De waarde om te verdrievoudigen
:type x: int, float of string
:rtype: int, float of string
"""
return 3 * x
We hebben besloten de docstring van meer informatie te voorzien voor nog meer duidelijkheid.
Aangezien het een simpele taak is die de functie moet doen, kan de berekening gelijk in de return gezet worden.
Opdracht 2¶
a. Schrijf de functie min_two(a, b) die twee getallen als argument accepteert en de kleinste waarde teruggeeft.
def min_two(a, b):
""" Geeft de laagste van de twee parameters terug
:param a, b: de twee waarden waarvan de laagste terug moet worden gegeven
:type a, b: int
:rtype: int
"""
if a < b:
return a
elif b < a:
return b
else:
return "Beide getallen zijn even groot!"
In de opdracht werd niet gezegd dat we er van uit mogen gaan dat de twee getallen niet hetzelfde kunnen zijn.
Daarom controleert de functie zowel of a kleiner is en of b kleiner is. Als geen van beide kleiner zijn dan zullen de argumenten even groot zijn.
b. Schrijf de functie min_three(a, b, c) die drie getallen als argument accepteert en de kleinste waarde teruggeeft.
def min_three(a, b, c):
""" Geeft de laagste van de drie parameters terug
:param a, b, c: de twee waarden waarvan de laagste terug moet worden gegeven
:type a, b, c: int
:rtype: int
"""
if a < b and a < c:
return a
elif b < a and b < c:
return b
elif c < a and c < b:
return c
else:
return "Er zijn getallen die even groot zijn!"
In de opdracht werd niet gezegd dat we er van uit mogen gaan dat de twee getallen niet hetzelfde kunnen zijn.
Daarom controleert de functie zowel of elke variable kleiner is dan de andere twee met de ‘and’ in de if statements.
Als geen van de drie argumenten kleiner zijn dan de andere dan zullen er twee hetzelfde zijn of alle zelfs alle drie.
Opdracht 3¶
Schrijf de functie absolute(x, y) dat twee getallen accepteert en de afstand berekent tussen de twee getallen.
def absolute(x, y):
""" Geeft de afstand tussen de twee parameters terug
:param x, y: de twee waarden waarvan de afstand terug moet worden gegeven
:type a, b, c: int
:rtype: int
"""
if x >= 0 and y >= 0:
if x > y:
# Bij twee positieve getallen dan krijg je de afstand
# door het kleinste getal van de grote te trekken.
return x - y
else: # y is groter
return y - x
elif x < 0 and y >= 0:
# y is groter dan de negatieve x.
# Omdat x negatief is, is de afstand y plus de positieve versie van x.
return y + (x * -1)
elif x >= 0 and y < 0:
# Nu is y negatief
return x + (y * -1)
else:
# Beide getallen zijn negatief.
# Vindt het kleinste getal.
if x < y:
# De afstand is altijd positief.
# We corrigeren door het kleinste getal positief te maken
# en het kleinste getal negatief te houden.
return (x * -1) + y # Plus een negatief getal wordt een aftrek som
else:
return (y * -1) + x
Als beide getallen positief zijn dan hoeft alleen de laagste van de hoogste af getrokken te worden.
Als een van beide getallen negatief is dan is het andere getal automatisch het grotere getal. Om dan de juiste waarde te krijgen moet de positieve versie van het negatieve getal opgeteld worden bij het al positieve getal.
Als beide getallen negatief zijn dan kunnen we de positieve versie van het kleinste getal nemen en het andere getal daarvan aftrekken.
We maken hiervoor gebruik van het feit dat in Python wanneer je een negatief getal ergens bij optelt het een aftrek operatie wordt.