Kurs Python · Moduł 2: Struktury danych

Krotki i zbiory - niezmienne zapisy i unikalne gatunki

5 min czytania
W tej lekcji4

Witaj! Na ekspedycji trafiasz na dane, dla których lista nie jest najlepszym wyborem. Pierwszy przykład to współrzędne wodopoju: para liczb, która nigdy nie powinna się zmienić. Gdyby jakaś funkcja przez pomyłkę nadpisała jedną z nich, cały zespół szukałby wody w złym miejscu. Drugi przykład to rejestr gatunków widzianych dzisiaj. Zebra przeszła obok obozu pięć razy, ale to wciąż jeden gatunek, a lista zapisze ją pięciokrotnie.

Python ma na te sytuacje dwa narzędzia. Krotka (tuple) to zapis, którego nie da się zmienić. Zbiór (set) to kolekcja, w której każdy element występuje tylko raz.

Krotka - wpis atramentem

Lista przypomina notatki ołówkiem, które możesz zmazywać i dopisywać. Krotka to wpis atramentem w dzienniku: raz zapisany, zostaje na zawsze. Tworzysz ją nawiasami okrągłymi, a elementy oddzielasz przecinkami:

1waterhole = (-2.33, 34.83)
2lion_record = ("Lew", 190, True)
3print(waterhole[0])    # -2.33
4print(lion_record[1])  # 190

Indeksowanie działa dokładnie tak jak w liście, od zera. Krotka może też mieszać typy danych: lion_record przechowuje nazwę, wagę i informację, czy zwierzę jest niebezpieczne.

Przecinek tworzy krotkę

Jest jeden szczegół, na którym potyka się wielu początkujących: to przecinek, a nie nawiasy, tworzy krotkę. Najlepiej widać to przy krotce z jednym elementem, sprawdzonej funkcją type() z pierwszego modułu:

1single = ("Lew",)
2not_a_tuple = ("Lew")
3print(type(single))       # <class 'tuple'>
4print(type(not_a_tuple))  # <class 'str'>

Bez przecinka Python widzi zwykły tekst w nawiasach, takich samych jak w działaniach matematycznych. Z tego samego powodu zapis point = 10.5, 20.3 również tworzy krotkę. Nawiasy są wtedy opcjonalne, ale polecam zawsze je pisać, bo od razu widać, że to krotka.

Niezmienność

Spróbujmy poprawić współrzędną wodopoju tak, jak zrobilibyśmy to w liście:

1waterhole = (-2.33, 34.83)
2waterhole[0] = -2.40  # TypeError!

Python zgłasza błąd TypeError, bo krotki nie da się zmienić po utworzeniu. To nie wada, tylko gwarancja: współrzędne nie zmienią się przez literówkę w innej części programu. Dlatego krotka nie ma metod append(), remove() ani sort(). Zostały jej tylko dwie metody, które czytają dane: count() i index().

Rozpakowywanie krotki

Krotkę z rekordem możesz jednym ruchem rozłożyć na osobne zmienne. Po lewej stronie znaku = podajesz tyle nazw, ile elementów ma krotka:

1name, weight, dangerous = lion_record
2print(name)    # Lew
3print(weight)  # 190

Każda zmienna dostała element z tej samej pozycji, a sama krotka została bez zmian. Jeśli liczba nazw nie zgadza się z liczbą elementów, Python zgłosi ValueError. Rozpakowywanie znasz zresztą z pętli for key, value in catalog.items(), bo metoda items() podaje właśnie krotki z kluczem i wartością.

Kiedy więc krotka, a kiedy lista? Moja zasada: jeśli dane opisują jedną rzecz o stałej budowie, jak współrzędne albo rekord zwierzęcia, wybierz krotkę. Jeśli to kolekcja, która rośnie i maleje, jak dzisiejsze obserwacje, wybierz listę.

Zbiór - rejestr unikalnych gatunków

Wróćmy do zebry, która odwiedziła obóz pięć razy. Zbiór działa jak lista obecności przy wodopoju: każdy gatunek odhaczasz raz, nawet jeśli przyszedł pić dziesięć razy. Najprościej zbudujesz go, przepuszczając listę przez funkcję set():

1sightings = ["Zebra", "Lew", "Zebra", "Słoń", "Zebra"]
2species = set(sightings)
3print(species)       # {'Lew', 'Zebra', 'Słoń'}
4print(len(species))  # 3

