729G04 Programmering och diskret matematik Föreläsning 5
Föreläsningsöversikt Repetition av listmetoder Scope Defaultvärden Algoritmer: ex. linjär/binär sökning Flödesdiagram Pseudokod Rekursion Programmering som problemlösning Kodstil
Defaultvärden för funktionsargument
Värde på argument om inget annat anges def greet(name="zorro"): print("hello " + name + "!") >>> greet() 'Hello Zorro!' >>> greet("el Maco") 'Hello El Maco!' >>> Ibland vill vi kunna säga till en funktion att den ska anta ett viss värde på argumentet om vi inte säger något annat.
Sätt att ange parametrar def eggs(size, colour="red", boiled=true):... # Alla nedanstående anrop är OK! eggs(5) eggs(5, "green", False) eggs(2, boiled=false) eggs(boiled="false", colour="green", size=4) Om man namnger argument får man skicka dem i vilken ordning som helst. Ickenamngivna argument måste stå i rätt ordning. Argument utan defaultvärde måste skickas till anropet.
Demo av defaultvärden https://www.ida.liu.se/codela/as/729g04/05def ault
Scope
Kan Gregorius räcka upp handen tack?
Scope - ingen "Gregorius" definierad i detta klassrum! Fråga internet - David? Gå ut på stan och fråga efter David?
Var finns min variabel? Var en variabel definieras påverkar hur "synlig" den är. Variabler i funktioner syns bara i funktionen de definierades i. De är lokala variabler. Variabler som definieras utanför funktioner är globala variabler och kan ses innifrån en funktion. För att tilldela (med =) en global variabel ett annat värde lokalt i en funktion - använd nyckelordet global i funktionen.
Scope - globala och lokala variabler greeting_local_name() name = "Sam" def greeting_local_name(): name = "Ham" greeting = "Hello" print(greeting, name) Variabel Värde name "Ham" greeting "Hello" greeting_global_name() def greeting_global_name(): global name name = "Spam" greeting = "Hello" print(greeting, name) Variabel name greeting greeting() Värde "Spam" "Hello" def greeting(greeting, name): print greeting + name Variabel Värde name greeting lokalt argument lokalt argument
Användning av globala variabler treasure_locations = [] def add_treasure_location_to_global(location): global treasure_locations treasure_locations += [location] def add_treasure_location_to_local(location): treasure_locations += [location] Den andra funktionen kommer att resultera i ett felmeddelande eftersom variabeln treasure_locations inte finns definierad i den kontexten (lokalt scope)
Block har inga egna lokala variabler my_name_is = "Slim Shady" for i in range(5): my_name_is = my_name_is + " " + str(i) print my_name_is print my_name_is for-blocket har ingen egenlokal variabelrymd, utan delar den med den yttre kontexten
Tips om debugging Mot slutet på varje kapitel i Think Python (icke-interaktiva) finns tips på sätt som man kan debugga sin kod.
Algoritmer
Algoritmer Beskrivning av systematisk lösning av en specifik uppgift Exempel hur man sorterar en lista hur man söker efter något i en trädstruktur hur man använder ett viss schiffer hur man räknar ut vilken väg som är kortast mellan två platser hur man känner igen termer i en text hur man konvertera ett decimaltal till ett binärt tal
Exempel: Vem är längst?
Vem är längst? A B C
Vem är längst? 120 100 160 A B C
Vem är längst? 1 2 3 4 5 6 A B C D E F G H I
Vem är längst? A B C D E F G H I 1 1 2 71 6 56 5 57 55 84 2 17 89 13 31 22 30 34 65 61 3 69 3 2 62 68 86 8 23 67 4 14 7 2 32 7 11 63 60 16 5 32 2 70 89 3 72 68 59 15 6 53 7 24 8 26 28 67 54 64
Egenskaper hos problem När är ett problem svårt? Var det andra problemet svårare? Hur definierar vi svårt? Hur tar vi egentligen reda på vem som var längst? Vad måste vi göra?
Från "datorns" perspektiv Har datorn en översiktsbild? Kan datorn titta på en hel lista på en gång? Datorn kan "titta" på ett element i taget Vi måste tillhandahålla arbetsminne Datorn kommer inte ihåg något "automatiskt"
Hur datorn "ser" listan [5, 20, 30, 40, 42]
5
20
30
40
42
Att beskriva en algoritm: Pseudokod och flödesdiagram
Pseudokod Nästan kod Fast med lite mer naturligt språk behöver inte ha med alla variabeldeklarationer är programspråksneutral gör det lättare att abstrahera Nivån på psudokoden kan varieras från väldigt kodnära till mer abstrakt (övergripande)
Pseudokodsexempel def plocka_ut_siffror(inputlista): siffror = tom lista Gå igenom alla element i inputlista Spara alla siffror i inputlista till listan siffror Returnera listan siffror.
Flödesdiagram/Flödesschema Visuell notation för att beskriva algoritmer och processer Inte så populär längre, då programmeringsspråken utvecklats (jmf högnivåspråk / lågnivåspråk) http://en.wikipedia.org/wiki/flowchart
Symboler i flödesdiagram
Flödesdiagram
Exempel på pseudokod och flödesdiagram
Simple Reflex Agent
Pseudokodsexempel function SIMPLE-REFLEX-AGENT(percept) returns an action presistent: rules, a set of condition-action rules state <- INTEPRET-INPUT(percept) rule <- RULE-MATCH(state, rules) action <- rule.action return action
Katt
Katt def kattsimulering(): while True: vakna() för alla saker på bordet vält(sak) sov()
Längst?
Algoritm som ger svaret på frågan "Hur lång är den längsta personen?" Sätt er två och två och försök skriva ner algoritmen. Ni får 3 minuter. Anta att ni får en lista med längden på de olika personerna, t.ex. [["a", 6], ["b", 4], ["c", 7], ["d", 5], ["e", 3]] Använd en icke-formell beskrivning, pseudokod eller ett flödesdiagram.
Hur lång är den längsta personen? Initiera högsta uppmätta längdvärde till 0. Gå igenom listan med person-längder en i taget. Om vi stöter på ett längdvärde som är större än det tidigaste längsta värdet kom ihåg det värdet. När vi har gått igenom hela listan, vet vi hur lång den längsta personen är.
Hur lång är den längsta personen? def max_length(height_list): max_length = 0 for person in height_list: if person[1] > max_length: max_length = person[1] return max_length
Vem är längst? Observera att frågan "Vem är längst?" och "Hur lång är den längsta personen?" Är två olika frågor. En algoritm som löser det ena problemet löser inte det andra!
Algoritm som ger svaret på frågan "Vem är den längsta personen?" Sätt er två och två och försök skriva ner algoritmen. Ni får 3 minuter. Anta att ni får en lista med längden på de olika personerna och deras längder, t.ex. [["a", 6], ["b", 4], ["c", 7], ["d", 5], ["e", 3]] Använd en icke-formell beskrivning, pseudokod eller ett flödesdiagram.
Vem längsta personen? Gå igenom listan med person-längder en i taget Om vi stöter på en person som är längre än den längsta hittills, kom ihåg den personen (index). När vi har gått igenom hela listan, vet vi vem den längsta personen är.
Vem är längst? def find_tallest(height_list): tallest_person = None for person in height_list: if person[1] > tallest_person[1]: tallest_person = person print("person " + tallest_person[0] + " is the tallest: " + str(tallest_person[1]) + " cm")
Rekursion
F(4) = F(4-1) + F(4-2) = F(3) + F(2) F(4)? F(3) = F(3-1) + F(3-2) = F(2) + F(1) 2 1 F(2) = F(2-1) + F(2-2) = F(1) + F(0) 1 1 1 0 F(2) = F(2-1) + F(2-2) = F(1) + F(0) F(1) = 1 F(1) = 1 F(0) = 0 1 0 F(1) = 1 F(0) = 0
def fib(n): if n == 0: return 0 elif n == 1: return 1 else: return fib(n-1) + fib(n-2)
Rekursion = anrop till sig själv En loop kan åstadkommas genom att en funktion anropar sig själv. funktion hitta_bokstaven_g(lista): om listan inte är tom, om det första på listan är ett g, säg "Hittat" annars, anropa hitta_bokstaven_g(allt i listan utom första)
Rekursiv funktion som går igenom en lista Slutvilkor: är listan tom? Returnera värde Titta på första elementet på listan, gör eventuellt något med det och slå ihop det med resultatet av ett rekursivt anrop med resten av listan.
Summera tal i lista # denna funktion antar att alla värden i listan är heltal def summera(heltalslista): if heltalslista == []: return 0 else: return heltalslista[0] + summera(heltalslista[1:]) Slutvilkor: är listan tom? Returnera värde Titta påförsta elementet på listan, göreventuellt något med det och slå ihop det med resultatetav ett rekursivtanrop med restenav listan.
Räkna heltal def antal_heltal(lista): if lista == []: return 0 elif type(lista[0]) == int: return 1 + antal_heltal(lista[1:]) else: return antal_heltal(lista[1:]) Slutvilkor: är listan tom? Returnera värde Titta påförsta elementet på listan, göreventuellt något med det och slå ihop det med resultatetav ett rekursivtanrop med restenav listan.
Rekursiv lösning på "hitta längst" def max_length(values, max_val=0): if values == []: return 0 elif values[0] > max_val: return max_length(values[1:], values[0]) else: return max_length(values[1:], max_val) Slutvilkor: är listan tom? Returnera värde Titta påförsta elementet på listan, göreventuellt något med det och slå ihop det med resultatetav ett rekursivtanrop med restenav listan. Special: vi skickar med det längsta värdet hittills i det rekursiva anropet.
Rekursion https://www.ida.liu.se/codela/as/729g04/05a
Algoritmer: Linjär och binär sökning
Linjär sökning Linjär sökning innebär att man tittar på varje element i ordning. Kan användas på all data som går att beskriva som en sekvens. T.ex: Vem är längst?
Binär sökning Kan användas när vi har en ordnad sekvens. Titta på det mittersta elementet och jämför med sökkriterierna Givet att sekvensen är ordnad vet vi åt vilket håll vi ska gå, men inte hur långt vi ska gå. Titta på det mittersta elementet i den hälft som vi vet att målet bör finnas inom. Fortsätt atthalvera sökområdet tills vi hittar det vi söker efter eller det inte finns någon mer stans att leta.
Vi definierar mitten-index Om längden == 1, så mitten = första (index 0) Om längden == 2, så mitten = andra (index 1) Om längden == 3, så mitten = andra (index 1) Om längden == 4, så mitten tredje (index 2) Dvs, mitten = heltalsdelen av längden/2 [a], mitten = a [a, b], mitten = b [a, b, c], mitten = b [a, b, c, d], mitten = c
Exempel - leta efter j Finns bokstaven j bland dessa bokstäver? a, b, c, d, e, f, g, h, i, j, k, l
Börja i mitten a b c d e f g h i j k l mittenindex = 6/2 = som kommer innan j
Leta på den sida om mitten som innehåller j a b c d e f g h i j k l Vi har hittat ett j!
Exempel - leta efter c Finns bokstaven e bland dessa bokstäver? a, b, c, d, e, f, g, h, i, j, k, l
Börja i mitten a b c d e f g h i j k l mitten = g g kommer efter e, alltså vill vi titta till vänster om g
Leta på den sida av mitten som innehåller c a b c d e f g h i j k l mitten = d c är till vänster om d
Leta på den sida av mitten som innehåller c a b c d e f g h i j k l mitten = b c är till höger om d
Leta på den sida av mitten som innehåller c a b c d e f g h i j k l mitten = c vi har hittat c
Övergång till kod
Mittersta elementet [a]: mitten_index = int(1/2) = 0 [a, b]: mitten_index = int(2/2) = 0 [a, b, c]: mitten_index = int(3/2) = 1 [a, b, c, d]: mitten_index = int(4/2) = 2
Exempel på binär sökning hitta e i listan p vi letar mellan index 0 och index 11. längd: 12 mitten_index = int(12/2) = 6 p: "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" 0 1 2 3 4 5 6 7 8 9 10 11 p[6] == "j" Eftersom "e" < "j" så letar vi mellan index 0 och 5 nästa varv
Exempel på binär sökning hitta e i listan p vi letar mellan index 0 och index 5. längd: 6 mitten_index = int(6/2) = 3 p: "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" 0 1 2 3 4 5 6 7 8 9 10 11 p[3] == "d" Eftersom "e" > "d" så letar vi mellan index 4 och 5 nästa varv
Exempel på binär sökning hitta e i listan p vi letar mellan index 4 och index 5. längd: 2 mitten_index = int(2/2) = 1?! Men mitten är ju 5? Vi reviderar mitten-definitionen till: start_index + int(längden/2), där längden = slut_index+1 - start_index p: "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" 0 1 2 3 4 5 6 7 8 9 10 11 p[5] == "f" Eftersom "f" > "e" så letar vi mellan index 4 och 4 nästa varv
Exempel på binär sökning hitta e i listan p vi letar mellan start_index 4 och slut_index 4. längden= 4+1-4 = 1; mitten_index = 4 + int(1/2) = 4 start_index + int(längden/2), där längden = slut_index+1 - start_index p: "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" 0 1 2 3 4 5 6 7 8 9 10 11 p[4] == "e" Vi har hittat "e"!
Frågor kvaratt ställa? När vet vi att vi inte kommer hitta det vi letar efter?
Binär sökning i kod def binary_search(element, sequence): """Denna funktion returnerar första påträffade index för värdet element i sequence. Om element inte hittas, returneras -1""" start_index = 0 end_index = len(sequence) # om start_index == end_index, # så är start_index-end_index == 0, dvs längden == 0 while start_index!= end_index: length = end_index + 1 Ð start_index middle_index = start_index + int(length / 2) if sequence[middle_index] == element: return middle_index elif sequence[middle_index] < element: start_index = middle_index + 1 # om inte sequence[middle_index] < element är sant, # så måste sequence[middle_index] >= element else: end_index = middle_index - 1 # vi kan ha hoppat ur loopen när start_index == end_index och # det indexet ger det vi letar efter, så vi kollar det if sequence[middle_index] == element: return middle_index else: return -1
När kan man använda binär sökning? När elementen står i sekvensen är sorterade antingen i stigande eller fallande ordning, och vi vet om sorteringsordningen. Stigande ordning: För alla element n, så är n+1 större eller lika med n. Sjunkande ordning: För alla element n, så är n+1 mindre eller lika med n. OBS! Binär sökning berättar inte om det finns fler element med det korrekta värdet.
Kodstil
PEP PEP stands for Python Enhancement Proposal. A PEP is a design document providing information to the Python community, or describing a new feature for Python or its processes or environment. The PEP should provide a concise technical specification of the feature and a rationale for the feature. PEP 1
Kodstil PEP 8 Style Guide for Python Code http://www.python.org/dev/peps/pep-0008/ PEP 257 Docstring Conventions PEP 8 + PEP 257 ska följas från och med labb 5!
PEP 8 PEP 8 täcker delar av Python som inte täcks av denna kurs Strunta i de delar av PEP 8 som tar upp saker som inte tas upp i denna kurs.
Varför bry sig om kodstil? Ökad läsbarhet - på radnivå Lättare att hitta i koden - disposition Minskad risk för missförstånd Mindre frustration
PEP 8 - områden White Space - olika slags mellanrum i koden namngivning kommentarer disposition - var skrivs vad
PEP 257 - områden Hur skriver man en docstring - den kommentar i koden som dokumenterar vad en funktion gör Kommentarer på en rad Kommentarer som går över flera rader
White Space
Indentering Blanda inte indenteringstecken i samma fil. Dvs, använd antingen mellanslag eller tab-tecken. Mellanslag föredras. Rekommenderad ökning av antal mellanslag per indenteringsnivå är 4
Rätt Fel
Mellanslag Mellanslag mellan operatorer och värden Inga mellanslag innan parenteser i funktionsanrop
Radbryt Två tomma rader mellan funktionsdefinitioner (rader med kommentarer räknas inte som tomma rader) Använd inte för många radbryt i funktioner. Du behöver inte en tom rad mellan varje sats. Använd tomma rader för att visa logisk struktur - tänk "nytt stycke"
Radbryt Max radlängd: 79 tecken Python tillåter radbryt inom parenteser. Använd detta. Använd \ för att bryta en rad om det inte finns andra alternativ.
Exempel på radbryt
Namngivning
Variabelnamn och funktionsnamn (PEP 8) små bokstäver _ mellan "ord" beskrivande variabelnamn/funktionsnamn
Kommentarer
Funktionskommentarer (PEP 257) Beskriv funktionen i funktionskommentaren. Funktionskommentaren skrivs mellan tre citattecken i början och tre citatattecken i slutet. Om kommentaren sträcker sig över mer än en rad, ska de tre sista citattecknena läggas på en egen rad. Använd 72 som längsta radlängd för funktionskommentarer som går över flera rader.
Kommentarsexempel
Kodkommentarer Block-kommenterar Indenterad till samma nivå som efterföljande kod. Inline-kommentar Använd hellre block-kommentarer
Kommentarsexempel
Import
import Alla importer sker i början av filen En import per rad
Visualisering av rekursion
Rekursiva funktioner kontra iterativa loopar Rekursiva funktioner ligger nära matematiska funktioner. Loopar som använder for eller while, kallas för iterativa loopar. Rekursiva funktioner kan ta både längre tid och kan kräva mer minne än iterativa lösningar. Vissa problem kan dock ibland utryckas "enklare" med en rekursiv funktion.
Att skriva en rekursiv funktion Funktionen måste returnera ett värde Funktionsdefinitionen har minst ett argument Ett slutvillkor måste finnas
Nästlad loop jmf med rekursion Nästlad loop: loop i loop Rekursiv funktion: funktionsanrop i funktionsanrop
Klassiskt exempel - Träd Ett träd delar på sig i två grenar om dess längd är längre än x. Varje gren är ett träd.
Träd import turtle def draw_tree(length, min_length, step, angle): if length > min_length: # stam turtle.fd(length) # höger gren (som är ett träd) turtle.rt(angle) draw_tree(length*step, min_length, step, angle) # vänster gren (som är ett träd) turtle.lt(angle*2) draw_tree(length*step, min_length, step, angle) # backa tillbaka till start turtle.rt(angle) turtle.bk(length) s = turtle.screen() s.setup(width=600, height=600) turtle.mode("logo") turtle.speed(10) draw_tree(100, 10, 0.8, 35) turtle.exitonclick()