Je hebt drie proefbuizen met een maximale capaciteit van 3, 5 en 8 eenheden. De eerste twee proefbuizen zijn helemaal leeg en de derde is tot de rand gevuld met een vloeistof.

raadsel

De proefbuizen hebben geen markeringen, waardoor het onmogelijk is om een exacte hoeveelheid vloeistof af te meten met een proefbuis die niet volledig vol is. Hoe kan je zonder morsen en in zo weinig mogelijk stappen exact 4 eenheden vloeistof afmeten als je in elke stap vloeistof kan gieten van een proefbuis (de bron) in een andere proefbuis (het doel) en daarbij maar mag stoppen bij welke van deze twee gevallen eerst gebeurt: de bron is leeg of het doel is vol?

Onderstaand schema geeft aan hoe je in 6 stappen exact 4 eenheden vloeistof kunt afmeten. Dit is ook het kleinste aantal stappen waarin dit kan, maar er zijn wel nog andere mogelijkheden om in 6 stappen exact 4 eenheden af te meten.

oplossing

We hebben markeringen aangebracht om te tonen dat elke proefbuis in elke stap een geheel aantal eenheden vloeistof bevat. De dunne groene pijlen wijzen voor elke stap van de bron naar het doel.

Opgave

Dit raadsel kan veralgemeend worden naar $$n \in \mathbb{N}_0$$ proefbuizen, die we van links naar rechts nummeren vanaf nul. De capaciteit van de proefbuizen wordt beschreven door een tuple $$(c_0, c_1, \ldots, c_{n-1})$$, waarbij proefbuis $$i$$ maximaal $$c_i \in \mathbb{N}_0$$ (int) eenheden vloeistof kan bevatten ($$0 \leq i < n$$). De toestand van de proefbuizen op een bepaald moment wordt beschreven door een tuple $$(v_0, v_1, \ldots, v_{n-1})$$, waarbij proefbuis $$i$$ momenteel $$v_i \in \mathbb{N}$$ (int; $$0 \leq v_i \leq c_i$$) eenheden vloeistof bevat ($$0 \leq i < n$$). De capaciteit wordt dus beschreven als een toestand waarbij alle proefbuizen volledig gevuld zijn.

De drie proefbuizen uit de inleiding hebben capaciteit $$(3, 5, 8)$$ en bevinden zich initieel in toestand $$(0, 0, 8)$$.

capaciteitbegintoestand

Er bestaat een standaardprocedure om een oplossing van dit veralgemeend raadsel te vinden — als er een oplossing bestaat. Die procedure houdt een wachtrij $$Q$$ bij met toestanden die nog moeten verwerkt worden en ook de voorgangers van alle toestanden die ooit aan wachtrij $$Q$$ toegevoegd werden. Wachtrij $$Q$$ is een lijst (list) die initieel enkel de begintoestand bevat. De voorgangers houden we bij in een dictionary $$P$$ (dict) die toestanden afbeeldt op hun vorige toestand. Initieel beeldt dictionary $$P$$ enkel de begintoestand af op de waarde None, om aan te geven dat de begintoestand geen voorganger heeft.

Elke stap van de procedure verwerkt toestand $$p$$ aan het begin van wachtrij $$Q$$. Eerst wordt toestand $$p$$ aan het begin van wachtrij $$Q$$ verwijderd. Daarna worden alle mogelijke toestanden bepaald die kunnen volgen op toestand $$p$$. Een volgende toestand wordt bekomen door vloeistof te gieten van proefbuis $$x$$ (de bron) in proefbuis $$y$$ (het doel). Dit kan enkel als i) de bron $$x$$ en het doel $$y$$ verschillende proefbuizen zijn, ii) de bron $$x$$ niet leeg is, en iii) het doel $$y$$ niet vol is.

Onderstaande figuur illustreert dat de begintoestand $$(0, 0, 8)$$ van het raadsel uit de inleiding kan gevolgd worden door toestanden $$(0, 5, 3)$$ en $$(3, 0, 5)$$. Aan de linkerkant geeft de dunne groene pijl aan dat toestand $$(0, 5, 3)$$ volgt na toestand $$(0, 0, 8)$$ door vloeistof te gieten van proefbuis 2 in proefbuis 1. Aan de rechterkant geeft de dunne groene pijl aan dat toestand $$(3, 0, 5)$$ volgt na toestand $$(0, 0, 8)$$ door vloeistof te gieten van proefbuis 2 in proefbuis 0. Voor alle andere combinaties van een bron en een doel kan er niet gegoten worden in toestand $$(0, 0, 8)$$.

volgende toestanden

