De meeste legpuzzels kun je slechts één keer oplossen en dan ben je klaar. Maar de Daily Calendar Puzzle1 is een ontzettend leuke en verslavende puzzel die elke dag een nieuwe uitdaging in petto heeft. Al wat je moet doen is de tien puzzelstukken in het kalenderframe leggen zodat in de drie uitsparingen een weekdag, een dag en een maand overblijven.

Daily Calendar Puzzle
De Daily Calendar Puzzle is een ontzettend leuke en verslavende puzzel die elke dag een nieuwe uitdaging in petto heeft.
Daily Calendar Puzzle
De Daily Calendar Puzzle is een ontzettend leuke en verslavende puzzel die elke dag een nieuwe uitdaging in petto heeft.

Het zou een leugen zijn om te zeggen dat alle datums makkelijk te vormen zijn, maar voor elke dag is er minstens één oplossing te vinden. Kun je de datum van vandaag leggen? Je verjaardag? Een historische gebeurtenis?

Opgave

Het kalenderframe van een Daily Calendar Puzzle bestaat uit een $$8 \times 7$$ rooster met 8 rijen en 7 kolommen. De 56 vierkante vakjes in het rooster krijgen elk een volgnummer van 0 tot en met 55 (int) door het rooster van links naar rechts en van boven naar onder te overlopen. Er zijn zes vakjes waarop geen puzzelstuk kan gelegd worden: 6, 13, 49, 50, 51 en 52 (hieronder weergegeven met een donkerder achtergrondkleur). Alle andere vakjes hebben een uniek label: achtereenvolgens (op volgnummer) de Engelse drie-letter-afkortingen voor de twaalf maanden van het jaar (JanDec), de dagnummers (131) en de Engelse drie-letter afkortingen voor de zeven weekdagen (SunSat).

kalenderframe
Het kalenderframe van een Daily Calendar Puzzle waarin we de vakjes van links naar rechts en van boven naar onder genummerd hebben vanaf nul.

Elk puzzelstuk krijgt als label een unieke hoofdletter (str) om het van andere puzzelstukken te kunnen onderscheiden. De plaats waar een puzzelstuk in het frame neergelegd wordt, stellen we voor als een collectie (list, tuple of set) met de volgnummers van de vakjes die door het puzzelstuk bedekt worden. Dit beschrijft meteen ook de vorm van het puzzelstuk. Hieronder hebben we bijvoorbeeld een recht oranje puzzelstuk neergelegd op plaats (0, 1, 2, 3), waardoor het de vier vakjes aan de linkerkant van de bovenste rij bedekt.

puzzelstuk
Het oranje puzzelstuk bedekt de vakjes met volgnummers 0, 1, 2 en 3.

Definieer een klasse Kalender waarmee het frame van een Daily Calendar Puzzle kan voorgesteld worden en waarin puzzelstukken kunnen neergelegd en weggenomen worden. Bij het aanmaken van een nieuw frame (Kalender) moeten er geen argumenten doorgegeven worden: elk frame heeft dezelfde afmetingen en bij aanvang liggen er nog geen puzzelstukken in het frame. Een frame $$f$$ (Kalender) moet minstens de volgende methoden ondersteunen:

Als er een frame $$f$$ (Kalender) wordt doorgegeven aan de ingebouwde functie str, dan moet een stringvoorstelling (str) van het frame teruggegeven worden. Daarin vormt elke rij een afzonderlijke regel met de stringvoorstelling van de vakjes op die rij in frame $$f$$, opgelijst van links naar rechts en telkens van elkaar gescheiden door één spatie.

Voorbeeld

>>> frame = Kalender()
>>> frame.vakje(0)
'Jan'
>>> frame.vakje(39)
' 26'
>>> frame.vakje(48)
'Wed'
>>> frame.vakje(52)
'###'
>>> print(frame)
Jan Feb Mar Apr May Jun ###
Jul Aug Sep Oct Nov Dec ###
  1   2   3   4   5   6   7
  8   9  10  11  12  13  14
 15  16  17  18  19  20  21
 22  23  24  25  26  27  28
 29  30  31 Sun Mon Tue Wed
### ### ### ### Thu Fri Sat
>>> frame.puzzelstuk('A')
Traceback (most recent call last):
AssertionError: puzzelstuk A ligt er niet
>>> print(frame.neerleggen('A', (0, 1, 2, 3)))
#A# #A# #A# #A# May Jun ###
Jul Aug Sep Oct Nov Dec ###
  1   2   3   4   5   6   7
  8   9  10  11  12  13  14
 15  16  17  18  19  20  21
 22  23  24  25  26  27  28
 29  30  31 Sun Mon Tue Wed
### ### ### ### Thu Fri Sat
>>> frame.puzzelstuk('A')
{0, 1, 2, 3}
>>> print(frame.neerleggen('B', (4, 5, 9, 10, 11)))
#A# #A# #A# #A# #B# #B# ###
Jul Aug #B# #B# #B# Dec ###
  1   2   3   4   5   6   7
  8   9  10  11  12  13  14
 15  16  17  18  19  20  21
 22  23  24  25  26  27  28
 29  30  31 Sun Mon Tue Wed
