Języki skryptowe - Python¶
Wykład 9¶
- typy sekwencyjne: set i frozenset
- generatory
- omówienie zadań z listy 6
Typy sekwencyjne¶
- do tej pory poznaliśmy cztery typy sekwencyjne:
- str - typ tekstowy
- list - mutowalna sekwencja
- tuple - niemutowalna sekwencja
- range - niemutowalna sekwencja liczb
Sekwencja set¶
- set jest mutowalną sekwencją
- różnice względem list
- nie może zawierać duplikatów
- jest nieuporządkowana
- może zawierać tylko hashowalne obiekty
Definiowanie set¶
zbior = {1, 4, 6, 2, 1} # nawiasy klamrowe type(zbior)
set
print(zbior)
{1, 2, 4, 6}
zbior[0] # nie ma indeksowania zbiorów
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-3-119e4f5506a9> in <module>() ----> 1 zbior[0] # nie ma indeksowania zbiorów TypeError: 'set' object does not support indexing
set z list¶
lista = [1, 4, 6, 2, 1] zbior = set(lista) # stwórz zbiór na bazie listy print(zbior)
{1, 2, 4, 6}
Dodawanie elementów do zbioru¶
zbior = {1, 2, 3} print(zbior)
{1, 2, 3}
zbior.add(4) # dodaj 4 print(zbior)
{1, 2, 3, 4}
zbior.add(4) # jeszcze raz dodaj 4 print(zbior) # nie ma duplikatów
{1, 2, 3, 4}
Elementy niehashowalne¶
zbior = {1, 2, 3} lista = [4, 5] # lista jest niehashowalna # zbiór nie może przechowywać # elementów niehashowalnych zbior.add(lista)
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-8-c1da28ad9f1d> in <module>() 5 # zbiór nie może przechowywać 6 # elementów niehashowalnych ----> 7 zbior.add(lista) TypeError: unhashable type: 'list'
Sekwencje hashowalne¶
zbior = {1, 2, 3} krotka = (4, 5) # krotka jest hashowalna # więc można dodać krotkę # do zbioru zbior.add(krotka) print(zbior)
{(4, 5), 1, 2, 3}
Usuwanie elementów ze zbioru¶
zbior = {1, 2, 3} zbior.remove(1) # usuń 1 print(zbior)
{2, 3}
# zwróci błąd jeśli element nie istnieje zbior.remove(1)
--------------------------------------------------------------------------- KeyError Traceback (most recent call last) <ipython-input-11-15d16e079b36> in <module>() 1 # zwróci błąd jeśli element nie istnieje ----> 2 zbior.remove(1) KeyError: 1
Bezpieczne usuwanie?¶
zbior = {1, 2, 3} # aby uniknąć błędów i przerwania programu # można sprawdzić, czy na pewno element # znajduje się w zbiorze if 1 in zbior: zbior.remove(1)
# lub skorzystać z try...except try: zbior.remove(1) except: pass
# lub skorzystać z discard zbior.discard(1)
Zastosowanie zbiorów¶
# usuwanie duplikatów z listy lista = [2, 1, 2, 3, 4, 3, 2, 1, 5, 5, 2] lista = list(set(lista)) # uwaga - tracimy kolejność print(lista)
[1, 2, 3, 4, 5]
Część wspólna zbiorów¶
A = {1, 2, 3, 4, 5} B = {3, 4, 5, 6, 7} A.intersection(B) # część wspólna A i B
{3, 4, 5}
# lub krócej A & B
{3, 4, 5}
Część wspólna list¶
A = [1, 2, 3, 4, 5] B = [3, 4, 5, 6, 7] # rzutuje A i B na zbiory # wyznacza ich część wspólną # rzutuje na listę wspolna = list(set(A) & set(B)) print(wspolna)
[3, 4, 5]
frozenset¶
- jak set ale niemutowalny, czyli nie można modyfikować zawartości
zamrozony_zbior = frozenset([1, 2, 3, 4, 5]) type(zamrozony_zbior)
frozenset
zamrozony_zbior.add(1) # nie można dodawać / usuwać
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-20-c3b7844219a4> in <module>() ----> 1 zamrozony_zbior.add(1) # nie można dodawać / usuwać AttributeError: 'frozenset' object has no attribute 'add'
Przykład - Totolotek¶
from random import randint # z wykładu 7 def losowanie(): """Losuje 6 liczb od 1 do 49.""" # nie jesteśmy zabezpieczni przed powtórzeniami return sorted([randint(1, 49) for _ in range(6)]) def check(a, b): """Sprawdza ilość takich samych elementów.""" return len([n for n in a if n in b]) def play(): """Gra w lotka.""" lotto = losowanie() # losowanie lotto kupon = losowanie() # kupon chybił-trafił return check(kupon, lotto)
Totolotek z set¶
from random import randint def losowanie(): """Losuje 6 liczb od 1 do 49.""" los = set() # tutaj zapiszemy wyniki losowania while len(los) < 6: # dodawaj aż 6 unikatów los.add(randint(1, 49)) return los def check(a, b): """Sprawdza ilość takich samych elementów.""" return len(a & b)
Totolotek - test¶
lotto = losowanie() kupon = losowanie() print(lotto, kupon, sep="\n")
{35, 7, 12, 16, 48, 29} {32, 10, 13, 26, 29, 31}
check(lotto, kupon)
1
Totolotek z frozenset¶
from random import sample # frozenset bo losowania nie będziemy modyfikować def losowanie(): """Losuje 6 liczb od 1 do 49.""" los = sample(range(1, 50), 6) # sample gwarantuje brak powtórzeń return frozenset(los) # zwracamy jako zamrożony zbiór def check(a, b): """Sprawdza ilość takich samych elementów.""" return len(a & b)
Totolotek - test¶
lotto = losowanie() kupon = losowanie() print(lotto, kupon, sep="\n")
frozenset({37, 46, 48, 19, 25, 31}) frozenset({38, 40, 41, 18, 19, 30})
check(lotto, kupon)
1
Generatory¶
- funkcja, która zachowuje się jak iterator (czyli można np. wykorzystać ją w pętli)
- zamiast tworzyć całą listę obiektów, które będą przechowywane w pamięci
- oszczędza czas i pamięć
Dygresja: range¶
%timeit -n1 range(1000000) # tworzy obiekt range
1 loop, best of 3: 1.68 µs per loop
# kolejne elementy range # muszą zostać zapisane w pamięci %timeit -n1 list(range(1000000))
1 loop, best of 3: 53.3 ms per loop
Ciąg geometryczny¶
def geometryczny(a1, q, n): """Generuje n wyrazów ciągu geometrycznego.""" ciag = [a1] for _ in range(n-1): # n-1 bo pierwszy już jest ciag.append(ciag[-1]*q) # następny = poprzedni * iloraz return ciag
ciag = geometryczny(1, 3, 10) print(ciag)
[1, 3, 9, 27, 81, 243, 729, 2187, 6561, 19683]
Ciąg geometryczny - generator¶
def gen_geometryczny(a1, q, n): """Generuje n wyrazów ciągu geometrycznego.""" for _ in range(n): yield a1 # zwróć obecną wartość a1 a1 *= q # i czekaj na kolejną iterację
Generator a lista¶
ciag1 = geometryczny(1, 3, 10) # zwraca listę ciag2 = gen_geometryczny(1, 3, 10) # zwraca generator
print(ciag1)
[1, 3, 9, 27, 81, 243, 729, 2187, 6561, 19683]
print(ciag2)
<generator object gen_geometryczny at 0x7f9bac1ec678>
Generator jak iterator¶
next(ciag2) # pierwszy element
1
next(ciag2) # kolejny element itd
3
# a najczęściej w pętli for i in gen_geometryczny(1, 3, 10): print(i, end=', ')
1, 3, 9, 27, 81, 243, 729, 2187, 6561, 19683,
Czas¶
%timeit -n3 geometryczny(1, 3, 10000)
3 loops, best of 3: 12.8 ms per loop
%timeit -n3 gen_geometryczny(1, 3, 10000)
3 loops, best of 3: 978 ns per loop
Czas dokładniej¶
def test_lista(): """Pętla po liście""" for element in geometryczny(1, 3, 10000): pass def test_generator(): """Pętla po generatorze""" for element in gen_geometryczny(1, 3, 10000): pass
%timeit -n3 test_lista()
3 loops, best of 3: 13.6 ms per loop
%timeit -n3 test_generator()
3 loops, best of 3: 12.1 ms per loop
Lista czy generator¶
- generator będzie z reguły szybszy
- generator może być nieskończony
- jednak nie można wykonywać operacji na "elementach", np. sortowania
- lista może być efektywniejsza, jeśli planujemy więcej pętli
"Wyczerpanie" generatora¶
ciag = gen_geometryczny(1, 3, 20) # generuje 20 wyrazów
for i in range(10): # wydrukuj 10 pierwszych print(next(ciag), end=' ')
1 3 9 27 81 243 729 2187 6561 19683
for element in ciag: # nie zaczyna od pierwszego! print(element, end=' ')
59049 177147 531441 1594323 4782969 14348907 43046721 129140163 387420489 1162261467
Wielekrotne użycie¶
def lista_loop(n=10): """Wykonuje n pętli po liście""" ciag = geometryczny(1, 3, 10000) for _ in range(n): for element in ciag: pass def gen_loop(n=10): """Wykonuje n pętli po generatorze""" for _ in range(n): for element in gen_geometryczny(1, 3, 10000): pass
%timeit -n3 lista_loop()
3 loops, best of 3: 14.2 ms per loop
%timeit -n3 gen_loop()
3 loops, best of 3: 77.4 ms per loop
Powtórka: listy składane (list comprehensions)¶
# chcemy stworzyć listę zawierającą # kwadraty wszystkich cyfr kwadraty = [] for x in range(10): kwadraty.append(x**2) print(kwadraty)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# a korzystając z listy skladanej kwadraty = [x**2 for x in range(10)] print(kwadraty)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Append vs lista składana¶
def create_list(n=100000): """Tworzy listę kwadratów pierwszych n liczb naturalnych""" kwadraty = [] for x in range(n): kwadraty.append(x**2) return kwadraty def list_comprehension(n=100000): """Tworzy listę kwadratów pierwszych n liczb naturalnych""" return [x**2 for x in range(n)]
Czas¶
%timeit -n3 x = create_list() # lista tworzona przez append
3 loops, best of 3: 48.3 ms per loop
%timeit -n3 y = list_comprehension() # lista składana
3 loops, best of 3: 42.5 ms per loop
create_list() == list_comprehension() # wynik ten sam
True
Przykład¶
def force(m, a): """Zwraca wartość siły [N]. Liczy siłę jaką należy zadziałać na ciało o masie m [kg], aby nadać mu przyspieszenie a [m/s^2]. """ return m*a wagi = [10, 20, 30, 40, 50] # kg przyspieszenia = [1, 2, 3, 4, 5] # m/s^2
# stwórz tablicę z wartościami sił sily = [force(m, a) for m, a in zip(wagi, przyspieszenia)] print(sily)
[10, 40, 90, 160, 250]
Generator "jak lista składana"¶
# lista składana list_comprehension = [x**2 for x in range(10)] # generator expression generator = (x**2 for x in range(10))
for x in generator: print(x, end=' ')
0 1 4 9 16 25 36 49 64 81
# lub prościej for x in (x**2 for x in range(10)): print(x, end=' ')
0 1 4 9 16 25 36 49 64 81
Uwagi na koniec¶
- unikaj tworzenia list przez append, jeśli można wykorzystać list comprehension
- unikaj tworzenia list jeśli jest to zbędne (użyj generatorów)
- unikaj generator function na rzecz generator expression
Próbne kolokwium - zadanie 1¶
Napisz skrypt, który wygeneruje listę zawierającą 100 pierwszych wyrazów ciągu geometrycznego (dla zadanych a1 i q - pierwszy wyraz ciągu i iloraz).
Następnie wydrukuje na ekranie ten ciąg oraz jego podciąg zawierający tylko parzyste wyrazy.
Zadanie 1 - wersja I¶
# podnoszenie q do potęgi jest bardzo czasochłonne! def geometryczny_v1(a1, q, n): """Zwraca n-ty wyraz ciągu geometrycznego (a1, q)""" return a1*q**(n-1) # nie wymaga wyjaśnienia (oby...)
# składamy listę na bazie naszej funkcji ciag = [geometryczny_v1(1, 3, n+1) for n in range(100)]
# print(ciag) # drukuje cały ciąg # print(ciag[1::2]) # drukuje tylko parzyste wyrazy
Zadanie 1 - wersja II¶
def geometryczny_v2(a1, q, n, ciag=[]): """Tworzy listę n pierwszych wyrazów ciągu (a1, q)""" for _ in range(n): ciag.append(a1) # dodaj obecny wyraz a1 *= q # następny wyraz return ciag
ciag = geometryczny_v2(1, 3, 100)
# print(ciag) # drukuje cały ciąg # print(ciag[1::2]) # drukuje tylko parzyste wyrazy
Zadanie 1 - wersja III¶
def geometryczny_v3(a1, q, n): """Generator ciągu geometrycznego (a1, q)""" for _ in range(n): yield a1 # zwróć obecny wyraz i czekaj a1 *= q # następny wyraz
ciag = geometryczny_v3(1, 3, 100) # generator!
# drukuj cały ciąg # for x in ciag: # print(x, end=' ') # nie ma wycinków generatorów # ale można skorzystać z itertools.islice
Zadanie 1 - wersja IV¶
def geometryczny_v4(a1, q, n, ciag=[]): """Ciąg geometryczny (a1, q) rekurencyjnie""" if not n: return ciag # zwróć ciąg gdy dotrzemy do n ciag.append(a1) # dodaj obecny element # wywołaj rekurencyjnie z kolejnym elementem return geometryczny_v4(a1*q, q, n-1, ciag)
ciag = geometryczny_v4(1, 3, 100)
# print(ciag) # drukuje cały ciąg # print(ciag[1::2]) # drukuje tylko parzyste wyrazy
Próbne kolokwium - zadanie 2¶
Napisz funkcję, która sprawdza, czy podana liczba jest liczbą pierwszą. Funkcja powinna zwracać True lub False.
Napisz skrypt, który sprawdzi, czy podana przez użytkownika liczba jest liczbą pierwszą.
Zadanie 2 - wersja I¶
def is_prime_v1(n): """Sprawdza czy n jest liczbą pierwszą.""" if n < 2: return False # 0 i 1 nie są pierwsze if n < 4: return True # 2 i 3 są pierwsze if n % 2 == 0: return False # parzyste nie są pierwsze i = 3 while i < n: if n % i == 0: return False # dzieli się przez i i += 1 return True # nie dzieli się przez nic
is_prime_v1(11)
True
Dygresja o dzielnikach¶
- Niech \(n = a * b\)
- Jeśli \(a = b\), to \(a = b = \sqrt(n)\)
- Jeśli \(a \neq b\), to albo \(a < \sqrt(n)\) albo \(b < \sqrt(n)\)
- Co oznacza, że jeśli nie znajdziemy dzielnika mniejszego od \(\sqrt(n)\), to nie znajdziemy og też dalej
Zadanie 2 - wersja II¶
def is_prime_v2(n): """Sprawdza czy n jest liczbą pierwszą.""" if n < 2: return False # 0 i 1 nie są pierwsze if n < 4: return True # 2 i 3 są pierwsze if n % 2 == 0: return False # parzyste nie są pierwsze i = 3 while i*i < n: # szukamy do pierwiastka z n if n % i == 0: return False # dzieli się przez i i += 1 return True # nie dzieli się przez nic
is_prime_v2(11)
True
Sito Eratostenesa¶
Implementacja¶
def sieve_v1(n): """Create Sieve of Eratosthenes""" is_prime = [False, False] + [True] * n # ustaw flagi na True i = 2 while i * i < n: # do pierwiastka z n if is_prime[i]: for j in range(2*i, n, i): # zmień flagę is_prime[j] = False # dla wszystkich wielokrotności i += 1 return is_prime
primes = sieve_v1(100) for i in range(100): if primes[i]: print(i, end=' ')
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97
Próbne kolokwium - zadanie 3¶
Napisz skrypt, który wymaga trzech argumentów z linii komend (wsk. sys.argv), czyli: python moj_skrypt.py arg1 arg2 arg3
które są długościami boków trójkąta (można założyć, że podane argumenty są liczbami). Na tej podstawie skrypt powinien wydrukować na ekranie następujące informacje:
- obwód i pole trójkąta
- informację czy trójkąt jest równoboczny, równoramienny czy różnoboczny
- informację czy trójkąt jest prostokątny, ostrokątny czy rozwartokątny
W przypadku złej liczby argumentów program powinien wyświetlić odpowiedni komunikat i zakończyć działanie.
Zadanie 3¶
- Pełny skrypt można znalęźć tutaj: zadanie 3
Zadanie 3 - część I¶
%%writefile zad3.py """Moduł do obsługi trójkątów.""" import sys import math def help(): """Drukuje instrukcję obsługi i zamyka program""" print("usage: python triangle.py bok1 bok2 bok3") sys.exit(1) def generate(a, b, c): """Zwraca posortowane boki trójkąta jako floaty.""" return sorted([float(a), float(b), float(c)])
Overwriting zad3.py
Zadanie 3 - część II¶
%%writefile -a zad3.py def check(a, b, c): """Zamyka program, jeśli nierówność trójkąta nie jest spełniona.""" if a + b <= c: print("Nierówność trojkąta nie jest spełniona.\n") help() def obwod(a, b, c): """Zwraca obwód trójkąta""" return a + b + c def pole(a, b, c): """Zwraca pole trójkąta""" p = obwod(a, b, c) / 2 # wzór Herona return math.sqrt(p*(p - a)*(p - b)*(p - c))
Appending to zad3.py
Zadanie 3 - część III¶
%%writefile -a zad3.py def typ_boki(a, b, c): """Zwraca typ trójkąta (ze względu na boki)""" if a == b == c: return "równoboczny" elif a == b or b == c: return "równoramienny" else: return "różnoboczny" def typ_katy(a, b, c): """Zwraca typ trójkąta (ze względu na kąty)""" x = a**2 + b**2 - c**2 if x == 0: return "prostokątny" elif x > 0: return "ostrokątny" else: return "rozwartokątny"
Appending to zad3.py
Zadanie 3 - część IV¶
%%writefile -a zad3.py if __name__ == "__main__": try: a, b, c = generate(sys.argv[1], sys.argv[2], sys.argv[3]) except: help() check(a, b, c) print("Obwód trójkąta =", obwod(a, b, c)) print("Pole trójkąta =", pole(a, b, c)) print("Trójkąt jest", typ_boki(a, b, c)) print("Trójkąt jest", typ_katy(a, b, c))
Appending to zad3.py
Zadanie 3 - test¶
%%bash python zad3.py
usage: python triangle.py bok1 bok2 bok3
%%bash python zad3.py 3 4 5
Obwód trójkąta = 12.0 Pole trójkąta = 6.0 Trójkąt jest różnoboczny Trójkąt jest prostokątny
Próbne kolokwium - zadanie 4¶
Korzystając z metody równego podziału (bisekcji) znajdź przybliżone miejsce zerowe funkcji: f(x) = x**3 + 2*x*2 - 4*x - 10
w przedziale [1:3].
Metoda bisekcji¶
- \(f(x)\) jest ciągła w przedziale \([a, b]\)
- i przyjmuje różne znaki na końcach przedziału \(f(a)f(b) < 0\)
- idea:
- sprawdzamy, czy środek \(x\) jest miejscem zerowym -> koniec
- dzielimy przedział na dwa: \([a, x]\) i \([x, b]\)
- wybieramy przedział, dla którego znaki są różne na brzegach
- powtarzamy do skutku
Zadanie 4¶
def bisekcja(f, a, b, epsilon=0.0001): """Szuka miejsca zerowego f w [a, b] z dokładnością epislon. Zakładamy b > a oraz f(a)*f(b) < 0 """ while b - a > epsilon: # dopóki mieścimy się w epsilon x = (b + a) / 2 # środek przedziału if f(x) == 0: # miejsce zerowe znalezione return x elif f(x)*f(a) < 0: # nowy przedział [a, x1] b = x else: # nowy przedział [x1, b] a = x return (b + a) / 2 # zwracamy przybliżoną wartość
Zadanie 4 - test¶
def funkcja(x): return x**3 + 2*x**2 - 4*x - 10 x0 = bisekcja(funkcja, 1, 3) print("x0 =", x0) print("f(x0) =", funkcja(x0))
x0 = 2.117950439453125 f(x0) = 0.00014644321370838043