tisdag 21 maj 2013

Exempel på rekursion

Tre exempel på rekursiva funktioner


def fakultet(n):
    if n == 0 : return 1
    else : 
        return n*fakultet(n-1)
 
def artitmetisk_summa(n, d):
    if n == 0 : return 0
    else : return n*d+artitmetisk_summa(n-1, d)
    
   
def binar_sokning(a, l, min, max):
    if l[max] == a : i=max
    else :
        mitt = (min+max) // 2
        if a > l[mitt] : i=binar_sokning(a,l, mitt, max)
        else : i=binar_sokning(a,l, min, mitt)
    return i

print("Fakultet " + str(fakultet(5)))
print("Summa " + str(artitmetisk_summa(4, 2)))
lista=[1, 3, 5, 8, 9, 10, 12, 16]
print("Sökning " + str(binar_sokning(16, lista, 0, 7)))


En rekursiv funktion kallar på sig själv tills ett begränsande villkor är uppfyllt. Fakultetsberäkningen ovan kan beskrivas med denna bild:


Först byggs stacken av funktionsanrop upp tills fakultet(0) returnerar 1. När detta sker vänder det och stacken krymper då varje steg returnerar sina värden. Slutligen returnerar fakultet(5) sitt värde till print som skriver ut det.

Uppgift 3.3
Skriv och testa en rekursiv funktion för att beräkna en geometrisk summa. (Du kan använda ** för upphöjt i Python.)


måndag 15 april 2013

Rörelse och interaktion

Automatisk rörelse

Tkinter har metoden after( tid, funktion) som anropar en funktion efter tiden tid som anges i millisekunder. Denna kan användas för att skapa rörelse i ett grafisk program.

Övning 3.9
Skriv in och testa programmet nedan:

from tkinter import *


class Dot_Canvas(Canvas):
    
    def __init__(self, root):
        super().__init__(root, width=400, height=300, bg="white")
        self.running=False
        self.bind("<Button-1>", self.start_stop)
        self.x=30
        self.y=30
        self.dx=5
        self.dy=5
        self.photo = PhotoImage(file="../greendot.gif")
        self.dot_id=self.create_image(self.x, self.y, image=self.photo)
        self.pack()
    
    def start_stop(self, event):
        if not self.running :
            self.running=True
            self.move_dot()
        else :
            self.running=False
        
    def move_dot(self):
        self.move(self.dot_id, self.dx, self.dy)
        self.x=self.x+self.dx
        self.y=self.y+self.dy
        if self.x < 10 or self.x > 390 : 
            self.dx=-self.dx
        if self.y < 10 or self.y > 290 : 
            self.dy=-self.dy
        if self.running :
            self.after(50, self.move_dot)

root = Tk()
root.title('Moving dot')
Dot_Canvas(root)
mainloop()

Notera funktionen hos programmet. När användaren klickar på musen sätts variabeln running till sant och metoden move_dot kallas på. Sist i metoden move_dot finns raden self.after(50, self.move_dot) denna kallar på move_dot efter 1/20 sekund. Alltså kommer move_dot att köras varje 1/20 sekund. En funktion eller metod som kallar på sig själv kallas rekursiv. När användaren klickar på musen igen blir running falskt och metoden move_dot slutar att kalla på sig själv.

 En mask

Genom att skapa en lista av gröna punkter och deras positioner man göra en studsande mask:

Övning 3.10
Skriv och testa:

from tkinter import *


class Dot_Canvas(Canvas):
    
    def __init__(self, root):
        super().__init__(root, width=400, height=300, bg="white")
        self.running=False
        self.bind("<Button-1>", self.start_stop)
        self.x=[10,20,30,40,50]
        self.y=[10,20,30,40,50]
        self.dx=[5,5,5,5,5]
        self.dy=[5,5,5,5,5]
        self.photo = PhotoImage(file="../greendot.gif")
        self.dot_id=[]
        for i in range(len(self.x)) :
            self.dot_id.append(self.create_image(self.x[i], self.y[i], image=self.photo))
        self.pack()
    
    def start_stop(self, event):
        if not self.running :
            self.running=True
            self.move_dot()
        else :
            self.running=False
        
    def move_dot(self):
        for i in range(len(self.dot_id)) :
            self.move(self.dot_id[i], self.dx[i], self.dy[i])
            self.x[i]=self.x[i]+self.dx[i]
            self.y[i]=self.y[i]+self.dy[i]
            if self.x[i] < 10 or self.x[i] > 390 : 
                self.dx[i]=-self.dx[i]
            if self.y[i] < 10 or self.y[i] > 290 : 
                self.dy[i]=-self.dy[i]
        if self.running :
            self.after(50, self.move_dot)

