Rješenja nekih zadataka, koje ste rješavali u C-u, pomoću pythona

Ideja je u pythonu riješiti zadatke koje ste već rješavali u C-u i time produbiti znanje pythona.

Zadaća 1 (map(), lambda, math.sin(), sum, [ ... for ... in ...], ( ... for ... in ...) )

Zadatak 3b

import math
N = int(input())
L = range(0,N+1)
L = (x*math.pi/float(N) for x in L)
L = map( math.sin, L )
print(sum(L))

Pokrenemo program i unesemo npr. 10, rezultat je:

6.313751514675044

U interaktivnom modu možemo pratiti izvršavanje:

>>> import math
>>> N = 10
>>> L = list(range(0,N+1))
>>> L = [x*math.pi/10. for x in L]
>>> list(map( lambda x: "%.2f" % x, L ))  # ispis samo 2 decimale
['0.00', '0.31', '0.63', '0.94', '1.26', '1.57', '1.88', '2.20', '2.51', '2.83', '3.14']
>>> L = list(map( math.sin, L ))
>>> list(map( lambda x: "%.2f" % x, L ))  # ispis samo 2 decimale
['0.00', '0.31', '0.59', '0.81', '0.95', '1.00', '0.95', '0.81', '0.59', '0.31', '0.00']
>>> sum(L)
6.313751514675044

Pozor

Za razliku od samostalnog programa gdje smo svaki međurezultat koristili samo jednom u interaktivnom primjeru smo npr. L iz 4. retka koristili jednom u 5. a drugi put u 6. retku. To je važno uočiti jer u slučaju da nam neka lista treba samo jednom možemo koristiti tzv. iteratore koji ne zauzimaju toliko memorije koliko liste i računaju se tek kad zatreba. Mana iteratora je da ih se može koristiti samo jednom. Također treba uočiti da je rezultat od map() iterator, kao i rezultat od ( ... for ... in ...). Naprotiv, rezultat od [ ... for ... in ...] je lista. Više o generatorima u odjeljku Generatori/iteratori.

Kako iteratore možemo upotrijebiti samo jednom, u drugom primjeru smo morali koristiti list() i pretvoriti iteratore u liste da bismo ih mogli koristiti više puta.

Zadaća 2 (str.join(), *args)

Zadatak 1b

Rješenje 1

L = [x * 0.1 for x in range(31,41)]

print("Tablica mnozenja 1b:")
for i in L:
  print("%5.2f" % (i*L[0]), end='')
  for j in L[1:]:
    print("|%5.2f" % (i*j), end='')
  print()

Rješenje 2

Moguće je koristiti direktan upis stringa u stdout pomoću sys.stdout.write.

import sys
L = [x * 0.1 for x in range(31,41)]

print("Tablica mnozenja 1b:")
for i in L:
  sys.stdout.write( "%5.2f" % (i*L[0]) )
  for j in L[1:]:
    sys.stdout.write( "|%5.2f" % (i*j) )
  print()

Rješenje 3

Za rješavanje ovog zadatka može poslužiti funkcija str.join().

print("-između-".join(["100","200"," ","abc"]))
100-između-200-između- -između-abc

Počnemo tako da generiramo tablicu množenja kao dvodimenzionalnu listu L2.

L = [x * 0.1 for x in range(31,41)]
L2 = [["%5.2f"%(i*j) for j in L] for i in L]
print(L2[0])
print("...")
print(L2[-1])
[' 9.61', ' 9.92', '10.23', '10.54', '10.85', '11.16', '11.47', '11.78', '12.09', '12.40']
...
['12.40', '12.80', '13.20', '13.60', '14.00', '14.40', '14.80', '15.20', '15.60', '16.00']

Zatim povežemo unutarnju listu znakom "|", a vanjsku listu znakom za novi red "\n".

L = [x * 0.1 for x in range(31,41)]
L2 = ["|".join(["%5.2f"%(i*j) for j in L]) for i in L]

print("Tablica mnozenja 1b:")
print("\n".join(L2))

Rješenje 4

