Voorbereiding

In Python kunnen functies gedefinieerd worden die men kan aanroepen met een willekeurig aantal argumenten. Deze argumenten worden gebundeld in tuple en toegekend aan de parameter die in de definitie van de functie wordt voorafgegaan door een sterretje (*). Voor deze variabele argumenten kunnen nul of meer gewone argumenten voorkomen. Dit wordt geïllustreerd in onderstaande Python sessie.

>>> def som(*termen):
...     resultaat = 0
...     for term in termen:
...         resultaat += term
...     return resultaat
...
>>> som()
0
>>> som(1, 2, 3, 4, 5)
15

De omgekeerde situatie kan ook voorkomen. Namelijk dat argumenten reeds in een lijst of een tuple zitten, maar moeten uitgepakt worden voor het aanroepen van een functie die ze als afzonderlijke positionele argumenten wil meekrijgen. Dit uitpakken kan gebeuren door een sterretje (*) voor de lijst of het tuple te plaatsen dat als argument aan de functie wordt doorgegeven. Dit wordt geïllustreerd in onderstaande Python sessie.

>>> termen = [1, 2, 3, 4, 5]
>>> som(*termen)
15
>>> som(*range(101))
5050

Zie de Python handleiding1 voor meer voorbeelden van tuple packing en tuple unpacking.

Probleemomschrijving

Water zorgt voor extra druk bij het duiken. De ademautomaat in een duikfles is dan ook speciaal ontworpen om zuurstof te leveren aan dezelfde druk als de omringende waterdruk. Dat betekent dat als een duiker de longen vult op een diepte van 33 voet2 (1 voet = 0,305 meter) — waar de druk ongeveer 2 psi (pound-force per square inch3) bedraagt — hij tweemaal zoveel lucht gebruikt als aan de oppervlakte. Een volle duikfles zal op 33 voet (ongeveer 10 meter) diep dus slechts half zo lang meegaan als aan de oppervlakte. Een fles die aan de oppervlakte pas na 1 uur zou opgebruikt zijn, is op 66 voet diep — waar de druk 3 psi bedraagt — al na 20 minuten leeg.

De SAC-verhouding (Surface Air Consumption) is een belangrijk begrip onder scubaduikers ("SCUBA" was oorspronkelijk een acroniem voor self contained underwater breathing apparatus, maar wordt nu algemeen gebruikt als een woord op zichzelf) om te kunnen inschatten hoeveel zuurstof ze zullen gebruiken op een bepaalde diepte. De hoeveelheid lucht die een duiker inademt is immers afhankelijk van verschillende factoren: watertemperatuur, lichamelijke conditie, manier van ademen, ervaring van de duiker, mate van inspanning en hoeveelheid stress. Een onervaren duiker zal in koude omstandigheden en bij slecht zicht een aanzienlijk hogere SAC-verhouding hebben dan een ervaren duiker in dezelfde omstandigheden.

Na elke duik wordt de hoeveelheid zuurstof die een duiker op een bepaalde diepte verbruikt heeft genormaliseerd naar zijn zuurstofverbruik aan de oppervlakte. Hiervoor wordt gebruikgemaakt van de volgende formule \[ \text{SAC} = \frac{33(s - f)}{t(d + 33)} \] waarbij $$d$$ de diepte (in voet) voorstelt waarop werd gedoken, $$s$$ de druk in de duikfles (in psi) voor de duik, $$f$$ de druk in de duikfles (in psi) na de duik en $$t$$ de lengte van de duik (in minuten). Doorgaans wordt de SAC-verhouding van een individuele duiker uitgemiddeld over verschillende duiken heen.

Opgave

Definieer een klasse Duik waarvan de objecten verschillende attributen hebben die de gegevens van één enkele duik bijhouden, zoals de druk voor en na de duik, het aanvangsuur en einduur van de duik, en de diepte waarop gedoken werd. Typische waarden zijn een startdruk van 3000 psi, een einddruk van 700 psi, een diepte van 30 tot 80 voet en een duur van 30 minuten (bij 80 voet) tot 60 minuten (bij 30 voet). De SAC-verhouding ligt typisch tussen 10 en 20. De klasse Duik moet ook een methode Duik.SACverhouding() hebben, waarmee de SAC-verhouding voor die duik kan berekend worden. De klasse Duik moet dus ondersteuning bieden voor volgende methoden:

