W tym „odcinku” tutoriala poznamy pewne „różności”, które mogły nam umknąć poprzednio i świetnie wypełnią braki, jakie możemy mieć w wiedzy, zanim ruszymy do bardziej zaawansowanych tematów.

Jakiegoś tematu przewodniego tego odcinka nie ma, po prostu wypełniamy braki w wiedzy.

Zadanie – podaj ilość unikalnych elementów

Mamy za zadanie podać ilość unikalnych elementów, występujących, niech będzie w liście. Powinniśmy umieć sobie z tym poradzić bez problemu. Oto jedno podejście:

def unique_elements(lst):
    unique_lst = []
    for el in lst:
        if el not in unique_lst:
            unique_lst.append(el)
    return len(unique_lst)


print(unique_elements([1,1,2,2,3,3,3]))
#3
print(unique_elements([1,2,3,4,4]))
#4

Tutaj przyjmujemy listę, tworzymy drugą, pustą. Iterujemy po pierwszej i tylko jeżeli elementu takiego nie mamy (not in) w unique_list, taki element do niej dodajemy. Zwracamy długość listy zawierającej tylko unikalne wartości.

Set, czyli zbiór

Mamy taki typ danych jak zbiór, który może nam pomóc rozwiązać takie zadanie szybciej, także z komputerowego punktu widzenia. Set, czyli zbiór, to taka lista unikalnych wartości. Tworzymy go tak:

msg = "hello"
msg_set = set(msg)
print(msg_set)
#{'e', 'o', 'h', 'l'}

Jak widać, podobne toto do słownika, jeżeli o literał idzie (znak {}) ale w słowniku są pary klucz, wartość. A to taki zbiór, ale elementy nie mogą się powtarzać.

A gdybyśmy tak starali się „na sztywno” stworzyć zbiór z duplikatami?

msg_set = {"h", "e", "l", "l", "o"}
print(msg_set)
#{'h', 'e', 'o', 'l'}

Nie działa. Podobnie jak dodawanie czegoś, co już jest:

msg_set = {"h", "e", "l", "l", "o"}
print(msg_set)
#{'l', 'e', 'o', 'h'}
msg_set.add("h")
print(msg_set)
#{'l', 'e', 'o', 'h'}

Nie działa. Ciekawym jest to, że te elementy nie są uporządkowane, występują w różnych kolejnościach. Jak widać zachowanie kolejności nie jest priorytetem w zbiorze, jedynie stworzenie kolekcji unikalnych, niepowtarzających się danych. Stąd możemy wysnuć (słuszny) wniosek, że zbiór działa szybciej niż lista. I jeżeli listę możemy zastąpić zbiorem, to powinniśmy.

Spróbujmy napisać funkcję, która zwraca liczbę unikalnych elementów, używając zbioru:

def unique_elements(lst):
    return len(set(lst))


print(unique_elements([1,1,2,2,3,3,3]))
#3
print(unique_elements([1,2,3,4,4]))
#4

To takie proste.

Literały – lista, zbiór, słownik, tupla

Zacznijmy od stworzenia listy, zbioru, słownika i tupli przy pomocy funkcji, nie literałów.

my_list = list("abc")
my_set = set("aabbcc")
my_dict = dict(name="John", age=30)
my_tuple = tuple([11,22])
print(my_list)
print(my_set)
print(my_dict)
print(my_tuple)
# ['a', 'b', 'c']
# {'b', 'c', 'a'}
# {'name': 'John', 'age': 30}
# (11, 22)

Lista to sekwencja uporządkowanych danych, jej literał zawiera nawiasy []. Zbiór (set) to nieuporządkowany zbiór unikalnych danych, jego literał zawiera nawiasy {} z elementami po przecinku wypisanymi. Słownik to zbiór par klucz-wartość, jego literał zawiera nawiasy { } oraz pary klucz : wartość, oddzielone dwukropkiem oraz przecinkiem od innych par. Tupla to niemutowalna lista, nawias tradycyjny ( ) oraz elementy po przecinku.

Tak wygląda tworzenie tych typów danych z użyciem literałów:

my_list = ["a", "b", "c"]
my_set = {"a", "b", "c"}
my_dict = {"name": "John", "age": 30}
my_tuple = (11,22)
print(type(my_list))
print(type(my_set))
print(type(my_dict))
print(type(my_tuple))
# <class 'list'>
# <class 'set'>
# <class 'dict'>
# <class 'tuple'>

W przypadku pustych literałów, możemy się zastanawiać, czym jest {} – pustym słownikiem a może pustym zbiorem?

print(type({}))
# <class 'dict'>

Pusty słownik, co nie powinno dziwić, słowniki są używane częściej niż zbiory. Problematyczna jest także tupla jednoelementowa:

print(type((10)))
# <class 'int'>

Jak widać sam nawias nie czyni tuplą (krotką, listą niemutowalną). Za to przecinek tak, nawet bez nawiasów:

tpl1 = 10,
tpl2 = (10,)
print(type(tpl1), type(tpl2))
# <class 'tuple'> <class 'tuple'>

