← Strona główna

08 — Krotki i zbiory

Niemutowalne krotki, zbiory bez duplikatów, operacje teoriomnogościowe.

Struktury i pętle

1. Krotki (tuple) — podstawy

Krotka to niemutowalna, uporządkowana sekwencja elementów. Wygląda jak lista, ale po utworzeniu nie można jej modyfikować — nie można dodawać, usuwać ani zmieniać elementów.

  • Tworzona w nawiasach okrągłych: (elem1, elem2, ...)
  • Nawiasy są opcjonalne — wystarczy przecinek: a, b = 1, 2
  • Jednoelementowa krotka wymaga przecinka: (5,) — bez niego to po prostu 5
  • Może zawierać elementy różnych typów
  • Indeksowanie i wycinki działają tak samo jak dla list
  • Niemutowalna — szybsza i bezpieczniejsza niż lista
Kiedy używać krotki zamiast listy? Gdy dane nie powinny się zmieniać — współrzędne punktu, RGB koloru, data, wymiary. Krotki mogą być kluczami słownika, listy nie.
Przykład — tworzenie i dostęp
# Tworzenie krotek
punkt    = (3, 7)
kolor    = (255, 128, 0)
dane     = ("Kasia", 17, "3A")
pusta    = ()
jedna    = (42,)      # przecinek obowiązkowy!

# Bez nawiasów — też krotka
wspolrz  = 10, 20
print(type(wspolrz))  # <class 'tuple'>

# Indeksowanie — jak lista
print(punkt[0])       # 3
print(kolor[-1])      # 0
print(dane[1:])       # (17, '3A')

# Rozpakowywanie
x, y = punkt
print(x, y)           # 3 7

imie, wiek, klasa = dane
print(imie, wiek)     # Kasia 17

# _ dla ignorowanych wartości
imie, _, klasa = dane
print(imie, klasa)    # Kasia 3A

2. Metody i operacje na krotkach

Krotka ma tylko dwie metody (bo nie można jej modyfikować). Operacje odczytu działają tak samo jak dla list.

Operacja/MetodaDziałaniePrzykład
len(t)długość krotkilen((1,2,3)) → 3
t.count(x)liczba wystąpień x(1,2,1).count(1) → 2
t.index(x)indeks pierwszego x(3,5,7).index(5) → 1
x in tczy x jest w krotce3 in (1,3,5) → True
t1 + t2łączenie krotek(1,2)+(3,4) → (1,2,3,4)
t * npowielenie(0,)*3 → (0,0,0)
min(t), max(t)min i maxjak dla list
sorted(t)zwraca posortowaną listęzwraca list, nie tuple
Niemutowalność — co to znaczy w praktyce? t[0] = 99TypeError. Nie można też dodać ani usunąć elementu. Można jednak tworzyć nowe krotki z istniejących: nowa = t + (99,).
Przykład — operacje na krotkach
oceny = (4, 5, 3, 5, 4, 2, 5)

print(len(oceny))          # 7
print(oceny.count(5))      # 3
print(oceny.index(3))      # 2
print(min(oceny))          # 2
print(max(oceny))          # 5
print(sum(oceny))          # 28

# Łączenie
a = (1, 2, 3)
b = (4, 5, 6)
c = a + b
print(c)                   # (1, 2, 3, 4, 5, 6)

# Konwersja lista ↔ krotka
lista  = [1, 2, 3]
krotka = tuple(lista)
print(krotka)              # (1, 2, 3)

z_krotki = list(krotka)
print(z_krotki)            # [1, 2, 3]

# Krotki jako klucze słownika
pozycje = {(0,0): "start", (5,3): "cel"}
print(pozycje[(0,0)])      # start

# Swap bez zmiennej pomocniczej
a, b = 10, 20
a, b = b, a
print(a, b)                # 20 10

3. Zbiory (set) — podstawy

Zbiór to nieuporządkowana kolekcja unikalnych elementów. Automatycznie eliminuje duplikaty. Idealny gdy ważna jest przynależność, nie kolejność.

  • Tworzony w nawiasach klamrowych: {elem1, elem2, ...}
  • Pusty zbiór: set() — nie {} bo to pusty słownik!
  • Elementy muszą być niemutowalne (str, int, tuple) — nie może zawierać list
  • Nie zachowuje kolejności — nie można indeksować
  • Sprawdzanie przynależności (in) jest bardzo szybkie — O(1)
  • Mutowalny — można dodawać i usuwać elementy
