Znaki (Python)

Znaki (Python)

Avtor: Saša Udir

Navodilo naloge

Dana je tabela znakov. V tabeli so znaki bodisi presledki, bodisi znaki '*'. Tabela je napisana na datoteki in sicer tako, da je v prvi vrstici datoteke podana dimenziji tabele (najprej celo število, ki predstavlja število vrstic in nato presledek in nato celo število, ki predstavlja število stolpcev), nato pa vrstica tabele ustreza vrstici na datoteki.

Sestavi rekurzivno metodo Zapolni(string ImeVhodDat, string imeIzhodDat, int vr, int st), ki prebere podatke z vhodne datoteke in ustvari datoteko, ki predstavlja tabelo, kjer z znakom '+' "zapolni" zaprt del lika, katerega koordinati (indeksa) sta tretji in četrti parametra metode. Indekse štejemo od 0 dalje.


Tako klic Zapolni("Vh.txt", "Izh.txt", 2, 8) ustvari naslednjo datoteko:

(NavodiloZnaki.gif)
opis slike


Dva elementa tabele sta med seboj povezana po štirih smereh (in ne morda osmih). Predpostavi, da imamo na robovih tabele vedno znake '*'. Če je na ustreznem mestu znak #, se seveda tabela ne spremeni (torej je izhod nova datoteka z identično vsebino kot jo ima vhodna datoteka)! Prav tako dobimo izhodno datoteko z identično vsebino kot je vhodna datoteka, če so koordinate izven tabele.

Opis problema in ideja rešitve

PREMISLIMO:

Potrebovali bomo štiri funkcije:

  • funkcijo, ki bo prebrala datoteko in vrnila seznam oz. tabelo,
  • funkcijo, ki bo rekurzivno zapolnila zaprti del lika,
  • funkcijo, ki bo spremenjen seznam oz. spremenjeno tabelo zapisala v novo datoteko,
  • skupno funkcijo, ki bo povezovala zgornje tri funkcije.

Kako zapolniti lik v seznamu?

Postavimo se na element v seznamu. Če je element presledek, zapolnimo element s + in rekurzivno pogledamo lokacije okoli nas.

ALGORITEM

  • def preberiIzDatoteke(imeDatoteke)
  • zapolniSez(sez,vr,st)
  • ZapisiVDatoteko(sez, imeDatoteke)
  • Zapolni(ImeVhodDat, ImeIzhodDat, vr, st)

Branje vhodne datoteke

Preden lahko začnemo razmišljati kako bomo zapolnili zaprti del lika (če bo to potrebno), moramo seveda najprej prebrati podano datoteko v seznam.
Za to bo poskrbela funkcija preberiIzDatoteke(imeDatoteke).


Najprej preverimo, če datoteka sploh obstaja. V nasprotnem primeru sprožimo napako.

  • Uvozimo knjižnico os.
  • os.path.isfile(imeDatoteke) vrne True, če datoteka obstaja oz. False, če ne.

Sedaj lahko odpremo datoteko za branje.

  • dat=open(imeDatoteke, 'r')
  • Posamezne vrstice bomo brali z metodo readline().

Ko začnemo brati, pazimo, da prva vrstica vsebuje podatke o dimenzijah. Zato prva vrstica ne pride v poštev za v seznam. Shranimo jo v nov seznam, ki ga poimenujemo dim. Seveda moramo prej razbiti niz, ki smo ga prebrali (vrstica=dat.readline()).


Pomagamo si s funkcijami oz. metodami:

  • vrstica.strip () --> vrne kopijo niza vrstica in na koncu odreže bele znake
  • vrstica.split()--> vrne seznam besed v nizu vrstica (besede so ločene z enim ali več presledki)
  • dim=vrstica.strip().split()
  • dim[0]=int(dim[0]) --> ničti element seznama dim pretvori v celo število.
  • dim=[int(dim[0]), int(dim[1])]