Tworzenie pustych typów danych:

my_list = []
my_set = set()
my_dict = {}
my_tuple = tuple()
print(type(my_list))
print(type(my_set))
print(type(my_dict))
print(type(my_tuple))
# <class 'list'>
# <class 'set'>
# <class 'dict'>
# <class 'tuple'>

Comprehensions – list, set, dict

Comprehensions czyli składanie to bardzo potężne narzędzie, dostępne zarówno dla list, zbiorów jak i słowników. Wystarczy znać literały tychże i zrozumieć ogólną koncepcję, i już można się nimi posługiwać.

Oto przykład list comprehension:

my_list = [num ** 2 for num in range(1,6)]
print(my_list)
#[1, 4, 9, 16, 25]

Bierzemy liczby od 1 do 5 (bez 6) i w tym zakresie wrzucamy je podniesione do kwadratu.

Przykład dictionary comprehension:

my_dict= {num: num ** 2 for num in range(1,6)}
print(my_dict)
#{1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

Posługując się odpowiednim literałem bierzemy liczby w zakresie 1 do 5 (bez 6) i jako klucza używamy tej liczby zaś jako wartości, tej liczby podniesionej do kwadratu.

Oto przykład set comprehension:

my_set = {num ** 2 for num in range(1,6)}
print(my_set)
#{1, 4, 9, 16, 25}

Posługując się odpowiednim literałem (podobnym do słownika, ale jednak innym, tutaj nie mamy par klucz wartość dzielonych dwukropkiem) wrzucamy do tego zbioru liczby od 1 do 5 podniesione do kwadratu. Nie muszą być w kolejności, zbiór tego nie zachowuje. Natomiast zbiór to kolekcja unikalnych elementów. Oto bardziej praktyczny przykład składania zbioru:

msg = "Hello World"
my_set = {char.lower() for char in msg}
print(my_set)
# {'o', 'l', 'w', 'r', 'h', ' ', 'd', 'e'}

Do zbioru zapisujemy, małą literą, każdy znak z napisu msg. Kolejność nie jest zachowana, natomiast duplikaty nie wchodzą.

Możemy jeszcze dołączyć filtrowanie i zabronić wejścia spacjom:

msg = "Hello World"
my_set = {char.lower() for char in msg if char != " "}
print(my_set)
# {'d', 'w', 'e', 'h', 'o', 'l', 'r'}

Możemy też jeszcze bardziej dopracować warunek, aby tylko znaki będące literami alfabetu do tego zbioru wchodziły:

msg = "123 Hello World!!!"
my_set = {char.lower() for char in msg if char.isalpha()}
print(my_set)
# {'e', 'l', 'o', 'w', 'd', 'r', 'h'}

Indeksy i wycinki

Element pierwszy ma zawsze indeks zerowy, zaś element ostatni to długość minus 1, albo -1 w zapisie skróconym:

msg = "Hello World"
print(msg[0])
print(msg[len(msg) -1])
print(msg[-1])
# H
# d
# d

Element przedostatni to długość -2 albo w zapisie skróconym -2:

msg = "Hello World"
print(msg[len(msg) -2])
print(msg[-2])
# l
# l

Listy i tuple też podlegają indeksowaniu:

my_lst = [1,2,3]
my_tuple = (22,33)
print(my_lst[0], my_lst[-1])
print(my_tuple[0], my_tuple[-1])
# 1 3
# 22 33

Możemy też tworzyć wycinki od-do gdzie pierwszy argument to odkąd, drugi to dokąd, ale już bez niego:

msg = "Hello World"
print(msg[0:5])
#Hello
print(msg[:5])
#Hello

Czyli [0:5] lub w zapisie skróconym [:5] to wycinek od indeksu 0 włącznie, do indeksu 5, bez indeksu 5 (tam akurat mamy spację).

Kolejny przykład wycinka:

msg = "Hello World"
print(msg[6:])
#World

Tutaj idziemy od indeksu 6 włącznie aż do samego końca. Tutaj inny przykład:

msg = "Hello World!"
print(msg[6:-1])
#World

[6:-1] czyli od indeksu 6 włącznie do ostatniego znaku, bez ostatniego znaku.

Warto jeszcze znać sposób na odwracanie napisów/list:

msg = "Hello World!"
print(msg[::-1])
#!dlroW olleH

A także notację służącą do kopiowania list:

lst1 = [1,2,3]
lst2 = lst1[:]
print(lst1, lst2)
print(lst1 == lst2)
print(lst1 is lst2)
# [1, 2, 3] [1, 2, 3]
# True
# False

Zamienianie zmiennych zawartością

Kolejny element składni Pythona, który w zasadzie komentuje się sam, ale trzeba znać ten zapis. Zamieniamy zmienne zawartością:

num1 = 5
num2 = 10
print(num1, num2)
# 5 10
num1, num2 = num2, num1
print(num1, num2)
# 10 5

Więcej elementów składni Pythona poznamy w następnych odcinkach tutoriala.