L = [1,2,3]
print(L)
print(*L)
print(*L,sep='//')
[1, 2, 3]
1 2 3
1//2//3

Vidimo da ako je L=[1,2,3] tada je print(L) isto što i print([1,2,3]) (ispis liste), a print(*L) isto što i print(1,2,3) (ispis 3 broja).

L = [x * 0.1 for x in range(31,41)]

print("Tablica mnozenja 1b:")
for x in L:
  print( *["%5.2f" % (x*y) for y in L], sep='|' )
Tablica mnozenja 1b:
 9.61| 9.92|10.23|10.54|10.85|11.16|11.47|11.78|12.09|12.40
 9.92|10.24|10.56|10.88|11.20|11.52|11.84|12.16|12.48|12.80
10.23|10.56|10.89|11.22|11.55|11.88|12.21|12.54|12.87|13.20
10.54|10.88|11.22|11.56|11.90|12.24|12.58|12.92|13.26|13.60
10.85|11.20|11.55|11.90|12.25|12.60|12.95|13.30|13.65|14.00
11.16|11.52|11.88|12.24|12.60|12.96|13.32|13.68|14.04|14.40
11.47|11.84|12.21|12.58|12.95|13.32|13.69|14.06|14.43|14.80
11.78|12.16|12.54|12.92|13.30|13.68|14.06|14.44|14.82|15.20
12.09|12.48|12.87|13.26|13.65|14.04|14.43|14.82|15.21|15.60
12.40|12.80|13.20|13.60|14.00|14.40|14.80|15.20|15.60|16.00

Više o pozivima funkcija, *args i **kwargs u odjeljku Pozivanje funkcija.

Zadaća 3 (zip, input, math.sqrt())

Zadatak 4

Promotrimo ovaj primjer:

>>> list(zip( [100,200], [3,4] ))
[(100, 3), (200, 4)]
>>> [i+j for i,j in zip([100,200],[3,4])]
[103, 204]

Vidimo kako je moguće jednostavno raditi operacije nad dvjema listama, tako da se uzima prvi (100) s prvim (3), drugi (200) s drugim (4), itd. To iskoristimo za rješavanje zadatka.

import math
N = int(input())
a = []
b = []
for i in range(0,N):
  a.append( float(input()) )

for i in range(0,N):
  b.append( float(input()) )

ab = (i*j for i,j in zip(a,b))
a2 = (i*i for i in a)
b2 = (i*i for i in b)

ab = sum(ab)
a2 = math.sqrt(sum(a2))
b2 = math.sqrt(sum(b2))

print("cos(theta) je", ab/a2/b2)

Unesemo 2 1 0 1 1 kao input. Rezultat je:

cos(theta) je 0.7071067811865475

Zadaća 4

Problemi opisani u zadaći 4 ne javljaju se u pythonu jer u pythonu “nema” pointera, a i provjerava se da indeks polja bude unutar trenutno dopuštenog opsega. [U CPythonu možemo doznati adresu pomoću funkcije id(), ali na adrese ne možemo upisivati direktno.]

Zadaća 5 (str.replace())

Zadatak 3

Trivijalno jer python podržava rad s proizvoljno dugim cijelim brojevima.

Zadatak 4

Trivijalno jer već postoji takva funkcija u pythonu.

print("1456".replace("1","123"))
123456

Zadaća 6 (itertools.groupby())

Zadaci 2a i 2b

Za rješavanje ovog zadatka dobro može poslužiti funkcija groupby iz modula itertools. Ta funkcija razdvaja listu ili string na dijelove:

>>> import itertools
>>> [k for k,v in itertools.groupby("aaabbcd")] 
['a', 'b', 'c', 'd']
>>> [list(v) for k,v in itertools.groupby("aaabbcd")] 
[['a', 'a', 'a'], ['b', 'b'], ['c'], ['d']]
>>> [len(list(v)) for k,v in itertools.groupby("aaabbcd")] 
[3, 2, 1, 1]
>>> [k*len(list(v)) for k,v in itertools.groupby("aaabbcd")] 
['aaa', 'bb', 'c', 'd']

