Göm meny
Gäller för: VT24

Laboration 2: Autocomplete och autocorrect

Laboration 2 består av tre delar som behandlar villkorssatser, jämförelser och sanningsvärden, användning av for- och while-loopen, sträng- och list-hantering med hjälp av loopar, samt inläsning och användning av data lagrad på fil i CSV-format.

Redovisning, inlämning och kompletteringar

  • Information om den muntliga redovisningen, samt eventuella kompletteringar kan ni läsa om sidan Redovisning.
  • Information om inlämning av Pythonuppgifter hittar ni på sidan Inlämningar.
  • Efter redovisning lämnas koden för Del 2 in via Sendlab.

Checklista för redovisning

  • Demonstrera programmet ni gjort i Del 2, Uppgift 2.
  • Ge exempel på följande i er kod:
    • villkorssats
    • jämförelse
    • sanningsvärde
    • loop (for eller while)
  • Peka ut och förklara er kod som läser in ordfrekvensinformation (Uppgift 2, Del 2).
  • Förklara hur er lösning på uppgift 2 använder ordfrekvensinformationen vilka steg den tar för att komma fram till de(t) ord den returnerar.

Del 1: Pythonuppgifter 2

Börja med Pythonuppgifter 2 innan ni börjar på Del 2. Pythonuppgifter 2 går igenom det mesta ni behöver för att lösa uppgiften i del 2.

Del 2: Autocomplete

I Del 2 ska ni skriva en autocomplete-funktion i programmeringsspråket Python. En autocomplete-funktion utgår från vad användaren börjat att skriva och förutspår resten av ordet. Funktionen kan förstås göras i princip hur enkel eller sofistikerad som helst. Den absolut enklaste formen kanske jämför indata mot en ordlista och returnerar det “första bästa”, medan en mer avancerad metod tar hänsyn till hur vanligt förekommande ordet är, eller vilka ord användaren har skrivit tidigare.

I den här laboration kommer ni att skriva koden i en texteditor och köra den i terminalen.

Det är starkt rekommenderat att göra minst Övning 0-3 från lektionen innan ni börjar på Del 2 på Laboration 2.

Kodskelett

Nedan hittar ni det kodskelett som ni ska utgå ifrån. Innan ni går vidare, läs koden och försök klura ut vad som skulle hända om man körde den.

  • Var sker det första anropet till en funktion? Till vilken funktion?
  • Hur gör man för att avsluta programmet?

Kopiera och spara sedan ned kodskelettet nedan i en fil på lämplig plats i er hemkatalog.

def main():
    word = ""
    while word != "q":
        word = input("Type word: ").lower()
        suggestion = autocomplete(word)
        print("Autocompletion finished: ", suggestion)

def autocomplete(word):
    """Return autocomplete suggestions."""
    pass

main()

Relevanta filer

Förrutom ovanstående kod, kommer ni få tillgång till en liten samling ord- och frekvensfiler i olika konstellationer och filformat. Korpusfilerna finns i /courses/TDDE44/kursmaterial/laboration2/.

Jobbar du på egen dator kan du ladda ner dem: laboration2.zip

shuffled.csv och alphabetical.csv är kommaseparerade filer där varje rad innehåller ett ord och ordets frekvens (d.v.s. hur ofta det förekommer i en viss textmängd). Nedan är ett exempel ur shuffled.csv:

.
.
spårvagn,4
armkrok,2
december,46
nattkylan,1
barhuvad,1
.
.

Filerna shuffled_words och alphabetical_words innehåller samma ord som respektive .csv-fil, men utan frekvenserna. På samma sätt innehåller shuffled_freq och alphabetical_freq samma frekvenser som återfinns i respektive .csv-fil, fast utan orden.

Ni får själva bedöma vilken eller vilka av korpusfilerna som ni behöver för att lösa uppgiften.

Ordlistorna är extraherade från Stockholm-Umeå-korpus 2.0 (SUC 2.0). SUC 2.0 är en balanserad korpus som är sammanställd från texter från olika källor såsom böcker och nyhetstexter. Ord som innehåller siffror eller skiljetecken är bortplockade, men utöver det är ordlistan inte filtrerad eller censurerad på något sätt utan reflekterar källmaterialet för SUC.

Diverse tips till Laboration 2, del 2

Tips: kod-disposition

För program som rymms i en fil används oftast följande disposition:

  1. importer (där andra moduler importeras)
  2. funktionsdefinitioner (en eller fler definitioner av den/de funktioner som används i programmet)
  3. anrop till funktioner (ett eller fler anrop till de funktioner som gör att något händer)

Nedan följer ett lite mer konkret exempel:

# högst upp finns importer
import random
import med


# sedan följer definitioner av de funktioner som används i programmet
def main():
    # eventuella förberedelser följt av eventuell huvudloop
    data = load_data("datafile")
    while True:
        # sats1
        # sats2
        # ...
        process_something1(arg1, arg2)
        # ...
        process_something2(arg3)


