Słownik w Pythonie — Pytania Rekrutacyjne (Dict i Set)
Praca i rekrutacja

Słownik w Pythonie — Pytania Rekrutacyjne (Dict i Set)

O
Odpytywarka.pl
| | 23 wyświetlenia

Dlaczego Słowniki i Sety Padają na Rozmowach

Słowniki i sety to drugi najczęściej testowany temat na rozmowach z Pythona — zaraz po listach. Pytanie typu "co wybierzesz: dict czy list?" pojawia się w ~30% wywiadów junior, bo odpowiedź zdradza, czy kandydat myśli o złożoności i strukturach danych.

Ten artykuł zbiera pytania rekrutacyjne o słowniki i sety w Pythonie w jednym miejscu: definicje, idiomatyczne wzorce, złożoności operacji, klasyczne pułapki. Każdy snippet jest działający — uruchom lokalnie i przećwicz przed spotkaniem.

Wszystkie przykłady zakładają Python 3.12+. Identifiers w kodzie są po angielsku (konwencja branżowa).

Dict — pary key-value i hashing

dict przechowuje pary key-value: klucz identyfikuje wartość, a klucze w jednym słowniku są unikalne. Najczęstsze zastosowania to mapowanie, cache, indeks albo grupowanie danych.

Dostęp do wartości używa hash klucza. Dla d[key], d.get(key), key in d, przypisania i usuwania średnia złożoność to O(1), a pesymistyczna O(n) przy wielu kolizjach hash.

scores = {"alice": 10, "bob": 7}
scores["bob"] = 9
scores["carol"] = 8

print(scores["bob"])           # 9
print(scores.get("missing"))   # None

Pułapka rekrutacyjna: d[key] dla brakującego klucza rzuca KeyError, a dict.get(key) domyślnie zwraca None. Jeśli None jest poprawną wartością biznesową, podaj jawny default albo sprawdzaj key in d.

Set — unikalne wartości i membership testing

set przechowuje unikalne elementy bez par key-value. Jest dobry, gdy pytanie brzmi: "czy element już wystąpił?", "czy są duplikaty?" albo "jaka jest część wspólna dwóch kolekcji?".

Podobnie jak dict, set opiera się na hashingu. Operacje x in s i s.add(x) mają średnio O(1), a pesymistycznie O(n) przy patologicznych kolizjach hash.

tags = {"python", "sql", "python"}

print(len(tags))           # 2 — duplikat "python" usunięty
print("python" in tags)    # True
print(sorted(tags))        # ['python', 'sql']

Pułapka rekrutacyjna: {} tworzy pusty dict, NIE pusty set. Pusty set twórz przez set(), bo {1, 2} jest setem, ale same puste klamry historycznie oznaczają słownik.

Kiedy dict, kiedy set — wzorce decyzyjne

Wybierz dict, gdy oprócz klucza potrzebujesz powiązanej wartości: liczby wystąpień, danych użytkownika, pozycji w tablicy, wyniku obliczenia. To jest wzorzec mapping: z jednego obiektu przechodzisz do drugiego.

Wybierz set, gdy sama obecność elementu jest informacją. Typowe zadania junior: wykrycie duplikatu, szybkie filtrowanie już przetworzonych rekordów, sprawdzenie przecięcia tagów lub walidacja dozwolonych wartości.

user_to_role = {"alice": "admin", "bob": "viewer"}
blocked_users = {"mallory", "trent"}

print(user_to_role["alice"])         # admin
print("alice" in blocked_users)      # False

Pułapka rekrutacyjna: d[k] = v na nowym kluczu nie rzuca błędu, tylko tworzy wpis. Jeśli aktualizacja ma dotyczyć wyłącznie istniejącego klucza, użyj if k in d:; przy inicjalizacji rozważ setdefault.

Złożoności operacji na dict i set

Dla dict operacje get item, set item, delete item i k in d są średnio O(1). Pesymistycznie mogą być O(n), bo wiele różnych kluczy może trafić w kolidujące hashe.

Dla set x in s i dodawanie są średnio O(1). Union s | t ma O(len(s) + len(t)). Intersection s & t ma średnio O(min(len(s), len(t))), a pesymistycznie O(len(s) * len(t)) przy złych kolizjach.

a = {1, 2, 3}
b = {3, 4}

print(sorted(a | b))   # [1, 2, 3, 4] — union O(s + t)
print(sorted(a & b))   # [3] — intersection O(min(s, t))