Duplikaty zniknęły, a lista sightings nadal ma pięć wpisów, bo funkcja set() zbudowała nową kolekcję. Kolejność na wydruku może być u Ciebie inna, ponieważ zbiór nie pamięta kolejności elementów. Z tego powodu nie ma w nim indeksów i species[0] zakończy się błędem.

Tworzenie i zmienianie zbioru

Zbiór możesz też zapisać wprost, w nawiasach klamrowych. Uwaga na pułapkę: puste nawiasy {} tworzą pusty słownik, a nie zbiór, więc pusty zbiór zapisujesz jako set().

1visitors = {"Zebra", "Lew"}
2visitors.add("Słoń")
3visitors.add("Zebra")
4print(len(visitors))      # 3
5print("Lew" in visitors)  # True

W przeciwieństwie do krotki zbiór jest mutowalny. Metoda add() dopisała słonia, ale druga zebra niczego nie zmieniła, bo już była w rejestrze. Operator in sprawdza obecność bardzo szybko, nawet w ogromnym zbiorze. Do usuwania służą dwie metody: remove(), która przy braku elementu zgłasza błąd, oraz discard(), która wtedy po prostu nic nie robi. Przy niepewnych danych polecam discard().

Operacje na zbiorach

Prawdziwą siłę zbiorów widać dopiero wtedy, gdy porównujesz dwa rejestry. Rano i wieczorem zespół notuje gatunki przy wodopoju, a trzy operatory odpowiadają na trzy różne pytania:

1morning = {"Zebra", "Lew", "Słoń"}
2evening = {"Zebra", "Hipopotam", "Słoń"}
3print(morning & evening)  # {'Zebra', 'Słoń'}
4print(morning | evening)  # {'Zebra', 'Lew', 'Słoń', 'Hipopotam'}
5print(morning - evening)  # {'Lew'}

Operator & to część wspólna, czyli gatunki widziane rano i wieczorem. Operator | to suma: wszystko, co pojawiło się w ciągu dnia, bez powtórzeń. Operator - to różnica, czyli gatunki z rana, których wieczorem zabrakło. Żaden z nich nie zmienia zbiorów morning ani evening, każdy buduje nowy zbiór z wynikiem. Przy różnicy liczy się kolejność: evening - morning da {'Hipopotam'}. Na liczbach wszystko działa tak samo, więc {1, 2, 3} & {2, 3, 4} zwraca {2, 3}.

Którą strukturę wybrać?

Znasz już cztery struktury danych i każda ma swoje miejsce w ekwipunku badacza:

  • lista - uporządkowana i zmienna, dopuszcza duplikaty, jak dziennik obserwacji,
  • krotka - uporządkowana i niezmienna, jak współrzędne albo stały rekord,
  • zbiór - bez kolejności i bez duplikatów, jak rejestr unikalnych gatunków,
  • słownik - pary klucz i wartość, jak katalog odkryć z kartami.

Gdy potrzebujesz unikalnych elementów, wybieraj zbiór zamiast ręcznego pilnowania duplikatów w liście.

Pamiętaj: krotka to wpis atramentem, którego nie zmienisz, a zbiór to rejestr, w którym każdy gatunek występuje tylko raz. W następnej lekcji Darwin pokaże Ci list comprehensions, a w projekcie na końcu modułu krotki i zbiory pomogą Ci przeanalizować dziennik ekspedycji.

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Czym krotka (tuple) różni się od listy?

  2. 2. Jaka jest główna cecha zbioru (set)?

  3. 3. Co zwraca: {1, 2, 3} & {2, 3, 4}?

Zadania praktyczne w grze

  • Edytor kodu

    Utwórz krotkę 'coordinates' z wartościami: 10.5, 20.3, 15.7

  • Edytor kodu

    Usuń duplikaty z listy numbers = [1, 2, 2, 3, 3, 3, 4] funkcją set(), zapisz wynik w zmiennej unique i wyświetl go

  • Układanie w poziomie

    Ułóż elementy w prawidłowej kolejności:

  • Klikanie w kolejności

    Kliknij elementy w prawidłowej kolejności:

  • Układanie w pionie

    Uporządkuj kroki:

Przydatne artykuły