def load_data(filename):
    # pass används för att kunna skriva icke-kompletta funktionsdefinitioner
    # utan att få syntax-fel när man kör programmet
    pass


def process_something1(arg1, arg2):
    pass


def process_something2(arg3):
    pass


# efter definitionerna av funktioner som programmet använder görs ofta ett anrop
# till programmets huvudfunktion, alternativt flera funktioner om det är flera
# funktioner som ska köras.
#
# många gånger lägger man dessa anrop innanför en vilkorssats som endast kör
# anropen när programmet körs som ett skript (återkommer till detta senare i
# kursen)
if __name__ == '__main__':
    main()

Tips: början på en sträng och andra strängrelaterade saker

Det finns en hel del funktionalitet som hör ihop med specifika datatyper som man kan komma åt genom s.k. punktnotation. Exempel på detta är att använda .append() på en lista. Dessa kallas för metoder (man kan säga att metoder är funktioner som appliceras på värden via punktnotation; mer om detta senare i kursen).

För strängar finns .lower() som returnerar strängen i enbart gemener (se kodskelettet). Andra metoder för strängar är:

  • str.endswith()
  • str.find()
  • str.join()
  • str.rstrip()
  • str.split()
  • str.startswith()
  • str.strip()

Med ovanstående notation refererar str till ett strängvärde. Vad dessa gör får du söka dig fram till.

Tips: problemuppdelning

För att lösa problem på ett datalogiskt sätt delar man oftast upp ett problem i mindre delproblem. Ett exempel på en problemuppdelning som kan göras är:

  1. Hitta möjliga kandidater på ordförslag
  2. Välj den bästa kandidaten

Genom att dela upp ett problem i delproblem kan man underlätta felsökning av kod genom att det blir lättare att avgöra i vilken del av koden som problemet kan finnas. Man kan t.ex. dela upp sin kod så att varje delproblem löses i en separat funktion. Resultatet från funktionen som löser ett delproblem skickas vidare till nästa funktion:

def large_problem(data1, data2):
    result1 = subproblem1(data1, data2)
    result2 = subproblem2(result1)

Tips: ladda in data

  • För att undvika att läsa in data från fil varje gång ni kör autocomplete, kan ni istället lägga till och skicka med ett extra argument till funktionen autocomplete() som innehåller den information som behövs. Vissa ändringar jämfört med kodskeletet kan behövas.
  • Inläsning av data görs då någonstans innan ni går in i loopen som frågar användaren efter ett ord och sedan kör autocomplete()-funktionen.

Uppgift 1

Er första uppgift är att skriva en funktion som returnerar förslag på hur användaren kan fortsätta på det användaren börjat skriva på. För steg 1 ska inte filerna med frekvensinformation användas.

Fundera ut vad ni vill att autocomplete ska göra

Fundera först på vilken strategi ni vill implementera. Ni kan exempelvis låta er funktion returnera det första bästa ordet som börjar på rätt bokstäver, eller låta den returnera en uppsättning förslag som användaren får välja mellan.

De begränsningar ni har att jobba med är att funktionen ska returnera något som kan skrivas ut.

En körning kan exempelvis se ut så här:

Type word: bast
Autocompletion finished:  bastu
Type word: enhö
Autocompletion finished:  enhörning
Type word: fik
Autocompletion finished:  fiktiv
Type word: programmer
Autocompletion finished:  programmering

En variant med autocomplete som returnerar fler än ett ord skulle kunna se ut så här (inte verkliga exempel):

Type word: bast
Autocompletion finished: bastu, bastun
Type word: enhö
Autocompletion finished: enhörning
Type word: fik
Autocompletion finished: fiktiv, fiktiva
Type word: program
Autocompletion finished: programmet, programmen

Formulera en plan med de steg som ska göras

Försök att formulera vilka steg som behövs för att funktionen ska göra det ni vill att den ska göra. Dra nytta av att ni är två som jobbar tillsammans; ni kan skriva på var sitt förslag och sedan be den andra personen läsa det och förklara det tillbaka till dig.

Implementera funktionen

Skriv sedan klart funktionen autocomplete() ovan så att den förutsäger slutet på ett inmatat ord enligt er valda strategi. Det är tillåtet att ändra på antalet argument som funktionen autocomplete() använder.

Uppgift 2

Er andra uppgift är att

  1. skriva en ny autocomplete-funktion som föreslår ord baserat på ordfrekvensdata
  2. dokumentera era funktioner med funktionskommentarer (se nedan)

Skriv en helt ny funktion istället för att ändra på er existerande funktion. I er kod kan ni enkelt byta vilken funktion som används genom att kommentera bort den raden som använder den gamla funktionen, och sedan lägga till en rad som använder er nya funktion. Genom att behålla den gamla funktionen tar ni heller inte sönder den kod som som ni skrev till steg 1:

#suggestion = autocomplete(word)
suggestion = autocomplete_using_freq(word)

Ni får själva välja om ni använder en av de existerande .csv-filerna för att läsa in ord och deras ordfrekvenser, eller om ni använder er av ett fil-par (t.ex. alphabetical_words + alphabetical_freq) för att läsa in ordfrekvensdata.

Dokumentera även er kod med funktionskommentarer (se nedan).

Funktionskommentarer, eller dokumentationssträngar

Dokumentera i funktionskommentaren hur ni tänkt. Funktionskommentarer, också kallade dokumentationssträngar (eng. docstring), skriver man genom att med början på raden direkt efter funktionshuvudet lägga till en speciell typ av sträng som börjar med tre citattecken och avslutas med tre citattecken. Se exempel nedan.

def get_most_frequent_word(document):
    """Returnera det mest frekventa ordet i document.
    
    Funktionen räknar ut ordfrekvenserna i strängen document och returnerar
    sedan det mest frekvent förekommande ordet. Ingen lexikal analys
    genomförs vilket alltså betyder förekomster av "grön" och "gröna" inte slås
    ihop till förekomst av samma ord."""

    # här följer sedan koden som tillhör till funktionen
    ...

En bra funktionskommentar börjar med en kort beskrivning av vad funktionen kommer göra och returnera. Enligt pythonstandard (PEP257), ska första raden i en funktionskommentar vara en mening i aktiv form. Behövs ytterligare förklaring lägger man till en tom rad och kan sedan skriva ett stycke med flera meningar.

I exemplet ovan och i en hel del kod ni stöter på i den här kursen står kommentarer på svenska. Detta har pedagogiska orsaker och ute i verkligheten använder vi engelska.

Del 3, Autocorrect (frivillig)

I del 2 letade ni bland tillgänglig data efter ord som började på det användaren hade skrivit. I del 3 ska ni istället försöka leta bland tillgänglig data efter ord som användaren försökte skriva.

För den frivilliga uppgiften ska ni:

  1. skriva ytterligare en funktion som istället för komplettera det användaren skrivit istället korrigerar det användaren skrivit
  2. dokumentera era funktioner med funktionskommentarer

Redigeringsavstånd istället för frekvensinformation

I del 2 handlade problemet om att hitta ord som börjar på det användaren har skrivit för att kunna fylla på med återstående bokstäver.

Här i del 3 handlar problemet istället om att hitta ord som användaren försökte skriva. Det finns olika sätt att göra detta. En variant är att anta att användanden skrivit rätt antal bokstäver, men att någon eller några bokstäver är fel.

Skriv en funktion som utgår från denna premiss, dvs skriv funktionen autocorrect() som tar in minst ett argument (användarens ord) och försök returnera en rättstavad variant av ordet.

Inläst data (t.ex. alphabetical_words) ska fortfarande användas (för att hitta rätt ord).

Exempel på autocorrect-körning:

Type word: bast
Autocorrection finished: bäst
Type word: furt
Autocorrection finished: fort
Type word: progrannet
Autocorrection finished: programmet

Antag att endast kända ord med samma längd som input-ordet är möjliga kandidater.

Vad är redigeringsavstånd?

Redigeringsavståndet mellan två strängar är hur många redigerande steg (raderingar, infogningar och substitueringar av tecken) som krävs för att transformera ett ord till ett annat. Ett vanligt mått på redigeringsavstånd är Levenshtein-avståndet.

För att beräkna av Levenshtein-avståndet mellan två ord kan ni använda en färdig funktion som finns implementerad i filen /courses/TDDE44/kursmaterial/laboration2/med.py. Kopiera filen från kurskatalogen till samma katalog som er autocomplete-pythonfil ligger i. Sedan lägger ni till raden

import med

högst upp i er python-kod. Nu kan ni beräkna Levenshtein-avståndet mellan två strängar i er kod genom att anropa med.minimum_edit_distance() med de två strängar ni vill räkna på som argument:

# skriv ut redigeringsavståndet mellan kisse och katt
print(med.minimum_edit_distance('kisse', 'katt'))

# ovanstående kommer skriva ut följande strängen "4", dvs att fyra redigeringar
# behövs för att ändra strängen "kisse" till strängen "katt"

Fortsatt förfining

Om ni har tid och är intresserade kan ni uttöka er lösning till något av följande:

  • Kombinera redigeringsavståndsdata med frekvensdata för att välja förslag.
  • Hitta möjliga kandidater till rättstavning även bland ord som är längre eller kortare än det användaren skrev.
  • Kombinera autocorrect med autocomplete.

OBS! Ingen av de ovanstående förfinade varianterna krävs för att få godkänt, utan är endast exempel på möjlig fortsatt utveckling.


Sidansvarig: Johan Falkenjack
Senast uppdaterad: 2024-01-08