Branje tabele iz datoteke v seznam

Sedaj lahko začnemo polniti seznam sez. Seveda ga je potrebno prej definirati (sez=[]).
Seznam sez nam bo predstavljal tabelo iz datoteke.


Beremo vsako vrstico posebej.

  • Ko preberemo vrstico, dobimo niz, torej moramo niz spremeniti v seznam znakov. A prej odstranimo bele znake (niz.strip()).
  • Nato pa z metodo list naredimo seznam znakov prebrane vrstice. Ta seznam dodamo seznamu sez, ki smo ga prej definirali.

Ko imamo seznam, najprej preverimo, če je datoteka smiselna (da bo program čim bolj stabilen.)

  • Torej, če se ujemajo podane dimenzije z dimenzijo seznama in če so v seznamu res samo zvezdice ('*') in presledki (' ').
  • Če seznam ne ustreza pravilom, sprožimo napako.


Če ne pride do napake in se celotna funkcija izvrši, vrnemo seznam sez.

Koda funkcije preberiIzDatoteke

(PreberiIzDatoteke.png)

Rekurzivna funkcija za zapolnitev lika

Sedaj ko imamo seznam, se lotimo pisanja rekurzivne funkcije.
Funkcija bo sprejela tri argumente.

  • Prvi argument bo seznam, ki smo ga definirali s pomočjo datoteke.
  • Drugi in tretji argument predstavljata pozicijo polja (vrstica, stolpec), kjer začnemo polniti lik z znakom +.

def zapolniSez (sez, vr, st)

Ustavitveni pogoji:

Vprašamo se, kaj vse mora nujno »ustaviti funkcijo«.
Funkcija se ustavi če:

  • je podana lokacija izven tabele
  • je lokacija že zapolnjena z znakom + ali *.

Če kateri od ustavitvenih pogojev velja, kličemo samo return (končamo funkcijo, seznam se ne spremeni).

Rekurzivno klicanje – funkcija kliče samo sebe

Če se funkcija ne ustavi pri nobenem od ustavitvenih pogojev, štirikrat rekurzivno pokličemo polja okoli nas:

  • Za pozicijo nad nami povečamo vrstico (parameter vr) za ena, stolpec (parameter st) ostane enak.
  • Za pozicijo pod nami zmanjšamo vrstico za ena, stolpec je enak.
  • Za pozicijo levo od trenutne zmanjšamo stolpec za ena, vrstica ostane enaka.
  • Za pozicijo desno od trenutne povečamo stolpec za 1, vrstica ostane enaka.

Koda funkcije zapolniSez.png

(zapolniSez.png)

Zapisovanje v datoteko

Sedaj lahko začnemo razmišljati o funkciji, ki bo zapisovala v izhodno datoteko. Tej bomo podali zgolj seznam in ime izhodne datoteke.

def ZapisiVDatoteko (sez, imeDatoteke)

Najprej bomo preverili, če datoteka, ki jo želimo ustvariti, že obstaja. Če obstaja, uporabnika toliko časa sprašujemo za novo ime, dokler to ime za datoteko še ne obstaja v trenutnem direktoriju.

  • Če tega ne bi preverjali, bi se prejšnja istoimenska datoteka prepisala.
  • Pomagamo si z os.path.isfile(imeDatoteke)
  • Če vrne True, ponovno vprašamo za ime

    • imeDatoteke = input (»Datoteka s tem imenom že obstaja. Vpišite novo ime: «)

Ko vemo, da bomo varno ustvarili datoteko, le-to ustvarimo.

  • dat=open(imeDatoteke, »w«)

    • S parametrom »w« povemo, da bomo datoteko odprli za pisanje.

V prvo vrstico datoteke zapišemo dimenzijo seznama/tabele.

  • Vrstico dobimo z ukazom len(sez).
  • Stolpec dobimo z ukazom len(sez[0]).

