Ta domača naloga je bila težka, ker ni zahtevala veliko programiranja. Zahtevala je, da veste, kaj zahteva od vas. To pa zahteva razumevanje objektnega programiranja.

Testi

Testi: testi-objektni-boti.py

Naloga

Obvezna naloga

Vračamo se k botom. Definirati bo potrebno štiri razrede.

Unit

Razred Unit zna shranjevati čipe. Ima atribut chips, ki vsebuje seznam prejetih čipov (v začetku je prazen), in metodo receive(chip), s katero sprejme čip s podano številko. Poleg tega ima najbrž tudi konstruktor; kaj počne, odkrijte sami.

>>> a = Unit()
>>> a.chips
[]
>>> a.receive(13)
>>> a.chips
[13]
>>> a.receive(25)
>>> a.chips
[13, 25]

Rešitev

class Unit:
def __init__(self):
    self.chips = []

def receive(self, chip):
    self.chips.append(chip)

Naloga pravi ima atribut chips; priskrbel ga bo konstruktor. Poleg tega pravi, da ima metodo receive(chip). Ta doda čip v self.chips.

Output

Razred Output je izpeljan iz razreda Unit. Njegov konstruktor sprejme argument -- številko izhoda. Razred Output dopolni podedovano metodo receive tako, da takrat, ko prejme kak čip, to izpiše v obliki Output 42: 13, pri čemer je 42 številka izhoda (tisto, kar je prejel kot argument konstruktorju), 13 pa številka čipa.

o = Output(133)
>>> o.chips
[]
>>> o.receive(42)
Output 133: 42
>>> o.chips
[42]
>>> o.receive(13)
Output 133: 13
>>> o.chips
[42, 13]

Izpis mora biti natančno takšen, kot je zgoraj (a seveda z drugimi številkami).

Rešitev

class Output(Unit):
    def __init__(self, number):
        super().__init__()
        self.number = number

    def receive(self, chip):
        super().receive(chip)
        print("Output {}: {}".format(self.number, chip))

Obe metodi -- konstruktor in receive -- s super pokličeta podedovano metodo. Konstruktor si poleg tega zapomni številko izhoda, receive pa izpiše, kar hoče naloga.

Bot

Razred Bot ima atribut outputs. outputs je seznam botov ali izhodov, ki jih ta Bot podaja čipe. Za razliko od predprejšnje naloge, ima lahko bot tudi več kot dva izhoda.

Bot ima metodo attach(u), s katero dodamo nov izhod v seznam outputs. Torej: klic b.attach(u) le doda u v seznam b.outputs. Nič drugega.

>>> b = Bot()
>>> a = Bot()
>>> o = Output(42)
>>> p = Output(13)
>>> b.attach(a)
>>> b.attach(o)
>>> b.attach(p)
>>> b.outputs
[Bot object at 0x102182e10>, <Output object at 0x102182e80>, <Output object at 0x102182f28>]

Metoda process preveri, ali je število čipov, ki jih ima bot, enako številu izhodov. Če ni, vrne False. Sicer pošlje čipe na izhode: na prvi izhod pošlje čip z najmanjšo številko, na drugi izhod čip z naslednjo številko in tako naprej. Na koncu pobriše svoj seznam čipov in vrne True.

>>> b.receive(3)
>>> b.receive(1)
>>> b.process()
False
>>> b.chips
[3, 1]
>>> b.receive(2)
>>> b.process()
Output 42: 2
Output 13: 3
True
>>> b.chips
[]

Rešitev

class Bot(Unit):
    def __init__(self):
        super().__init__()
        self.outputs = []

    def attach(self, unit):
        self.outputs.append(unit)

    def process(self):
        if len(self.chips) != len(self.outputs):
            return False
        for output, chip in zip(self.outputs, sorted(self.chips)):
            output.receive(chip)
        self.chips.clear()
        return True

Konstruktor doda atribut self.outputs in metoda attach shranjuje bote vanj.

Metoda process preveri ali je prejeti čipov (self.chips) toliko kot izhodov (self.outputs). Če je tako, gre čez seznam parov izhodov in čipov, ki jih mimogrede še uredimo po velikosti.

In potem pride najtežja vrstica naloga: output.receive(chip). To je bistvo vsega. Ni kaj programirati, le razumeti. output je tule tisto, čemur hoče bot poslati številko chip. Ker na bote priključujemo izhode in druge bote, bo output bodisi Output bodisi Bot. Oba razreda sta izpeljana iz Unit in imata metodo receive. process torej pokliče output-ovo metodo receive in jih kot argument da chip.

Autobot

Razred AutoBot je izpeljan iz razreda Bot. Razlikuje se po tem, da ima pametnejši receive: po tem, ko prejme čip, preveri, ali je število čipov, ki jih ima, enako številu izhodov. Če je, kar sam pokliče process.

>>> b = AutoBot()
>>> a = Bot()
>>> o = Output(42)
>>> p = Output(13)
>>> b.attach(a)
>>> b.attach(o)
>>> b.attach(p)
>>> b.receive(3)
>>> b.receive(1)
>>> b.process()
False
>>> b.chips
[3, 1]
>>> b.receive(2)
Output 42: 2
Output 13: 3
>>> b.chips
[]

Rešitev

