TDDE44 Programmering, grundkurs¶

Föreläsning 1.3¶

Johan Falkenjack, johan.falkenjack@liu.se¶

Föreläsningsöversikt Fö 1.1 - 1.3¶

  • Introduktion
  • Kursadmin
  • Linux och datorer
  • Python interaktivt och som skript
  • Rättningsskriptet
  • Programmeringskoncept
    • Literaler och datatyper
    • Satser och uttryck
    • Programflöde — funktioner
    • Strängar, listor och tupler
    • Moduler
    • Programflöde — villkor och sanningsuttryck

Kort repetition¶

In [1]:
def add_two(value):
    return value + 2
In [2]:
my_number = 5
print(add_two(my_number))
7
In [3]:
print(my_number)
5
In [4]:
print(print(add_two(my_number)))
7
None

Inte bara tal och aritmetik

https://media3.giphy.com/media/v1.Y2lkPTc5MGI3NjExbDN1c2loNHQ2d3V5bXkxaTcweG80NDE4OThocWJzcjdiOG1hcWF1OSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/WRQBXSCnEFJIuxktnw/giphy.gif
via GIPHY
  • answer = 42
  • Vadå "bara"?
  • Programmering är egentligen mycket mer, speciellt när vi börjar tala om värden som inte är atomära.

Sekvenser och samlingar¶

  • Sekvenser: Värden som kan delas upp i delar som har en strikt ordning. T.ex.
    • Sträng: str
    • Listor: list
    • Tupler: tuple
  • Samlingar: Värden som i sig innehåller andra värden. T.ex.
    • Listor: list
    • Tupler: tuple
    • Associationstabeller: dict
    • Mängder: set

Gemensamt för sekvenser i Python¶

  • Har en längd som vi kan ta reda på genom att anropa funktionen len med sekvensen som argument.
  • Operatorerna + och * fungerar oftast på samma sätt för alla sekvenstyper.
  • Vi kan indikera enskilda element eller delsekvenser med hjälp av Pythons subskriptnotation och reglerna för indexering.

Demonstration av listor, strängar och tupler¶

  • str, list, tuple
  • [], "", '', tuple() resp [1], "a", 'a', (1,)
    • Parenteser för tupel resp. uttrycksprioritet, lättförväxlat
  • Konkatenering, multiplicering, och len
  • Lägga till element genom konkatenering my_list = my_list + ['nytt element']
  • Subskript
    • Elementaccess mha. indexering (positiva och negativa index)
    • Delsekvenser mha. slice (start, stop, step)
    • Slice har alltid samma typ som sekvensen som slice:as, vi kan utnyttja detta för att göra lösningar mer generella.

