Løkker


While-løkker

For å utføre en blokk med kode flere ganger, kan man benytte en while-løkke. Koden inne i løkken utføres så lenge betingelsen er truthy.

x = 0
while x < 5:
    print("Jeg skal være snill", x)
    x += 1

print("Ferdig!")

Et irriterende program.1

name = ''
while name != 'your name':
    print('Please type your name.')
    name = input()
print('Thank you!')

En iterasjon er en gjennomkjøring av kodeblokken inni en løkke. While -løkker er hendig når man ikke vet på forhånd hvor mange iterasjoner man trenger.

def count_number_of_digits(x):
    x = abs(x)
    count = 0
    while x > 0:
        count += 1
        x //= 10
    return count

print(count_number_of_digits(123)) # 3
print(count_number_of_digits(-4321)) # 4

Eksempel: det n’te ikke-negative heltallet med en gitt egenskap.

def is_multiple_of_5_or_6(x):
    return ((x % 5) == 0) or ((x % 6) == 0)

def nth_multiple_of_5_or_6(n):
    found = 0
    guess = -1
    while found <= n:
        guess += 1
        if is_multiple_of_5_or_6(guess):
            found += 1
    return guess

print("De første ti tallene som er delelig med 5 eller 6: ")
i = 0
while i < 10:
    print(nth_multiple_of_5_or_6(i), end=" ")
    i += 1
print()
Uendelig løkke

Når man skriver en while-løkke, kan man risikere at løkken varer evig dersom betingelsen alltid blir tilfredsstilt.

def print_numbers_between(a, b):
    while a < b + 1:
        print(a, end=" ")
        a + 1     # Glemt tilordning, a endres ikke

print_numbers_between(1, 10)

Når vi kjører koden på denne nettsiden/i Brython, avbrytes løkken av en timeout. Om koden kjøres som en fil eller i REPL, vil løkken vare evig.

For å komme seg ut av en evig løkke, trykk ctrl + c når fokuset er på terminalen hvor koden kjøres.

Break og continue

Break benyttes for å bryte ut av en løkke.

def annoying_loop():
    while True:
        print('Please type your name.')
        name = input()
        if name == 'your name':
            break
    print('Thank you!')

annoying_loop()

Continue benyttes for å hoppe over resten av kodeblokken og gå tilbake til begynnelsen av løkken.

def login_screen():
    while True:
        print("Brukernavn: ", end="")
        username = input()

        if username == "":
            break # Avbryter løkken

        if username != "admin":
            print("Fant ikke bruker")
            continue # Avbryter resten av iterasjonen

        print(f"Passord for {username}: ", end="")
        password = input()
        if password == "42":
            print("Du er nå logget inn")
            return # Avbryter hele resten av funksjonen

    print("Slår av maskinen nå.")

login_screen()
For-løkker og range

Det er ofte at vi vet på forhånd hvor mange ganger vi ønsker at løkken skal kjøres. Da bør vi benytte en for-løkke.

En for-løkke er kortere å skrive enn en while-løkke, og reduserer faren for feil og bugs.

for i in range(5):
    print("Jeg skal være snill", i)

print("Ferdig!")
def print_numbers_between(a, b):
    for i in range(a, b + 1):
        print(i, end=" ")

print_numbers_between(1, 10)

range()-funksjonen gir oss en ordnet samling med tall. Funksjonen kan ta ett, to eller tre heltalls argumenter.

# Ett argument: begynner med 0 og går opp til (og ikke
# inkludert) det gitte argumentet. Eksempel: 0 1 2 3
for i in range(4):
    print(i, end=" ")
print()

# To argument: tallene begynner på det første tallet,
# og går opp til (og ikke inkludert) det andre tallet.
# Eksempel: 6, 7, 8, 9, 10
for i in range(6, 11):
    print(i, end=" ")
print()

# Tre argument: de to første argumentene betyr det 
# samme som for to argumenter. Det tredje argumentet
# beskriver hvor store hopp som gjøres mellom hver
# iterasjon. Eksempel:  3, 5, 7, 9 (hopper med 2)
for i in range(3, 11, 2):
    print(i, end=" ")
print()
# Hopp kan også være negativ. Eksempel: 10 9 8 7 6
for i in range(10, 5, -1):
    print(i, end=" ")
print()
# En range kan være tom
print("---")
for i in range(3, 3):
    print(i, end=" ")
print()
for i in range(19, 3):
    print(i, end=" ")
print()
for i in range(5, 10, -1):
    print(i, end=" ")
print()
print("---")
# En range kan lagres som en variabel
r = range(7)

for i in r:
    print(i, end=" ")
    if i == 4:
        break
print()

# Kan brukes flere ganger
for i in r:
    print(i, end=" ")
print()

En range er en samling med tall. En streng er en samling med bokstaver. En for-løkke kan brukes for å iterere over alle typer samlinger.

s = "foo"
for letter in s:
    print(letter, end=", ")
print()

for x in (s, "towel", 42, None):
    print(x, end=", ")