Koristeći to, lako je napraviti rle.py i unrle.py.

#!/usr/bin/env python3

import sys
import itertools

def rle(src,dest):
  str = src.read()
  str = "".join( ["%d%s" % (len(list(v)),k) for k,v in itertools.groupby(str)] )
  dest.write(str)

with open(sys.argv[1],"r") as src:
  with open(sys.argv[2],"w") as dest:
    rle(src,dest)
#!/usr/bin/env python3

import sys

def unrle(src,dest):
  str = src.read()
  assert len(str) % 2 == 0
  ponavljanja = str[0::2] #svaki drugi parni
  ponavljanja = map(int,ponavljanja)
  znakovi = str[1::2] #svaki drugi neparni
  str = [z*p for z,p in zip(znakovi,ponavljanja)]
  str = "".join(str)
  dest.write(str)

with open(sys.argv[1],"r") as src:
  with open(sys.argv[2],"w") as dest:
    unrle(src,dest)

Da bi se ti programi mogli pokretati kao samostalni, potrebno je dodati pravo izvršavanja.

$ chmod u+x rle.py
$ chmod u+x unrle.py

Kad datoteku z6src.py

aaaabbbbaaaaccccdef

komprimiramo

$ ./rle.py z6src.txt z6out.txt

dobijemo

4a4b4a4c1d1e1f

Kad taj rezultat dekomprimiramo

$ ./unrle.py z6out.txt z6out2.txt

dobijemo nazad početni sadržaj

aaaabbbbaaaaccccdef

Naravno, ovdje se pretpostavljalo, kako je rečeno u zadatku, da se znakovi neće ponavljati više od 9 puta.

Zadaća 7

Zadatak 2

Rješenje je:

#!/usr/bin/env python3

import sys
import struct
import os

def isprint(c):
  return 0x20<=ord(c)<=0x7e

def konverzija(c):
  if isprint(c):
    return c.decode('ascii')
  else:
    return "[%02x]" % ord(c)

D, P, N, T = sys.argv[1:5]
P = int(P)
N = int(N)
rjecnik = {}
rjecnik["c"] = "c"
rjecnik["d"] = "i"
rjecnik["ld"] = "l"
rjecnik["f"] = "f"
rjecnik["lf"] = "d"

with open(D,"rb") as f:
  fmt = '%d%s' % (N,rjecnik[T])
  duljina = struct.calcsize(fmt)
  f.seek( P, os.SEEK_SET )
  byteovi = f.read( duljina )
  #print(f"s pozicije {P} procitati {duljina} procitano {len(byteovi)}:", repr(byteovi), file=sys.stderr)
  assert len(byteovi) == duljina

vrijednosti = struct.unpack( fmt, byteovi )
if T == "c":
  print( *map( konverzija, vrijednosti ), sep='' )
else:
  print( *vrijednosti )

Tu smo koristili pythonov dictionary i time smo izbjegli potrebu za velikim brojem if-ova. Funkcija isprint ne postoji u pythonu, ali možemo pogledati kako je napravljena u C-u pa taj kôd prevesti u python. Gornju datoteku nazovemo rr.py.

Krećemo od datoteke iz jednog od prethodnih primjera:

$ hexdump -C bin1.bin
00000000  74 65 6b 73 74 00 00 00  00 00 00 00 0a 00 00 00  |tekst...........|
00000010  9a 99 99 99 99 99 b9 3f                           |.......?|
00000018

Kad postavimo flag izvršavanja datoteci rr.py možemo radi testiranja pokrenuti program nad podacima u bin1.bin.

$ chmod u+x rr.py
$ ./rr.py bin1.bin 0 10 c
tekst[00][00][00][00][00]
$ ./rr.py bin1.bin 12 1 d
10
$ ./rr.py bin1.bin 16 1 lf
0.1

Zaključujemo da postoji prazan prostor (zbog alignmenta) u byteovima redni broj 10 i 11.

Zadaća 8 (try ... except ... else, raise)

Zadatak 2

