andere opgaven: acht koninginnen | boter, kaas en eieren | logiquiz | sudoku

Logiquiz puzzel oplossen

Hoe een logiquiz puzzel oplossen, of hoe zelf een logiquiz ontwerpen ? Hier wordt uitgelegd hoe je dat met behulp van de computer kan doen.

Inhoud:
de puzzel
benadering
programma afloop
invoer
voorbeeld
zelf logiquiz ontwerpen
code

De puzzel:

Een logiquiz puzzel (ook wel logikwis of logigram genoemd) behandelt een gebeurtenis. Door middel van aanwijzingen moet door logisch nadenken worden beredeneerd hoe (wie-wat-waar-wanneer-hoeveel-...) die gebeurtenis heeft plaatsgevonden. Voor een voorbeeld: zie hier.
Als deze puzzel met de hand wordt opgelost, worden de aanwijzingen gebruikt om (on)mogelijke eigenschappen te matchen of tegen elkaar weg te strepen, net zolang tot er één combinatie overblijft.

Benadering:

Een andere manier waarop de puzzel kan worden opgelost, is: werk alle mogelijke combinaties uit, en elimineer de combinaties die niet voldoen aan de aanwijzingen, dan blijft er (als het goed is) één combinatie over: de oplossing.
 
Om alle mogelijke oplossingen te vinden, is het begrip permutatie interessant, dat geeft aan hoeveel mogelijke combinaties er bestaan.
Bijvoorbeeld: op hoeveel verschillende manieren zijn drie gekleurde ballen (rood, groen, blauw) te verdelen over drie posities ?
dat zijn 6 verschillende manieren:

hoeveel permutaties mogelijk zijn, is te berekenen met het begrip faculteit. Op een standaard rekenmachine is dit te vinden onder de '!'-knop: bv faculteit 3 = 3! = 3 x 2 x 1 = 6
 
om het voorbeeld van de gekleurde ballen automatisch te kunnen verwerken wordt hier een getalnotatie gebruikt:

 
Bovenstaande geldt voor één eigenschap ('kleur'), dit kan worden uitgebreid met een extra eigenschap (bijvoorbeeld 'vorm'):

het aantal combinaties is nu 3! x 3! = 36 (kartesiaans produkt)
 
De moeilijkere logiquiz puzzels hebben 5 'voorwerpen' met ieder 3 'eigenschappen', dat betekent (5!)3 = 1,728,000 mogelijke combinaties (!).
 
Laat een computer dat nou geen probleem vinden...

Programma afloop:

1. ingeven van de opgave ( opgave() ): de 'actoren', eigenschappen en waarden.
 
2. verzamelen alle permutaties van het probleem ( permuteer() )
Tegelijkertijd wordt bij iedere permutatie ook de inverse permutatie bepaald, met hetzelfde indexnummer; dit maakt het mogelijke niet alleen een eigenschap van een 'actor' te bepalen, maar ook omgekeerd (de actor behorend bij een eigenschap)
 
3. het controleren van de logica tegen een permutatie ( logika() )
 
4. indien oplossing(en) zijn gevonden: afdrukken in leesbaar formaat ( druk_oplossing_af() )
 

Invoeren van een puzzel:

De op te lossen puzzel wordt ingegeven in de functies opgave() en logika()
 
Voor de logica staan deze functies ter beschikking:
waarde(): geeft de waarde (van een eigenschap van een actor)
actor(): geeft de actor (behorend bij een eigenschap (waarde) )
eigenschap_match(): bepaalt (True/False) of twee waarden tot één en dezelfde actor behoren.
 
alle denkbare Python operatoren zijn bruikbaar. Nesten van functies is uiteraard mogelijk. Met deze drie functies kan de moeilijkste puzzel worden 'gekraakt'.
 
Voorbeeld: voor de fictieve opgave 'schoolverkeer' moet worden bepaald:
'Hoe komt iedere scholier op school, hoe lang duurt het om daar te komen, en wat is hun standaard smoes als ze te laat zijn ?'