Pułapka rekrutacyjna: średnie O(1) nie znaczy "zawsze natychmiast". Resizing tablicy hash i kolizje mogą podbić koszt pojedynczej operacji, więc w odpowiedzi rekrutacyjnej warto dodać "average case".

Hashable vs unhashable — kluczowe ograniczenie

Hashable oznacza, że obiekt ma hash niezmienny przez całe życie obiektu, obsługuje porównywanie, a obiekty równe mają ten sam hash. str, int, float, bool i tuple z hashable elementami zwykle działają jako klucze.

list, dict i set to wbudowane mutable kontenery bez hasha, więc nie mogą być kluczami ani elementami seta. frozenset może być kluczem, jeśli wszystkie jego elementy są hashable.

point = (10, 20)
locations = {point: "warehouse"}
skills = {frozenset({"python", "sql"})}

print(locations[(10, 20)])                          # warehouse
print(frozenset({"python", "sql"}) in skills)       # True — frozenset equality jest order-independent

Pułapka rekrutacyjna: zwykły set nie jest hashable, a frozenset jest hashable tylko wtedy, gdy zawiera wyłącznie hashable elementy. set nigdy nie będzie kluczem dict; frozenset — tak, pod warunkiem że wszystkie elementy są hashable.

Kolejność wstawiania w dict (od Python 3.7)

Od Pythona 3.7 dict gwarantuje insertion order jako cechę języka. Aktualizacja istniejącego klucza nie zmienia jego pozycji, a usunięcie i ponowne dodanie klucza wstawia go na końcu.

set nie gwarantuje sensownej kolejności biznesowej. Możesz zobaczyć jakiś porządek przy wypisywaniu, ale nie należy na nim opierać testów, logiki programu ani odpowiedzi w zadaniu rekrutacyjnym.

data = {"a": 1, "b": 2, "c": 3}
data["b"] = 20
print(list(data))   # ['a', 'b', 'c'] — update NIE zmienia pozycji 'b'

del data["b"]
data["b"] = 200
print(list(data))   # ['a', 'c', 'b'] — delete + reinsert wstawia 'b' na końcu

Pułapka rekrutacyjna: dict.keys() i dict.values() zwracają view objects, nie listy. Te widoki dynamicznie odzwierciedlają zmiany w słowniku — po dodaniu klucza widok pokaże nowy stan.

Operacje algebry zbiorów na set

Sety mają czytelne operatory algebry zbiorów: | (union), & (intersection), - (difference), ^ (symmetric difference). To często skraca rozwiązanie zadań o tagach, uprawnieniach lub duplikatach.

a | b zawiera elementy z obu zbiorów, a & b wspólne, a - b elementy tylko z a, a a ^ b elementy występujące dokładnie po jednej stronie.

left = {"alice", "bob", "carol"}
right = {"bob", "dave"}

print(sorted(left | right))   # ['alice', 'bob', 'carol', 'dave'] — union
print(sorted(left & right))   # ['bob'] — intersection
print(sorted(left - right))   # ['alice', 'carol'] — difference
print(sorted(left ^ right))   # ['alice', 'carol', 'dave'] — symmetric difference

Pułapka rekrutacyjna: operatory |, &, -, ^ wymagają set-like operandów. Metody (np. set.difference(iterable)) przyjmują dowolny iterable. Metoda wywołana na set zwraca set, na frozenset zwraca frozenset. Wynik traci duplikaty wejścia.

Dict i set comprehension

Dict comprehension tworzy słownik z wyrażenia {key: value for item in iterable}. Używaj go, gdy transformacja jest krótka, lokalna i czytelna — np. budowanie indeksu po id.

Set comprehension ma składnię {expr for item in iterable} i automatycznie usuwa duplikaty. Dobry do normalizacji wartości, ekstrakcji unikalnych pól i przygotowania szybkiego membership testing.

names = ["Alice", "Bob", "Alice"]

name_lengths = {name.lower(): len(name) for name in names}
unique_names = {name.lower() for name in names}

print(name_lengths)            # {'alice': 5, 'bob': 3} — duplikat 'Alice' nadpisał wartość
print(sorted(unique_names))    # ['alice', 'bob']

Pułapka rekrutacyjna: jeśli comprehension ma wiele warunków, zagnieżdżeń albo efektów ubocznych, zwykła pętla jest lepsza. Rekruterzy częściej nagradzają czytelność niż "sprytny" one-liner.

Aliasing i kopiowanie dict