### ### ### ### Thu Fri Sat
>>> frame.neerleggen('B', (7, 8, 14, 21, 28))
Traceback (most recent call last):
AssertionError: puzzelstuk B ligt er al
>>> frame.neerleggen('C', (13, 14, 15))
Traceback (most recent call last):
AssertionError: puzzelstuk C kan niet gelegd worden
>>> frame.neerleggen('C', (0, 1, 7, 8))
Traceback (most recent call last):
AssertionError: puzzelstuk C kan niet gelegd worden
>>> print(frame.neerleggen('C', [7, 8, 14, 21, 28]).neerleggen('D', [15, 16, 22, 23, 29]))
#A# #A# #A# #A# #B# #B# ###
#C# #C# #B# #B# #B# Dec ###
#C# #D# #D#   4   5   6   7
#C# #D# #D#  11  12  13  14
#C# #D#  17  18  19  20  21
 22  23  24  25  26  27  28
 29  30  31 Sun Mon Tue Wed
### ### ### ### Thu Fri Sat
>>> print(frame.wegnemen('A'))
Jan Feb Mar Apr #B# #B# ###
#C# #C# #B# #B# #B# Dec ###
#C# #D# #D#   4   5   6   7
#C# #D# #D#  11  12  13  14
#C# #D#  17  18  19  20  21
 22  23  24  25  26  27  28
 29  30  31 Sun Mon Tue Wed
### ### ### ### Thu Fri Sat
>>> frame.wegnemen('Z')
Traceback (most recent call last):
AssertionError: puzzelstuk Z ligt er niet
>>> print(frame.neerleggen('A', (0, 1, 2, 3)))
#A# #A# #A# #A# #B# #B# ###
#C# #C# #B# #B# #B# Dec ###
#C# #D# #D#   4   5   6   7
#C# #D# #D#  11  12  13  14
#C# #D#  17  18  19  20  21
 22  23  24  25  26  27  28
 29  30  31 Sun Mon Tue Wed
### ### ### ### Thu Fri Sat
>>> print(frame.neerleggen('E', {17, 24, 25, 26, 33}).neerleggen('F', {18, 19, 20, 27, 34}))
#A# #A# #A# #A# #B# #B# ###
#C# #C# #B# #B# #B# Dec ###
#C# #D# #D# #E# #F# #F# #F#
#C# #D# #D# #E# #E# #E# #F#
#C# #D#  17  18  19 #E# #F#
 22  23  24  25  26  27  28
 29  30  31 Sun Mon Tue Wed
### ### ### ### Thu Fri Sat
>>> print(frame.neerleggen('G', (30, 31, 32, 38, 45)).neerleggen('H', (35, 36, 37, 42, 44)))
#A# #A# #A# #A# #B# #B# ###
#C# #C# #B# #B# #B# Dec ###
#C# #D# #D# #E# #F# #F# #F#
#C# #D# #D# #E# #E# #E# #F#
#C# #D# #G# #G# #G# #E# #F#
#H# #H# #H# #G#  26  27  28
#H#  30 #H# #G# Mon Tue Wed
### ### ### ### Thu Fri Sat
>>> frame.uitlezen()
Traceback (most recent call last):
AssertionError: ongeldige datum
>>> print(frame.neerleggen('I', (39, 40, 41, 48)).neerleggen('J', (46, 47, 54, 55)))
#A# #A# #A# #A# #B# #B# ###
#C# #C# #B# #B# #B# Dec ###
#C# #D# #D# #E# #F# #F# #F#
#C# #D# #D# #E# #E# #E# #F#
#C# #D# #G# #G# #G# #E# #F#
#H# #H# #H# #G# #I# #I# #I#
#H#  30 #H# #G# #J# #J# #I#
### ### ### ### Thu #J# #J#
>>> frame.uitlezen()
'Thu, 30 Dec'

Epiloog

Er zijn 28 972 628 verschillende manieren waarop de 10 puzzelstukken in het rooster kunnen gelegd worden. Daarvan zijn er 4 937 780 (17.04 %) die een maand, een dag en een weekdag open laten en 4 864 096 (16.79 %) als we daaruit ook nog ongeldige datums zoals 30 februari of 31 november schrappen. De makkelijkste datum is dinsdag 7 juni (Tue, 7 Jun): die kan op 10 374 verschillende manieren gevormd worden. De moeilijkste datum is maandag 6 april (Mon, 6 Apr): die kan op slechts 97 verschillende manieren gevormd worden.

Spoiler alert

Mocht je zelf verslaafd raken aan deze puzzel: dit is een overzicht2 met een willekeurig geselecteerde oplossing voor elk van de $$12 \times 31 \times 7$$ verschillende dagen van het jaar. Daarmee kan je alvast je frustraties opbergen op dagen waar je er zelf niet aan uitraakt.

Onder de naam dageraat3 werd er ook een versie van de puzzel met zeshoekige vakjes uitgebracht.

dageraat
Versie met zeshoekige vakjes.
dageraat
Versie met zeshoekige vakjes.
dageraat
Versie met zeshoekige vakjes.