aanwijzinglogica (python)
'Mark komt iedere dag met de taxi'actor('taxi') == 'mark'
'Daphne's reistijd verschilt met die van de wandelaar'waarde('reistijd', 'daphne') != waarde('reistijd', actor('te voet'))
'de fietser is twee keer zo lang onderweg als Diederik en degene zich niet lekker voelde, samen'waarde('reistijd', actor('fietsen')) == 2 * ( waarde('reistijd', 'diederik') + waarde('reistijd', actor('misselijk')))
'Karel heeft niet als smoes dat zijn band lek is'actor('lekke band') != 'karel'
'de stepper stond niet voor een open brug'not(eigenschap_match('step', 'brug open'))
'degene die een kapotte wekker had, deed er niet 10 of 15 minuten over'waarde('reistijd', actor('wekker kapot')) not in [10,15]
'de wandelaar voelde zich niet zo lekker'eigenschap_match('misselijk', 'te voet')
'de stepper deed er langer over dan de taxi-er'waarde('reistijd', actor('step')) > waarde('reistijd', actor('taxi'))
 
Tips:
• zodra 1 van de voorwaarden niet waar is, wordt de rest van de logika verder genegeerd; maw. plaats de logika die de meeste oplossingen uitsluit, vooraan (meestal te herkennen aan '=='). Dit bespaart rekentijd.
• als er gerekend moet worden (in bovenstaand voorbeeld met de reistijd), geef de eigenschappen dan ook een numerieke waarde

Voorbeeld

Onderstaande puzzel is afkomstig van de website: logiquiz.nl
 
Opgave Gastsprekers © logiquiz.nl
 
Groep 8 ontvangt dit schooljaar enkele gastsprekers.

aanwijzinglogica (python)
1. in december komt iemand, die geen tycho heet, over vuurwerk vertellen.
In mei spreekt een 45-jarige over vrijheid.
actor('december') == actor('vuurwerk')
actor('december') != 'tycho'
actor('mei') == actor('vrijheid')
actor('mei') == actor(45)
2. Jeanet vertelt over nepwapensactor('nepwapens') == 'jeanet'
3. Degene die in februari over alcohol spreekt is 2 jaar jonger dan Petereigenschap_match('februari', 'alcohol')
waarde('leeftijd', actor('februari')) == waarde('leeftijd', 'peter') - 2
4. Tycho is 42 jaar oud. Cor is ouder dan Ninawaarde('leeftijd', 'tycho') == 42
waarde('leeftijd', 'cor') > waarde('leeftijd', 'nina')
5. Wie in juni spreekt, heeft het niet over EHBOnot eigenschap_match('juni', 'ehbo')
 
Deze opgave is uitgewerkt in onderstaande python code.

Zelf een logiquiz ontwerpen

Met dit programma is het ook eenvoudig om zelf een logiquiz te ontwerpen:
Bedenk een onderwerp met bijbehorende actoren, eigenschappen, en waarden.
Zet deze op papier (of Excel) tegen elkaar uit, en kies één enkele oplossing.
 
Bedenk nu enkele aanwijzingen die overeenkomen met de oplossing, en vertaal die in (python) logika regels.
Bekijk hoeveel oplossingen dit oplevert:
• als er geen (0) oplossingen zijn dan is een aanwijzing strijdig met de oplossing
• als er nog ~10 (of meer) oplossingen zijn dan zullen nog meer aanwijzingen moeten worden toegevoegd
• als er slechts enkele oplossingen overblijven, vergelijk dan die oplossingen onderling om specifieke aanwijzingen te bedenken.
Totdat er één oplossing overblijft.
 
De moeilijkheidsgraad van de puzzel wordt voornamelijk bepaald door de mate waarin de aanwijzingen elkaar 'overlappen'.
 
Veel plezier en succes !!
 

De code


#!/usr/bin/env python
"""will search for a solution of a Logiquiz puzzle"""
__author__ = "Mark Nauta"
__copyright__ = "Copyright 20"
__credits__ = "Mark Nauta"
__license__ = "GPL"
__version__ = "0.0.2"
__maintainer__ = "Mark Nauta"
__email__ = "markatnadropuntnl"
__status__ = "Production"

# see the full story: https://nadro.nl/mark/puzzels/logiquiz.html  

from datetime import datetime
from itertools import permutations
from itertools import product
from math import factorial
import sys

actoren, eigenschappen, waarden = [], [], []
n, permutatie_index = 0, 0
ps, qs, alle_permutaties = [], [], []

def opgave():
    global actoren, eigenschappen, waarden
    actoren = [ 'cor', 'jeanet', 'nina', 'peter', 'tycho' ] 
    eigenschappen = [ 'leeftijd', 'onderwerp', 'maand' ]
    waarden = [ [ 40, 42, 45, 48, 50 ], ['alcohol', 'ehbo', 'nepwapens', 'vrijheid', 'vuurwerk'], ['oktober', 'december', 'februari', 'mei', 'juni'] ] 