Ten slotte worden alle volgende toestanden van $$p$$ van klein naar groot verwerkt: die volgorde wordt bepaald door eerst de hoeveelheid vloeistof in de proefbuizen op positie $$0$$ te vergelijken, en zolang de proefbuizen op positie $$x$$ dezelfde hoeveelheid vloeistof bevatten vervolgens de hoeveelheid in de proefbuizen op positie $$x + 1$$ te vergelijken. Voor elke volgende toestand $$s$$ die nooit eerder aan wachtrij $$Q$$ toegevoegd werd, voegen we $$s$$ achteraan toe aan de wachtrij en onthouden we dat $$s$$ volgt op toestand $$p$$ door $$s$$ af te beelden op $$p$$ in dictionary $$P$$.

Hieronder tonen we hoe deze procedure verloopt voor het raadsel uit de inleiding. De begintoestand $$(0, 0, 8)$$ staat aan de bovenkant van de boomstructuur. Elke toestand is met pijlen verbonden met alle mogelijke toestanden die erop kunnen volgen. Die pijlen worden in dictionary $$P$$ bijgehouden. De volgorde waarin de toestanden aan wachtrij $$Q$$ toegevoegd en er ook weer uitgehaald worden, bekom je door de blauwe toestanden in de boomstructuur af te lopen in de gebruikelijke leesvolgorde: van links naar rechts en van boven naar onder. De volgende toestanden die niet verder verwerkt worden — en dus niet opnieuw aan wachtrij $$Q$$ toegevoegd worden — zijn oranje gekleurd in de boomstructuur.

standaardprocedure

Voor het raadsel uit de inleiding bevat onderstaande tabel voor de opeenvolgende stappen die de procedure doorloopt i) de toestand die moet verwerkt worden, ii) de toestanden die daarop kunnen volgen, en iii) de acties die moeten uitgevoerd worden op de wachtrij. De volgende toestanden die niet moeten verwerkt worden omdat ze in een vorige stap al eens aan de wachtrij werden toegevoegd, zijn oranje gekleurd. De toestanden die aan het begin van de wachtrij weggehaald worden, zijn doorgehaald en rood gekleurd. De toestanden die achteraan de wachtrij toegevoegd worden, zijn groen gekleurd.

toestand volgende toestanden wachtrij
- - (0, 0, 8)
(0, 0, 8) (0, 5, 3) (3, 0, 5) (0, 0, 8) (0, 5, 3) (3, 0, 5)
(0, 5, 3) (0, 0, 8) (3, 2, 3) (3, 5, 0) (0, 5, 3) (3, 0, 5) (3, 2, 3) (3, 5, 0)
(3, 0, 5) (0, 0, 8) (0, 3, 5) (3, 5, 0) (3, 0, 5) (3, 2, 3) (3, 5, 0) (0, 3, 5)
(3, 2, 3) (0, 2, 6) (0, 5, 3) (3, 0, 5) (3, 5, 0) (3, 2, 3) (3, 5, 0) (0, 3, 5) (0, 2, 6)
(3, 5, 0) (0, 5, 3) (3, 0, 5) (3, 5, 0) (0, 3, 5) (0, 2, 6)
(0, 3, 5) (0, 0, 8) (0, 5, 3) (3, 0, 5) (3, 3, 2) (0, 3, 5) (0, 2, 6) (3, 3, 2)
(0, 2, 6) (0, 0, 8) (0, 5, 3) (2, 0, 6) (3, 2, 3) (0, 2, 6) (3, 3, 2) (2, 0, 6)
(3, 3, 2) (0, 3, 5) (1, 5, 2) (3, 0, 5) (3, 5, 0) (3, 3, 2) (2, 0, 6) (1, 5, 2)
(2, 0, 6) (0, 0, 8) (0, 2, 6) (2, 5, 1) (3, 0, 5) (2, 0, 6) (1, 5, 2) (2, 5, 1)
(1, 5, 2) (0, 5, 3) (1, 0, 7) (3, 3, 2) (3, 5, 0) (1, 5, 2) (2, 5, 1) (1, 0, 7)
(2, 5, 1) (0, 5, 3) (2, 0, 6) (3, 4, 1) (3, 5, 0) (2, 5, 1) (1, 0, 7) (3, 4, 1)
(1, 0, 7) (0, 0, 8) (0, 1, 7) (1, 5, 2) (3, 0, 5) (1, 0, 7) (3, 4, 1) (0, 1, 7)

