Basis

Deze pagina bevat uitvoerbare code.

Wat een tekst over zichzelf zegt, korter

In de basisopgave van week 1 schreef je functies die een tekst doorlichten: hoeveel woorden, welk woord het vaakst, hoeveel woorden maar één keer. Dat waren allemaal lussen. Een deel ervan bouwt een nieuwe lijst, set of dictionary op, en dat past nu op één regel. Een ander deel blijft een lus.

In deze opgave schrijf je functies over dezelfde teksten. Bij elke functie beslis je welke vorm past, en je vertaalt in beide richtingen: van lus naar comprehension en van comprehension naar lus.

Lus of comprehension

Een lus die een nieuwe verzameling opbouwt, met per element hoogstens één ding erbij, schrijf je als comprehension. Het lusrecept wijst de delen aan:

result = []  # 1. verzamelen: een nieuwe lijst

for word in words:  # 2. langs de woorden
    if len(word) <= 3:  # 3. of het erbij komt
        result.append(word)  # 3. wat erbij komt

wordt

result = [word for word in words if len(word) <= 3]

Een lus blijft een lus als hij per stap iets bijwerkt wat er al was, iets onthoudt tussen de stappen, of per stap iets doet in plaats van iets te verzamelen. Zie het tweede college van deze week.

De teksten

assets/teksten/kort.txt en assets/teksten/vuurtoren.txt zijn dezelfde bestanden als in week 1. read_text en word_list uit week 1 staan in de cel hieronder klaar. Voer die cel eerst uit.

TEKSTEN = "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()

Wat je gaat maken

Stap

Functie

Doet

Wat je oefent

1

long_words

de woorden die langer zijn dan n letters

een list comprehension schrijven

2

count_words

tellen hoe vaak elk woord voorkomt

kiezen tussen lus en comprehension

3

words_used_once

tellen hoeveel woorden maar één keer voorkomen

van lus naar comprehension

4

ngram_loop

een woord opdelen in stukjes van n letters

van comprehension naar lus

5

count_table

de telling als lijst van lijsten

een lijst van lijsten opbouwen

6

share

het aandeel van de vaakst gebruikte woorden

filteren in een lijst van lijsten

Stap 1: long_words(words, n)

Geeft een lijst met de woorden uit words die langer zijn dan n letters, in dezelfde volgorde. Gebruik een list comprehension.

Aanroep

Resultaat

long_words(["de", "vuurtoren", "draait"], 5)

["vuurtoren", "draait"]

long_words([], 5)

[]

long_words(word_list(read_text(TEKSTEN + "vuurtoren.txt")), 11)

["zesenvijftig", "tweehonderdzesendertig", "vuurtorenwachter", "geschiedenis", "tegenwoordig"]

# jouw oplossing
assert long_words(["de", "vuurtoren", "draait"], 5) == ["vuurtoren", "draait"]
assert long_words([], 5) == []
assert long_words(word_list(read_text(TEKSTEN + "vuurtoren.txt")), 11) == [
    "zesenvijftig",
    "tweehonderdzesendertig",
    "vuurtorenwachter",
    "geschiedenis",
    "tegenwoordig",
]

Stap 2: count_words(words)

Geeft een dictionary die elk woord koppelt aan het aantal keren dat het voorkomt.

Aanroep

Resultaat

count_words(["spam", "spam", "taart"])

{"spam": 2, "taart": 1}

count_words([])

{}

Er zijn twee manieren, en ze geven allebei hetzelfde resultaat:

Lus, uit week 1

Dict comprehension

begin met counts = {} en tel per woord één bij het aantal van dat woord op

{word: words.count(word) for word in set(words)}

Kies één van de twee, schrijf count_words en zet er in een commentaarregel bij waarom je die kiest. Denk daarbij aan wat er per stap gebeurt: hoe vaak loopt elke versie de woorden langs? Kijk je keuze na met de tekst onder de testcel.

# jouw oplossing
assert count_words(["spam", "spam", "taart"]) == {"spam": 2, "taart": 1}
assert count_words([]) == {}
assert count_words(word_list(read_text(TEKSTEN + "kort.txt")))["taarten"] == 3

Je keuze klopt als je de lus kiest, met als reden dat de lus de woorden één keer langsloopt en per stap één telling bijwerkt, terwijl de comprehension voor elk verschillend woord de hele lijst opnieuw langsloopt met count.

Stap 3: words_used_once(counts)

Geeft het aantal woorden dat precies één keer voorkomt. In week 1 was dat deze lus:

def words_used_once(counts):
    """Telt hoeveel woorden precies één keer voorkomen."""
    n = 0

    for word in counts:
        if counts[word] == 1:
            n = n + 1

    return n

