Olimpijske medalje

Tabela kaže razvrstitev desetih držav glede na število medalj na olimpijskih igrah v letih 2016 in 2012. Vidimo, da so tri države na tej lestvici napredovale, tri pa nazadovale.

letošnje prejšnje
1        1         United States
2        3 ^       Great Britain
3        2 v       China
4        4         Russia
5        6 ^       Germany
6        10 ^      Japan
7        7         France
8        5 v       South Korea
9        9         Italy
10       8 v       Australia

Napiši funkcijo napredek(s), ki kot argument prejme seznam števil v drugem stolpcu (npr. [1, 3, 2, 4, 6, 10, 7, 5, 9, 8]), kot rezultat pa vrne par (terko) s števili, ki povesta, koliko držav je na lestvici napredovalo in koliko nazadovalo.

Funkcija mora delovati za poljubno dolge sezname, ne le za deset držav.

Rešitev

Želel sem si - a redko videl - da bi tule uporabili enumerate. In, v splošnem, čim manj komplicirali, saj je bila naloga res lahka.

def napredek(s):
    gor = dol = 0
    for i, e in enumerate(s):
        if i + 1 < e:
            gor += 1
        elif i + 1 > e:
            dol += 1
    return gor, dol

Človek ne jezi se

V poenostavljeni igri Človek ne jezi se ima vsak igralec eno figuro. Vsi začnejo na polju 0. Ko je igralec na vrsti, vrže kocko in premakne figuro za toliko polj, kolikor pokaže kocka. Če pri tem pride na polje, na katerem že stoji kateri drugi igralec, gre oni, drugi igralec na polje 0.

Napiši funkcijo clovek_ne_jezi_se(igralcev, meti), ki kot argument dobi število igralcev in zaporedje metov, kot rezultat pa vrne številke polj, na katerih se po teh metih nahajajo igralci.

Rešitev

Ta naloga je bila predvsem vaja iz spretnosti.

def clovek_ne_jezi_se(igralcev, meti):
    pozicije = [0] * igralcev
    for poteza, met in enumerate(meti):
        igralec = poteza % igralcev
        nova = pozicije[igralec] + met
        pozicije = [0 if x == nova else x for x in pozicije]
        pozicije[igralec] = nova
    return pozicije

V prvi vrstici me zanima, ali znate elegantno pripraviti seznam iz ničel - [0] * igralcev. Nato uporabimo enumerate, da oštevilčimo poteze; igralec, ki je na potezi, je poteza % igralcev. Večina študentov je namesto tega pisala

    igralec = 0
    for met in meti:
        ...
        igralec += 1
        if igralec == igralcev:
            igralec = 0

ali

    igralec = 0
    for met in meti:
        ...
        igralec = (igralec + 1) % igralcev

Tudi s tem ni nič narobe.

Nato izračunamo novo pozicijo tega igralca. Trenutne pozicije zamenjamo z novo tabelo, v katero vse tiste elemente, ki so enaki nova zamenjamo z 0. Seveda se da to narediti tudi na bolj zapleten način.

Končno zares prestavimo še tega igralca, pozicije[igralec] = nova.

Kot ste videli pri reševanju te naloge, ne gre za preveč zapleteno reč, pač pa je lahko samo zelo zoprna in dolga, če si nekoliko neroden.

Zadnje liho

Napiši rekurzivno funkcijo zadnje_liho(s), ki vrne zadnje liho število v podanem seznamu ali None, če v njem ni lihih števil.

Rešitev

Ta je zanimiva, ker sem si predstavljal tole rešitev.

def zadnje_liho(s):
if not s:
    return None
t = zadnje_liho(s[1:])
if not t and s[0] % 2 == 1:
    t = s[0]
return t

oziroma krajše različice, kot je

def zadnje_liho(s):
    return s and (zadnje_liho(s[1:]) or s[0] % 2 and s[0]) or None

Tidve funkciji najprej preverita, ali je kakšno liho število v ostanku seznama. Če ga ni, preverita, ali je slučajno liho prvo.