class AutoBot(Bot):
    def receive(self, chip):
        super().receive(chip)
        if len(self.chips) == len(self.outputs):
            self.process()

Kot pove naloga: po tem, ko sprejme čip (super().receive(chip)), preveri, ali je smiselno poklicati process in ga pokliče.

Dodatna naloga

Napiši funkcijo read_file(filename), ki prebere datoteko (testi berejo datoteko input.txt iz predprejšnje naloge; skopirati jo je potrebon v direktorij s testi). Funkcija zgradi "mrežo" AutoBot-ov; vsak je ustrezno priključen na druge AutoBot-e in na izhode. Funkcija ne vrne kakega slovarja ali česa podobnega, tako kot v predprejšnji nalogi. Vrne seznam parov (bot, vrednost), ki pove, kateri boti v začetku dobijo katero vrednost. Prvi element para je objekt tipa AutoBot, drugi pa število. Se pravi, funkcija vrne toliko parov (bot, vrednost), kolikor je v datoteki vrstic v slogu value 17 goes to bot 32.

>>> read_file("input.txt")
[(<AutoBot object at 0x102485a20>, 67), (<AutoBot object at 0x10246fc18>, 31), (<AutoBot object at 0x1024876d8>, 29), (<AutoBot object at 0x10248b940>, 73), (<AutoBot object at 0x102487588>, 47), (<AutoBot object at 0x1024931d0>, 3), (<AutoBot object at 0x102485c50>, 43), (<AutoBot object at 0x102487ef0>, 71), (<AutoBot object at 0x10246f908>, 59), (<AutoBot object at 0x10246f940>, 41), (<AutoBot object at 0x102493d68>, 37), (<AutoBot object at 0x102497128>, 13), (<AutoBot object at 0x10246fa20>, 2), (<AutoBot object at 0x102493208>, 19), (<AutoBot object at 0x102493a90>, 61), (<AutoBot object at 0x102485c18>, 5), (<AutoBot object at 0x102497c18>, 11), (<AutoBot object at 0x102493240>, 53), (<AutoBot object at 0x10246fc18>, 23), (<AutoBot object at 0x102487a58>, 7), (<AutoBot object at 0x10248bba8>, 17)]

Test za to funkcijo naredi tole:

for bot, value in read_file("input.txt"):
    bot.receive(value)

Dobi seznam botov in pripadajočih začetnih vrednosti, ter tem botom dejansko pošlje te vrednosti. Ker so vsi boti v mreži pravzaprav autoboti, se bo mreža kar sama od sebe izvedla in testi preverijo, ali ustrezni outputi izpišejo, kar so prejeli.

Nasvet: funkcija naj interno uporablja začasni slovar, katerega ključi so številke botov, pripadajoče vrednosti pa AutoBot-i. Še preprosteje bo, če uporabiš defaultdict. Če se boš pametno organiziral(a), funkcija ne bo nič daljša od funkcije za 6 pri predprejšnji domači nalogi.

Rešitev

To je edini del naloge, kjer je bilo res potrebno kaj programirati.

from collections import defaultdict

def read_file(name):
    bots = defaultdict(AutoBot)
    initial = []
    for line in open(name):
        line = line.split()
        if line[0] == "bot":
            bot = bots[line[1]]
            if line[5] == "output":
                bot.attach(Output(int(line[6])))
            else:
                bot.attach(bots[line[6]])
            if line[10] == "output":
                bot.attach(Output(int(line[11])))
            else:
                bot.attach(bots[line[11]])
        else:
            initial.append((bots[line[5]], int(line[1])))
    return initial

Ker vhodna datoteka omenja bote s številkami, bomo morali ob branju datoteke včasih ustvariti novega bota (in si zapomniti, da sodi k tej in tej številki) bodisi uporabiti že ustvarjenega bota, ki sodi k določeni številki. Za to povezavo botov in številk bo skrbel slovar, ki bo, kot priporočajo navodila, kot ključe vseboval številke, kot pripadajoče vrednosti pa bote.

Še več. Bote shranjujemo v bots, ki je defaultdict(AutoBot). To je imenitno zato, ker se lahko delamo, da boti, ki jih potrebujemo, že obstajajo. Preprosto jemljemo jih iz slovarja in se ne oziramo na to, ali so nastali v kateri od prejšnjih vrstic, ali pa so nastali pravkar. Pomembno je le, da takrat ko rečemo bots[42] vedno dobimo istega bota.

Sestavimo torej slovar bots s privzetimi vrednostmi tipa AutoBot in seznam initial, ki bo vseboval začetne vrednosti botov. Gremo čez datoteko in tako kot v predprejšnji nalogi razbijemo vrstico na besede. Če vrstica opisuje akcijo bota, vzamemo iz slovarja bots bota s podano številko (ta bot bo sicer morda ravnokar nastal, avtomatsko). Nanj priključimo bodisi izhod bodisi bota, ki ga spet vzamemo iz slovarja (in bo morda pravkar nastal). Če pa vrstica opisuje začetno vrednost, dodamo v seznam začetnih vrednosti bota in vrednost, ki jo mora dobiti.

Funkcija vrne seznam začetnih vrednosti. Ta vsebuje le slabih 20 botov. A nanje so priključeni drugi boti, in na te drugi in tako naprej do izhodov.

Zadnja sprememba: četrtek, 25. marec 2021, 21.28