Dobljeno dimenzijo spremenimo v niz, nato jo zapišemo. Na koncu niza dodamo '\n' za novo vrsto.


Sedaj se samo še sprehodimo čez seznam, zapišemo vsako vrstico posebej in je naša naloga končana.

  • Elementi seznama sez so seznami.
  • Seznam najlažje »zlepimo v niz« z metodo join (" ". join (sez)).

    • Na ta način zlepimo s praznim nizom vse elemente tabele.
  • Zlepljen niz + '\n' je ravno naša nova vrstica.

    • Pazimo, da na koncu vsake vrstice dodamo '\n'. S tem določimo da program piše v novo vrstico.

Na koncu samo še zapremo datoteko.

Koda funkcije ZapisiVDatoteko

(ZapisiVDatoteko.png)

Skupna funkcija

Da ne bo potrebno vsakič klicati vseh treh funkcij, vse skupaj zložimo v eno funkcijo.

Zapolni(ImeVhodDat, ImeIzhodDat, vr, st)

Skupna funkcija se bo imenovala Zapolni in bo sprejela štiri parametre:

  • ImeVhodDat – ime vhodne datoteke (niz)
  • ImeIzhodDat – ime izhodne datoteke (niz)
  • vr – koordinata za vrstico
  • st – koordinata za stolpec


V tej funkciji pokličemo vse tri funkcije:

  1. Najprej dobimo seznam s klicom funkcije preberiIzDatoteke(ImeVhodDat).
  2. Zapolnimo zaprt del lika v seznamu s klicem funkcije ZapolniSez(sez, vr,st).
  3. Zapišemo spremenjen seznam v novo datoteko s klicem funkcije ZapisiVDatoteko(sez, imeDatoteke). Pred klicem funkcij preverimo, če so vsi podani parametri funkcije pravilnega tipa. Pomagamo si s stavkom assert.

Koda skupne funkcije Zapolni

(Zapolni.png)

Testiranje

S testiranjem bomo preverili, če se funkcija »obnaša« pravilno oz. tako kot bi pričakovali.
Kot bomo videli kasneje si bomo pomagali z varovalnimi mrežami try, except (stavek except: polovi vse napake znotraj try-a.)


V primeru, da funkcijo kličemo pravilno in ne pričakujemo napak, uporabimo naslednjo kodo:

try:
    klic funkcije
except Exception as e:
    print (‘Prišlo je do napake!’)
    print(e)


Če se bo funkcija pravilno izvršila, se nam na ekran ne bo izpisalo nič. Če pa bo ob klicu prišlo do napake, bo napako ulovil stavek Except. Izpisalo se bo sporočilo »Prišlo je do napake« in izpisal se bo tudi opis napake(e), ki ga vrne Python.


V primeru, da pričakujemo oz. vemo, da mora funkcija sprožiti napako uporabimo kodo:
try:
    klic funkcije
    print(‘V ta print ne bi smelo priti!’)
except:
    pass


Ker pričakujemo, da bo ob neustreznih parametrih prišlo do napake ob klicu funkcije, za klicem spišemo nek stavek print. Če bo vse prav in bo do napake res prišlo, se ta print ne bo izvedel. Če se bo print izvedel, to pomeni, da funkcija ni sprožila napake in da je posledično spisana narobe.
Če se bo zgodilo pričakovano, torej če se bo sprožila napaka, vemo, da bo stavek Except »ulovil« napako. Zato stavek pass,ki ne naredi ničesar. To je prav, saj se je sprožila napaka.


Glede na to kakšen primer testiramo, v testno datoteko spišemo kodo kot v zgornjih dveh primerih.
Ko imamo spisane vse try, except stavke in smo z njimi res preverili vse možne primere, smo končali s pisanjem testne datoteke.
Izvršimo testno datoteko (pritisnemo F5 v testni datoteki). Če se v Python Shell-u ne izpiše nič, to pomeni, da se funkcija obnaša pravilno.