def logika():
    global permutatie_index
    oplossingen = []
    for permutatie_index in range(len(alle_permutaties)):
        if \
            actor('december') == actor('vuurwerk') and \
            actor('december') != 'tycho' and \
            actor('mei') == actor('vrijheid') and \
            actor('mei') == actor(45) and \
            actor('nepwapens') == 'jeanet' and \
            eigenschap_match('februari', 'alcohol') and \
            waarde('leeftijd', actor('februari')) == waarde('leeftijd', 'peter') - 2 and \
            waarde('leeftijd', 'tycho') == 42 and \
            waarde('leeftijd', 'cor') > waarde('leeftijd', 'nina') and \
            not eigenschap_match('juni', 'ehbo'):
          oplossingen.append(permutatie_index)
    return oplossingen

def waarde(eigenschap, actor):
    '''
    retourneert de waarde (van een eigenschap) van een actor
    -> eigenschap
    -> actor
    <- waarde van die eigenschap
    '''
    actor_index = actoren.index(actor)
    eigenschap_index = eigenschappen.index(eigenschap)
    perm_index = alle_permutaties[permutatie_index][eigenschap_index]
    perm = ps[perm_index] 
    value = waarden[eigenschap_index][perm[actor_index]]
    return value

def actor(waarde_):
    '''
    retourneert een actor behorend bij een waarde
    -> waarde (iedere unieke waarde toegestaan)
    <- actor
    '''
    property_index = _eigenschap_index(waarde_)
    value_index = waarden[property_index].index(waarde_)
    perm_index = alle_permutaties[permutatie_index][property_index]
    perm = qs[perm_index] 
    deze_actor = actoren[perm[value_index]]
    return deze_actor

def eigenschap_match(waarde1, waarde2):
    '''
    vergelijkt twee waarden van één actor
    -> twee waarden
    <- boolean 
    '''
    return actor(waarde1) == actor(waarde2)

def _eigenschap_index(waarde_):
    '''
    zoekt van een waarde de index van de eigenschap
    '''
    indexes=[]
    for index in range(len(eigenschappen)):
        if waarde_ in waarden[index]:
            indexes.append(index)
    if len(indexes) == 1:
        return indexes[0]
    if len(indexes) == 0: 
        sys.exit('onbekende waarde: {}'.format(waarde_) )
    sys.exit('dubbele waarde: {}'.format(waarde_) )            

def permuteer(): 
    global ps, qs, alle_permutaties
    for permutation in permutations(range(n)):
        ps.append(list(permutation)) 
        qs.append( [ permutation.index(y) for y in range(n) ] ) 

    fact = [ i for i in range(factorial(n)) ] 
    alle_permutaties = [ list(item) for item in product(fact, repeat=len(eigenschappen)) ]

def _controle():
    out = 'waarschuwing: ' 
    if len(eigenschappen) != len(waarden):
        out += 'controleer aantal eigenschappen of aantal waarden\n'
    alle_waarden = []
    for waardereeks in waarden:
        if len(waardereeks) != len(actoren):
             out += 'controleer aantal actoren of waarden\n'
        alle_waarden += waardereeks
    if len(list(alle_waarden)) != len(actoren) * len(eigenschappen):
        out += 'controleer of waarden (onderling) uniek zijn\n'
    return out

def druk_oplossing_af(permindex):
    global permutatie_index
    permutatie_index = permindex
    out = '               | '
    for prop in eigenschappen:
        out += format(prop, '<15s') + '| '
    out += '\n'
    for subj in actoren:
        out += format(subj, '<15s') + '| '
        for prop in eigenschappen:
            out += format(str(waarde(prop, subj)), '<15s') + '| '
        out += '\n'
    print(out)

if __name__ == "__main__":
    #global n
    timerstart = datetime.now()
    opgave()
    melding = _controle()
    if len(melding) > 15:
        print(melding)
    n = len(actoren)
    permuteer()
    oplossingen = logika()
    for oplossing in oplossingen:
        druk_oplossing_af(oplossing)
    timerend = datetime.now()
    delta = timerend - timerstart
    print('{} solution(s) found in {:0.3f} sec.'.format(len(oplossingen), delta.seconds + delta.microseconds/1000000))