Duik.__init__(self, startdruk, einddruk, starttijd, eindtijd, diepte)

De initialisatiemethode __init__ moet ervoor zorgen dat objecten van de klasse Duik geïnitialiseerd worden met attributen die de druk aan het begin en het einde van de duik aangeven (gegeven als floats uitgedrukt in psi), de aanvangstijd en eindtijd van de duik, en de diepte waarop werd gedoken (gegeven als floats, uitgedrukt in voet). Om makkelijker de gegevens van de duik te kunnen opgeven, worden de tijden doorgegeven als een string in het formaat uu:mm. De initialisatiemethode zal dus stringfuncties moeten gebruiken om het aantal uren en het aantal minuten (na middernacht) uit de string te knippen. Om de duur van een duik te bepalen, moet de methode dus de tijd normaliseren naar een aantal minuten sinds middernacht op basis van de berekening $$uu \times 60 + mm$$. Als de begin- en eindtijd werden omgezet naaar een aantal minuten sinds middernacht, dan kunnen we deze twee tijden van elkaar aftrekken om de duur van de duik (in minuten) te bepalen. Je mag er hierbij van uitgaan dat er nooit over middernacht heen gedoken wordt. Het is ook toegelaten om een hulpmethode Duik.bepaalDuur() toe te voegen aan de klasse Duik, waarin deze berekening voor de duur van een duik wordt uitgevoerd.

Duik.SACverhouding(self)

De methode SACverhouding geeft de SAC-verhouding van de duik terug, op basis van de omstandigheden waarin gedoken werd.

Om een reeks opeenvolgende duiken te kunnen voorstellen, definieer je ook een klasse Logboek. Een dergelijk logboek kan geïnitialiseerd worden met de gegevens van een reeks duiken, en achteraf kunnen ook nog individuele duiken aan het logboek toegevoegd worden. Op basis van het logboek moet de gemiddelde SAC-verhouding van de duiker kunnen berekend worden. De klasse Logboek moet dus ondersteuning bieden aan de volgende methoden:

Logboek.__init__(self, *duiken)

De initialisatiemethode __init__ moet ervoor zorgen dat objecten van de klasse Logboek kunnen geïnitialiseerd worden met een lijst van duiken (objecten van de klasse Duik). Om deze duiken als positionele parameters te kunnen meegeven aan de initialisatiemethode wordt gebruikgemaakt van de parameter *duiken, waardoor de parameters in een lijst gebundeld worden.

Logboek.nieuweDuik(self, duik)

De methode nieuweDuik voegt een gegeven duik (object van de klasse Duik) toe achteraan het logboek.

Logboek.gemiddeldeSACverhouding(self)

De methode gemiddeldeSACverhouding berekent de gemiddelde SAC-verhouding van een duiker over alle duiken in diens logboek heen, een geeft dit gemiddelde als resultaat terug.

Voorbeeld

>>> duik = Duik(3100, 1300, '11:52', '12:45', 35)
>>> duik.SACverhouding()
16.4816870144
>>> duik = Duik(2700, 1000, '11:16', '12:06', 40)
>>> duik.SACverhouding()
15.3698630137
>>> duik = Duik(2800, 1200, '11:26', '12:06', 60)
>>> duik.SACverhouding()
14.1935483871
>>> duik = Duik(2800, 1150, '11:54', '12:16', 95)
>>> duik.SACverhouding()
19.3359375

>>> logboek = Logboek(
...     Duik(3100, 1300, '11:52', '12:45', 35),
...     Duik(2700, 1000, '11:16', '12:06', 40),
...     Duik(2800, 1200, '11:26', '12:06', 60)
... )
>>> logboek.gemiddeldeSACverhouding()
15.3483661384
>>> logboek.nieuweDuik(Duik(2800, 1150, '11:54', '12:16', 95))
>>> logboek.gemiddeldeSACverhouding()
16.3452589788