Commit 8ba215e2 by Patryk Czarnik

teoria kolekcji - część 1

parent c9f719de
# Listy są mutowalne (można je zmieniać)
# Listy pamiętają kolejność elementów i można indeksować (listy są sekwencjami)
imiona = ['Ala', 'Adam', 'Iwona', 'Kasia', 'Janusz']
liczby = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
miks = [123, 'Ala', 3.14, [3,2,1]]
print(imiona)
print(liczby)
print('Liczba imion:', len(imiona))
print('Liczba liczb:', len(liczby))
print('len(miks):', len(miks))
# de facto to jest wywołanie takiej metody, ale "zwykły programista" powinien używać funkcji len() aby to uzyskać
print('_len:', imiona.__len__())
print()
# A tak tworzy się pustą listę (2 sposoby)
pusta1 = []
pusta2 = list()
print(pusta1, len(pusta1), type(pusta1))
print()
# Łatwo można przejrzeć wszystkie elementy lista i dla każdego coś zrobić
for imie in imiona:
print('Kolejną osobą jest', imie)
print()
for x in liczby:
print(x, end='; ')
print()
# W przypadku kolekcji zawierająch liczby, można skorzystać z gotowych funkcji:
print('Suma:', sum(liczby))
print('Max:', max(liczby))
print('Min:', min(liczby))
# max i min dla dowolnych typów, które da się porównywać
print('Maksymalne imię:', max(imiona))
print()
# Odczytać konkretny element listy wg jego pozycji można za pomocą [indeksowania]
print(imiona[2])
# Obowiązuje numeracja od 0
# Można uzywać indeksów ujemnych - wtedy liczymy od końca.
# -1 to ostatni element
print(imiona[-1])
# Gdy wyjdziemy poza zakres listy ( >= rozmiar albo < -rozmiar), to będzie wyjątek IndexError
#ERR print(imiona[10])
print()
# Listy są mutowalne - zawartość list można modyfikować.
# Zmiana elementu na określonej pozycji:
print(imiona)
imiona[0] = 'Alicja'
imiona[3] = 'Katarzyna'
# ale nie da się w ten sposób wpisać na nieistniejącą pozycję
#ERR imiona[5] = 'Alojzy'
print(imiona)
# Dodanie nowego elementu na końcu:
imiona.append('Krzysztof')
print(imiona)
# Wstawienie elementu w środek. Na pozycję 3 wstaw wartość Magdalena → reszta się przesunie
imiona.insert(3, 'Magdalena')
print(imiona)
# Aby za jednym zamachem dodać wiele elementów, można uzyć extend
inna_lista = ['Zuzia', 'Adrian', 'Bożena', 'Janusz']
#imiona.append(inna_lista)
# byłaby lista w liście: ['Alicja', 'Adam', 'Iwona', 'Magdalena', 'Katarzyna', 'Janusz', 'Krzysztof', ['Zuzia', 'Adrian', 'Bożena', 'Janusz']] rozmiar: 8
imiona.extend(inna_lista)
# 4 dodatkowe imiona dodane na końcu listy
# ['Alicja', 'Adam', 'Iwona', 'Magdalena', 'Katarzyna', 'Janusz', 'Krzysztof', 'Zuzia', 'Adrian', 'Bożena', 'Janusz'] rozmiar: 11
print(imiona, 'rozmiar:', len(imiona))
print()
# Listy można też dodawać za pomocą + , ale to działa podobnie jak + w matematyce
a = [1,2,3]
b = [4,5]
print('a:', a, 'b:', b)
print('3 * a:', 3 * a)
print('a + b:', a + b)
# Listy a i b nadal pozostały sobą
print('a:', a, 'b:', b)
c = a + b
print('c:', c)
# Aby w ten sposób zmodyfikować istniejącą listę, można użyć
a += b
print('a:', a, 'b:', b)
# efekt analogiczny do extend
print()
# Usunięcie elementu z określonej pozycji. Dalsze elementy przesuwają się ← w lewo
print(imiona)
del imiona[1]
print(imiona)
# Usunięcie pierwszego znalezionego elementu o podanej wartości
imiona.remove('Janusz')
print(imiona)
# jakiś sposób, aby uzyskać listę bez wystąpień podanej wartości:
#imiona = [imie for imie in imiona if imie != 'Janusz']
# Próba usunięcia elementu niesitniejącego kończy się błędem
# imiona.remove('Mikołaj')
# print(imiona)
print()
# Aby sprawdzić, czy lista coś zawiera, używamy operatora in
# Przy czym dla listy jest to kosztowne - Python musi przejrzeć całą listę
if 'Iwona' in imiona:
print('Lista zawiera imię Iwona')
else:
print('Lista nie zawiera imienia Iwona')
if 'Tomasz' in imiona:
print('Lista zawiera imię Tomasz')
else:
print('Lista nie zawiera imienia Tomasz')
print()
# Odwrócenie listy
print(liczby)
liczby.reverse()
print(liczby)
liczby.extend([33, 66, 99, 155])
print(liczby)
# Posortowanie listy
liczby.sort()
print(liczby)
print()
# Gdy korzysta się z listy jako "stosu", wtedy bardziej czytelne może być używanie operacji append i pop
l = []
l.append('Warszawa')
l.append('Kraków')
l.append('Łódź')
l.append('Wrocław')
l.append('Poznań')
print(l)
# pop usuwa ostatni element
print('3 razy pop() : ')
print(l.pop())
print(l.pop())
print(l.pop())
print(l)
l.append('Bydgoszcz')
l.append('Toruń')
print(l)
print()
print(imiona)
# Spr. czy element należy do listy
print('Katarzyna' in imiona)
kogoszukam = 'Zuzia'
if kogoszukam in imiona:
print('Znaleziono')
else:
print('Nie ma')
# Jeśli interesuje nas nie tylko czy obiekt należy do listy, ale także na jakiej pozycji się znajduje, używamy wtedy metody index
pozycja = imiona.index(kogoszukam)
print(f'Osoba {kogoszukam} znajduje się na pozycji {pozycja}')
# Wywołanie index dla elementu, którego nie ma w liście, kończy się wyjątkiem
# pozycja = imiona.index('Patryk')
# 2 sposoby, żeby sobie z tym poradzić:
# Podejście "defensywne"
kogoszukam = 'Patryk'
if kogoszukam in imiona:
pozycja = imiona.index(kogoszukam)
print(f'Osoba {kogoszukam} znajduje się na pozycji {pozycja}')
else:
print(f'Lista nie zawiera elementu {kogoszukam}')
# Podejście "ofensywne" - dość często praktykowane w Pythonie
# tym bardziej, że tutaj jest to ozwiązanie bardziej wydajne (tylko przeglądamy listę)
try:
print(f'Osoba {kogoszukam} znajduje się na pozycji {imiona.index(kogoszukam)}')
except ValueError:
print(f'Lista nie zawiera elementu {kogoszukam}')
print()
# Zwykła pętla for przegląda elementy listy, nie mamy informacji o pozycji
for imie in imiona:
print(imie)
print()
# Pętla w stylu języka C przeglądająca indeksy, w Pythonie wygląda tak:
for i in range(len(imiona)):
print(f'{i} → {imiona[i]}')
print()
# Rozwiązaniem bezpośrednio służącym do przejrzenia elementów listy wraz z ich pozycjami, jest enumerate
# enumerate zwraca kolekcję par, czyli tupli.
# gdyby nie używać rozpakowywania, to byłoby tak:
for para in enumerate(imiona):
print(para, 'typu', type(para))
print()
# Ale w praktyce zawsze używa się tu "rozpakowywania" (unpacking)
# czyli wpisuje się te dwie rzeczy na oddzielne zmienne
for i, imie in enumerate(imiona):
print(f'{i} → {imie}')
print()
# Samo enumerate tworzy kolekcję par:
print(enumerate(imiona))
print(list(enumerate(imiona)))
# enumerate domyślnie numeruje od 0, ale można podać inny numer początkowy
print(list(enumerate(imiona, 11)))
print()
# Operacja zip na podstawie dwóch list tworzy listę par.
# Typowe zastosowanie to pętla przechodząca jednocześnie po dwóch listach.
lista1 = ['Opel', 'Toyota', 'BMW', 'Audi', 'VW', 'Fiat']
lista2 = ['Astra', 'Prius', '5', 'A6', 'Passat TDI']
for a, b in zip(lista1, lista2):
print(a, b)
print()
# Samo zip tworzy listę par:
zipped = zip(lista1, lista2)
print(zipped)
print(list(zipped))
slownik = dict(zip(lista1, lista2))
print(slownik)
print('Koniec programu')
# Python posiada składnię "slices" ("wycinanki"),
# która pozwala w bardzo wygodny sposób wybierać fragmenty list (i innych sekwencji)
l = ['zero', 'jeden', 'dwa', 'trzy', 'cztery',
'pięć', 'sześć', 'siedem', 'osiem', 'dziewięć']
print(l)
print('len =', len(l))
print()
# Można wybierać pojedyncze elementy
print('l[0]', l[0])
print('l[3]', l[3])
print('l[9]', l[9])
# print(l[10]) # IndexError: list index out of range
# liczenie od końca
print('l[-3]', l[-3])
# ostatni
print('l[-1]', l[-1])
print('l[-1]', l[len(l)-1])
# Ujemny też może wyjść poza zakres
#ERR print('l[-15]', l[-15])
print()
# Notacją z dwukropkami wybiera się przedziały / fragmenty list
print('l[3:7]', l[3:7]) # pozycje od 3 do 6
# gdy mówimy o zakresach, to zawsze jest to zakres od lewej granicy włącznie, do prawej wyłączając
# (lewostronnie domknięty)
# przy czym numerujemy od zera
# odwołanie do pojedynczego elementu poza zakresem kończy się błędem IndexError
# print(l[20])
# wyjście poza listę w przypadku zakresu nie powoduje błędu - po prostu dostajemy krótszy fragment, czasami wręcz pusty
print('l[5:25]', l[5:25])
print('l[20:25]', l[20:25])
# W zakresach można podawać indeksy ujemne, które zostaną odpowiednio przeliczone
print('l[-8:-6]', l[-8:-6])
print('l[2:6]', l[2:6])
print('l[-8:6]', l[-8:6])
print('l[8:3]', l[8:3])
print()
# trzeci parametr składni "przedziałowej" / "slices" oznacza wielkość kroku
# (domyślnie krok = 1)
print('l[2:10:2]', l[2:10:2])
print('l[0:10:3]', l[0:10:3])
# Przy tym podejściu drugi parametr jest traktowany tak, że zostaną odczytane elementy o indeksach mniejszych niż ten parametr
print('l[1:5:3]', l[1:5:3]) # 4 < 5, więc 4 się wyświetli
print('l[1:6:3]', l[1:6:3])
print('l[1:7:3]', l[1:7:3]) # nieprawda, że 7 < 7, więc 7 się nie wyświetli
print('l[1:8:3]', l[1:8:3]) # 7 < 8, więc 7 się wyświetli
print()
# Niektóre parametry można pominąć. Pierwszy to domyślnie 0, drugi to domyślnie len
print('l[::2]', l[::2]) # wypisz wszystkie pozycje parzyste
# elementy od początku do 4
print('l[:5]', l[:5])
print('l[:5:]', l[:5:]) # równoważne powyższemu
# od 5 do końca
print('l[5:]', l[5:])
print()
print('l[:]', l[:])
print('l[::]', l[::])
print()
# Gdy krok jest ujemny, to przeglądamy elementy w odwrotnej kolejności
print('l[8:2:-2]', l[8:2:-2]) # 8 6 4
# Najprostszy sposób na odwrócenie listy:
print('l[::-1]', l[::-1])
print()
# Napisy (str) też są "sekwencjami" i można do nich stosować indeksowanie i wycinanie
tekst = 'Ala ma kota a Ola ma psa'
print(tekst[2])
print(tekst[-2])
print()
# spróbujcie wyciąć słowo "kota"
print(tekst[7:11])
print(tekst[::2])
# od tyłu:
print(tekst[::-1])
print('\n' + 60*'=' + '\n')
lista = ['Warszawa', 'Kraków', 'Łódź', 'Wrocław', 'Poznań', 'Katowice', 'Białystok', 'Toruń', 'Bydgoszcz']
print(lista)
# do wstawiania elementów można też użyć zakresów:
print(lista[5])
print(lista[5:5])
# działa tak jak insert(5, 'Trójmiasto')
lista[5:5] = ['Trójmiasto']
print(lista)
# Uwaga, inaczej zadziałałoby:
# lista[5] = ['Trójmiasto']
# print(lista)
print(lista[5:6]) # ['Trójmiasto']
# zastąp wycinek o długości jeden tymi trzema elementami
# zastępuje Trójmiasto tymi trzema miastami
lista[5:6] = ['Gdańsk', 'Sopot', 'Gdynia']
# trzy_miasta = ['Gdańsk', 'Sopot', 'Gdynia']
# lista[5:6] = trzy_miasta
print(lista)
# Usunie elementy Wrocław i Poznań
lista[3:6] = []
print(lista)
# Można też deletować zakresy:
del lista [1:4]
print(lista)
# str jest "niemutowalny" (immutable), więc zakresy są tylko do odczytu
#txt = 'ABCDEF'
#ERR txt[1:2] = 'XY'
# tupla, krotka
tupla = ('Ala', 'Ola', 'Ela')
print(tupla, type(tupla))
punkt = (1.25, -3.0)
print(punkt)
# tuple mogą zawierać duplikaty
# kolejność jest zachowywana
# można indeksować (lista[i], wycinanki...)
# tuple są "niemutowalne" (immutable)
print(tupla[1])
print(tupla[0:2])
# nie zadziałają:
# tupla[1] = 'Aleksandra'
# TypeError: 'tuple' object does not support item assignment
# tupla[2:3] = ('Ela', 'Ula')
# tupla.append('Ula')
# tupla.insert(2, 'Ewa')
# del tupla[1]
# Natomiast MOŻNA do istniejącej zmiennej wpisać nową tuplę:
tupla = ('Adam', 'Ludwik', 'Ksawery')
print(tupla)
# No bo w Pythonie zawsze można do zmiennej wpisać zupełnie nowy obiekt:
tupla = 'To nie jest tupla'
print(tupla)
print()
# Można też modyfikować obiekty, które znajdują się w tupli, o ile same te obiekty są mutowalne.
dwie_listy = (['Polska', 'Czechy'], ['Warszawa', 'Praga'])
print('typ:', type(dwie_listy))
print(dwie_listy)
dwie_listy[0].append('Słowacja')
dwie_listy[1].append('Bratysława')
print(dwie_listy)
# Było to legalne, bo operacje append wykonaliśmy na obiektach listy, a nie krotki.
print('Drugi kraj:', dwie_listy[0][1])
print()
# pusta tupla:
pusta_tupla_1 = ()
pusta_tupla_2 = tuple()
print(pusta_tupla_1, type(pusta_tupla_1))
print(pusta_tupla_2, type(pusta_tupla_2))
# jak utworzyć tuplę jednoelementową?
# nie tak:
tupla1 = (123)
print(tupla1, type(tupla1)) # int
# tylko tak:
tupla1 = (123,)
print(tupla1, type(tupla1)) # tuple
print()
# ZASTOSOWANIA
# "Normalny programista Pythona" zdecydowanie częściej używa list
# tuple są intensywnie używane wewnętrznie przez Pythona - m.in. przekazywanie parametrów do funkcji
# tuple są używane przy realizacji różnych Pythonowych sztuczek, np. przeglądanie zawartości słowników
# przy "rozpakowywaniu", zwracaniu wielu wyników na raz itp.
# Przykład zwracania dwóch wyników na raz i ich rozpakowania
def dzielenie_z_reszta(x, y):
return x//y, x%y
tupla_wynikowa = dzielenie_z_reszta(13, 5)
print('typ wyniku:', type(tupla_wynikowa), 'wynik:', tupla_wynikowa)
print(f'Wynik dzielenia to {tupla_wynikowa[0]} i {tupla_wynikowa[1]} reszty')
# "rozpakowywanie"
(iloraz, reszta) = tupla_wynikowa
print(f'Wynik dzielnia to {iloraz} i {reszta} reszty')
# Często można nie pisać okrągłych nawiasów
# To jest "styl Pajtoniczny"
a, b = dzielenie_z_reszta(20, 7)
print(a, b)
# Inne zastosowanie - krótkie przypisanie wielu zmiennych na raz
x, y, kto = 100, 200, 'Ala'
print(x, y, kto)
# "swap" czy zamiana wartości zmiennych
x, y = y, x
print(x, y, kto)
#############
# Teoretycznie każda kolekcja może zawierać elementy różnych typów. Np. lista
lista = ['Ala ma kota', 1234, 3.14, (1,2,3), 'Koniec']
print(lista)
# W typowym zastosowaniu lista zawiera wiele jendorodnych elementów (np. same napisy, albo same liczby)
# nieograniczonej długości. Typowe jest, że do list są dodawane nowe elemente w czasie działania programu.
# Lista jest bardziej jak kolumna w Excelu
imiona = ['Ala', 'Ola', 'Ela']
imiona.append('Ula')
print(imiona)
# Tuple natomiast często zawierają dane różnych typów lub kolejne pola pełnią różną rolę.
# Zwykle tuple mają z góry znany rozmiar i wiadomo co będzie na której pozycji
# (jak w rekordzie bazodanowym, jak wiersz w Excelu)
t2 = ('Ala', 'Kowalska', 30, 'Jasna', 'Warszawa', True)
print(t2)
# Python tego nie sprawdza, ale taka jest praktyka użycia...
# To nie jest ścisłe, to tylko delikatne wskazówki, z których można się wyłamać.
# Rada: gdy nie wiemy czego użyć, użyjmy listy.
# Zbiór (set)
zbior = {'Katowice', 'Gliwice', 'Ruda Śląska', 'Katowice', 'Chorzów', 'Bytom'}
print(zbior, 'liczba elementów:', len(zbior))
# Specyfika zbiorów:
# zbiory NIE zawierają duplikatów (dodanie duplikatu nie powoduje błędu, ale po prostu nie jest już uwzględniane)
# kolejność NIE jest zachowywana
# NIE można indeksować (zbior[i] nie działa, inaczej mówiąc zbiór NIE JEST sekwencją)
# print(zbior[2])
# zbiory są "mutowalne" (mutable)
# dodawanie i usuwanie elementów:
zbior.add('Sosnowiec')
print(zbior)
zbior.add('Sosnowiec')
print(zbior)
zbior.remove('Sosnowiec')
print(zbior)
# Usunięcie elementu, którego nie ma
# zbior.remove('Sosnowiec') # KeyError: 'Sosnowiec'
# Tak można sprawdzić czy kolekcja zawiera element:
if 'Zabrze' in zbior:
print('TAK')
else:
print('NIE')
# Takie sprawdzenie dla zbiorów jest wydajne (dzięki haszkodom itp. rozwiązaniom),
# a dla list i tupli jest niewydajne (Python w pętli przegląda całą zawartość)
# Typowe zastosowania zbiorów:
# - gdy zależy nam na braku duplikacji
# - gdy potrzebujemy móc szybko sprawdzić czy jakiś element istnieje, czy nie istnieje
# Pusty zbiór: tylko w ten sposób
pusty_zbior = set()
print(type(pusty_zbior), pusty_zbior)
print()
# Bo {} jest pustym słownikiem
# Dla zbiorów są dostępne operatory zbiorów, tzw. operacje teoriomnogościowe
# tylko dla zbiorów (a nie dla np. list)
z1 = {'Ala', 'Ola', 'Ela'}
z2 = {'Ola', 'Kasia'}
print('Pierwszy: ', z1)
print('Drugi: ', z2)
print('Suma zbiorów: ', z1 | z2)
print('Iloczyn zbiorów: ', z1 & z2) # inaczej: przecięcie, część wspólna
print('Różnica zbiorów: ', z1 - z2)
print('Różnica zbiorów: ', z2 - z1)
print('Różnica symetryczna: ', z1 ^ z2) # elementy, które są jednym lub drugim, ale nie w obu na raz
print()
# Kolekcje jednych typów można łatwo konwertować na inny typ:
lista = [20, 10, 20, 30, 10, 20, 100]
zbior = set(lista)
print('lista:', lista)
print('zbior:', zbior)
lista = list(zbior)
print('lista:', lista)
napis = 'ala ma kota'
literki = set(napis)
print('literki bez powtórzeń:', literki)
# range to generator, który generuje kolejne liczby z podanego zakresu
# range ma możliwości zbliżone do "wycinanek listowych" (slices):
# Najbardziej ogólna postać:
# range(start, stop, step)
# Liczba od której zaczynamy, PRZED którą kończymy, o ile idziemy w każdym kroku
# Najczęściej używa się range w połączeniu z pętlą for, aby wykonać operację dla kolejnych liczb.
for a in range(10, 30, 3):
print("a = ", a)
print()
# To jest równoważne takiej pętli while:
# (gdyby krok był ujemny, to warunek odwrotny >)
a = 10
while a < 30:
print("w a = ", a)
a += 3
print()
for b in range(20, 40, 2):
print(b, end=', ')
print()
# Możliwości...
# Domyślnym krokiem jest 1
# range(start, stop)
for b in range(20, 40):
print(b, end=', ')
print()
# Domyślnym początkiem jest 0
# range(stop)
for b in range(10):
print(b, end=', ')
print()
# Krok może być ujemny
for b in range(50, 40, -1):
print(b, end=', ')
print()
# Samo odwrócenie wartości start/stop nie wystarcza, bo domyślny krok to jest +1
# Tu nic się nie wypisze:
for b in range(90, 80):
print(b, end=', ')
print()
print("\n==============\n")
# Stara wersja notatek
# gdy podajemy jeden argument n, to generowane są liczby od 0 do n-1
for i in range(10):
print(i)
print("==============")
# gdy podajemy dwa argumenty poczatek , koniec,
# to generowane są liczby od poczatek wlacznie, do koniec wylaczajac
for i in range(5, 10):
print(i)
print("==============")
# trzeci parametr oznacza wielkość kroku, może być ujemny
for i in range(10, 20, 3):
print(i)
print("==============")
# gdy krok jest ujemny, to sprawdzane jest czy i > koniec
for i in range(10, 0, -1):
print(i)
print("==============")
# range jest "generatorem", to znaczy nie zawiera w sobie tych wszystkich liczb na raz, tylko potrafi je wygenerować
# można bez problemu stworzyć zmienną z zakresem, który nie zmieściłby się w pamięci
mega_zakres = range(0, 1000_000_000_000)
print(mega_zakres)
# gdybym próbował utworzyć taką listę, to pamięci zabraknie
# MemoryError
# uważajcie na Linuksie
# mega_lista = list(mega_zakres)
# print(len(mega_lista))
# Ale liczby są dostępne, gdy przetwarzamy je np. w pętli for
# for i in mega_zakres:
# print(i)
# if i > 1000000: break # żeby jednak się zakończył
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment