Een register bij een tekst

Deze pagina bevat uitvoerbare code.

Waar staat dat woord?

Achter in een studieboek staat vaak een register: een lijst van belangrijke woorden, met bij elk woord de bladzijden waarop het staat. Zoek je iets op, dan hoef je niet het hele boek door te lezen.

In dit werkcollege maak je samen een register bij een tekst. Een tekst heeft geen bladzijden, dus je gebruikt regelnummers. En welke woorden belangrijk zijn, kan een programma niet weten; daarom neem je alle woorden van een minimale lengte op. Korte woorden als de en het vallen dan vanzelf weg.

Het register

Neem deze tekst van drie regels:

De vuurtoren draait.
Het licht draait elke nacht.
De schipper ziet het licht.

Met de woorden van minstens vijf letters wordt het register een dictionary. Elk woord is een sleutel, en de waarde is de lijst van regelnummers waarop het staat. De woorden staan in de volgorde waarin ze voor het eerst voorkomen, en de regels tellen vanaf 1:

Woord

Regels

"vuurtoren"

[1]

"draait"

[1, 2]

"licht"

[2, 3]

"nacht"

[2]

"schipper"

[3]

read_text en word_list uit de basisopgave van week 1 staan in de cel hieronder klaar, samen met deze voorbeeldtekst als EXAMPLE. Voer die cel eerst uit.

# Dit notebook staat in practicals/ en de teksten in problems/assets/teksten/,
# vandaar het pad terug.
TEKSTEN = "../problems/assets/teksten/"
LEESTEKENS = ".,!?;:"


def read_text(filename):
    """Leest een heel bestand en geeft de inhoud terug als één string."""
    with open(filename) as file:
        return file.read()


def word_list(text):
    """Geeft de woorden van text in kleine letters, zonder leestekens."""
    schoon = ""

    for teken in text.lower():
        if teken in LEESTEKENS:
            schoon = schoon + " "
        else:
            schoon = schoon + teken

    return schoon.split()


EXAMPLE = (
    "De vuurtoren draait.\nHet licht draait elke nacht.\nDe schipper ziet het licht."
)

Wat je gaat maken

Stap

Functie

Doet

Vorm

1

index_words

de woorden van minstens n letters, elk één keer, op volgorde

lus

2

lines_with

de regelnummers waarop een woord staat

list comprehension

3

register

het hele register

dict comprehension

4

on_more_lines

de woorden die op meer dan één regel staan

set comprehension

5

print_register

het register afdrukken

lus

In de laatste kolom zie je welke vorm er past. Twee stappen blijven een lus, en bij die stappen staat waarom.

Stap 1: index_words(words, n)

Geeft een lijst van de woorden uit words met minstens n letters. Elk woord staat er één keer in, in de volgorde waarin het voor het eerst voorkomt.

Aanroep

Resultaat

index_words(["de", "licht", "draait", "licht"], 5)

["licht", "draait"]

index_words([], 5)

[]

Dit wordt een lus, en niet een comprehension. Per woord moet je nagaan of het al in je resultaat staat, en een comprehension kan niet kijken naar de lijst die hij zelf aan het opbouwen is. Een set comprehension laat de dubbele woorden wel weg, maar een set heeft geen volgorde, en het register wil de volgorde van de tekst.

Hint

Begin met een lege lijst. Voeg een woord alleen toe als het lang genoeg is én nog niet in de lijst staat: word not in result.

# jouw oplossing
assert index_words(["de", "licht", "draait", "licht"], 5) == ["licht", "draait"]
assert index_words([], 5) == []
assert index_words(word_list(EXAMPLE), 5) == [
    "vuurtoren",
    "draait",
    "licht",
    "nacht",
    "schipper",
]

Stap 2: lines_with(word, lines)

lines is een lijst van regels. Geeft de lijst van regelnummers waarop word staat, geteld vanaf 1.

Aanroep

Resultaat

lines_with("draait", EXAMPLE.split("\n"))

[1, 2]

lines_with("taart", EXAMPLE.split("\n"))

[]

text.split("\n") deelt een tekst op in regels.

Hint

enumerate(lines, 1) geeft je bij elke regel ook het regelnummer, vanaf 1. Maak van elke regel eerst een lijst woorden met word_list, en vraag dan met in of word erin staat. Vraag het niet aan de regel zelf: "licht" in "lichten" is ook True.

# jouw oplossing
assert lines_with("draait", EXAMPLE.split("\n")) == [1, 2]
assert lines_with("taart", EXAMPLE.split("\n")) == []
assert lines_with("licht", []) == []

Stap 3: register(text, n)

Geeft het register van text, met de woorden van minstens n letters: een dictionary van woord naar de lijst van regelnummers.

Aanroep

Resultaat

register(EXAMPLE, 5)

de dictionary uit de tabel bovenaan

register("", 5)

{}

Hint

Loop in een dict comprehension de woorden van index_words langs. De waarde bij elk woord komt uit lines_with. Een dictionary houdt de volgorde aan waarin je de sleutels erin zet.

# jouw oplossing
assert register(EXAMPLE, 5) == {
    "vuurtoren": [1],
    "draait": [1, 2],
    "licht": [2, 3],
    "nacht": [2],
    "schipper": [3],
}
assert register("", 5) == {}
assert len(register(read_text(TEKSTEN + "vuurtoren.txt"), 8)) == 19

Stap 4: on_more_lines(reg)

reg is een register. Geeft de set van woorden die op meer dan één regel staan.

Aanroep

Resultaat

on_more_lines(register(EXAMPLE, 5))

{"draait", "licht"}

on_more_lines({})

set()

Hint

Loop het register langs met .items(). Bij elk woord hoort een lijst regelnummers, en len zegt op hoeveel regels het staat.

# jouw oplossing
assert on_more_lines(register(EXAMPLE, 5)) == {"draait", "licht"}
assert on_more_lines({}) == set()
assert on_more_lines(register(read_text(TEKSTEN + "vuurtoren.txt"), 8)) == {
    "vuurtoren",
    "schipper",
    "vijftien",
    "seconden",
}

Stap 5: print_register(reg)

Drukt het register af: per woord één regel, met het woord en zijn regelnummers. Geeft niets terug.

Voor register(EXAMPLE, 5):

vuurtoren [1]
draait [1, 2]
licht [2, 3]
nacht [2]
schipper [3]

Dit wordt weer een lus. Afdrukken levert geen nieuwe verzameling op, dus een comprehension heeft hier niets te verzamelen.

# jouw oplossing
print_register(register(EXAMPLE, 5))
print_register(register(read_text(TEKSTEN + "vuurtoren.txt"), 8))

Tot slot

Het register van de vuurtorentekst heeft negentien woorden van minstens acht letters. Vier daarvan staan op meer dan één regel. Probeer ook eens een andere minimale lengte, of een eigen tekst.

Kijk nog eens naar de laatste kolom van het overzicht. Bij stap 2, 3 en 4 bouwde je een nieuwe lijst, dictionary of set op, en daar was een comprehension korter dan de lus. Bij stap 1 moest je onthouden wat je al had, en bij stap 5 deed je iets per regel: daar bleef het een lus.