Przypisanie a = b nie kopiuje słownika. Obie zmienne wskazują ten sam obiekt, więc zmiana przez jedną nazwę będzie widoczna przez drugą.

Do płytkiego kopiowania użyj copy() albo dict(original). Płytka kopia kopiuje zewnętrzny słownik, ale nie kopiuje głęboko zagnieżdżonych list, słowników ani innych mutowalnych wartości.

original = {"count": 1}
alias = original
copy_data = original.copy()

alias["count"] = 2
copy_data["count"] = 3

print(original["count"])    # 2 — alias zmienił oryginalny obiekt
print(copy_data["count"])   # 3 — copy_data jest niezależną kopią

Pułapka rekrutacyjna: aliasing często psuje rozwiązania z cache, konfiguracją albo stanem testów. Jeśli funkcja ma nie zmieniać wejścia, jawnie skopiuj strukturę albo buduj nową.

Bonus: defaultdict — auto-init brakujących kluczy

collections.defaultdict przyjmuje factory, która tworzy wartość dla brakującego klucza. Najczęstszy przypadek junior to "lista per klucz" — grupowanie rekordów bez ręcznego if key not in d.

To nadal jest słownik, ale groups[key] dla brakującego klucza utworzy wpis. Dzięki temu kod grupowania jest krótszy, a intencja czytelniejsza niż przy powtarzanym sprawdzaniu obecności.

from collections import defaultdict

groups = defaultdict(list)
groups["python"].append("alice")
groups["python"].append("bob")

print(groups["python"])     # ['alice', 'bob']
print(groups["missing"])    # [] — odczyt brakującego klucza UTWORZYŁ wpis przez factory
print(dict(groups))         # {'python': ['alice', 'bob'], 'missing': []}

Pułapka rekrutacyjna: defaultdict tworzy klucz przy odczycie przez groups[key]. Jeśli chcesz tylko sprawdzić, czy coś istnieje, użyj key in groups albo groups.get(key), żeby przypadkiem nie zmienić struktury.

Bonus: Counter — zliczanie i most_common

collections.Counter jest wyspecjalizowanym słownikiem do zliczania hashable elementów. W zadaniach rekrutacyjnych zastępuje ręczne inkrementowanie, gdy liczysz znaki, słowa, statusy, identyfikatory lub częstości odpowiedzi.

Counter.most_common(n) zwraca n najczęstszych elementów z licznikami. Counter obsługuje też arytmetykę: dodawanie sumuje liczniki, odejmowanie redukuje wartości, a operacje & i | biorą minima i maksima liczników.

from collections import Counter

first = Counter(["red", "blue", "red"])
second = Counter(["red", "green"])

print(first.most_common(1))   # [('red', 2)]
print(first + second)         # Counter({'red': 3, 'blue': 1, 'green': 1})
print(first - second)         # Counter({'red': 1, 'blue': 1})
print(first & second)         # Counter({'red': 2}) — minima liczników (intersection)

Pułapka rekrutacyjna: sortowanie słownika i Countera wymaga precyzji. sorted(d) sortuje klucze, a sortowanie po wartościach zapisuj jako sorted(d.items(), key=lambda item: item[1]), najlepiej z reverse=True dla malejących wyników.

Sygnały rozpoznawcze: kiedy dict, kiedy set

Sygnał → wybór:

"czy widziałem już tę wartość"        → set (membership test)
"jaka informacja przypisana do        → dict (key-value lookup)
 klucza"
"unikalność elementów"                → set (deduplication)
"liczenie wystąpień"                  → collections.Counter (subclass dict)
"grupowanie X po cesze Y"             → dict[Y].append(X) lub
                                        defaultdict(list)
"operacje algebraiczne (suma /        → set z `|`, `&`, `-`, `^`
 część wspólna / różnica)"
"top-K najczęstszych"                 → Counter.most_common(K)
"automatyczna domyślna wartość        → defaultdict(factory)
 dla brakującego klucza"

Sygnały odwrotne — kiedy NIE dict/set: porządek (set non-deterministic, dict insertion order ale nie sortowany — szczegóły Drzewa Binarne), range query (→ bisect), priority (→ heap).

Najczęstsze błędy

{} to pusty dict, nie pusty set. Klamry {1, 2} z elementami to set, ale same puste klamry historycznie oznaczają dict. Mechanizm błędu: kandydat pisze seen = {} myśląc że ma set, potem seen.add(x) rzuca AttributeError: 'dict' object has no attribute 'add'. Naprawa: zawsze seen = set() dla pustego setu.