root = Tk()
root.title('Mask')
Dot_Canvas(root)
mainloop()


 Dra saker med musen

Genom att skilja på händelserna när man trycker ner och drar med musknappen nedtryckt kan man dra saker över en Canvas med musen. Programmet nedan använder också metoden find_closest för att välja den närmaste av de två punkterna.

Övning 3.11
Skriv och testa:

from tkinter import *


class Dot_Canvas(Canvas):
    
    def __init__(self, root):
        super().__init__(root, width=200, height=200, bg="white")
        self.dra_id=None
        self.dra_x=0
        self.dra_y=0
        self.bind("<ButtonPress-1>", self.ned)
        self.bind("<B1-Motion>", self.dra)
        self.photo = PhotoImage(file="../greendot.gif")
        self.create_image(40, 40, image=self.photo)
        self.create_image(160, 160, image=self.photo)
        self.pack()
    
    def ned(self, event):
        self.dra_id = self.find_closest(event.x, event.y)    
        self.dra_x=event.x
        self.dra_y=event.y
        
    def dra(self, event):
        dx=event.x-self.dra_x
        dy=event.y-self.dra_y
        self.move(self.dra_id, dx, dy)
        self.dra_x=event.x
        self.dra_y=event.y

root = Tk()
root.title('Dot')
Dot_Canvas(root)
mainloop()

Inlämningsuppgift 3.2
Skriv något av följande program och lämna in:

1) En analog klocka. Använd create_line och coords för att få visarna att vridas. Kan kompletteras med möjlighet att ställa klockan med musen.

2) Femtonspel - användaren klickar på den siffra hen vill flytta. Programmet utför flytten om det är möjligt och upptäcker om siffrorna är i rad.

3) Masken - Användaren styr en mask med knapp för sväng 90 grader höger eller vänster. Masken blir längre och längre. Man får inte krocka med väggar eller sig själv. Hinder kan införas och mm.

onsdag 27 mars 2013

En målarduk

För att visa olika former av grafik finns objektet Canvas.  Canvas använder ett koordinatsystem med origo i övre vänstra hörnet och en pixel per enhet. Y-axeln är riktad nedåt.

Exempel:

from tkinter import *

root = Tk()

w = Canvas(root, width=200, height=100, bg="white")
w.pack()

w.create_line(0, 0, 200, 100)
w.create_line(0, 100, 200, 0, fill="red", dash=(4, 4))

w.create_rectangle(50, 25, 150, 75, fill="blue")

mainloop()

Metoder för att skapa objekt på en Canvas är:
  • create_line
  • create_rectangle
  • create_arc
  • create_oval
  • create_polygon
  • create_text
  • create_window
  • create_bitmap
  • create_image
Se sida 209-210 i boken samt här.

Som synes ovan kan du även konfigurera utseendet på olika objekt med hjälp av angivelser av färger (bg i koden ovan står för background colour) och andra attribut.

Uppgift 3.2
Rita en smiley med tkinters Canvas

Saker som rör sig och att reagera på musklick

Genom att binda en  händelsehanterare till en Canvas kan du göra olika saker vid musklick eller annat som användaren gör. Bindandet gör med metoden .bind("Namn", funktion).
Namn anger händelsen som ska bindas till funktionen. Se här för lista. Se även boken sid. 238-245
funktion är den funktion som ska köras vid händelsen. Alla händelsefunktioner har argumentet event som är en referens till händelseobjektet som innehåller data om händelsen.

Objekten som ritas på en Canvas kan modifieras och förflyttas efter att de ritats. Detta är möjlig då varje objekt ges ett unikt id-nummer i och med att det skapas. Vid förändringarna hänvisas till dessa id-nummer. Se även boken sid. 213-220

Nedanstående exempel ritar ut en bild med en grön prick. Om användaren klickar på musknappen flyttas pricken 10 pixlar snett nedåt.


from tkinter import *

root = Tk()
root.title('Dot')

def klick_b1(event):
    w.move(dot_id, 10, 10)

