In het laboratorium gebruikt men centrifuges om vloeibare mengsels op basis van dichtheid te scheiden. In een centrifuge zitten gaten waarin reageerbuizen kunnen geplaatst worden. Door de reageerbuizen met hoge snelheid te laten ronddraaien, zorgt de middelpuntvliedende kracht1 (centrifugale kracht) voor de scheiding. Dit is bijvoorbeeld een centrifuge met 20 gaten, waarvan er 8 met reageerbuizen gevuld zijn:

centrifuge
Een centrifuge met 8 proefbuizen verdeeld over 20 gaten.

Opgave

Voor een centrifuge met $$n$$ gaten die op gelijke afstand rond een cirkel liggen, voeren we een coördinatenstelsel in waarbij de X-as en de Y-as elkaar snijden in het middelpunt van die cirkel. Er is altijd een gat waarvan het middelpunt op de positieve X-as ligt. De gaten worden in tegenwijzerzin genummerd vanaf 0, te beginnen bij dit gat waarvan het middelpunt op de positieve X-as ligt.

centrifuge
De zes gaten van deze centrifuge worden in tegenwijzerzin genummerd vanaf nul, te beginnen bij het gat waarvan het middelpunt op de positieve X-as ligt. De vier oranje gaten (0, 1, 3 en 4) zijn gevuld met reageerbuizen en de twee grijze gaten (2 en 5) zijn leeg.

Een configuratie die beschrijft welke gaten van een centrifuge gevuld zijn met reageerbuizen, stellen we voor als een collectie (list, tuple of set) met de nummers (int) van de gevulde gaten. Daarbij maakt het niet uit in welke volgorde de nummers van de gevulde gaten opgelijst worden. Als in bovenstaande figuur de vier oranje gaten met reageerbuizen gevuld zijn en de twee donkergrijze gaten leeg zijn, dan kunnen we die configuratie bijvoorbeeld voorstellen als de verzameling {4, 1, 3, 0}.

Een centrifuge kan in wijzerzin of in tegenwijzerzin roteren. Bij één rotatiestap schuiven alle gaten (en reageerbuizen) één nummer door in wijzerzin of in tegenwijzerzin, zodat er telkens een gat met middelpunt op de positieve X-as blijft liggen. Dit wordt geïllustreerd in onderstaande figuur, waarbij we alle gaten van de centrifuge gevuld hebben met reageerbuizen die elk een unieke kleur hebben.

rotatiestap
Een centrifuge kan in wijzerzin of in tegenwijzerzin roteren. Bij één rotatiestap schuiven alle gaten (en reageerbuizen) één nummer door in wijzerzin of in tegenwijzerzin, zodat er telkens een gat met middelpunt op de positieve X-as blijft liggen. Dit wordt geïllustreerd in deze figuur, waarbij we alle gaten van de centrifuge gevuld hebben met reageerbuizen die elk een unieke kleur hebben.

Bij het spiegelen van een centrifuge wordt de configuratie van haar reageerbuizen gespiegeld ten opzichte van de X-as. Als we een gespiegelde centrifuge nog eens spiegelen, dan krijgen we terug de oorspronkelijke configuratie van de centrifuge.

spiegelen
Bij het spiegelen van een centrifuge wordt haar configuratie gespiegeld ten opzichte van de X-as. Als we een gespiegelde centrifuge nog eens spiegelen, dan krijgen we terug de oorspronkelijke configuratie van de centrifuge.

Definieer een klasse Centrifuge waarmee centrifuges kunnen voorgesteld worden waarvan sommige gaten met reageerbuizen gevuld zijn. Bij het aanmaken van een centrifuge (Centrifuge) moeten twee argumenten doorgegeven worden: i) het aantal gaten $$n$$ (int) en ii) een configuratie die beschrijft welke gaten van de centrifuge gevuld zijn met reageerbuizen.

Als er een centrifuge (Centrifuge) doorgegeven wordt aan de ingebouwde functie repr, dan moet die een stringvoorstelling (str) teruggeven die leest als een Python expressie waarmee een nieuwe centrifuge (Centrifuge) aangemaakt wordt met hetzelfde aantal gaten en dezelfde configuratie van gevulde gaten als de centrifuge die aan de functie repr werd doorgegeven. Hierbij moet de configuratie voorgesteld worden als een oplopend gesorteerde lijst (list) van nummers (int).