Strängar, datatypen str¶

  • Eng. string, från "string together"; t.ex. "a string of beads" → "a string of characters"
  • Representerar text och strängliteraler skrivs med citationstecken (enkla ' eller dubbla ", så länge man är konsekvent).
    • "abcd", 'efgh'
  • Man bör inte blanda citationstecken i samma fil, men...
    • Examinatorn har personligen den fula ovanan att använda ' för strängar med bara ett tecken, och " för längre strängar.
  • Python har inte en separat datatyp för tecken, "a" är identiskt med 'a' och båda är strängar.
  • Strängar och heltal är dock helt olika saker, alltså är strängen '42' inte samma sak som heltalet 42.
  • Detta kommer av språk som har en separat teckendatatyp, för enstaka tecken. I dessa språk är det vanligt att enkla citationstecken indikerar ett enskilt tecken medan dubbla citationstecken representerar strängar.

Vilka uttryck kan vi bygga med strängar?¶

  • + — utför konkatenering av strängar
  • Testa andra operatorer, vad händer med -, * och /?

Osynliga tecken¶

  • Vissa tecken är "osynliga", t.ex. tabtecken och radbrytningar
  • Specialtecken skrivs ofta med särskilda teckenkombinationer, t.ex.
    • tabtecken: "\t"
    • radbrytning: "\n"
  • "\tIndragen text avslutad med radbrytning.\n"

Varje tecken i en sträng har ett index¶

  • Index är heltal.
  • Numreringen börjar på 0
  • Strängen "EXEMPEL":

Åtkomst av tecken på en viss position i en sträng¶

  • Vi kan använda följande notation för att komma åt ett tecken på en viss position i en sträng
    • my_string[index]
  • my_string[0] är det första tecknet i variabeln my_string.
  • Negativa index kan användas för att titta "bakifrån"
    • my_string[-1] ger det sista tecknet i variabeln my_string

Längden av en sträng¶

  • För att ta reda på hur många tecken en sträng innehåller kan funktionen len() användas.
  • Exempel
In [5]:
name = "Alfred"
name_length = len(name)
print(name_length)
6

Delsträngar¶

  • my_string[start:end] från index start, inkludera tecknen på alla index mindre än end
  • my_string[:end] från första tecknet, inkludera tecknen på alla index mindre än end
  • my_string[start:] alla tecken från index start till slutet av strängen
In [6]:
my_string = "EXEMPEL"
my_string[1:4]
Out[6]:
'XEM'

Ett värde som en sträng¶

  • Vi kan använda funktionen str för att få en strängrepresentation av vilket värde som helst.
  • Exempel:
In [7]:
3+3
Out[7]:
6
In [8]:
str(3) + str(3)
Out[8]:
'33'
In [9]:
str(True)
Out[9]:
'True'

Listor, datatypen list¶

  • Används för att lagra en ordnad samling av värden (element i listan).
  • Syntax:
studenter = ["Ada", "Bertil", "Cecilia"]
  • Det går bra att blanda datatyper i en lista:
diverse = [5, 3.0, "rosa", [100, 200, 300]]
  • Precis som med strängar använder vi index för att komma åt ett värde på en viss position.
  • Index för listor börjar också på 0

Dellistor¶

  • Vi kan även plocka fram delar av en lista
  • my_list[start:end] från index start, inkludera alla värden med index mindre än end
  • my_list[:end] från början, inkludera alla värden med index mindre än end
  • my_list[start:] från index start till slutet av listan
In [10]:
my_list = ['h', 'e', 'j', 's', 'a', 'n']
my_list[1:4]
Out[10]:
['e', 'j', 's']

Operatorer som kan användas med listor¶

  • Operatorn + kan användas för att konkatenera två listor till en ny lista.
  • Operatorn * kan användas med ett heltal $n$ för att skapa en ny lista som är $n$ upprepningar av elementen i den ursprungliga listan.
In [11]:
fruits1 = ["apple", "pear"]
fruits2 = ["orange", "banana"]
all_fruits1 = fruits1 + fruits2
all_fruits2 = fruits2 + fruits1
In [12]:
print(all_fruits1)
['apple', 'pear', 'orange', 'banana']
In [13]:
print(all_fruits2)
['orange', 'banana', 'apple', 'pear']

Lägga till nytt värde till slutet på en lista¶

In [14]:
fruits = ["orange"]
print(fruits)
['orange']
In [15]:
fruits = fruits + ["banana"]
print(fruits)
['orange', 'banana']

Lägga till nytt värde till början på en lista¶

In [16]:
fruits = ["orange"]
print(fruits)
['orange']
In [17]:
fruits = ["banana"] + fruits
print(fruits)
['banana', 'orange']

Tupler, datatypen tuple¶

  • En ordnad samling av objekt.
    • Fungerar nästan exakt som listor, än så länge. Vi kommer se skillnaden längre fram i kursen. Fram till mitten av labb 2 kommer vi behandla dem identiskt.
  • Kan innehålla vilken annan typ av objekt som helst och vi kan blanda fritt i samma tupel.
  • Tupelliteraler skrivs som listor men med vanliga parenteser istället för hakparenteser, men med två specialfall:
    • Det finns ingen litteral för en tom tupel, istället skapar vi en tom tupel med funktionsanropet tuple().
    • En tupel med bara ett element måste ha ett komma efter det elementet: (1,)
  • En tupel med heltalen 1, 2 och strängen 'a': (1, 2, 'a')

Varför börjar vi räkna från 0?¶


"Så har det alltid varit!"

No description has been provided for this image

Hur vi ofta tänker på 0-indexerade sekvenser


No description has been provided for this image
"Index = position" och ett enskilt index indikerar elementet på den positionen. Vi blir tvugna att prata om "det noll:te elementet" och betyder i så fall "det första elementet" sequence[0] eller sequence[1].

Hur vi ofta tänker på 0-indexerade sekvenser


No description has been provided for this image
Ännu värre blir det när vi inkluderar negativa index. Inkonsekvent, indexerat från 0 i positiv riktning, indexerat från (-)1 i negativ riktning. Förvirrande!

Ett bättre sätt att tänka på 0-indexerade sekvenser


No description has been provided for this image
Varje index indikerar början av ett element. Uppslagning av ett index innebär att vi går till början av elementet, och läser ett helt element framåt. Slice läser från ett startindex till slutindex och vi får exakt de element som befinner sig just mellan dessa index.

Ett bättre sätt att tänka på 0-indexerade sekvenser


No description has been provided for this image
Med negativa index blir beteendet plötsligt konsekvent. För att läsa ut t.ex. 'M' kan vi gå vi till -4 och läsa ett element framåt.

Ett bättre sätt att tänka på 0-indexerade sekvenser


No description has been provided for this image
För tydlighet kan vi tänka oss att indexen "-0" respektive 7 skulle se ut på det här sättet.
(Här blir det förhoppningsvis också tydligt varför det blir fel om vi går till index 7 och försöker läsa ett element framåt.)

Index som offset¶

  • Sekvenser lagras ofta i så kallade fält (på engelska, och ofta på vedertagen svengelska: array) i minnet, dvs. i direkt följd från en viss minnesadress.
    • (Fortfarande mestadels sant.)
  • För att komma åt ett element på en viss position i sekvensen användes ett offset som indikerade hur många steg från sekvensens start man behövde gå för att komma till elementet man ville läsa.
  • Minnesadressen för fältet pekade på början av första elementet i fältet och första elementet låg alltså på sekvensens minnesadress + offset $0$.
  • Alltså: Index som börjar på $0$ är ekvivalent med offset.
In [18]:
spam = "EXEMPEL"
  • Vi antar att spam lagras på minnesplats 0x0007
No description has been provided for this image No description has been provided for this image
  • Hade vi börjat indexera från 1 hade vi antingen gjort uppslagningen besvärligare, eller slösat minnesutrymme på ett element som inte finns.
  • Den enkla vägen var att börja räkna från 0, och det gör vi därför nu i de flesta programmeringsspråk.
  • Normalt sett behöver vi inte bry oss om faktiska minnesadresser i Python och på moderna datorer är minnesadresserna mycket längre

Exempel¶

In [19]:
print(spam)
EXEMPEL
  • Om vi skriver ut variabeln spam får vi sekvensen som börjar på minnesadressen 0x0007 och är 7 tecken lång
No description has been provided for this image No description has been provided for this image

Exempel¶

In [20]:
print(spam[0])
E
  • Om vi använder indexering tar vi sekvensens minnesadress, adderar index-värdet, och läser ett element framåt från den beräknade minnesadressen
No description has been provided for this image No description has been provided for this image

Exempel¶

In [21]:
print(spam[1])
X
  • Om vi använder indexering tar vi sekvensens minnesadress, adderar index-värdet, och läser ett element framåt från den beräknade minnesadressen
No description has been provided for this image No description has been provided for this image

Exempel¶

In [22]:
print(spam[2:5])
EMP
  • Om vi använder "utsnitt" (eng. slice, och ofta på vedertagen svengelska) adderar start- och stopvärdena till sekvensens minnesadress och läser från den den första och fram till den andra beräknade minnesadressen.
No description has been provided for this image No description has been provided for this image

Sidospår: Indexering från 0 eller 1¶

  • BCPL (1967) var det första programmeringsspråket att helt enkelt exponera den underliggande egenskapen att indexering är ekvivalent med pekararitmetisk offset och numrera index från 0.
  • Det följde sedermera med till programmeringsspråket C (som inspirerades av BCPL), varifrån det spred sig till att bli norm för nästan alla moderna programmeringsspråk.
  • Matematikspråk som MATLAB/Octave, R, och Julia skiljer sig dock från mängden och indexerar från 1. Riktigt mastiga språk som Fortran låter programmeraren bestämma själv, det behöver inte ens vara positiva heltal. Galningar.
    • Ja, man gör bort sig hela tiden när man växlar mellan 0-indexerande och 1-indexerande språk.
    • Till er som målgrupp kan jag bara säga: Vänj er vid att dubbelkolla.
  • Det finns goda argument för att indexera från 0 som är oberoende av den historiska kopplingen till fält:
    • Why numbering should start at 0 av Edsger Dijkstra.
  • Mer om historien bakom hur 0-indexering blev en grej (och en beskrivning av föregående referens som "unresearched hippie voodoo nonsense"):
    • https://exple.tive.org/blarg/2013/10/22/citation-needed/

Styra programflöde med villkor¶

  • Än så länge rätt så ointressanta program som alltid gör samma operationer i samma ordning.
  • villkorssats
  • sanningsvärde
  • jämförelser
  • logiska operatorer

Exempel¶

Kassasystem för bussar med följande priser:¶

  • Barn under 12 år: Gratis
  • Ungdomar under 18 år: 20 kr
  • Ungdomar under 65 år: 30 kr
  • 65 år och äldre: 15 kr
  • Olika alternativ
  • Hur får vi Python att ge oss olika resultat här?
  • Kan vi uttrycka det som en matematisk formel? Hmm...
In [23]:
def plot_prices(degree):
    import numpy as np
    import matplotlib.pyplot as plt

    x = [0, 12, 18, 65]
    y = [0, 20, 30, 15]

    poly = np.polyfit(x, y, deg=degree)

    polyval = np.polyval(poly, range(100))

    fig, ax = plt.subplots()
    ax.plot(polyval, label=f'{degree} degree polynomial')
    ax.legend()
    return polyval
In [24]:
prices = plot_prices(3)
No description has been provided for this image
In [25]:
prices[12]
Out[25]:
np.float64(19.999999999999996)

Vad i he...¶

  • Okej, avrundningsfel.
  • Negativa priser för pensionärer, hade varit bra förklaring till varför det är så många pensionärer på bussen.

Villkorssatser, en bättre lösning¶

if-satsen¶

  • Sammansatt sats med en eller flera klausuler.
  • Första klausulen börjar alltid med nyckelordet if följt av ett sanningsuttryck (eng. boolean expression), dvs ett uttryck som evalueras till ett sanningsvärde (eng. boolean value).
  • Syntax:
if <boolean expression>:
    # Code block that runs if boolean is True
  • Vi kommer i den här kursen fokusera på if-satsen.
  • För den som programmerat tidigare och vill fördjupa sig kan man titta på match-konstruktionen, men det är överkurs.

Vad är en "boolean"?

No description has been provided for this image
George Boole (1815-1864)
  • Booleska värden, (eng. boolean value), ett annat ord för sanningsvärden.
  • I Python: Klassen bool med exakt två möjliga värden, True eller False.
  • Ofta resultat av någon jämförelseoperator: ==, !=, <, >, <=, >=.
In [26]:
print(1 <= 2)
True
  • Fått namn efter George Boole, matematiker som introducerade en algebra för sanningsvärden som sedermera kom att kallas Boolesk algebra.
  • Ni som läst digitalteknik är bekanta med detta redan.

Förenklat exempel¶

Kassasystem för bussar med följande priser:¶

  • Barn under 12 år: Gratis
  • Övriga: 30 kr
In [27]:
def get_price(rider_age):
    price = 30
    if rider_age < 12:
        price = 0
    return price
In [28]:
print(get_price(12))
30
  • Beskriv koden och testkör
  • Strikt två alternativ, så kan vi göra det tydligare? Ja!

else-klausulen¶

  • En del av en sammansatt sats, i det här fallet en if-sats.
  • Alltid den sista klausulen och hanterar en "annars"-situation.
  • Syntax:
if boolean:
    # Code that runs if boolean is True
else:
    # Code that runs if boolean is False
In [29]:
def get_price(rider_age):
    if rider_age < 12:
        price = 0
    else:
        price = 30
    return price
In [30]:
print(get_price(38))
30
  • Jag sa att if-satser är sammansatta satser med en eller flera klausuler.
  • Rent logiskt exakt samma program, men tydligare att det är antingen gratis, eller kostar 30 kr.
  • Inte alltid strikt nödvändig, men ökar nästan alltid läsbarheten.

Bara en klausul kommer att köras¶

  • Dvs. vi behöver inte variabeln price.
In [31]:
def get_price(rider_age):
    if rider_age < 12:
        return 0
    else:
        return 30
In [32]:
print(get_price(38))
30

Men vi hade ju fyra fall i vårt första exempel!¶

  • Två olika lösningar som lämpar sig olika väl i olika situationer:
  1. Nästling av if-satser, dvs if-satser inne i andra if-satser.
  2. elif-klausuler.

Nästlade if-satser

No description has been provided for this image
if A:
    # Code that runs if A evaluates to True.
else:
    if B:
        # Code that runs if A evaluates to
        # False but B evaluates to True.
    else:
        # Code that runs if both A
        # and B evaluate to False.

  • Notera att den första klausulen inte påverkas av sanningsvärdet på B.

Nästlade if-satser¶

  • Syntax:
if A:
    # Code that runs if A evaluates to True
else:
    if B:
        # Code that runs if A evaluates to False but B evaluates to True
    else:
        # Code that runs if both A and B evaluate to False
  • Notera att den första klausulen inte påverkas av sanningsvärdet på B.

Exempel, repetition¶

Kassasystem för bussar med följande priser:¶

  • Barn under 12 år: Gratis
  • Ungdomar under 18 år: 20 kr
  • Ungdomar under 65 år: 30 kr
  • 65 år och äldre: 15 kr

Nästlade if-satser för vårt exempel¶

In [33]:
def get_price(rider_age):
    if rider_age < 12:
        return 0
    else:
        if rider_age < 18:
            return 20
        else:
            if rider_age < 65:
                return 30
            else:
                return 15
In [34]:
print(get_price(15))
20
  • Fungerar men är förvirrande, ganska svårläst, och leder till djupare indentering ju fler fall vi har.
  • Ibland är logiken sådan att nästling är tydligare, men inte i så här enkla fall.

elif-klausulen¶

  • Syntaktiskt socker för nästlingen vi just såg.
  • Utökar if-satser med flera klausuler med olika villkor (men fortfarande bara en som körs).
  • Fått namn av att det rent logiskt är som att ha en ny if-sats i else-klausulen.
  • Syntax:
if A:
    # Code that runs if A evaluates to True
elif B:
    # Code that runs if A evaluates to False but B evaluates to True
else:
    # Code that runs if both A and B evaluate to False
  • Notera att den första klausulen inte heller här påverkas av sanningsvärdet på B.
  • Notera att den första klausulen inte påverkas av sanningsvärdet på boolean_b.

elif-klausuler för vårt exempel¶

In [35]:
def get_price(rider_age):
    if rider_age < 12:
        return 0
    elif rider_age < 18:
        return 20
    elif rider_age < 65:
        return 30
    else:
        return 15
In [36]:
print(get_price(12))
20
  • Bara den första klausulen där villkoret är sant kommer väljas.
  • Allt detta är en if-sats.

Logiska operatorer¶

  • Logiska operatorer används för att kombinera eller negera sanningsvärden.
  • Uttryck med logisk operator beräknas alltid till ett sanningsvärde, dvs antingen True eller False.
    • and: True om båda operander är sanna
    • or: True om minst en av operanderna är sanna
    • not: True om operanden är False, False om operanden är True
  • Exempel
sensor1 > 100 and sensor2 <= 10
  • Ofta har vi flera olika villkor.
  • Vi kombinerar dessa mha logiska operatorer.
  • Nej, ni behöver inte rita Karnaugh-diagram i den här kursen.

Exempel, vädret¶

No description has been provided for this image

Exempel, vädret¶

  • Notera att vi inte har några elif- eller else-klausuler nedan, så vi har 3 oberoende if-satser med 1 klausul vardera.
    • (Dvs. mer än en av utskrifterna kan ske.)
In [37]:
def tell_me_about_the_weather(temp, precipitation):
    if temp < 0 and precipitation:
        print("I think it's snowing.")
    if temp < 0 and not precipitation:
        print("At least it's not snowing.")
    if temp < -100 or temp > 100:
        print("I think something is very wrong.")
In [38]:
tell_me_about_the_weather(-6.2, False)
At least it's not snowing.

Moduler¶

Moduler¶

  • Specialiserade funktioner m.m. kan göras tillgängliga t.ex. genom att importera moduler i sin Python-kod.
  • Varje fil med Python-kod kan användas som en modul.
  • Det finns ett antal moduler som ingår i Pythons så kallade standardbibliotek (Python Standard Library).
    • Moduler i standardbiblioteket finns alltid tillgängliga i alla Python-installationer utan att behöva installera något extra.

Moduler¶

  • Tillhandahåller ytterligare funktionalitet, t.ex. funktioner för speciella tillämpningar
  • Exempel på moduler som följer med Python
    • random: har t.ex. funktioner som returnerar slumpvärden
    • sys: funktioner m.m. som har med systemet att göra (lägre abstraktionsnivå)
    • os: funktioner m.m. som har med operativsystemet att göra (högre abstraktionsnivå)

Import av modul och exempel på användning¶

  • För att få tillgång till en modul behöver den importeras med en import-sats, som oftast inleds med nyckelordet import.
  • Standard är att lägga alla importsatser i början av textfilen.
In [39]:
import random
  • Funktionen randint i modulen random ger ett slumpmässigt heltal på ett slutet intervall mellan två givna heltal
In [40]:
print(random.randint(1, 3))
print(random.randint(1, 3))
print(random.randint(1, 3))
3
3
2
  • Funktionen random i modulen random (ja, samma namn) ger ett slumpmässigt flyttal på det öppna intervallet $]0,1[$
In [41]:
random_float = random.random()
print(random_float)
0.5015541203533695

Import, namnrymder, punktnotation¶

  • import random
  • Ovanstående laddar in innehållet i modulen random och ser till att det hamnar i namnrymden random
  • Funktionen randint(heltal1, heltal2) i modulen random kommer man då åt genom att skriva
random.randint(heltal1, heltal2)
In [42]:
# import-exempel 1
import random

list_of_names = ["Ada", "Bea", "Cecilia", "Dolores"]

def random_greeting1(names):
    # random.choice() chooses a random element from a sequence
    name = random.choice(names)
    print("Hello " + name + "!")
    
def random_greeting2(names):
    # random.randint() randomly returns 
    random_index = random.randint(0, len(names)-1)
    print("Hello " + names[random_index] + "!")
In [43]:
random_greeting1(list_of_names)
Hello Dolores!
In [44]:
random_greeting2(list_of_names)
Hello Bea!
In [45]:
from random import choice, randint
# imports only choice and randint from random, but make them available in the global namespace, meaning we don't need the `random` prefix to actually use them

list_of_names = ["Ada", "Bea", "Cecilia", "Dolores"]

def random_greeting1(names):
    name = choice(names)
    print("Hello " + name + "!")

def random_greeting2(names):
    random_index = randint(0, len(names)-1)
    print("Hello " + names[random_index] + "!")
In [46]:
random_greeting1(list_of_names)
Hello Ada!
In [47]:
random_greeting2(list_of_names)
Hello Cecilia!