Pusty zbiór s = {} tworzy pusty słownik, nie zbiór! Pusty zbiór to s = set(). To częsty błąd — uważaj na egzaminie.
Przykład — tworzenie zbiorów
# Tworzenie zbiorów
owoce  = {"jabłko", "gruszka", "śliwka"}
liczby = {1, 2, 3, 4, 5}
pusty  = set()

# Duplikaty są automatycznie usuwane
z_dup  = {1, 2, 2, 3, 3, 3, 4}
print(z_dup)    # {1, 2, 3, 4}

# Z listy — usunięcie duplikatów
lista     = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
unikalne  = set(lista)
print(unikalne) # {1, 2, 3, 4, 5, 6, 9}

# Konwersja z powrotem na listę
czysta    = sorted(list(unikalne))
print(czysta)   # [1, 2, 3, 4, 5, 6, 9]

# Sprawdzanie przynależności — szybkie!
print("jabłko" in owoce)    # True
print("mango" in owoce)     # False
print(7 in liczby)           # False

4. Metody zbiorów

MetodaDziałanie
add(x)dodaje element x
remove(x)usuwa x — błąd gdy brak
discard(x)usuwa x — brak błędu gdy nie ma
pop()usuwa i zwraca losowy element
clear()usuwa wszystkie elementy
copy()płytka kopia zbioru
update(s2)dodaje wszystkie elementy z s2
len(s)liczba elementów
remove() vs discard() remove(x) rzuca KeyError gdy x nie istnieje. discard(x) po prostu nic nie robi. Na egzaminie gdy nie masz pewności czy element jest w zbiorze — używaj discard().
Przykład — metody zbiorów
s = {1, 2, 3, 4, 5}

# Dodawanie
s.add(6)
s.add(3)    # 3 już jest — nic się nie zmienia
print(s)    # {1, 2, 3, 4, 5, 6}

# Usuwanie
s.remove(6)
print(s)    # {1, 2, 3, 4, 5}

s.discard(99)   # nie ma 99 — brak błędu
s.discard(5)
print(s)    # {1, 2, 3, 4}

# update — dodaj z innego zbioru lub listy
s.update([10, 11, 12])
print(s)    # {1, 2, 3, 4, 10, 11, 12}

# Iterowanie — kolejność nieokreślona
for elem in s:
    print(elem, end=" ")

# Rozmiar
print(len(s))   # 7

5. Operacje teoriomnogościowe

Zbiory w Pythonie obsługują klasyczne operacje z teorii zbiorów. Można je wykonywać zarówno operatorami jak i metodami.

OperacjaOperatorMetodaZnaczenie
SumaA | BA.union(B)elementy w A lub B
PrzecięcieA & BA.intersection(B)elementy w A i B
RóżnicaA - BA.difference(B)elementy w A, nie w B
Różnica sym.A ^ BA.symmetric_difference(B)w A lub B, ale nie w obu
PodzbiórA <= BA.issubset(B)czy A ⊆ B
NadzbiórA >= BA.issuperset(B)czy A ⊇ B
RozłącznośćA.isdisjoint(B)czy A ∩ B = ∅
Przykład — operacje na zbiorach
A = {1, 2, 3, 4, 5}
B = {4, 5, 6, 7, 8}

# Suma — wszystko z obu
print(A | B)        # {1, 2, 3, 4, 5, 6, 7, 8}

# Przecięcie — wspólne
print(A & B)        # {4, 5}

# Różnica — w A, nie w B
print(A - B)        # {1, 2, 3}
print(B - A)        # {6, 7, 8}

# Różnica symetryczna
print(A ^ B)        # {1, 2, 3, 6, 7, 8}

# Relacje między zbiorami
C = {1, 2}
print(C <= A)       # True — C jest podzbiorem A
print(A >= C)       # True — A jest nadzbiorem C
print(A.isdisjoint({10, 11}))  # True — brak wspólnych

# Praktyczne zastosowanie
klasa_3A = {"Kasia", "Marek", "Zofia", "Piotr"}
klasa_3B = {"Marek", "Ola", "Tomek", "Zofia"}

