Observera att denna sida är under utveckling!

Lektion 7: Arbeta med text
Denna lektion innehåller Obligatorisk uppgift 3 (OU3)

Begrepp som diskuteras: Strängar, lexikon, sorteringar, reguljära uttryck.
Arbetssätt: Arbeta gärna tillsammans med någon, men skriv egen kod. Diskutera med varandra!

Försök göra övningarna innan du tittar på svaren! Fråga handledarna om det är något svar du inte förstår!

Tips: Använd debuggern

Uppskattad arbetstid: Schemalagd handledningstid inkl redovisning: 6 timmar. Utöver det räkna med eget arbete med lika många timmar.
Redovisning: Obligatorisk redovisning av uppgifter enligt kurssidan.
Notera att den pythonkod du redovisar skall följa kodningsreglerna

Textsträngar

Hittills har vi huvudsakligen använt textsträngar (typen str) som variabelvärden och i utskrifter. I denna lektion ska vi gå in på mer detaljer strängar.

Mycket av det vi gjort med listor kan vi göra med strängar. Strängar är dock, precis som tupler, oföränderliga dvs om vi vill ändra på något i en sträng så får vi göra en ny sträng.

Läs igenom minilektionen om strängar för att se några vanliga operationer på strängar.

Vi definierar en sträng att använda i vår exempelkod:

text = """Kvällens gullmoln fästet kransa. Älvorna på ängen dansa, och den bladbekrönta näcken gigan rör i silverbäcken. """

