Stel dat Python geen ingebouwde functies max()
en min()
zou hebben,
en je hebt geen kennis van wat er in de hoofdstukken hierna volgt. Ik
geef je de volgende opdracht:
Schrijf een programma dat twee groepen van drie getallen krijgt (je mag het programma schrijven voor specifieke getallen, maar je voegt later toe dat de gebruiker deze getallen ingeeft). Het programma telt de waardes van de kleinste getallen van iedere groep op, en ook zo voor de waardes van de middelste getallen, en de waardes van de grootste getallen. Daarna drukt het de uitkomsten af.
Hoe los je dit op? Je kunt beginnen met iets als:
# Eerst krijgen de variabelen in groep 1 (num11, num12, num13) en
# groep 2 (num21, num22, num23) een waarde.
kleinste1 = 0
kleinste2 = 0
middelste1 = 0
middelste2 = 0
grootste1 = 0
grootste2 = 0
if num11 < num12:
if num11 < num13:
kleinste1 = num11
else:
kleinste1 = num13
elif num12 < num13:
kleinste1 = num12
else:
kleinste1 = num13
print( kleinste1 ) # Test
# Deze code zoekt de kleinste van groep 1.
# Daarna doe je hetzelfde voor de kleinste van groep 2.
# Dan moet je ook zoiets doen voor de grootste van de 2 groepen.
# Daarna moet je nog iets verzinnen voor de middelste.
# Tenslotte doe je alle optellingen en drukt de resultaten af.
Je kunt je voorstellen dat deze aanpak, met geneste if
statements die
zes keer herhaald worden met verschillende assignments in de takken,
leidt tot een behoorlijk groot, onleesbaar, onbeheersbaar programma
waarvan het lastig te zien is of het correct is of niet. Je moet het
probleem op een slimmere manier aanpakken.
Stel je voor dat je een functie hebt die de kleinste van drie getallen bepaalt, een functie die de middelste van drie getallen bepaalt, en een functie die de grootste van drie getallen bepaalt. Dan is het programma triviaal geworden. Het is dan iets als:
num11, num12, num13 = 436, 178, 992
num21, num22, num23 = 880, 543, 101
def kleinste( n1, n2, n3 ):
return n1 # Geef iets terug
def middelste( n1, n2, n3 ):
return n1 # Geef iets terug
def grootste( n1, n2, n3 ):
return n1 # Geef iets terug
print( "som van kleinste =", kleinste( num11, num12, num13 ) +
kleinste( num21, num22, num23 ) )
print( "som van middelste =", middelste( num11, num12, num13 ) +
middelste( num21, num22, num23 ) )
print( "som van grootste =", grootste( num11, num12, num13 ) +
grootste( num21, num22, num23 ) )
Opmerking
In de code hierboven heb ik een meervoudige assignment gebruikt om de variabelen
num
\(xx\) een waarde te geven, om de lengte van de code te reduceren. Hierbij kun je in één statement meerdere variabele een waarde geven door links van het is-gelijk-teken een aantal variabelen te zetten, en rechts ervan evenveel waardes. De eerste waarde gaat naar de eerste variabele, de tweede waarde naar de tweede variabele, etcetera. Dit wordt verder uitgelegd in het hoofdstuk over tuples.
Het programma hierboven is leesbaar, begrijpbaar, en kan al getest
worden. Natuurlijk, de functies kleinste()
, middelste()
, en
grootste()
retourneren niet de correcte waardes. Misschien weet je
zelfs nog niet eens hoe je ze zou moeten implementeren. Maar je voelt
waarschijnlijk wel aan dat ze geïmplementeerd kunnen worden, en je weet
dat je de code voor deze functies later kunt schrijven, en in kleine
stapjes.
Hoe implementeer je kleinste()
? Zoals ik boven liet zien is het lastig
om dit met een geneste if
te doen (als je dat niet gelooft, kijk dan
niet naar mijn code en probeer het zelf te schrijven; het blijkt
moeilijk te zijn om de variabelen in gedachten te houden terwijl je de
geneste if
schrijft). Kan dit misschien op een wat leesbaardere manier
gedaan worden?
Is het lastig om de kleinste van twee getallen te retourneren? Nee, dat is heel gemakkelijk:
def kleinste_van_twee( n1, n2 ):
if n1 < n2:
return n1
return n2
Door een dergelijke functie te nesten, kun je de een kleinste()
functie maken die de kleinste van drie getallen bepaalt. Dat kun je ook
doen voor grootste()
. Het programma wordt dan:
num11, num12, num13 = 436, 178, 992
num21, num22, num23 = 880, 543, 101
def kleinste_van_twee( n1, n2 ):
if n1 < n2:
return n1
return n2
def grootste_van_twee( n1, n2 ):
if n1 > n2:
return n1
return n2
def kleinste( n1, n2, n3 ):
return kleinste_van_twee( kleinste_van_twee( n1, n2 ), n3 )
def middelste( n1, n2, n3 ):
return n1 # geef iets terug
def grootste( n1, n2, n3 ):
return grootste_van_twee( grootste_van_twee( n1, n2 ), n3 )
print( "som van kleinste =", kleinste( num11, num12, num13 ) +
kleinste( num21, num22, num23 ) )
print( "som van middelste =", middelste( num11, num12, num13 ) +
middelste( num21, num22, num23 ) )
print( "som van grootste =", grootste( num11, num12, num13 ) +
grootste( num21, num22, num23 ) )
Dit programma werkt voor de kleinste getallen en de grootste getallen.
Om het af te maken, moet nog iets bepaald worden voor de middelste. Wat
is het middelste van drie getallen? Dat is het getal dat overblijft als
je de kleinste en de grootste weghaalt. Kun je dit programmeren? Ik stel
een functie verwijder_twee_van_drie()
voor, die eerst de kleinste,
en dan de grootste van de twee overgebleven getallen verwijdert (wat
weer gelijk is aan de eerder bepaalde grootste). Om
verwijder_twee_van_drie()
gemakkelijk te kunnen bouwen, maak ik ook
verwijder_een_van_drie()
en verwijder_een_van_twee()
.
num11, num12, num13 = 436, 178, 992
num21, num22, num23 = 880, 543, 101
def kleinste_van_twee( n1, n2 ):
if n1 < n2:
return n1
return n2
def grootste_van_twee( n1, n2 ):
if n1 > n2:
return n1
return n2
def verwijder_een_van_drie( n1, n2, n3, verwijder ):
if n1 == verwijder:
return n2, n3
elif n2 == verwijder:
return n1, n3
return n1, n2
def verwijder_een_van_twee( n1, n2, verwijder ):
if n1 == verwijder:
return n2
return n1
def verwijder_twee_van_drie( n1, n2, n3, verwijder1, verwijder2):
num1, num2 = verwijder_een_van_drie( n1, n2, n3, verwijder1 )
return verwijder_een_van_twee( num1, num2, verwijder2 )
def kleinste( n1, n2, n3 ):
return kleinste_van_twee( kleinste_van_twee( n1, n2 ), n3 )
def middelste( n1, n2, n3 ):
return verwijder_twee_van_drie( n1, n2, n3,
kleinste( n1, n2, n3 ), grootste( n1, n2, n3 ) )
def grootste( n1, n2, n3 ):
return grootste_van_twee( grootste_van_twee( n1, n2 ), n3 )
print( "som van kleinste =", kleinste( num11, num12, num13 ) +
kleinste( num21, num22, num23 ) )
print( "som van middelste =", middelste( num11, num12, num13 ) +
middelste( num21, num22, num23 ) )
print( "som van grootste =", grootste( num11, num12, num13 ) +
grootste( num21, num22, num23 ) )
Het programma is nu klaar en het werkt. Het is best lang, maar alle
functies zijn gemakkelijk te begrijpen, en het is ook niet moeilijk om
te zien hoe het programma als geheel werkt. Het is nog steeds een stuk
korter dan de allereerste poging, met zes geneste if
statements, en
het is een stuk leesbaarder.
Er zijn natuurlijk andere manieren om het programma aan te pakken. Met een beetje inventiviteit kom je waarschijnlijk op slimmere manieren om de kleinste, middelste, en grootste te bepalen (ik ben nog niet echt tevreden over mijn aanpak van de middelste). Maar het programma werkt en is begrijpbaar, en dat is het belangrijkste.
Je kunt de aanpak die ik gekozen heb bekritiseren. Bijvoorbeeld, de berekening van de kleinste van de drie wordt twee keer uitgevoerd: de eerste keer om de kleinste te bepalen, en de tweede keer om de middelste te bepalen. Dat geldt ook voor de grootste. Kan dat geoptimaliseerd worden, zodat deze bepalingen slechts één keer plaatsvinden? Natuurlijk kan dat, bijvoorbeeld door twee extra variabelen op te nemen die de kleinste en grootste bijhouden. Maar waarom zou ik dat doen? Dat maakt het programma niet leesbaarder, en hoewel het het programma een beetje sneller maakt, spreken we daarbij over nanoseconden. Voor een programma als dit is snelheid niet belangrijk en volledig ondergeschikt aan leesbaarheid. Laat me nogmaals benadrukken dat het oplossen van een probleem op de eerste plaats komt, onmiddellijk gevolgd door het oplossen van het probleem op een leesbare en beheersbare manier. Efficiëntie komt pas veel later.
Wat je hiervan moet leren is dat als een programma bestaat uit een serie problemen die je moeilijk op kunt lossen, je het moet proberen op te splitsen in sub-problemen en sub-doelen, die je onafhankelijk van elkaar kunt aanpakken. Je kunt voor ieder van die sub-problemen alvast een functie introduceren als je het programma opzet, en voorlopig vul je die dan met iets simpels, bijvoorbeeld het retourneren van een vaste waarde. Je kunt dan in ieder geval je programma al testen. Later kun je dan beginnen met het één-voor-één invullen van ieder van die functies.