Objektni boti
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.