Pripravimo si testno datoteko

  • Odpremo novo Python datoteko.
  • Uvozimo metode/funkcije iz Python datoteke, kjer se nahaja naša testirana funkcija:
  • import Znaki
  • Do funkcij bomo dostopali tako: Znaki.ImeFunkcije()

Izbor testnih primerov

  • Stestiramo osnovne primere

    • pravilen klic, vhodna datoteka ustreza pravilom, podana lokacija znotraj tabele.
    • Pričakujemo, da se bo funkcija izvedla pravilno. Zapisala naj bi se pravilno tudi izhodna datoteka.
  • Stestiramo «robne primere«

    • Kaj se naredi, če podamo lokacijo izven tabele?

      • Se res ustvari izhodna datoteka in ima identično vsebino kot vhodna?
  • Stestiramo primere, kjer pričakujemo, da funkcija sproži napako.

    • Napačen klic parametrov (napačni tipi)
    • Vsebina datoteke je nepravilna:

      • primer z neujemanjem dimenzij
      • primer, kjer rob ne vsebuje samih zvezdic.
      • primer, ker se pojavljajo znotraj tabele nedovoljeni znaki (ne le * in ' ').

Osnovni primeri:

  • Za vhodno datoteko podamo datoteko, ki ustreza pravilom (prva vrstica predstavlja dimenzije, naslednje vrstice pa tabelo)
  • Za izhodno datoteko podamo ime datoteke, ki še ne obstaja v našem trenutni mapi.
  • Lokacijo podamo takšno, da se bo lik res zapolnil (torej ne lokacije, kjer je »zid« ali pa lokacije izven tabele).


Naredimo več takih primerov in pregledamo, če končen rezultat izhodne datoteke takšen, kot ga pričakujemo (če je zapolnjen cel lik, če je datoteka pravilne oblike…).

Primer treh pravilnih datotek in klicev funkcij:

Vh.txt:

(Vh.png)
Datoteka Vh.txt

Klic funkcije: Znaki.Zapolni('Vh.txt', 'Izh.txt', 11,2)
Pričakujemo, da bo predzadnja vrstica tabele v datoteki Izh.txt s samimi plusi (razen na robovih, kjer ostanejo *).

Vh1.txt:

(Vh1.png)
Datoteka Vh1.txt

Klic funkcije: Znaki.Zapolni('Vh1.txt', 'Izh1.txt', 6, 5)
Pričakujemo, da se bo v datoteki Izh1.txt zapolnil pravokotnik na levi s samimi plusi.

Vh2.txt:

(Vh2.png)
Datoteka Vh2.txt

Klic funkcije: Znaki.Zapolni('Vh2.txt', 'Izh2.txt', 1,5)
Pričakujemo, da bo lik v zgornjem levem kotu v datoteki Izh2.txt zapolnjen s samimi plusi.
Ker v vseh treh primerih pričakujemo, da ne bo prišlo do napake, bomo uporabili prvi način uporabe varovalne mreže try, except.
try:
    Zapolni('Vh.txt', 'Izh.txt', 11,2)
except Exception as e:
    print ('Prišlo je do napake!')
    print(e)

try:
    Zapolni('Vh2.txt', 'Izh2.txt', 6,5)
except Exception as e:
    print ('Prišlo je do napake!')
    print(e)

try:
    Zapolni('Vh2.txt', 'Izh2.txt', 1,5)
except Exception as e:
    print ('Prišlo je do napake!')
    print(e)

Robni primer

V primeru, da podamo lokacijo(vr,st) izven tabele, se mora prav tako ustvariti nova datoteka ImeIzhodDat.txt. Vsebina te datoteke je enaka vsebini izhodne datoteke.
Preverimo, da funkcija slučajno ne vrne napake.
Primer takega klica je Znaki.Zapolni('Vh2.txt', 'IzhL.txt', 7,1).