print()
Nøstede løkker
# Vi kan ha løkker inni løkker
def print_coordinates(rows, cols):
    for row in range(rows):
        for col in range(cols):
            print(f"({row}, {col})", end=" ")
        print()

print_coordinates(3, 5)
# Hvilken figur tegner vi her?
def mystery_figure(height):
    for row in range(height):
        for col in range(row):
            print("*", end="")
        print()

mystery_figure(5)
Primtall

Et positivt heltall \(x\) er et primtall dersom det ikke kan faktoriseres; altså deles opp i to heltallsfaktorer større enn 1 slik at produktet deres blir \(x\).

# Er x et primtall?
def is_prime(x):
    if x < 2: 
        return False
    for factor in range(2, x):
        if x % factor == 0:
            return False
    return True

# Skriv ut primtallene mellom 0 og 100
for i in range(100):
    if is_prime(i):
        print(i)

Raskere metode: la løkken gå opp til kvadratroten.

def is_prime(x):
    if x < 2:
        return False
    for factor in range(2, x):
        if x % factor == 0:
            return False
    return True

def faster_is_prime(x):
    if x < 2:
        return False
    if x == 2:
        return True
    if x % 2 == 0:
        return False
    max_factor = round(x**0.5)
    for factor in range(3, max_factor + 1, 2):
        if x % factor == 0:
            return False
    return True

# Sjekk at funksjonene gir samme svar
import sys
for x in range(100):
    if is_prime(x) != faster_is_prime(x):
        print(f"HUFFDA! is_prime({x}) gir {is_prime(x)}", end=" ")
        print(f"men faster_is_prime({x}) gir {faster_is_prime(x)}")
        sys.exit(1) 
print("Det ser ut som funksjonene gir samme svar")

# La oss ta tiden for å se forskjellen
import time
big_prime = 499 # Prøv 104729, 1010809, 10101023, 102030407
print(f"Tar tiden på is_prime({big_prime})", end="")
time0 = time.time()
print(f", returnerer {is_prime(big_prime)}", end="")
time1 = time.time()
print(f", tid = {(time1 - time0) * 1000:.3} ms")

print(f"Tar tiden på faster_is_prime({big_prime})", end="")
time0 = time.time()
print(f", returnerer {faster_is_prime(big_prime)}", end="")
time1 = time.time()
print(f", tid = {(time1 - time0) * 1000:.3} ms")

Det første primtallet er 2. Det andre primtallet er 3. Så følger 5, 7 og 11 som henholdsvis tredje, fjerde og femte primtall. Men hva er det hundrede primtallet? Mer generelt: hva er the \(n\)’te primtallet?

def is_prime(x):
    if (x < 2):
        return False
    if (x == 2):
        return True
    if (x % 2 == 0):
        return False
    max_factor = round(x**0.5)
    for factor in range(3, max_factor + 1, 2):
        if (x % factor == 0):
            return False
    return True

def nth_prime(n):
    count = 0
    i = -1
    while count < n:
        i += 1
        if is_prime(i):
            count += 1
    return i

print(nth_prime(1)) # 2
print(nth_prime(2)) # 3
print(nth_prime(100))
n’te tall med gitt egenskap

Vi har i kursnotatene over sett eksempler på hvordan finne det n’te primtallet, og også hvordan finne det n’te tallet som er delbart med enten 5 elle 6. Disse to metodene følger nøyaktig samme mønster, den eneste forskjellen er hvilken funksjon de kaller for å avgjøre hvorvidt det gjettede tallet har riktig egenskap. Vi kan gjøre koden generell, slik at vi kan finne det n’te tallet for enhver egenskap, så lenge vi kan skrive en funksjon som avgjør om et tall har egenskapen.

Eksempelet under illustrerer at funksjonsnavn er variabler. Forskjellen er at de referer til en “verdi” av typen function i stedet for en verdi som representerer data.

def nth_number_with_property(n, has_property):
    count = 0
    guess = -1
    while count < n:
        guess += 1
        if has_property(guess):
            count += 1
    return guess


# Skriv ut de 20 første tallene som er kvadrattall
def is_square(x):
    return (round(x**0.5)**2) == x

for i in range(1, 21):
    print(nth_number_with_property(i, is_square), end=" ")
print()

# Skriv ut de 20 første tallene som slutter på 42
def ends_with_42(x):
    return (x % 100) == 42
    
for i in range(1, 21):
    print(nth_number_with_property(i, ends_with_42), end=" ")
print()
Stil

Benytt alltid en for-løkke hvis det er naturlig. Dette gjør det lettere å forstå koden, og er mindre utsatt for bugs som gjør at programmet blir sittende fast i en uendelig løkke.

# Dårlig
repetitions = 5
x = 0
while x < repetitions:
    print("Jeg skal være snill", x)
    x += 1
# Bra
repetitions = 5
for x in range(repetitions):
    print("Jeg skal være snill", x)

  1. Fra Automate the Boring Stuff with Python. Al Sweigart, CC BY-NC-SA 3.0. ↩︎