Schrijf dezelfde functie in één regel, met sum en een list comprehension.

Aanroep

Resultaat

words_used_once({"spam": 2, "taart": 1})

1

words_used_once({})

0

Hint

Je hebt alleen de aantallen nodig, niet de woorden. counts.values() geeft je de aantallen. Maak een lijst met een 1 voor elk aantal dat 1 is, en tel die op.

# jouw oplossing
assert words_used_once({"spam": 2, "taart": 1}) == 1
assert words_used_once({}) == 0
assert (
    words_used_once(count_words(word_list(read_text(TEKSTEN + "vuurtoren.txt")))) == 94
)

Stap 4: ngram_loop(word, n)

Een woord kun je opdelen in stukjes van n letters die elkaar overlappen. Voor n = 2 zijn dat letterparen: "toren" wordt ["to", "or", "re", "en"]. Zoekmachines en spellingcontroles vergelijken woorden op zulke stukjes: een woord met een tikfout deelt er nog de meeste met het bedoelde woord.

Deze functie doet het met een list comprehension:

def ngram(word, n):
    """Geeft de overlappende stukjes van n letters van word, op volgorde."""
    return [word[i : i + n] for i in range(len(word) - n + 1)]

Schrijf ngram_loop(word, n), die precies hetzelfde doet, met een lus.

Aanroep

Resultaat

ngram_loop("toren", 2)

["to", "or", "re", "en"]

ngram_loop("toren", 5)

["toren"]

ngram_loop("toren", 6)

[]

Hint

Beantwoord de vragen van het lusrecept. Wat je verzamelt en wat je teruggeeft, zie je aan de vierkante haken. Wat je langsloopt, staat na for, en wat er per stap bijkomt, staat vooraan.

# jouw oplossing
def ngram(word, n):
    """Geeft de overlappende stukjes van n letters van word, op volgorde."""
    return [word[i : i + n] for i in range(len(word) - n + 1)]


assert ngram_loop("toren", 2) == ["to", "or", "re", "en"]
assert ngram_loop("toren", 5) == ["toren"]
assert ngram_loop("toren", 6) == []
for word in ["", "a", "vuurtoren"]:
    for n in [1, 2, 3]:
        assert ngram_loop(word, n) == ngram(word, n)

Stap 5: count_table(counts)

Geeft de telling als lijst van lijsten: voor elk woord een lijst [woord, aantal], in de volgorde van de dictionary.

Aanroep

Resultaat

count_table({"spam": 2, "taart": 1})

[["spam", 2], ["taart", 1]]

count_table({})

[]

# jouw oplossing
assert count_table({"spam": 2, "taart": 1}) == [["spam", 2], ["taart", 1]]
assert count_table({}) == []
assert count_table(count_words(word_list(read_text(TEKSTEN + "vuurtoren.txt"))))[0] == [
    "de",
    8,
]

Stap 6: share(table, n_words, minimum)

table is een tabel uit stap 5, en n_words het aantal woorden van de tekst. Geeft voor elk woord dat minstens minimum keer voorkomt een lijst [woord, percentage]. Het percentage is het aandeel van dat woord in de tekst, in hele procenten naar beneden afgerond: 100 * aantal // n_words.

Aanroep

Resultaat

share([["de", 3], ["toren", 1]], 4, 2)

[["de", 75]]

share([], 4, 2)

[]

Schrijf het als één list comprehension.

Hint

Elk element van table is een lijst van twee. Die pak je uit zoals een paar: for word, count in table. Het filter kijkt naar count, en wat erin komt, is een nieuwe lijst van twee.

# jouw oplossing
assert share([["de", 3], ["toren", 1]], 4, 2) == [["de", 75]]
assert share([], 4, 2) == []
words = word_list(read_text(TEKSTEN + "kort.txt"))
assert share(count_table(count_words(words)), len(words), 3) == [
    ["ik", 15],
    ["taarten", 15],
    ["en", 15],
]

Tot slot

In kort.txt zijn ik, taarten en en samen al 45 procent van de woorden. In vuurtoren.txt haalt geen enkel woord meer dan 4 procent, naar beneden afgerond zoals share doet, en de woorden die het hoogst scoren, zijn korte woorden: een, de, en en het.

Kijk nog eens terug naar stap 2 en 3. Allebei tellen ze, en toch werd de ene een lus en de andere een comprehension. Het verschil zit in wat er per stap gebeurt: stap 2 werkt per woord een telling bij, stap 3 verzamelt alleen een 1 per aantal. Die vraag, wat gebeurt er per stap, beslist ook in de weken hierna welke vorm je kiest.