Basis¶
Het burgerservicenummer¶
Een systeem dat een burgerservicenummer aanneemt, moet een typefout kunnen herkennen voordat er iets wordt opgeslagen of doorgestuurd. Dat kan zonder ergens iets op te zoeken, want de controle zit in het nummer zelf ingebouwd.
De elfproef¶
Een BSN heeft negen cijfers. Vermenigvuldig elk cijfer met het gewicht van zijn index en tel de uitkomsten op. Is die som deelbaar door elf, dan is het nummer geldig.
Index |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
|---|---|---|---|---|---|---|---|---|---|
Gewicht |
9 |
8 |
7 |
6 |
5 |
4 |
3 |
2 |
-1 |
De index telt vanaf nul, net als bij een string: het cijfer op index 0 is het eerste en heeft gewicht 9. Let op het laatste gewicht: het cijfer op index 8 telt mee met min één.
Voor 900000132:
9×9 + 0×8 + 0×7 + 0×6 + 0×5 + 0×4 + 1×3 + 3×2 + 2×-1
81 + 0 + 0 + 0 + 0 + 0 + 3 + 6 + -2 = 88
88 = 8 × 11, dus dit nummer is geldig.
Verwissel de laatste twee cijfers en je krijgt 900000123, met som 85. Dat is
niet deelbaar door elf, dus de proef ziet de verwisseling.
Pak je eigen BSN erbij en reken het na. Verander daarna één cijfer en reken opnieuw: de som klopt dan niet meer.
Wat je gaat maken¶
Zes functies, elk met één taak. De latere gebruiken de eerdere.
Stap |
Functie |
Doet |
|---|---|---|
1 |
|
kijken of één teken een cijfer is |
2 |
|
één cijfer uit het nummer halen |
3 |
|
de negen cijfers maal hun gewicht optellen |
4 |
|
controleren of het nummer de goede vorm heeft |
5 |
|
vorm en elfproef combineren tot één oordeel |
6 |
|
dat oordeel omzetten in een leesbaar antwoord |
Stap 1: is_digit(c)¶
Geeft True als het teken c een cijfer is.
Python heeft hiervoor een kant en klare verzameling. Zet bovenaan je bestand:
from string import digits
digits is de string "0123456789", en met de operator in vraag je of een
teken daarin voorkomt.
Aanroep |
Resultaat |
|---|---|
|
|
|
|
# jouw oplossing
assert is_digit("7") is True
assert is_digit("0") is True
assert is_digit("x") is False
Stap 2: digit_at(bsn, i)¶
Geeft het cijfer op index i terug als getal. De index telt vanaf nul, dus
digit_at(bsn, 0) geeft het eerste cijfer.
Aanroep |
Resultaat |
|---|---|
|
|
|
|
|
|
Hint
bsn[i] geeft een string van één teken. Met int() maak je daar een getal van.
# jouw oplossing
assert digit_at("900000132", 0) == 9
assert digit_at("900000132", 7) == 3
assert digit_at("900000132", 8) == 2
Stap 3: weighted_sum(bsn)¶
Geeft de som van de negen cijfers, elk vermenigvuldigd met het gewicht van zijn
index. Gebruik hiervoor digit_at uit stap 2.
Aanroep |
Resultaat |
|---|---|
|
|
|
|
|
|
Hint
Je hebt nog geen lussen gehad, dus schrijf de negen termen uit: negen keer een cijfer maal zijn gewicht, onder elkaar.
# jouw oplossing
assert weighted_sum("900000132") == 88
assert weighted_sum("900000123") == 85
assert weighted_sum("123456789") == 147
Stap 4: has_nine_digits(bsn)¶
Geeft True als bsn uit precies negen tekens bestaat en die allemaal cijfers
zijn. Gebruik hiervoor is_digit uit stap 1.
Aanroep |
Resultaat |
|---|---|
|
|
|
|
|
|
Hint
Eerst de lengte met len(), en daarna negen controles met and aan elkaar
geregen.
Ja, dat is negen keer bijna hetzelfde. Onthoud dat gevoel: volgende week ben je ervan af.
# jouw oplossing
assert has_nine_digits("900000132") is True
assert has_nine_digits("90000013") is False
assert has_nine_digits("90000013x") is False
Stap 5: is_valid(bsn)¶
Geeft True als bsn negen cijfers heeft en de gewogen som deelbaar is door
elf. Gebruik hiervoor has_nine_digits uit stap 4 en weighted_sum uit stap 3.
Aanroep |
Resultaat |
|---|---|
|
|
|
|
|
|
|
|
|
|
Hint
Zet de controle op negen cijfers vooraan: als die faalt mag weighted_sum niet
meer aan de beurt komen, want die leest alle negen cijfers.
# jouw oplossing
assert is_valid("900000132") is True
assert is_valid("999999990") is True
assert is_valid("900000123") is False
assert is_valid("123456789") is False
assert is_valid("90000013") is False
Stap 6: check(bsn)¶
Geeft een string terug die zegt wat er aan de hand is.
Aanroep |
Resultaat |
|---|---|
|
|
|
|
|
|
Dit is de functie die een echt systeem zou aanroepen. Een gebruiker die een cijfer vergeet, wil iets anders horen dan een gebruiker die er twee heeft omgedraaid.
# jouw oplossing
assert check("900000132") == "geldig"
assert check("90000013") == "geen negen cijfers"
assert check("900000123") == "mislukt op de elfproef"
Uitproberen¶
Haal de nummers uit de voorbeelden hierboven door check heen, en verzin er zelf
een paar bij door twee cijfers om te draaien. Je eigen nummer mag er ook
doorheen. Dit notebook blijft wel als bestand staan: deel je je map later of zet
je hem online, dan gaat je nummer mee.
Je hebt nu in zes kleine stappen gebouwd wat elk aanmeldformulier in Nederland doet.
De parkeerautomaat¶
Bij de uitgang van een parkeergarage staat een automaat. Je voert je kenteken in, de automaat weet hoe lang je binnen stond, en er rolt een bedrag uit. Achter dat bedrag zitten drie regels.
Regel |
Toelichting |
|---|---|
De eerste 15 minuten zijn gratis |
ook als je er precies 15 staat |
Daarna kost elk begonnen uur 2 euro |
geteld vanaf het moment dat je binnenreed |
Je betaalt nooit meer dan 12 euro |
het dagtarief |
Iemand die 70 minuten stond, staat langer dan een kwartier, dus er wordt gerekend. 70 minuten is twee begonnen uren, dus 2 x 2 = 4 euro. Dat is minder dan 12, en dus is het bedrag 4 euro.
Opdracht¶
Schrijf de functie parking_fee(minutes) die een aantal minuten accepteert en
het bedrag in hele euro’s teruggeeft.
Aanroep |
Resultaat |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Hierboven kreeg je de opdeling in zes functies cadeau. Nu maak je hem zelf:
verdeel het probleem in kleinere functies die elk één ding doen, zoals
is_digit en weighted_sum dat deden. Welke dat zijn, bepaal jij. Meer dan
if, return, // en % heb je niet nodig.
Schrijf voor elke functie die je maakt een docstring, en test haar met minstens
twee assertions waarvan er één een randgeval dekt. Bij parking_fee zijn 15 en
16 minuten zulke randgevallen: precies daar springt het bedrag van 0 naar 2.
# jouw oplossing
Tot slot¶
Twee keer heb je in deze opgave negen dingen uitgeschreven die bijna hetzelfde
zijn: negen termen in weighted_sum en negen aanroepen in has_nine_digits.
Dat kan, omdat een BSN altijd negen cijfers lang is.
Een IBAN is dat niet. Een Nederlandse heeft achttien tekens, een Maltese eenendertig. Dezelfde aanpak zou daar functies van eenendertig regels opleveren, en voor elk land een andere.
Volgende week zie je hoe je dat in een paar regels schrijft, ongeacht de lengte.