w = Canvas(root, width=200, height=200, bg="white")
w.bind("<Button-1>", klick_b1)
photo = PhotoImage(file="../greendot.gif")
dot_id=w.create_image(30, 30, image=photo)
w.pack()
mainloop()

Övning 3.7
Skriv in programmet ovan och testa. Bilden på den gröna pricken laddar du ner och placerar i din projektmapp:

  

Global eller klass

Funktioner kan normalt endast läsa globala variabler, inte ändra dem. Detta är ett problem för händelsebindningar i Python. Du skulle behöva skicka med informationen till funktionen som körs vid händelsen men tyvärr går inte det på ett enkelt sätt då man endast anger funktionens namn. Det finns två olika lösningar på problemet:

global

För att kunna ändra globala variabler i en funktion måste de deklareras först i funktionen med nyckelordet global. Ex:

from tkinter import *

root = Tk()
root.title('Dot')
x=30
y=30
dx=10
dy=10

def klick_b1(event):
    global x, y
    w.move(dot_id, dx, dy)
    x=x+dx
    y=y+dy
    print(str(x)+","+str(y))

w = Canvas(root, width=200, height=200, bg="white")
w.bind("<Button-1>", klick_b1)
photo = PhotoImage(file="../greendot.gif")
dot_id=w.create_image(x, y, image=photo)
w.pack()
mainloop()

Klass
Att använda global passar bra i mindre program men det är bättre att hantera objekt när programmen blir lite större. Genom att skapa en egen underklass till Canvas kan du lägga till de data och funktioner du behöver och hantera dessa. Denna lösning ger ser ut så här:

from tkinter import *


class Dot_Canvas(Canvas):
    
    def __init__(self, root):
        super().__init__(root, width=200, height=200, bg="white")
        self.bind("<Button-1>", self.klick_b1)
        self.x=30
        self.y=30
        self.dx=10
        self.dy=10
        self.photo = PhotoImage(file="../greendot.gif")
        self.dot_id=self.create_image(self.x, self.y, image=self.photo)
        self.pack()
        
    def klick_b1(self, event):
        self.move(self.dot_id, self.dx, self.dy)
        self.x=self.x+self.dx
        self.y=self.y+self.dy
        print(str(self.x)+","+str(self.y))

root = Tk()
root.title('Dot')
Dot_Canvas(root)
mainloop()

Lösningen med klass är som synes  "klumpigare" för små testprogram men har stora fördelar då programmen blir större. Den nya klassen Dot_Canvas innehåller allt den behöver för sin funktion och kan skapas enkelt i huvudprogrammet.

Använd valfri metod för att lösa nedanstående:

Övning 3.8
Ändra i programmet ovan så att punkten ser ut att studsa när den når sidorna på fönstret.


Uppgift 3.3
Händelseinformationen som finns i objektet event i koden ovan innehåller bland annat information om var muspekaren var när händelsen inträffade. Detta är lagrat som event.x och event.y. Använd exemplet ovan som grund till ett program som placerar den gröna pricken där användaren klickar.

onsdag 20 mars 2013

Textrutan Entry

Entry är en textruta för att skriva in enradstexter. Innehållet i textrutan sparas i en angiven variabel som är ett objekt av typen StringVar().  För att komma åt texten i textrutan används metoden get på textvariabeln. Se ex:

from tkinter import *

def ny_text(): 
    lbl.config(text=en1_text.get())
        
root=Tk()
root.title('Textruta')
root.minsize(200, 60)
en1_text = StringVar() #Variabeln som innehåller textrutans text
en1=Entry(root, textvariable=en1_text).pack(fill=X)
btn = Button(root, text ="Skriv", command = ny_text)
btn.pack(fill=X)
lbl = Label(root, text = "")
lbl.pack(fill=X)

root.mainloop()

För att ställa in texten i textrutan används metoden set("min text") på textvariabeln.

Övning 3.6
Ändra i programmet ovan så att knappen tömmer textrutan samtidigt som texten skrivs i etiketten.

Inlämningsuppgift 3.1
Använd ovanstående samt Övning 2.4 för att göra en grafisk version av ett program som löser andragradsekvationer och skriver ut svaret.

onsdag 13 mars 2013

Knappar som gör något

För att något ska hända när du trycker på en knapp anges vilket kommando (vanligen en funktion) som ska köras. Se exempel:

from tkinter import *