Denna sträng innehåller bokstäver, skiljetecken, blanktecken och radslutstecken. Det är en flerraderssträng, en sådan omges av tecknen """. Radslutstecknen syns normalt inte men gör så att texten blir raduppdelad vid utskrift:

>>> print(text) Kvällens gullmoln fästet kransa. Älvorna på ängen dansa, och den bladbekrönta näcken gigan rör i silverbäcken. >>> text 'Kvällens gullmoln fästet kransa.\nÄlvorna på ängen dansa,\noch den bladbekrönta näcken\ngigan rör i silverbäcken.\n'
När texten skrivs ut med funktionen print(text) skrivs den ut som vanligt med radbyten. När texten skrivs ut i interactive mode (i shell-fönstret) med bara >>>textskrivs radslutstecknen ut och texten omges av '.

Räkna tecken och strängar

KodVärdeKommentar
len(text) 111 Totala antalet tecken.
text.count(' ') 12 Antalet blanktecken.
text.count('n ') 4 Observera att 'n' i slutet av raderna inte följs av ett blanktecken.
text.count('\n') 4 Antalet rader (egentligen antalet radslutstecken — sista raden behöver inte nödvändigtvis följas av ett radslutstecken).

Räkna bokstäver

Antag att vi vill räkna antalet bokstäver i texten. Ett uppenbart sätt att göra det på är att gå igenom strängen tecken för tecken och räkna de tecken som är bokstäver. Anropet s.isalpha() returnerar True returnerar om strängen s bara innehåller bokstäver.

# Räkna bokstäver n = 0 for c in text: # c kommer successivt innehålla alla tecken i text if c.isalpha(): # Räkna om c är en bokstav n += 1 print(f'Antal bokstäver: {n}')
(Anmärkning: Alternativ kod med "list comprehension":

print(len([c for c in text if c.isalpha()]))

Prova och se att den fungerar!)

Räkna "svenska" bokstäver

Metoden isalpha fungerade uppenbarligen även för svenska bokstäver och den fungerar även för ett stort antal andra nationella tecken som t ex ü, é, á, è, î, Ç, ô, och Á.

Det finns ingen metod "isswedish" motsvarande isalpha utan vi får använda operatorn in för att se om ett tecken är å, ä eller ö:

# Räknar 'svenska' bokstäver åäöÅÄÖ n = 0 for c in text: if c.lower() in 'åäö': n += 1 print(f'Antal svenska bokstäver: {n}')
Vi har demonstrerat metoden lower som returnerar en sträng där all versaler är utbytta till motsvarande gemena.

Det hade naturligtvis gått lika bra att istället testa mot strängen 'åäöÅÄÖ'.

Anmärkning: Försök modifiera koden nedan, som räknar tecknen a..z, A...Z, mha av list comprehension:

print(len([c for c in text if c.isalpha()]))

så att den inte räknar åäöÅÄÖ. Lösningsförslag

print(len([c for c in text if c.isalpha() and not (c.lower() in 'åäö')]))

Övningar

  1. Skriv kod som räknar och skriver ut antalet nationella bokstäver dvs bland annat å, ä, ö, ü, é, á, è, î, Ç, ô, Á, ...
    Ledning: Det är lättare att känna till de internationella bokstäverna än alla nationella! Lösningsförslag
    test_text = 'aVcdåäöÅÄÖüéáèîçÇôÜÁxyZX'
    iletters = 0
    letters = 0
    for c in test_text:
        if c.isalpha():
            letters += 1
        if c.lower() in 'abcdefghijklmnopqrstuvwxyz':
            iletters += 1
    print(f'Antal nationella bokstäver: {letters - iletters}')
    

Byta ut tecken

Antag att vi vill ta "internationalisera" texten genom att byta ut å mot aa, ä mot ae och ö mot oe.

Metod 1: Metoden replace

txt = text.replace('å', 'aa') txt = txt.replace('ä', 'ae') txt = txt.replace('ö', 'oe') txt = txt.replace('Å', 'Aa') txt = txt.replace('Ä', 'Ae') txt = txt.replace('Ö', 'Oe') print(txt)
Kom ihåg att strängar är oföränderliga! Vi måste alltså ta emot den nya sträng som replace returnerar.

Metod 2: Använd ett lexikon

Vi kan skapa en översättningstabell med hjälp av ett lexikon ("dictionary" - se minilektionen om lexikon!) Vi lägger in de svenska bokstäverna som nycklar och motsvarande internationella teckenkombinationer som värden:
transtab = {'å': 'aa', 'ä': 'ae', 'ö': 'oe', 'Å': 'Aa', 'Ä': 'Ae', 'Ö': 'Oe'} txt = '' for c in text: # Iterera över tecknen if c in transtab: # Om tecknet c finns som nyckel txt += transtab[c] # lägg till dess värde else: # annars txt += c # lägg till c print(txt)

Räkna bokstavsfrekvenser

Antag att vi vill räkna bokstavsfrekvenser. Ett lexikon med enskilda bokstäver som nycklar och antal förekomster som värde fungerar bra:
freq = {} # Skapa ett tomt lexikon for c in text: # Iterera över tecknen if c.isalpha(): # Vi intresserar oss bara för bokstäver c = c.lower() # Skiljer inte på små och stora if c in freq: # Om denna bokstav redan finns som nyckel freq[c] += 1 # Öka dess frekvens else: # annars freq[c] = 1 # Lägg in den med frekvensen 1 print(freq)
Resultat:
{'k': 5, 'v': 3, 'ä': 6, 'l': 8, 'e': 8, 'n': 13, 's': 5, 'g': 4, 'u': 1, 'm': 1, 'o': 3, 'f': 1, 't': 3, 'r': 6, 'a': 8, 'p': 1, 'å': 1, 'd': 3, 'c': 3, 'h': 1, 'b': 3, 'ö': 2, 'i': 3}
Utskriften inte särskilt njutbar. Dels borde den vara ordnad antingen i bokstavsordning eller i frekvensordning och dels borde vara uppdelad på flera rader.

I minilektionen om lexikon beskrivs flera sätt att iterera över ett lexikon. I detta fall väljer vi att först skapa en lista av tupler med nyckel-värdepar som vi sorterar med hjälp av listobjektets sort-metod:

# Skapa en lista av tupler, dvs nyckel-värdepar från lexikonet freq. lista = list(freq.items()) # list är en funktion. print('Lista :', lista) lista.sort() # Sortera lista map nycklarna, stigande ordning print('Sorterat:', lista)
vilket ger utskrifterna:
Lista : [('k', 5), ('v', 3), ('ä', 6), ('l', 8), ('e', 8), ('n', 13), ('s', 5), ('g', 4), ('u', 1), ('m', 1), ('o', 3), ('f', 1), ('t', 3), ('r', 6), ('a', 8), ('p', 1), ('å', 1), ('d', 3), ('c', 3), ('h', 1), ('b', 3), ('ö', 2), ('i', 3)] Sorterat: [('a', 8), ('b', 3), ('c', 3), ('d', 3), ('e', 8), ('f', 1), ('g', 4), ('h', 1), ('i', 3), ('k', 5), ('l', 8), ('m', 1), ('n', 13), ('o', 3), ('p', 1), ('r', 6), ('s', 5), ('t', 3), ('u', 1), ('v', 3), ('ä', 6), ('å', 1), ('ö', 2)
I minilektionen om listor beskrivs hur man kan dela upp utskriften av en lista på flera rader:
# e är elementen (tupel) i lista. Vi vill att index skall börja med 1. for index, e in enumerate(lista, start=1): print(e, end=' ') # Skriv ut tupeln, 8 st per rad if index % 8 == 0: print() # Gör radbyte var 8:e varv i loopen print('\n')
som ger utskriften:
('a', 8) ('b', 3) ('c', 3) ('d', 3) ('e', 8) ('f', 1) ('g', 4) ('h', 1) ('i', 3) ('k', 5) ('l', 8) ('m', 1) ('n', 13) ('o', 3) ('p', 1) ('r', 6) ('s', 5) ('t', 3) ('u', 1) ('v', 3) ('ä', 6) ('å', 1) ('ö', 2)
Det blir dock snyggare om man tar ut komponenterna ur tuplarna:
for index, e in enumerate(lista, start=1): # Varje element i lista är e, en tupel som består av e[0] och e[1] print(f'{e[0]}:{e[1]:2d}', end=' ') # Skriv mellanslag efter varje utskrift if index % 8 == 0: print() print('\n\n')
som ger:
a: 8 b: 3 c: 3 d: 3 e: 8 f: 1 g: 4 h: 1 i: 3 k: 5 l: 8 m: 1 n:13 o: 3 p: 1 r: 6 s: 5 t: 3 u: 1 v: 3 ä: 6 å: 1 ö: 2

Sortering på frekvens

När man, som vi gjort ovan, sorterar en lista med tupler så sker sorteringen på det första värdet i tupeln (dvs på bokstaven). För att sortera på frekvens måste man, på något sätt, ange att det är frekvensen som ska användas som sorteringsnyckel, inte bokstaven.

Både till standardfunktionen sorted och till metoden sort för list-objekt kan man skicka med en funktion som anger hur komponenterna ska jämföras. Om listelementen består av tupler (som i vårt fall) skriver vi en funktion:

def part2(e): return e[1]
Funktionen returnerar alltså den andra komponenten i tupel. För att sortera på frekvenser kan nu skriva:
# Funktionen sorted returnerar en sorterad lista # Sortering baseras på värdet av funktionen part2 i fallande ordning freq_order = sorted(lista, key=part2, reverse=True) for i, e in enumerate(freq_order, start=1): print(f'{e[1]:2d} {e[0]}', end='\t') # Skriv TAB efter varje utskrift if i % 6 == 0: print() # Gör radbyte var 6:e varv i loopen print()
som ger utskriften:
13 n 8 a 8 e 8 l 6 r 6 ä 5 k 5 s 4 g 3 b 3 c 3 d 3 i 3 o 3 t 3 v 2 ö 1 f 1 h 1 m 1 p 1 u 1 å
Observera:
  1. Vi ger funktions-namnet som värde till key-parametern. Sorteringsfunktionen kommer att anropa denna funktion för varje element i listan för att få fram ett "sorteringsvärde" för elementet.
  2. Parametern reverse=True anger att att resultatet ska bli i fallande ordning dvs med största värdet först.
  3. Vi har använt tabulatorteckenet som värde på end-parametern till print

Övningar

  1. Sätt lst = ['alpha', 'bravo', 'charlie', 'delta', 'echo', 'foxtrot']
    Vad blir resultatet av lst.sort(key=part2)? Lösningsförslag
    En lista sorterad på den andra bokstaven i orden:
    ['echo', 'delta', 'charlie', 'alpha', 'foxtrot', 'bravo']
    
  2. Ett annat sätt att åstadkomma sorteringen på frekvenser är att utifrån variabeln lista göra en ny lista där man byter ordning på frekvens och bokstav och sedan sorterar. Skriv koden för detta! Lösningsförslag
    För den som läst på "list comprehensions" går det elegant på detta sätt
    swapped = sorted([(x[1], x[0]) for x in lista], reverse=True)
    
    
    Det går naturligtvis också att bygga upp listan med en for-loop:
    swapped = []
    for x in lista:
        swapped.append((x[1], x[0]))
    swapped.sort(reverse=True)
    

Ordanalyser

Hittills har det mesta handlat om att göra saker med enskilda bokstäver men nu ska vi göra liknande analyser av orden i en text.

Att hitta ord i en text - reguljära uttryck

Det första problemet är att lokalisera ord i en sträng. I minilektionen om strängar nämns en metod split som vid första anblicken verkar användbar. Minlektionens exempel är koden
'Ta det lugnt'.split(' ')
som returnerar listan ned de i strängen ingående orden: ['Ta', 'det', 'lugnt'].

Om vi använder den metoden på Stagnelius text i början av lektionen dvs om vi gör print(text.split(' ')) får vi följande nedslående utskrift:

['Kvällens', 'gullmoln', 'fästet', 'kransa.\nÄlvorna', 'på', 'ängen', 'dansa,\noch', 'den', 'bladbekrönta', 'näcken\ngigan', 'rör', 'i', 'silverbäcken.\n']
Det blev 13 "ord" i stället för 16. Uppdelningen görs enbart av blanktecken. Punkter, kommatecken och radbyten radbyten ingår i "orden".

Vi behöver något sätt att tala om att de "ord" vi är ute efter enbart får innehålla bokstäver. I andra sammanhang, t.ex. vi analys av texten i ett Python-program, vill vi kanske tillåta understrykningstecknen att ingå i orden.

Det finns ett mycket generellt verktyg som kan hjälpa oss med detta: reguljära uttryck. Med sådana kan man definiera mönster och leta efter dessa i en text. Matchande delar av texten kan också bytas ut på intrikata sätt.

Reguljära uttryck är inte alls begränsade till Python utan kan hanteras av många programmeringsspråk och editorer.

I denna kurs tar vi bara upp några mycket enkla exempel men råder den som ska arbeta med text att sätta sig den världen. Det finns mycket material på nätet.

För att plocka ut ord i betydelsen "en följd av en eller flera bokstäver" gör vi på följande sätt:

import re wordlist = re.findall(r'[a-zA-ZåäöÅÄÖ]+', text) print(wordlist)
som ger följande utskrift:
['Kvällens', 'gullmoln', 'fästet', 'kransa', 'Älvorna', 'på', 'ängen', 'dansa', 'och', 'den', 'bladbekrönta', 'näcken', 'gigan', 'rör', 'i', 'silverbäcken']
Kommentarer:
  1. Reguljära uttryck definieras i paketet re som alltså importeras.
  2. Parametern till re.findall är en "rå sträng" dvs den börjar med r' (eller r") i stället för med bara ' (eller "). I en rå sträng tolkas inte escape-tecknet på vanligt sätt (tab, newline, ...) utan behålls och har andra betydelser i reguljära uttryck.
  3. Uttrycket [a-zA-ZåäöÅÄÖ] matchar vilket tecken som helst mellan a och z, mellan A och Z samt å, ä, ö och Å, Ä, Ö dvs en internationell eller 'svensk' bokstav.
  4. Tecknet + efter []-gruppen anger att det kan vara en eller flera bokstäver.
  5. Mönstret matchar helt enkelt en obruten följd av en eller flera bokstäver.
  6. Mönstermetoden findall returnerar alltså en lista av alla matchande sekvenser.

Obligatoriska uppgifter

Notera att den pythonkod du redovisar skall följa kodningsreglerna
  1. Skriv ett program som läser en textfil och producerar statistik. Programmet ska skriva ut det totala antalet ord och antalet olika ord. Programmet ska också ge en lista på de n vanligaste orden och de m ovanligaste orden. Värdena på m och n ska läsas in med input. Se minilektionen Läsa och skriva som beskriver hur man läser en fil.
  2. Gör en variant av programmet som läser en fil (typiskt ett datorprogram). Programmet ska producera en lista med radnummer över filen och en referenslista som för varje ord anger på vilka rader det förekommit. Python-ord som for, if, def... ska inte tas med i listan liksom inga ord från kommentarerna.

    Exempel: Om koden som räknar bokstavsfrekvenser ovan används som indata ska utskriften bli:

    1 2 freq = {} # Skapa ett tomt lexikon 3 for c in text: # Iterera över tecknen 4 if c.isalpha(): # Vi intresserar oss bara för bokstäver 5 c = c.lower() # Skiljer inte på små och stora 6 if c in freq: # Om denna bokstav redan finns som nyckel 7 freq[c] += 1 # Öka dess frekvens 8 else: # annars 9 freq[c] = 1 # Lägg in den med frekvensen 1 10 print(freq) Referenslista: c [3, 4, 5, 5, 6, 7, 9] freq [2, 6, 7, 9, 10] isalpha [4] lower [5] print [10] text [3]
Tips: Python-ord som for, if, def ... ska inte tas med i listan liksom inga ord från kommentarerna.

Fråga

Hur många timmar har du arbetat med denna lektion?


Gå till nästa lektion eller gå tillbaka