Basis

Deze pagina bevat uitvoerbare code.

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

is_digit

kijken of één teken een cijfer is

2

digit_at

één cijfer uit het nummer halen

3

weighted_sum

de negen cijfers maal hun gewicht optellen

4

has_nine_digits

controleren of het nummer de goede vorm heeft

5

is_valid

vorm en elfproef combineren tot één oordeel

6

check

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

is_digit("7")

True

is_digit("x")

False

# 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

digit_at("900000132", 0)

9

digit_at("900000132", 1)

0

digit_at("900000132", 7)

3

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

weighted_sum("900000132")

88

weighted_sum("900000123")

85

weighted_sum("123456789")

147

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

has_nine_digits("900000132")

True

has_nine_digits("90000013")

False

has_nine_digits("90000013x")

False

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

is_valid("900000132")

True

is_valid("999999990")

True

is_valid("900000123")

False

is_valid("123456789")

False

is_valid("90000013")

False

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

check("900000132")

"geldig"

check("90000013")

"geen negen cijfers"

check("900000123")

"mislukt op de elfproef"

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

parking_fee(0)

0

parking_fee(15)

0

parking_fee(16)

2

parking_fee(60)

2

parking_fee(61)

4

parking_fee(359)

12

parking_fee(600)

12

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.