Tako bi to naredil vsak zrel programer, ki je že kdaj delal v funkcijskih jezikih, kjer imamo pogosto neposreden dostop le do prvega, ne pa tudi do zadnjega elementa seznama.

Študenti so to obrnili drugače: če iščemo s konca, pač iščimo s konca.

def zadnje_liho(s):
    if not s:
        return None
    if s[-1] % 2 == 1:
        return s[-1]
    return zadnje_liho(s[:-1])

To je seveda preprostejše in v Pythonu povsem legalno.

Največ dvakrat

Napiši funkcijo najvec_dve(s), ki v podanem seznamu pusti do dve (ne nujno zaporedni) pojavitvi vsakega elementa in pobriše vse nadaljnje. Funkcija mora vrniti None; spreminja naj podani seznam. Seznam ne vsebuje nujno števil, predpostaviti pa smete, da so njegovi elementi nespremenljivi (immutable).

Če imamo s = [4, 1, 2, 4, 1, 3, 3, 1, 2, 5, 4, 3, 7, 4], mora biti po klicu najvec_dve(s) seznam s enak [4, 1, 2, 4, 1, 3, 3, 5, 7].

Rešitev

Ta vam je (pričakovano) dala vetra, zato sem jo dal bolj na konec. Ker morate spreminjati seznam, je potrebno uporabljati del ali pop - ne moremo kar tako sestaviti novega seznama. Pri tem pa imamo težavo: če gremo z zanko for čez seznam, je brisanje elementov znotraj zanke zelo slaba ideja. Ne deluje. Primere smo videli.

Drugo, kar je zahtevala naloga, je, da vodite evidenco o tem, kolikokrat se je določen element že pojavil. Tu ste se večinoma spomnili uporabiti slovar, ali, še boljše, defaultdict.

Rešitev je lahko, recimo ta

from collections import defaultdict

def najvec_dve(s):
    kolikokrat = defaultdict(int)
    i = 0
    while i < len(s):
        e = s[i]
        kolikokrat[e] += 1
        if kolikokrat[e] > 2:
            del s[i]
        else:
            i += 1

Namesto for uporabimo while. Če element pobrišemo, ne povečamo števca, saj se bo naslednji element premaknil na mesto trenutnega.

Preprosteje je sestaviti nov seznam, katerega elemente potem prepišemo v s.

def najvec_dve(s):
    t = []
    for e in s:
        if t.count(e) < 2:
            t.append(e)
    s[:] = t

Ta rešitev sicer ni najhitrejša, ker uporablja count. Lepše bi bilo spet uporabiti defaultdict, vendar ... takole je pa krajše.

Ena od študentk se je spomnila domiselne rešitve. Preštela je viške, obrnila seznam, odstranila viške in ga spet obrnila. K njeni rešitvi lahko dodam le še, da bi lahko uporabila Counter in tako dobimo:

from collections import Counter
def najvec_dve(s):
    s.reverse()
    for n, r in Counter(s).items():
        for i in range(r - 2):
            s.remove(n)
    s.reverse()

Kudos za idejo.

Podjetje

Napiši razred Podjetje, s katerim je mogoče početi tole.

>>> megashop = Podjetje(1000)
>>> megashop.kapital
1000
>>> tralala = Podjetje(300)
>>> tralala.kapital
300
>>> megashop.prejme(200)
>>> megashop.kapital
1200
>>> megashop.placa(100, tralala)
>>> megashop.kapital
1100
>>> tralala.kapital
400

Rešitev

Želel sem si, da bi opazili, da je kapital atribut, ne metoda, saj ga izpišemo, ne kličemo. Poleg tega me je zanimalo, ali boste znali pravilno narediti placa, saj tam nastopata dva objekta, self, in oni drugi objekt, kateremu self plačuje. Če to dvoje vemo/znamo, je naloga čisto preprosta.

class Podjetje:
    def __init__(self, kapital):
        self.kapital = kapital

    def prejme(self, koliko):
        self.kapital += koliko

    def placa(self, koliko, komu):
        self.kapital -= koliko
        komu.prejme(koliko)
Last modified: Tuesday, 4 October 2016, 3:14 PM