Za vhodno datoteko uporabimo datoteko Vh2.txt, ki ima 7 vrstic in 9 stolpcev. Hkrati preverimo, če se funkcija res »obnaša«, kot da sprejme indeks vrstice in ne dejansko vrstico. Torej je vrstica z indeksom 7 že izven tabele. Zadnja sedma vrstica ima indeks 6.


Ker je podana lokacija izven tabele, se, kot smo že prej omenili, naredi datoteka IzhL.txt z identično vsebino kot Vh2.txt.


Ne pričakujemo napake, zato uporabimo prvi način uporabe varovalne mreže.
try:
    Zapolni(‘Vh2.txt’, ‘IzhL.txt’, 7,1)
except Exception as e:
    print (‘Prišlo je do napake!’)
    print(e)

Primeri, kjer funkcija vrne napako

Stestiramo primere, kjer pričakujemo, da funkcija vrne napako. Vprašamo se, kdaj mora funkcija sprožiti napako.
Ločimo dva primera:

  • napačni prametri (npr. dimenzija vr ali st je podana kot niz)
  • vhodna datoteka ne ustreza pravilom

Uporabljali bomo drugi način uporabe varovalne mreže.

Napačni parametri

Funkcijo na primer pokličemo Znaki.Zapolni('Vh2.txt', 'IzhL.txt', '7','1').

  • Funkcija mora sprožiti napako – 3. in 4. element morata biti celi števili, ne niza.
    try:
        Zapolni('Vh2.txt', 'IzhL.txt', '7','1')
        print(‘V ta print ne bi smelo priti!’)
    except:
        pass

Vhodna datoteka ne ustreza pravilom

Neujemanje dimenzij

Preverimo, če funkcija sproži napako, kadar se podana dimenzija v prvi vrstici datoteke razlikuje od dejanske dimenzije tabele. Primer take datoteke je Vh4.txt, kjer je dejanska dimenzija tabele 7x15.

(Vh4.png)
datoteka Vh4.txt

Varovalna mreža in klic funkcije:
try:
    Zapolni('Vh4.txt', 'Izh4.txt', 5, 5).
    print(‘V ta print ne bi smelo priti!’)
except:
    pass

Rob seznama ne vsebuje zvezdice

Funkcija zahteva, da ima po robovih za elemente '*'. Preverimo, če v nasprotnem primeru funkcija res vrne napako.
Primer datoteke, ki ne vsebuje po robovih samih zvezdic, je Vh5.txt:

(Vh5.png)
datoteka Vh5.txt

Varovalna mreža in klic funkcije:
try:
    Zapolni('Vh5.txt', 'Izh5.txt', 5, 5).
    print(‘V ta print ne bi smelo priti!’)
except:
    pass

Nedovoljeni znaki znotraj tabele

Funkcija dovoljuje znotraj tabele samo dva znaka, zvezdico in presledek.
Preverimo, če vrne napako, kadar zgornja trditev ne drži.
Taka datoteka je vh6.txt:

(Vh6.png)
datoteka Vh6.png

Varovalna mreža in klic funkcije:
try:
    Zapolni('Vh6.txt', 'Izh6.txt', 1, 4).
    print(‘V ta print ne bi smelo priti!’)
except:
    pass

Test


S testnimi primeri bomo preverili, če se funkcija obnaša pravilno v različnih primerih.

  • Preverimo, če se funkcija res v vseh primerih obnaša pravilno.


Zaženemo testno datoteko. Opazimo, da se v Python Shell-u ne izpiše nič. To pomeni, da funkcija gotovo v vseh primeri deluje pravilno. Preveriti moramo samo še, če so vse datoteke, ki pa so vseeno nastale, pravilne oz. se je zapolnil pravilni lik. Opazimo, da funkcija deluje pravilno.

Testiranje - film

0%
0%