w_obu    = klasa_3A & klasa_3B
print(f"W obu klasach: {w_obu}")  # Marek, Zofia
tylko_3A = klasa_3A - klasa_3B
print(f"Tylko w 3A: {tylko_3A}")

6. frozenset i porównanie struktur danych

frozenset to niemutowalny zbiór — jak krotka dla listy. Może być kluczem słownika lub elementem innego zbioru.

Cechalisttuplesetfrozenset
Mutowalny
Kolejność
Duplikaty
Indeksowanie
Klucz słownika
Szybkie inO(n)O(n)O(1)O(1)
Wybór struktury Lista — gdy kolejność ważna i dane się zmieniają. Krotka — stałe dane, klucze słownika. Zbiór — gdy chcesz unikalności i szybkiego sprawdzania przynależności.
Przykład — frozenset i wybór struktury
# frozenset — niemutowalny zbiór
fs = frozenset([1, 2, 3, 4])
print(fs)           # frozenset({1, 2, 3, 4})

# Może być kluczem słownika
grupy = {
    frozenset({"Kasia", "Marek"}): "Projekt A",
    frozenset({"Zofia", "Piotr"}): "Projekt B"
}

# Szybkie sprawdzanie przynależności — set vs list
import time

duza_lista = list(range(1_000_000))
duzy_zbior = set(duza_lista)

# set jest wielokrotnie szybszy dla 'in'
print(999_999 in duzy_zbior)  # True — O(1)
print(999_999 in duza_lista)  # True — O(n), wolniej

# Usuwanie duplikatów z zachowaniem kolejności
def unikalne_z_kolejnoscia(lst):
    widziane = set()
    wynik    = []
    for x in lst:
        if x not in widziane:
            widziane.add(x)
            wynik.append(x)
    return wynik

lst = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
print(unikalne_z_kolejnoscia(lst))
# [3, 1, 4, 5, 9, 2, 6]

Zadania przykładowe z omówieniem

Zadanie 1 Wspólne zainteresowania łatwe

Wczytaj zainteresowania dwóch osób (oddzielone przecinkami). Wypisz: zainteresowania wspólne, unikalne dla pierwszej osoby, unikalne dla drugiej, oraz wszystkie łącznie bez powtórzeń.

Rozwiązanie
wejscie1 = input("Zainteresowania osoby 1 (po przecinku): ")
wejscie2 = input("Zainteresowania osoby 2 (po przecinku): ")

zbior1 = set(z.strip().lower() for z in wejscie1.split(","))
zbior2 = set(z.strip().lower() for z in wejscie2.split(","))

wspolne    = zbior1 & zbior2
tylko1     = zbior1 - zbior2
tylko2     = zbior2 - zbior1
wszystkie  = zbior1 | zbior2

print(f"Wspólne:       {wspolne or 'brak'}")
print(f"Tylko osoba 1: {tylko1 or 'brak'}")
print(f"Tylko osoba 2: {tylko2 or 'brak'}")
print(f"Wszystkie:     {wszystkie}")
Omówienie krok po kroku
  1. Set comprehension z split()
    set(z.strip().lower() for z in wejscie.split(",")) — dzielimy po przecinku, każdy element czyścimy ze spacji i zamieniamy na małe litery, a set eliminuje duplikaty.
  2. Operatory zbiorów
    & daje elementy w obu zbiorach, - daje elementy tylko w lewym, | daje wszystkie elementy z obu. Czytelne i krótkie.
  3. or 'brak' przy pustym zbiorze
    Pusty zbiór jest falsy. wspolne or 'brak' wypisze "brak" gdy zbiór jest pusty — zamiast brzydkiego set().
Zadanie 2 Zwracanie wielu wartości z funkcji łatwe

Napisz funkcję statystyki(liczby) przyjmującą listę liczb i zwracającą krotkę: (min, max, suma, średnia). Użyj jej wynik w rozpakowaniu.

Rozwiązanie
def statystyki(liczby):
    if not liczby:
        return None
    mini    = min(liczby)
    maxi    = max(liczby)
    suma    = sum(liczby)
    srednia = suma / len(liczby)
    return mini, maxi, suma, srednia

dane = [5, 3, 8, 1, 9, 2, 7, 4, 6]

mini, maxi, suma, srednia = statystyki(dane)
print(f"Min:     {mini}")
print(f"Max:     {maxi}")
print(f"Suma:    {suma}")
print(f"Średnia: {srednia:.2f}")

