D 0 0 0 9 E Introduktion till programmering D0009E Föreläsning 9: Tupler och dictionaries 1
D 0 0 0 9 E Sammansatta datatyper Strängar Sekvenser av tecken Icke muterbara Syntax: "abcde" Listor Sekvenser av vad som helst Muterbara Syntax: [1, 2, 3] Nytt för idag: tupler Sekvenser av vad som helst (likt listor) Icke muterbara (likt strängar) Syntax: (1, 2, 3) Också nytt för idag: dictionaries listor där index kan vara vad som helst (t.ex. en sträng) muterbara 2
Tupel-operationer Grundidé: samma operationer som för listor >>> ('a', 0) * 3 ('a', 0, 'a', 0, 'a', 0) >>> x = (1, 2, 3) + ('a', 'b') >>> x[:] (1, 2, 3, 'a', 'b') >>> x[1:3] (2, 3) >>> x[0] 1 Men: >>> x[0] = 8 TypeError: object doesn't support item assignment 3
En lite udda detalj Minns: listan med endast ett element x skrivs [x] ej samma sak som att skriva endast x Tupeln med endast ett element x kan dock inte skrivas (x) detta betyder ju enbart x (med onödiga parenteser) Pythons fix för att lösa problemet: ett extra komma! Alltså: tupeln med endast ett element x skrivs (x,) Varför är detta viktigt? Se exempel: >>> (1, 2, 3) + (4) TypeError: can only concatenate tuple (not "int") to tuple >>> (1, 2, 3) + (4,) (1, 2, 3, 4) 4
5 Tupler kontra listor Varför behövs tupler över huvud taget om det enda som skiljer gentemot listor är att man inte kan mutera tupler? Svar 1: Tupler kan av denna anledning användas på sätt som listor inte kan, inte minst fungera som nycklar i dictionaries Svar 2: Det finns en stark tradition (inom många språk) att skilja på homogena strukturer med variabel längd (listor) och heterogena strukturer med fix längd (tupler) Svar 3: Likheten mellan tupler och argumentlistor är elegant...!
Tupler som resultat En funktion som kastar om sina två argument: def swap(x, y): return (y, x) En funktion som hittar en listas extremvärden: def bounds(list): min = max = list[0] for x in list: if x < min: min = x if x > max: max = x return (min, max) 6
7 Tupeltilldelning Syntaktisk finess i Python: I stället för temp = a a = b b = temp kan man skriva (a, b) = (b, a) Här (liksom i många andra sammanhang) går det bra att utelämna parenteserna runt en tupel: a, b = b, a Observera: alla uttryck på högersidan evalueras före någon av tilldelningarna får effekt
Kontrollfråga Varför fungerar inte följande variant av swap? def swap2(x, y): x, y = y, x Svar: för att effekten av tilldelningen endast är lokal! Se följande stackdiagram för anropet swap2(a, b) a b 3 7 -toplevelswap2 x y 3 7 7 3 8
Listor och tupler Listor förväntas oftast innehålla samma typ av element i alla positioner, medan längden kan variera: for x in list:... Tupler däremot förutsätts ofta ha en låst längd, men kan ha olika typer av element i olika positioner: (k,v) = tuple: if k == 'SMD': return v+1 Notera: Python tillåter egentligen både heterogena listor och iteration över tupler, men mönstren ovan blir svåra att använda om uppdelningen mellan listor och tupler inte följs 9
10 Dictionaries: Om indexering Strängar, listor och tupler har ett gemensamt drag: enskilda element identifieras av sin position Vi använder heltal för att tala om positioner: plats 0, plats 1, plats -1 (konkret uttryckt som lista[1], tupel[-1], etc) Nytt för idag: dictionaries (kataloger) en sammansatt datatyp där index kan vara av valfri icke muterbar typ Exempel på lämpliga indextyper: strängar, flyttal, heltal (gammal bekant!), par av heltal, par av strängar och heltal, boolska värden, tripler, etc Själva elementen kan vara av godtycklig typ och mutering stöds (dictionaries påminner här om listor)
11 Exempel Ett dictionary som översätter engelska till spanska: >>> eng2sp = {} >>> eng2sp['one'] = 'uno' >>> eng2sp['two'] = 'dos' >>> print eng2sp['one'] 'uno' >>> print eng2sp {'one' : 'uno', 'two' : 'dos'} Index i ett dictionary kallas nycklar och själva elementen kallas värden I exemplet ovan är både nycklarna och värdena strängar
12 Exempel Man kan ange nyckel-värde-par redan från början: >>> eng2sp = {'one' : 'uno', 'two' : 'dos', 'three' : 'tres'} Dock kan resultat vid utskrift ibland vara överraskande: >>> print eng2sp {'one' : 'uno', 'three' : 'tres', 'two' : 'dos'} Sens moral: om ordningen är viktig, använd listor. För dictionaries väljer Python den lämpligaste ordningen Storleken (antalet nyckel-värde-par) fås på ett bekant sätt: >>> len(eng2sp) 3
13 Viktig distinktion Ett dictionary för fruktlagerinventering: >>> inventory = {'apples' : 430, 'bananas : 312, 'oranges' : 525, 'pears' : 217} Om päronen blir slutsålda: >>> inventory['pears'] = 0 Om päronen helt försvinner ur sortimentet: >>> del inventory['pears'] >>> print inventory {'apples' : 430, 'bananas : 312, 'oranges' : 525} >>> print inventory['pears'] KeyError: 'pears'
Viktig distinktion Om 17 apelsiner köps in: >>> inventory['oranges'] = inventory['oranges'] + 17 Detta förutsätter dock att katalogen redan innehåller ett värde för nyckeln 'oranges'. I annat fall: >>> inventory['oranges'] = 17 Värdet för en icke existerande nyckel är alltså inte automatiskt 0! [Varför skulle det vara just 0? Varför inte lika gärna 0.0 eller "" eller False eller None eller [] eller () eller {}? Eller (398, "xyz", [2,2])?] 14
15 Om metoder Ett begrepp som vi ska återkomma till i kap 14 Vi tjuvstartar dock idag genom att titta på några fördefinierade dictionary-metoder! En metod är (något förenklat uttryckt) en funktion som anropas med en annorlunda syntax Mönster: i stället för funktionsnamn(argument1, argument2,..., argumentn) skrivs argument1.metodnamn(argument2,..., argumentn) Argument1 är oftast en sammansatt datatyp av något slag
16 Dictionary-metoder Ger en lista med katalogens nycklar: >>> eng2sp.keys() ['one', 'three', 'two'] Ger en lista med mostvarande värden: >>> eng2sp.values() ['uno, 'tres', 'dos'] Ger den fullständiga listan med nyckel-värde-par: >>> eng2sp.items() [('one, 'uno'), ('three, 'tres'), ('two, 'dos')] Observera att denna lista innehåller samma information som katalogen själv!
17 Att undersöka nycklar Testar om ett dictionary definierar en viss nyckel: >>> eng2sp.has_key('one') True >>> eng2sp.has_key('deux') False Kan även uttryckas som >>> 'deux' in eng2sp.keys() Notera att metoder inte automatiskt dubblerar som globala funktioner: >>> has_key(eng2sp, 'deux') NameError: has_key is not defined
18 Aliasing Dictionaries är på samma sätt som listor muterbara, vilket kan skapa problem då många namn refererar till samma dictionary Metoden copy() skapar en ny klon av ett dictionary: >>> opposites = {'up' : 'down', 'right' : 'wrong'} >>> alias = opposites >>> copy = opposites.copy() >>> alias['right'] = 'left' >>> opposites['right'] 'left' >>> copy['right'] = 'privilege' >>> opposites['right'] 'left'
Exempel på användning: glesa matriser Idé från kap 8: representera matriser som listor av listor Exempel: matrisen 0 0 0 1 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 3 0 representeras som matrix = [ [0,0,0,1,0], [0,0,0,0,0], [0,2,0,0,0], [0,0,0,0,0], [0,0,0,3,0] ] Där finns dock väldigt många nollor... 19
20 Matriser med dictionaries Nycklar: (rad,kolumn)-par Idé: lagra endast de positioner där värdet är skilt från 0: matrix = {(0,3) : 1, (2,1) : 2, (4,3) : 3} Tar betydligt mindre plats än den förra representationen Att slå upp ett element: >>> matrix[(0,3)] 1 Ekvivalent: >>> matrix[0,3] 1 Men hur blir det på de "tomma" platserna?
21 Indexering med default-värde Ett bekant problem! Det vi vill är att indexering ska ge rätt värde om nyckeln är definierad värdet 0 i övriga fall Vi har argumenterat att vanlig indexering inte gärna kan bete sig just på det viset (varför just 0?) Men vi kan kan gott tänka oss följande funktion: def get(dict, key, default): if dict.has_key(key): return dict[key] else return default Denna funktion finns fördefinierad, fast som en metod!
22 Metoden get Vi använder alltså metoden get på vår glesa matris: >>> matrix.get((0,3), 0) 1 >>> matrix.get((1,3), 0) 0 Denna metod löser även problemet vi hade med att uppdatera vårt fruktlager: inventory['oranges'] = inventory.get('oranges', 0) + n Observera än en gång att metoden get egentligen inte är något annat än vanlig indexering, fast med ett defaultvärde (och en något klumpigare syntax)
Mer exempel: memo-tabeller Vår rekursiva definition av Fibonacci-funktionen (kap 5) var visserligen elegant, men behäftad med vissa effektivitetsproblem... fibonacci(4) fibonacci(3) fibonacci(2) fibonacci(2) fibonacci(1) fibonacci(1) fibonacci(0) 23 fibonacci(1) fibonacci(0) Hur många gånger beräknas basfallet i t ex fibonacci(20)?
Mer exempel: memo-tabeller I stället för att göra samma beräkning om och om igen kan vi spara resultatet av tidigare beräkningar, och endast göra de rekursiva anropen då de verkligen behövs Sparade anrop (dvs par av argument och resultat) lagras lämpligen i ett dictionary, som vi låter mutera under beräkningens gång: def fibonacci(n): results = {0:1, 1:1} return fib_rec(results, n) def fib_rec(previous, n): if not previous.has_key(n): previous[n] = fib_rec(previous, n-1) + fib_rec(previous, n-2) return previous[n] Ett sådant dictionary kallas memo-tabell eller hint 24
25 Mer exempel: memo-tabeller Körning med den nya definitionen (extra spårutskrift för previous): >>> fibonacci(2) {0: 1, 1: 1, 2: 2} 2 >>> fibonacci(1) {0: 1, 1: 1} 1 >>> fibonacci(10) {0: 1, 1: 1, 2: 2, 3: 3, 4: 5, 5: 8, 6: 13, 7: 21, 8: 34, 9: 55, 10: 89} 89 >>> fibonacci(50) # ingen spårutskrift 20365011074L >>>
26 Om långa heltal Värdet 20365011074L betyder att Pythoninterpretatorn har tvingats gå över till en annan intern representation av heltal, så kallade långa heltal (long integers) Vanliga heltal är begränsade till intervallet -2**31... 2**31 på hyffsat moderna maskiner, medan långa heltal har fördelen att inte ha någon egentlig övre eller undre gräns (annat än datorns tillgängliga minne!) Operationer på långa heltal är dock långsammare än de för vanliga heltal (som kan använda inbyggda maskininstruktioner) I övrigt kan långa heltal användas precis som förväntat
Ännu ett exempel Kod som räknar bokstävers förekomster: lettercounts = {} for letter in "Mississippi": lettercounts[letter] = lettercounts.get(letter, 0) + 1 Ustrkrift av resultatet: >>> print lettercounts {'M' : 1, 's' : 4, 'p' : 2, 'i' : 4} Kan man få detta sorterat i alfabetisk ordning? Svar ja: >>> letteritems = lettercounts.items() >>> print letteritems.sort() [('M', 1), ('i', 4), ('p', 2), ('s', 4)] Notera den inbyggda list-metoden sort! 27