Voorts moeten op een centrifuge $$c$$ (Centrifuge) minstens de volgende methoden kunnen aangeroepen worden:

Zorg ervoor dat operator == kan gebruikt worden (c == d) om te controleren of twee centrifuges $$c$$ en $$d$$ (Centrifuge) gelijk zijn. Dat is het geval als beide centrifuges evenveel gaten hebben en als één van de centrifuges over een aantal stappen kan geroteerd worden zodat beide centrifuges dezelfde gevulde gaten hebben, eventueel na spiegeling van één van beide centrifuges. De operator == mag de toestand van de centrifuges $$c$$ en $$d$$ niet wijzigen.

Zorg ervoor dat operator += kan gebruikt worden (c += d) om alle gaten die gevuld zijn in centrifuge $$d$$ (Centrifuge) op te vullen in centrifuge $$c$$ (Centrifuge). Hierbij mag de toestand van centrifuge $$d$$ niet wijzigen. Als centrifuges $$c$$ en $$d$$ niet evenveel gaten hebben of als er een gevuld gat is in centrifuge $$d$$ dat ook al gevuld is in centrifuge $$c$$, dan mag de toestand van centrifuge $$c$$ niet wijzigen en moet er een AssertionError opgeworpen worden met de boodschap gaten kunnen niet gevuld worden.

Zorg ervoor dat operator -= kan gebruikt worden (c -= d) om alle gaten die gevuld zijn in centrifuge $$d$$ (Centrifuge) leeg te maken in centrifuge $$c$$ (Centrifuge). Hierbij mag de toestand van centrifuge $$d$$ niet wijzigen. Als centrifuges $$c$$ en $$d$$ niet evenveel gaten hebben of als er een gevuld gat is in centrifuge $$d$$ dat leeg is in centrifuge $$c$$, dan mag de toestand van centrifuge $$c$$ niet wijzigen en moet er een AssertionError opgeworpen worden met de boodschap gaten kunnen niet geleegd worden.

Tip

Gebruik de speciale methode __iadd__2 voor operator overloading van += en de speciale methode __isub__3 voor operator overloading van -=. Beide methodes moeten een verwijzing teruggeven naar het object dat moet bijgewerkt worden, want c += d is equivalent met c = c.__iadd__(d) en c -= d is equivalent met c = c.__isub__(d).

Voorbeeld

>>> centrifuge = Centrifuge(6, {4, 1, 3, 0})
>>> centrifuge
Centrifuge(6, [0, 1, 3, 4])
>>> centrifuge.roteer()
Centrifuge(6, [1, 2, 4, 5])
>>> centrifuge.roteer(wijzerzin=True)
Centrifuge(6, [0, 1, 3, 4])
>>> centrifuge.spiegel()
Centrifuge(6, [0, 2, 3, 5])
>>> centrifuge.spiegel()
Centrifuge(6, [0, 1, 3, 4])

>>> centrifuge = Centrifuge(6, [2, 0, 5])
>>> centrifuge == Centrifuge(6, [2, 4, 5])
True
>>> centrifuge == Centrifuge(6, [3, 4, 5])
False
>>> centrifuge
Centrifuge(6, [0, 2, 5])
>>> centrifuge.roteer().roteer().spiegel()
Centrifuge(6, [2, 4, 5])
>>> centrifuge.spiegel().roteer(True).roteer(True)
Centrifuge(6, [0, 2, 5])
>>> centrifuge += Centrifuge(6, {1, 4})
>>> centrifuge
Centrifuge(6, [0, 1, 2, 4, 5])
>>> centrifuge += Centrifuge(6, [1, 4, 2])
Traceback (most recent call last):
AssertionError: gaten kunnen niet gevuld worden
>>> centrifuge -= Centrifuge(6, {1, 4})
>>> centrifuge
Centrifuge(6, [0, 2, 5])
>>> centrifuge -= Centrifuge(6, (1, 5))
Traceback (most recent call last):
AssertionError: gaten kunnen niet geleegd worden