Aliasing zamiast kopii. b = a nie kopiuje dict, tylko nadaje drugą nazwę temu samemu obiektowi — modyfikacja b["key"] = x zmienia też a. Mechanizm: Python przekazuje referencję. Naprawa: b = a.copy() (shallow) albo copy.deepcopy(a) (zagnieżdżone struktury).

d[k] = v na nowym kluczu nie rzuca błędu. Tiche tworzenie wpisów może maskować literówki w nazwach kluczy. Mechanizm błędu: d["typo"] = 5 zamiast d["type"] = 5 — kod działa, ale stan jest błędny. Naprawa: if k in d: d[k] = v gdy oczekujesz że klucz istnieje, albo użyj typed dict / dataclass dla strukturalnych danych.

set nie jest hashable. Próba s = {{1, 2}, {3, 4}} (set of sets) rzuca TypeError. Mechanizm: set jest mutable, więc nie ma stabilnego hash. Naprawa: użyj frozenset jako elementu — s = {frozenset({1, 2}), frozenset({3, 4})}. Szczegóły: Haszowanie w Zadaniach.

defaultdict[key] tworzy klucz przy samym odczycie. Mechanizm błędu: if d[key] == something: przy braku klucza nie rzuca KeyError, ale tworzy key z domyślną wartością — modyfikuje state przy czytaniu. Naprawa: sprawdzaj przez if key in d: albo użyj zwykłego dict.get(key, default) dla read-only checks.

Najczęściej zadawane pytania

Czy {} to pusty dict czy pusty set?

To pusty dict. Pusty set tworzysz przez set(). Klamry {1, 2} z elementami to set, ale same puste klamry historycznie oznaczają słownik (dict istniał w Pythonie zanim set otrzymał skróconą składnię).

Jaka jest złożoność lookup w dict?

Średnio O(1), pesymistycznie O(n). Dict używa hashowania kluczy, więc dostęp przez d[key], d.get(key), key in d jest stały w średnim przypadku. Pesymistyczny O(n) zachodzi przy patologicznych kolizjach hash, które są rzadkie z domyślnymi typami.

Co to znaczy, że obiekt jest hashable?

Obiekt ma hash niezmienny przez całe życie obiektu, obsługuje porównywanie, a obiekty równe mają ten sam hash. Niezmienne typy wbudowane (str, int, tuple z hashable elementami) są hashable. list, dict, set — nie. frozenset — tak, jeśli elementy są hashable.

Czy dict zachowuje kolejność wstawiania?

Tak, od Pythona 3.7 to gwarantowana cecha języka. Aktualizacja istniejącego klucza nie zmienia jego pozycji. Usunięcie i ponowne dodanie wstawia klucz na końcu. set natomiast NIE ma takiej gwarancji.

Czym różni się defaultdict od zwykłego dict?

defaultdict(factory) automatycznie tworzy wartość dla brakującego klucza, zamiast rzucać KeyError. Najczęstszy wzorzec: defaultdict(list) do grupowania (groups[key].append(item) bez ręcznego sprawdzania if key not in groups).

Kiedy użyć Counter zamiast ręcznego zliczania?

Zawsze, gdy liczysz wystąpienia hashable elementów (znaki, słowa, statusy). Counter(iterable) w jednej linii daje słownik z licznikami. Plus most_common(n) zwraca top-n bez własnego sortowania, a operatory +, -, &, | pozwalają na arytmetykę między Counterami.

Powiązane tematy

Sprawdź swoją wiedzę quizem

Najlepszy sposób na utrwalenie wiedzy z dictów i setów to przerobienie ich w formie quizu. Wygenerowaliśmy quiz na bazie tego artykułu — odpowiedz na 15 pytań i sprawdź, gdzie masz luki przed rozmową.

Rozwiąż quiz: Pythonowe słowniki i sety

Możesz też wgrać własny PDF z notatkami (do 20 MB) na odpytywarka.pl — w 30-60 sekund wygenerujemy quiz z 15 pytaniami jednokrotnego wyboru z pierwszych 3 stron dokumentu, za darmo i bez rejestracji do rozwiązania.

Źródła

O

Odpytywarka.pl

Platforma do automatycznego generowania quizów z materiałów edukacyjnych. Artykuły oparte na badaniach z zakresu psychologii uczenia się i edukacji.

Więcej o platformie

Powiązane artykuły

Stwórz własny quiz

Zamień swoje notatki, podręczniki lub dowolny PDF w interaktywny quiz. Nauka jeszcze nigdy nie była tak prosta!