Tekst genereren met Markovprocessen

Deze pagina bevat uitvoerbare code.

Opdracht: Tekst genereren met Markovprocessen

Opdracht 1: De functie create_dictionary

import random

# Dit notebook staat in solutions/ en het bestand in practicals/assets/,
# vandaar het pad terug. In de opgave zelf is dat gewoon "a.txt".
A_TXT = "../practicals/assets/a.txt"


def create_dictionary(filename):
    """Geeft de woordovergangen uit een UTF-8-tekstbestand als dictionary terug."""
    with open(filename, encoding="utf-8") as file:
        text = file.read()

    words_follow = {}
    previous_word = "$"

    for new_word in text.split():
        if previous_word not in words_follow:
            words_follow[previous_word] = [new_word]
        else:
            words_follow[previous_word] += [new_word]

        if new_word[-1] in ".?!":
            previous_word = "$"
        else:
            previous_word = new_word

    return words_follow


assert create_dictionary(A_TXT) == {
    "krijg": ["toch"],
    "voor": ["de"],
    "wil": ["taarten", "42"],
    "toch": ["spam"],
    "Ik": ["wil", "krijg", "wil"],
    "spam": ["en"],
    "42": ["en", "taarten!"],
    "$": ["Ik", "Ik", "Ik"],
    "taarten": ["en", "voor"],
    "de": ["vakantie?"],
    "en": ["42", "spam.", "taarten"],
}

previous_word begint bij het startsymbool "$", en elk woord dat op een zin-einde volgt (., ? of !) zet previous_word daar weer op terug. Zo komt elk zinsbegin in de lijst bij "$" terecht, precies zoals het college en de opgave het beschrijven.

De test hierboven is exact het voorbeeld uit de opgave: create_dictionary('a.txt') levert de dictionary op die daar staat.

Opdracht 2: De functie generate_text

def generate_text(words_follow, n_words):
    """Drukt n_words woorden af volgens de woordovergangen in words_follow.

    n_words is positief. Begint een nieuwe zin na een eindleesteken of een
    woord zonder opvolger; de dictionary bevat daarvoor het startsymbool '$'.
    """
    previous_word = "$"
    words = []

    for _ in range(n_words):
        new_word = random.choice(words_follow[previous_word])
        words.append(new_word)
        if new_word[-1] in ".?!" or new_word not in words_follow:
            previous_word = "$"
        else:
            previous_word = new_word

    print(" ".join(words))


generate_text(create_dictionary(A_TXT), 20)

# Controleer de afgedrukte tekst zonder een willekeurige zin vast te leggen.
from contextlib import redirect_stdout
from io import StringIO
from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as directory:
    training_file = Path(directory) / "training.txt"
    training_file.write_text("A B C", encoding="utf-8")
    transitions = create_dictionary(training_file)
    for length, expected in [(3, "A B C"), (4, "A B C A")]:
        output = StringIO()
        with redirect_stdout(output):
            generate_text(transitions, length)
        assert output.getvalue().strip() == expected

    training_file.write_text("één café.", encoding="utf-8")
    assert create_dictionary(training_file) == {"$": ["één"], "één": ["café."]}

    training_file.write_text("A B A. A B C. B A C. C C C.", encoding="utf-8")
    transitions = create_dictionary(training_file)
    assert transitions == {
        "$": ["A", "A", "B", "C"],
        "A": ["B", "B", "C."],
        "B": ["A.", "C.", "A"],
        "C": ["C", "C."],
    }
    output = StringIO()
    with redirect_stdout(output):
        generate_text(transitions, 20)
    generated = output.getvalue().split()
    assert len(generated) == 20
    assert "$" not in generated
    previous_word = "$"
    for word in generated:
        assert word in transitions[previous_word]
        if word[-1] in ".?!" or word not in transitions:
            previous_word = "$"
        else:
            previous_word = word
Ik wil taarten voor de vakantie? Ik wil taarten voor de vakantie? Ik wil taarten en taarten voor de vakantie?

Deze uitwerking kiest de toegestane herstart bij $ als een woord geen opvolger heeft. De functie blijft het gevraagde aantal woorden afdrukken.

Door de willekeur kun je bij algemene invoer geen vaste zin verwachten. Je kunt wel het aantal woorden, het startsymbool en de geldige overgangen controleren. De assertions hierboven doen dat voor de practicumtekst en toetsen ook het lezen van UTF-8. Bij A B C is er steeds maar één keuze: drie woorden geven A B C, vier woorden geven na de herstart A B C A.