U pythonu postoji funkcija read slična traženoj. Python se brine oko dealokacije. Duljina N nam nije potrebna jer uvijek možemo koristiti len. Prikazat ćemo dva rješenja: prvo nepotpuno ali iznimno jednostavno, a zatim, drugo, potpuno rješenje.

Za obradu grešaka u pythonu je prirodno koristiti tzv. try..except konstrukciju. Izvršavanje krene u try blok i izvršava se red po red ali ako dođe do greške odmah se nepovratno napušta try blok i prelazi u prvi kompatibilan except blok. (U except blok se ulazi samo ako se dogodi greška u try bloku koji mu prethodi. U else blok se ulazi samo ako se čitav prethodni try blok izvršio bez greške.)

#!/usr/bin/env python3

import sys

def readfile( fn ):
  with open( fn, "rb" ) as f:
    s = f.read()
  return s

try:
  p = readfile( sys.argv[1] )
except Exception as e:
  print("greška ", e)
  errcode = 1
else:
  print(f"datoteka je uspješno pročitana, i sadrži {len(p)} byteova")
  errcode = 0

sys.exit(errcode)

Ako bismo baš htjeli da povratne vrijednosti budu diferencirane kao što se traži u zadatku potrebno je dodati još provjera.

#!/usr/bin/env python3

import sys

class Gr(Exception): pass
class GrOtvaranje(Gr): pass
class GrRezMem(Gr): pass
class GrCitanje(Gr): pass
class GrNepoznata(Gr): pass

def readfile( fn ):
  try:
    try:
      f = open( fn, "rb" )
    except IOError as e:
      raise GrOtvaranje(e)
    else:
      try:
        with f:
          s = f.read()
      except IOError as e:
        raise GrCitanje(e)
      except MemoryError as e:
        raise GrRezMem(e)
  except Gr as e:
    raise
  except Exception as e:
    raise GrNepoznata(e)
    
  return s

try:
  p = readfile( sys.argv[1] )
  print(f"datoteka je uspješno pročitana, i sadrži {len(p)} byteova")
  errcode = 0
except GrOtvaranje as e:
  print("greška prilikom otvaranja datoteke", e)
  errcode = 1
except GrRezMem as e:
  print("greška prilikom rezerviranja memorije", e)
  errcode = 2
except GrCitanje as e:
  print("greška prilikom citanja datoteke", e)
  errcode = 3
except GrNepoznata as e:
  print("nepoznata greška u readfile", e)
  errcode = 4
except:
  print("neočekivana greška", sys.exc_info())
  errcode = 5

sys.exit(errcode)

Isprobajmo sad oba primjera. Pogledajmo output sljedećih naredbi upisanih u terminal:

$ chmod +x readfile.py
$ chmod +x readfile2.py
$ ./readfile.py bin1.bin; echo $?
datoteka je uspješno pročitana, i sadrži 24 byteova
0
$ ./readfile2.py bin1.bin; echo $?
datoteka je uspješno pročitana, i sadrži 24 byteova
0

Isprobajmo kako rade kad im se zada pogrešno ime datoteke odnosno ime datoteke koja ne postoji:

$ ./readfile.py nepostojeca.datoteka; echo $?
greška  [Errno 2] No such file or directory: 'nepostojeca.datoteka'
1
$ ./readfile2.py nepostojeca.datoteka; echo $?
greška prilikom otvaranja datoteke [Errno 2] No such file or directory: 'nepostojeca.datoteka'
1

Isprobajmo što se događa u slučaju kad im se zada ime postojeće datoteke za koju je ukinuto pravo čitanja:

$ chmod u-r bin1.bin
$ ./readfile.py bin1.bin; echo $?
greška  [Errno 13] Permission denied: 'bin1.bin'
1
$ ./readfile2.py bin1.bin; echo $?
greška prilikom otvaranja datoteke [Errno 13] Permission denied: 'bin1.bin'
1
$ chmod u+r bin1.bin

Vidimo da oba programa ispravno obrađuju greške. Poanta je da koristeći try/except lako možemo postići osnovnu obradu grešaka kao u prvom primjeru, te po potrebi nadograđivati kao u drugom primjeru.