def sag_haj(): 
    lbl.config(text="Hej!")
        
root=Tk()
root.title('Count')
root.minsize(200, 100)
btn = Button(root, text ="Säg hej", command = sag_haj)
btn.pack(fill=X)
lbl = Label(root, text = "")
lbl.pack(fill=X)

root.mainloop()

Övning 3.4
Skriv in ovanstående program och testa,

Notera
I Python anges namnet på funktionen som skall köras. Den anropas inte. Där av endast namnet sag_haj utan () efter.

Global

För att kunna ändra globala variabler i en funktion måste du ange att du vill använda den globala variabeln mednyckelordet global anges före du utför ändringen. Detta är användbart när vi ska få knappar att göra saker om vi inte vill skapa objekt. Se nedan.

from tkinter import *

n=0

def oka_ett(): 
    global n
    n=n+1
    lbl.config(text=str(n))
        
root=Tk()
root.title('Count')
root.minsize(200, 100)
btn = Button(root, text ="+1", command = oka_ett)
btn.pack(fill=X)
lbl = Label(root, text = str(n))
lbl.pack(fill=X)

root.mainloop()


Genom att tala om att vi ska använda den globala variabeln n i funktionen oka_ett() kan vi utföra ändringen med kommandot från knappen.

Övning 3.5
Lägg till en knapp  i ovanstående program som minskar värdet på n med ett.

måndag 11 mars 2013

Placering i rutnät

Placering av etiketter och andra widgets i rutnät är en vanlig layoutmetod.

Nedanstående exempel skapar följande layout:



from tkinter import *

root = Tk()
root.title("tk")
root.minsize(200, 200)
#Skapar etiketter med ram
l1 = Label(root, text="Uppe Vänster", relief=RIDGE)
l2 = Label(root, text="Mitten Vänster", relief=RIDGE)
l3 = Label(root, text="Stor", relief=RIDGE)
l4 = Label(root, text="En längre text under", relief=RIDGE)
#Placerar in dem i rutnätet och anger att de ska fylla rutan
l1.grid(row=1, column=1, sticky=W+E+N+S)
l2.grid(row=2, column=1, sticky=W+E+N+S)
l3.grid(row=1, column=2,  columnspan=2, rowspan=2, sticky=W+E+N+S)
l4.grid(row=3, column=1, columnspan=3, sticky=W+E+N+S)

# Hur rader och columner ska uppföra sig vid ändring av fönsterstorleken
root.grid_columnconfigure(1, weight=1)
root.grid_columnconfigure(2, weight=1)
root.grid_columnconfigure(3, weight=1)
root.grid_rowconfigure(1, weight=1)
root.grid_rowconfigure(2, weight=1)
root.grid_rowconfigure(3, weight=1)

root.mainloop()

Se även boken sid. 165-166

Uppgift 3.1
Ändra i programmet ovan så att resultatet blir som i bilden nedan.

onsdag 6 mars 2013

Inroduktion till grafiskt gränssnitt

Vi använder det grafiska standardbiblioteket Tkinter. De objekt som det innehåller kallas widgets.

För att förenkla skapar vi en standardmodul som vi kan använda som mall när vi skapar våra objekt. Modulen sköter en del av det bakgrundsarbete som behövs.

Övning 3.1
En standardkod för att skapa grafiska objekt:

from tkinter import *

class myapp_tk(Tk):
    def __init__(self,parent):
        Tk.__init__(self,parent)
        self.parent = parent
        self.initialize()

    def initialize(self):
        pass #Här skrivs den kod som skapar de olika objekten i fönstret.

if __name__ == "__main__":
    app = myapp_tk(None)
    app.title('my application')
    app.mainloop()

Denna kos skapar ett widgetobjekt med namnet app och startar upp metoden mainloop(). Metoden mainloop är den loop som söter hantering av grafiken och reaktion på användarens inmatningar. Den är aktiv så länge objektet finns.

Att lägga till objekt i widgeten

Du lägger till objekt widgeten genom att först skapa ett nytt objekt som tillhör widgeten och sedan placera detta med metoden pack().

Övning 3.2
En etikett (label) med text. Byt ut innehållet i initialize(self) till:

    def initialize(self):
        self.label=Label(text="Hello World")
        self.label.pack()

Övning 3.3
Använd informationen i boken på sidan 158-163 för att testa pack-metoden och knappar.