Er zijn twee mogelijke gevallen waarbij de procedure stopt. Als wachtrij $$Q$$ geen toestanden meer bevat, dan kunnen we geen stap meer zetten en besluiten dat de gevraagde hoeveelheid vloeistof niet kan afgemeten worden. Als toestand $$p$$ aan het begin van wachtrij $$Q$$ een proefbuis heeft die de gevraagde hoeveelheid vloeistof bevat, dan kunnen we dictionary $$P$$ gebruiken om (achterwaarts) de route te reconstrueren vanaf de begintoestand tot toestand $$p$$. Deze route beschrijft hoe we met zo weinig mogelijk keer gieten de gevraagde hoeveelheid vloeistof kunnen afmeten, en is dus een oplossing voor het raadsel.

Voor het raadsel uit de inleiding zien we in bovenstaande figuur dat toestand $$(3, 4, 1)$$ de eerste toestand aan het begin van de wachtrij is met een proefbuis die 4 eenheden vloeistof bevat. De blauwe pijlen tonen de route van begintoestand $$(0, 0, 8)$$ naar toestand $$(3, 4, 1)$$ die de oplossing van het raadsel beschrijft die door de standaardprocedure wordt gevonden: \[ (0, 0, 8) \longrightarrow (0, 5, 3) \longrightarrow (3, 2, 3) \longrightarrow (0, 2, 6) \longrightarrow (2, 0, 6) \longrightarrow (2, 5, 1) \longrightarrow (3, 4, 1)\]

Dit is de inhoud van dictionary $$P$$ op het moment dat toestand $$(3, 4, 1)$$ op kop van de wachtrij staat bij het uitvoeren van de standaardprocedure voor het raadsel uit de inleiding. De blauwe pijlen geven aan hoe de route van begintoestand $$(0, 0, 8)$$ naar toestand $$(3, 4, 1)$$ achterwaarts kan gereconstrueerd worden door te beginnen bij toestand $$(3, 4, 1)$$ (groene cirkel) en telkens de vorige toestand op te zoeken tot aan de begintoestand die geen vorige toestand meer heeft (rode cirkel).

reconstructie route

Gevraagd wordt:

Al deze functies mogen ervan uitgaan dat de argumenten die eraan doorgegeven worden geldig zijn, zonder dat dit expliciet moet gecontroleerd worden.

Voorbeeld

>>> gieten((0, 2, 6), (3, 5, 8), 2, 0)
(3, 2, 3)
>>> gieten((0, 2, 6), (3, 5, 8), 2, 1)
(0, 5, 3)
>>> gieten((3, 2, 3), (3, 5, 8), 1, 0)
>>> gieten((3, 2, 3), (3, 5, 8), 2, 2)

>>> volgende_toestanden((0, 0, 8), (3, 5, 8))
{(0, 5, 3), (3, 0, 5)}
>>> volgende_toestanden((0, 5, 3), (3, 5, 8))
{(3, 2, 3), (0, 0, 8), (3, 5, 0)}
>>> volgende_toestanden((3, 0, 5), (3, 5, 8))
{(0, 3, 5), (0, 0, 8), (3, 5, 0)}

>>> wachtrij = [(0, 0, 8)]
>>> voorganger = {(0, 0, 8): None}
>>> stap(wachtrij, voorganger, (3, 5, 8))
>>> wachtrij
[(0, 5, 3), (3, 0, 5)]
>>> voorganger
{(0, 0, 8): None, (0, 5, 3): (0, 0, 8), (3, 0, 5): (0, 0, 8)}
>>> stap(wachtrij, voorganger, (3, 5, 8))
>>> wachtrij
[(3, 0, 5), (3, 2, 3), (3, 5, 0)]
>>> voorganger
{(0, 0, 8): None, (0, 5, 3): (0, 0, 8), (3, 0, 5): (0, 0, 8), (3, 2, 3): (0, 5, 3), (3, 5, 0): (0, 5, 3)}

>>> route((3, 5, 0), voorganger)
[(0, 0, 8), (0, 5, 3), (3, 5, 0)]

>>> oplossing((0, 0, 8), (3, 5, 8), 4)
[(0, 0, 8), (0, 5, 3), (3, 2, 3), (0, 2, 6), (2, 0, 6), (2, 5, 1), (3, 4, 1)]
>>> oplossing((0, 5, 0, 17), (3, 5, 11, 17), 7)
[(0, 5, 0, 17), (0, 0, 5, 17), (0, 5, 5, 12), (0, 0, 10, 12), (0, 5, 10, 7)]
>>> oplossing((0, 0, 0, 0, 13), (5, 7, 7, 11, 13), 12)
[(0, 0, 0, 0, 13), (0, 0, 0, 11, 2), (5, 0, 0, 6, 2), (0, 0, 0, 6, 7), (5, 0, 0, 1, 7), (0, 0, 0, 1, 12)]

Epiloog