# Można też używać jako krotki
wynik = statystyki(dane)
print(f"Zakres: {wynik[0]}–{wynik[1]}")
Omówienie krok po kroku
  1. return mini, maxi, suma, srednia
    Python pakuje te cztery wartości w krotkę automatycznie. To najczystszy sposób zwracania wielu wartości z funkcji.
  2. Rozpakowywanie krotki
    mini, maxi, suma, srednia = statystyki(dane) — lewa strona musi mieć dokładnie tyle zmiennych ile elementów w krotce. Każda zmienna dostaje odpowiednią wartość.
  3. Zabezpieczenie przed pustą listą
    if not liczby: return None — pusta lista jest falsy. Bez tego min([]) rzuciłby ValueError.
Zadanie 3 Sprawdzanie unikalności i duplikatów średnie

Wczytaj n liczb całkowitych. Używając zbiorów wypisz: które liczby się powtarzają, ile jest unikalnych wartości oraz czy wszystkie liczby są różne.

Rozwiązanie
n      = int(input("Ile liczb? "))
liczby = []
for i in range(n):
    liczby.append(int(input(f"Liczba {i+1}: ")))

zbior     = set(liczby)
duplikaty = set()

for l in liczby:
    if liczby.count(l) > 1:
        duplikaty.add(l)

print(f"Wczytano:       {liczby}")
print(f"Unikalne:       {sorted(zbior)}")
print(f"Ile unikalnych: {len(zbior)}")

if duplikaty:
    print(f"Powtarzające:   {sorted(duplikaty)}")
else:
    print("Wszystkie liczby są różne!")
Omówienie krok po kroku
  1. set(liczby) — natychmiastowe unikalne
    Konwersja listy na zbiór automatycznie usuwa duplikaty. len(zbior) to liczba unikalnych wartości.
  2. Znajdowanie duplikatów przez count()
    Dla każdej liczby sprawdzamy ile razy występuje na liście. Jeśli więcej niż raz — trafia do zbioru duplikatów. Zbiór gwarantuje że każdy duplikat pojawi się tylko raz.
  3. sorted() na zbiorze
    Zbiory nie mają kolejności — sorted(zbior) zwraca posortowaną listę unikalnych elementów. Czytelniejszy wynik.

Zadania do samodzielnego rozwiązania

Ćwicz rozpakowywanie krotek i operacje na zbiorach — to często pojawia się na egzaminie w nieoczywistych miejscach.

1★☆☆

Zamiana bez zmiennej

Wczytaj trzy liczby a, b, c. Wykonaj rotację: a→b, b→c, c→a — używając rozpakowywania krotki w jednej linii.

Wskazówka: a, b, c = c, a, b
2★☆☆

Unikalne litery

Wczytaj dwa słowa. Wypisz litery które są tylko w pierwszym, tylko w drugim i w obu. Ignoruj wielkość liter i spacje.

Wskazówka: set(slowo.lower()), operatory &, -, |
3★★☆

Funkcja minmax

Napisz funkcję minmax(lista) zwracającą krotkę (minimum, maksimum) bez użycia wbudowanych min() i max(). Przetestuj na kilku listach.

Wskazówka: zainicjuj obie wartości pierwszym elementem, iteruj resztą
4★★☆

Sito z użyciem zbioru

Zaimplementuj Sito Eratostenesa używając zbioru zamiast listy. Zacznij od zbioru wszystkich liczb od 2 do n i odejmuj wielokrotności.

Wskazówka: zbior -= {i*k for k in range(2, n//i+1)}
5★★☆

Krotki w słowniku

Stwórz słownik gdzie kluczem jest krotka (x, y) a wartością odległość punktu od początku układu. Wypełnij dla punktów (0–3, 0–3). Wypisz posortowane po odległości.

Wskazówka: (x**2 + y**2)**0.5, sorted(d.items(), key=lambda x: x[1])
6★★★

Pokrycie zbiorami

Masz uniwersum liczb 1–20 i listę podzbiorów. Znajdź minimalną liczbę podzbiorów potrzebną do pokrycia całego uniwersum (greedy — zawsze wybieraj podzbiór pokrywający najwięcej niepokrytych elementów).

Wskazówka: while niepokryte, wybierz podzbiór z max len(p & niepokryte)