{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "k00",
   "metadata": {},
   "source": [
    "# Słowniki, krotki i zbiory w Pythonie\n",
    "\n",
    "*Wersja notatnika: 2026-10-08 · na bazie wersji z 2025-10-07 · przygotowanie: dr inż. Szczepan Górtowski*\n",
    "\n",
    "Trzy kolekcje, które uzupełniają listę:\n",
    "- **słownik (`dict`)** — odwzorowanie klucz → wartość, szukanie po kluczu w stałym czasie,\n",
    "- **krotka (`tuple`)** — niemutowalna sekwencja, może być kluczem słownika,\n",
    "- **zbiór (`set`)** — unikatowe elementy bez kolejności, działania jak w matematyce.\n",
    "\n",
    "Po notatniku wiesz też, **jak** słownik znajduje klucz tak szybko i dlaczego zbiór nie pamięta kolejności.\n",
    "\n",
    "### Jak korzystać\n",
    "1. Uruchamiaj komórki po kolei: `Ctrl + Enter` albo `Shift + Enter`.\n",
    "2. Przy ramkach **Przewidź, zanim uruchomisz** najpierw zgadnij, potem uruchom, na końcu rozwiń odpowiedź.\n",
    "3. W zadaniach edytuj komórki z `TODO` i uruchom komórkę **Sprawdź**."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k01",
   "metadata": {},
   "source": [
    "## Spis treści\n",
    "1. Słownik (`dict`): po co i jak\n",
    "2. **Nowe:** jak słownik znajduje klucz — hash i szuflady\n",
    "3. Operacje na słownikach: dodawanie, usuwanie, łączenie\n",
    "4. Iteracja po słowniku: klucze, wartości, pary\n",
    "5. Bezpieczny dostęp: `get`, domyślne wartości, zagnieżdżenia\n",
    "6. **Nowe:** kolejność kluczy i `dict.fromkeys`; słownik pod maską\n",
    "7. Krotki (`tuple`): niemutowalne sekwencje i rozpakowywanie — z pułapką\n",
    "8. Zbiory (`set`): unikalność, działania zbiorowe — z dwiema pułapkami\n",
    "9. Comprehensions dla `dict` i `set`\n",
    "10. Kiedy `list`, `tuple`, `set`, `dict`?\n",
    "11. **Nowe:** JSON, czyli słownik w podróży\n",
    "12. Zadania 3.0–3.3 i zadanie 3.4 dla chętnych"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k02",
   "metadata": {},
   "source": [
    "## 1) Słownik (`dict`): po co i jak\n",
    "\n",
    "Słownik przechowuje pary **klucz → wartość**. Klucze muszą być **haszowalne** (np. `str`, `int`, `float`, `tuple`\n",
    "z haszowalnymi elementami), wartości mogą być dowolne. Zastosowania: konfiguracja, indeks produktów po identyfikatorze,\n",
    "liczniki i agregacje."
   ]
  },
  {
   "cell_type": "code",
   "id": "k03",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "person = {\n",
    "    \"first_name\": \"Anna\",\n",
    "    \"last_name\": \"Nowak\",\n",
    "    \"age\": 28,\n",
    "    \"skills\": [\"python\", \"sql\"],\n",
    "}\n",
    "print(person[\"first_name\"])\n",
    "print(person)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k04",
   "metadata": {},
   "source": [
    "## 2) Nowe: jak słownik znajduje klucz — hash i szuflady\n",
    "\n",
    "Słownik to szafa z ponumerowanymi szufladami. Dla klucza Python liczy **hash** — liczbę całkowitą — i z niej wybiera\n",
    "szufladę. Dlatego nie przegląda wszystkich kluczy po kolei: idzie prosto do właściwej szuflady. Szukanie po kluczu\n",
    "trwa średnio **O(1)**, niezależnie od rozmiaru słownika.\n",
    "\n",
    "Dla liczb całkowitych hash to zwykle sama liczba. Dla napisów hash jest **losowany przy każdym starcie Pythona**\n",
    "(ochrona przed atakami na serwery) — uruchom ponownie środowisko i porównaj."
   ]
  },
  {
   "cell_type": "code",
   "id": "k05",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "SZUFLAD = 8\n",
    "for klucz in [17, 42, 9, \"Anna\", \"Jan\"]:\n",
    "    h = hash(klucz)\n",
    "    print(f\"{klucz!r:<7} hash = {h:>21}  → szuflada {h % SZUFLAD}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k06",
   "metadata": {},
   "source": [
    "Gdy dwa klucze trafiają do tej samej szuflady, mamy **kolizję** — Python szuka wtedy kolejnej wolnej szuflady według\n",
    "ustalonej reguły. Przećwiczysz to na kartkach w ćwiczeniu „Szafa z szufladami”."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k07",
   "metadata": {},
   "source": [
    "## 3) Operacje na słownikach: dodawanie, usuwanie, łączenie\n",
    "\n",
    "- dodanie albo zmiana: `d[k] = v`,\n",
    "- usunięcie: `del d[k]` albo `d.pop(k, domyślna)`,\n",
    "- łączenie: `d | x` (nowy słownik, Python 3.9+) albo `d.update(x)` (zmienia `d`)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k08",
   "metadata": {},
   "source": [
    "> **Przewidź, zanim uruchomisz:** jaką wartość ma klucz `'b'` w `merged`? W `d` jest `'b': 2`, a w `x` — `'b': 9`.\n",
    ">\n",
    "> <details><summary>Odpowiedź</summary>\n",
    ">\n",
    "> `9` — przy łączeniu `|` wygrywa słownik **z prawej**. Cały wynik: `{'b': 9, 'c': 3, 'd': 4}`.\n",
    ">\n",
    "> </details>"
   ]
  },
  {
   "cell_type": "code",
   "id": "k09",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "d = {\"a\": 1, \"b\": 2}\n",
    "d[\"c\"] = 3\n",
    "print(\"po dodaniu c:\", d)\n",
    "del d[\"a\"]\n",
    "print(\"po usunięciu a:\", d)\n",
    "x = {\"b\": 9, \"d\": 4}\n",
    "merged = d | x  # nowy słownik\n",
    "print(\"merged:\", merged)\n",
    "d.update({\"e\": 5})  # zmienia d w miejscu\n",
    "print(\"po update:\", d)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k10",
   "metadata": {},
   "source": [
    "## 4) Iteracja po słowniku: klucze, wartości, pary\n",
    "\n",
    "`d.keys()`, `d.values()` i `d.items()` ułatwiają iterację. Najczęstszy wzorzec: `for k, v in d.items(): ...`."
   ]
  },
  {
   "cell_type": "code",
   "id": "k11",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "d = {\"a\": 1, \"b\": 2, \"c\": 3}\n",
    "for k in d.keys():\n",
    "    print(\"klucz:\", k)\n",
    "for v in d.values():\n",
    "    print(\"wartość:\", v)\n",
    "for k, v in d.items():\n",
    "    print(f\"{k} -> {v}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k12",
   "metadata": {},
   "source": [
    "## 5) Bezpieczny dostęp: `get`, domyślne wartości, zagnieżdżenia\n",
    "\n",
    "`d[\"brak\"]` podnosi `KeyError`. `d.get(\"klucz\", \"domyślne\")` zwraca wartość albo domyślną — bez błędu."
   ]
  },
  {
   "cell_type": "code",
   "id": "k13",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "person = {\"name\": \"Basia\", \"meta\": {\"city\": \"Poznań\"}}\n",
    "print(person.get(\"age\", \"brak danych\"))\n",
    "meta = person.get(\"meta\", {})\n",
    "print(meta.get(\"city\", \"nieznane\"))\n",
    "\n",
    "try:\n",
    "    print(person[\"age\"])\n",
    "except KeyError as e:\n",
    "    print(\"KeyError:\", e)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k14",
   "metadata": {},
   "source": [
    "## 6) Nowe: kolejność kluczy i `dict.fromkeys`; słownik pod maską\n",
    "\n",
    "Od **Pythona 3.7** słownik oficjalnie pamięta **kolejność wstawiania** kluczy (wcześniej to był szczegół\n",
    "implementacji). Z tego wynika sztuczka na zadanie 2.4 z zajęć o listach: `dict.fromkeys(lista)` tworzy słownik,\n",
    "którego klucze to elementy listy bez powtórzeń — w kolejności pierwszego wystąpienia."
   ]
  },
  {
   "cell_type": "code",
   "id": "k15",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "lista = [3, 1, 3, 2, 1]\n",
    "print(list(dict.fromkeys(lista)))"
   ]
  },
  {
   "cell_type": "code",
   "id": "k16",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "import time\n",
    "\n",
    "def unikalne(lista):            # rozwiązanie z zajęć 2: in na liście to O(n)\n",
    "    wynik = []\n",
    "    for x in lista:\n",
    "        if x not in wynik:\n",
    "            wynik.append(x)\n",
    "    return wynik\n",
    "\n",
    "dane = list(range(5_000)) * 2\n",
    "start = time.perf_counter()\n",
    "unikalne(dane)\n",
    "czas_lista = time.perf_counter() - start\n",
    "\n",
    "start = time.perf_counter()\n",
    "list(dict.fromkeys(dane))\n",
    "czas_slownik = time.perf_counter() - start\n",
    "\n",
    "print(f\"lista:   {czas_lista * 1000:8.2f} ms\")\n",
    "razy = czas_lista / czas_slownik\n",
    "print(f\"słownik: {czas_slownik * 1000:8.2f} ms  ({razy:,.0f} razy szybciej)\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k17",
   "metadata": {},
   "source": [
    "Słownik, tak jak lista, rezerwuje miejsce na zapas. Szafa z szufladami nie może być pełna — wtedy kolizji jest\n",
    "za dużo. CPython powiększa słownik, gdy zapełni ok. **2/3** szuflad. Zobacz, przy ilu kluczach słownik rośnie."
   ]
  },
  {
   "cell_type": "code",
   "id": "k18",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "import sys\n",
    "\n",
    "d = {}\n",
    "poprzedni = sys.getsizeof(d)\n",
    "for n in range(1, 100):\n",
    "    d[n] = n\n",
    "    rozmiar = sys.getsizeof(d)\n",
    "    if rozmiar != poprzedni:\n",
    "        print(f\"przy {n:>2}. kluczu: {poprzedni:>5} B → {rozmiar:>5} B\")\n",
    "        poprzedni = rozmiar"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k19",
   "metadata": {},
   "source": [
    "## 7) Krotki (`tuple`): niemutowalne sekwencje i rozpakowywanie\n",
    "\n",
    "Krotka jest jak lista, ale **niemutowalna**: po utworzeniu nie zmienisz jej elementów. Dzięki temu może być kluczem\n",
    "słownika i zajmuje mniej pamięci. Przydaje się na pary i rekordy, wyniki funkcji i dane, które nie powinny się zmieniać."
   ]
  },
  {
   "cell_type": "code",
   "id": "k20",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "wsp = (52.40, 16.90)\n",
    "print(wsp[0], wsp[1])\n",
    "x, y = wsp\n",
    "print(\"x =\", x, \"y =\", y)\n",
    "\n",
    "pairs = [(1, \"a\"), (2, \"b\")]\n",
    "d = dict(pairs)\n",
    "print(d)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k21",
   "metadata": {},
   "source": [
    "> **Przewidź, zanim uruchomisz:** jaki typ ma `(1)`, a jaki `(1,)`?\n",
    ">\n",
    "> <details><summary>Odpowiedź</summary>\n",
    ">\n",
    "> `(1)` to zwykła liczba `int` w nawiasie. Krotkę tworzy **przecinek**: `(1,)` to krotka jednoelementowa.\n",
    ">\n",
    "> </details>"
   ]
  },
  {
   "cell_type": "code",
   "id": "k22",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "print(type((1)), type((1,)))\n",
    "\n",
    "a, b = 1, 2\n",
    "a, b = b, a                       # zamiana bez zmiennej pomocniczej\n",
    "print(\"a =\", a, \"b =\", b)\n",
    "\n",
    "def min_max(liczby):\n",
    "    return min(liczby), max(liczby)   # funkcja zwraca krotkę\n",
    "\n",
    "najmniejsza, najwieksza = min_max([4, 8, 1, 9])\n",
    "print(najmniejsza, najwieksza)\n",
    "\n",
    "krotka = (1, 2, 3, 4, 5)\n",
    "lista_5 = [1, 2, 3, 4, 5]\n",
    "print(\"krotka:\", sys.getsizeof(krotka), \"B\")\n",
    "print(\"lista: \", sys.getsizeof(lista_5), \"B\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k23",
   "metadata": {},
   "source": [
    "### Pułapka: krotka z listą w środku\n",
    "\n",
    "Krotka jest niemutowalna — ale lista w jej środku już tak. Do tego `+=` to **przypisanie**. Ten przykład opisuje\n",
    "oficjalne FAQ Pythona: *Why does a_tuple[i] += ['item'] raise an exception when the addition works?*"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k24",
   "metadata": {},
   "source": [
    "> **Przewidź, zanim uruchomisz:** co wypisze `print(t)` po próbie `t[0] += [3]`? Będzie błąd czy nie?\n",
    ">\n",
    "> <details><summary>Odpowiedź</summary>\n",
    ">\n",
    "> Jedno i drugie: Python zgłosi `TypeError: 'tuple' object does not support item assignment`, a mimo to `t` to już `([1, 2, 3], 'x')`. Najpierw lista się wydłuża, potem przypisanie do krotki się nie udaje.\n",
    ">\n",
    "> </details>"
   ]
  },
  {
   "cell_type": "code",
   "id": "k25",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "t = ([1, 2], \"x\")\n",
    "try:\n",
    "    t[0] += [3]\n",
    "except TypeError as e:\n",
    "    print(\"TypeError:\", e)\n",
    "print(t)\n",
    "\n",
    "try:\n",
    "    {t: \"wartość\"}\n",
    "except TypeError as e:\n",
    "    print(\"krotka z listą jako klucz → TypeError:\", e)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k26",
   "metadata": {},
   "source": [
    "## 8) Zbiory (`set`): unikalność, działania zbiorowe\n",
    "\n",
    "Zbiór przechowuje **unikatowe** elementy bez kolejności. Zastosowania: usuwanie duplikatów, szybkie `in`, działania\n",
    "na zbiorach: suma `|`, iloczyn `&`, różnica `-` i różnica symetryczna `^`.\n",
    "\n",
    "W oryginalnej wersji zbiory w tym przykładzie nazywały się `A` i `B`, jak w zadaniu 3.2 — przez co test zadania\n",
    "przechodził bez rozwiązania. Teraz to języki, które znają Ania i Bartek."
   ]
  },
  {
   "cell_type": "code",
   "id": "k27",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "jezyki_ani = {\"Python\", \"SQL\", \"Excel\", \"R\", \"Python\"}\n",
    "jezyki_bartka = {\"SQL\", \"Python\", \"Java\", \"C#\"}\n",
    "print(\"Ania po deduplikacji:\", jezyki_ani)\n",
    "print(\"suma        |\", jezyki_ani | jezyki_bartka)\n",
    "print(\"iloczyn     &\", jezyki_ani & jezyki_bartka)\n",
    "print(\"różnica     -\", jezyki_ani - jezyki_bartka)\n",
    "print(\"symetryczna ^\", jezyki_ani ^ jezyki_bartka)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k28",
   "metadata": {},
   "source": [
    "> **Przewidź, zanim uruchomisz:** ile elementów ma `{1, 1.0, True}`? A jaki typ ma `{}`?\n",
    ">\n",
    "> <details><summary>Odpowiedź</summary>\n",
    ">\n",
    "> Jeden: `{1}`. `1 == 1.0 == True` i mają ten sam hash, więc zbiór traktuje je jak jeden element. `{}` to pusty **dict** — pusty zbiór tworzysz przez `set()`.\n",
    ">\n",
    "> </details>"
   ]
  },
  {
   "cell_type": "code",
   "id": "k29",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "print({1, 1.0, True})\n",
    "print({1: \"a\", True: \"b\", 1.0: \"c\"})\n",
    "print(type({}), type(set()))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k30",
   "metadata": {},
   "source": [
    "### Dlaczego zbiór „miesza” kolejność?\n",
    "\n",
    "Kolejność elementów zbioru wynika z ich hashy, a hash napisów zależy od ziarna losowania `PYTHONHASHSEED`.\n",
    "Uruchomimy ten sam kod w trzech osobnych procesach Pythona z różnym ziarnem."
   ]
  },
  {
   "cell_type": "code",
   "id": "k31",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "import os\n",
    "import subprocess\n",
    "\n",
    "kod_zbioru = 'print({\"Python\", \"SQL\", \"Excel\", \"R\", \"Java\"})'\n",
    "for ziarno in [\"1\", \"2\", \"3\"]:\n",
    "    srodowisko = {**os.environ, \"PYTHONHASHSEED\": ziarno}\n",
    "    wynik = subprocess.run([sys.executable, \"-c\", kod_zbioru],\n",
    "                           capture_output=True, text=True, env=srodowisko)\n",
    "    print(f\"PYTHONHASHSEED={ziarno}:\", wynik.stdout.strip())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k32",
   "metadata": {},
   "source": [
    "## 9) Comprehensions dla `dict` i `set`\n",
    "\n",
    "Zwięzły zapis budowania kolekcji z pętli i warunku:\n",
    "- `{x * x for x in range(5)}` — zbiór,\n",
    "- `{x: x * x for x in range(5)}` — słownik."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k33",
   "metadata": {},
   "source": [
    "> **Przewidź, zanim uruchomisz:** w jakiej kolejności wypisze się `set_comp`?\n",
    ">\n",
    "> <details><summary>Odpowiedź</summary>\n",
    ">\n",
    "> Nie ma gwarancji kolejności. U nas wyszło `{0, 64, 4, 36, 16}` — zbiór nie jest posortowany.\n",
    ">\n",
    "> </details>"
   ]
  },
  {
   "cell_type": "code",
   "id": "k34",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "set_comp = {x * x for x in range(10) if x % 2 == 0}\n",
    "dict_comp = {x: x * x for x in range(5)}\n",
    "print(set_comp)\n",
    "print(dict_comp)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k35",
   "metadata": {},
   "source": [
    "## 10) Kiedy `list`, `tuple`, `set`, `dict`?\n",
    "\n",
    "| | `list` | `tuple` | `set` | `dict` |\n",
    "|---|---|---|---|---|\n",
    "| kolejność | tak | tak | nie | tak (klucze, od 3.7) |\n",
    "| zmiana po utworzeniu | tak | **nie** | tak | tak |\n",
    "| duplikaty | tak | tak | **nie** | klucze — nie |\n",
    "| `x in ...` | O(n) | O(n) | **O(1)** | **O(1)** po kluczu |\n",
    "| może być kluczem słownika | nie | tak* | nie | nie |\n",
    "\n",
    "\\* jeśli wszystkie jej elementy są haszowalne.\n",
    "\n",
    "- `list` — sekwencja z kolejnością i duplikatami; częste dodawanie na końcu.\n",
    "- `tuple` — stały, bezpieczny rekord; może być kluczem w słowniku.\n",
    "- `set` — szybkie testy przynależności i deduplikacja.\n",
    "- `dict` — odwzorowanie klucz → wartość, szybki dostęp po kluczu."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k36",
   "metadata": {},
   "source": [
    "## 11) Nowe: JSON, czyli słownik w podróży\n",
    "\n",
    "Wiele API w internecie odpowiada w formacie **JSON** — a JSON to zapis słowników i list. Moduł `json` zamienia\n",
    "słownik na tekst (`dumps`) i tekst z powrotem na słownik (`loads`)."
   ]
  },
  {
   "cell_type": "code",
   "id": "k37",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "import json\n",
    "\n",
    "person = {\"first_name\": \"Anna\", \"age\": 28,\n",
    "          \"skills\": [\"python\", \"sql\"], \"student\": True}\n",
    "tekst = json.dumps(person, ensure_ascii=False)\n",
    "print(tekst)\n",
    "z_powrotem = json.loads(tekst)\n",
    "print(z_powrotem[\"skills\"][0], z_powrotem == person)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k38",
   "metadata": {},
   "source": [
    "## 12) Zadanie 3.0 — podstawy słownika\n",
    "\n",
    "**Instrukcje:**\n",
    "- Zbuduj słownik `produkty` z kluczami `'id_1'`, `'id_2'` i wartościami będącymi cenami (liczby zmiennoprzecinkowe).\n",
    "- Dodaj klucz `'id_3'` i usuń `'id_2'`.\n",
    "- Zmień wartość `'id_1'` na `9.99`.\n",
    "- Wydrukuj cenę dla `'id_1'` oraz bezpiecznie pobierz cenę `'id_999'` metodą `get` z domyślną wartością `0.0`."
   ]
  },
  {
   "cell_type": "code",
   "id": "k39",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# TODO: uzupełnij"
   ]
  },
  {
   "cell_type": "code",
   "id": "k40",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# Sprawdź\n",
    "try:\n",
    "    assert isinstance(produkty, dict)\n",
    "    assert 'id_3' in produkty and 'id_2' not in produkty\n",
    "    assert abs(produkty.get('id_1', 0) - 9.99) < 1e-9\n",
    "    assert abs(produkty.get('id_999', 0.0) - 0.0) < 1e-9\n",
    "except AssertionError:\n",
    "    print(\"❌ Testy nie przeszły — popraw rozwiązanie.\")\n",
    "except Exception as e:\n",
    "    print(\"❌ Kod kończy się błędem:\", type(e).__name__, e)\n",
    "else:\n",
    "    print(\"✅ Zadanie 3.0 zaliczone!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k41",
   "metadata": {},
   "source": [
    "## Zadanie 3.1 — krotki i rozpakowywanie\n",
    "\n",
    "**Instrukcje:**\n",
    "- Zdefiniuj krotkę `punkt = (2.0, 3.5)` i rozpakuj ją do zmiennych `x` i `y`.\n",
    "- Zbuduj słownik `pomiar = {(rok, miesiac): wartosc}` dla co najmniej 3 par; dla `(2025, 2)` ustaw wartość `12`.\n",
    "- Pokaż dostęp do wartości przez klucz będący krotką."
   ]
  },
  {
   "cell_type": "code",
   "id": "k42",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# TODO: uzupełnij"
   ]
  },
  {
   "cell_type": "code",
   "id": "k43",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# Sprawdź\n",
    "try:\n",
    "    assert isinstance(punkt, tuple) and len(punkt) == 2\n",
    "    assert x == 2.0 and y == 3.5\n",
    "    assert isinstance(pomiar, dict) and len(pomiar) >= 3\n",
    "    assert pomiar[(2025, 2)] == 12\n",
    "except AssertionError:\n",
    "    print(\"❌ Testy nie przeszły — popraw rozwiązanie.\")\n",
    "except Exception as e:\n",
    "    print(\"❌ Kod kończy się błędem:\", type(e).__name__, e)\n",
    "else:\n",
    "    print(\"✅ Zadanie 3.1 zaliczone!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k44",
   "metadata": {},
   "source": [
    "## Zadanie 3.2 — zbiory i działania zbiorowe\n",
    "\n",
    "**Instrukcje:**\n",
    "- Utwórz zbiór `A` z duplikatami i pokaż, że zostały usunięte.\n",
    "- Zdefiniuj zbiór `B` i policz: `A | B`, `A & B`, `A - B`, `A ^ B`.\n",
    "- Wydrukuj informację, czy liczba `5` należy do `A`."
   ]
  },
  {
   "cell_type": "code",
   "id": "k45",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# TODO: uzupełnij"
   ]
  },
  {
   "cell_type": "code",
   "id": "k46",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# Sprawdź\n",
    "try:\n",
    "    assert isinstance(A, set) and isinstance(B, set)\n",
    "    assert (A | B) == A.union(B) and (A & B) == A.intersection(B)\n",
    "    assert (A - B) == A.difference(B) and (A ^ B) == A.symmetric_difference(B)\n",
    "    assert len(A & B) > 0, 'dobierz A i B tak, żeby miały wspólne elementy'\n",
    "except AssertionError:\n",
    "    print(\"❌ Testy nie przeszły — popraw rozwiązanie.\")\n",
    "except Exception as e:\n",
    "    print(\"❌ Kod kończy się błędem:\", type(e).__name__, e)\n",
    "else:\n",
    "    print(\"✅ Zadanie 3.2 zaliczone!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k47",
   "metadata": {},
   "source": [
    "## Zadanie 3.3 — comprehensions i mini-test\n",
    "\n",
    "**Instrukcje:**\n",
    "- Zbuduj `set_comp` z kwadratów **parzystych** liczb od 0 do 10 (set comprehension).\n",
    "- Zbuduj `dict_comp` mapujący `x -> x**2` dla `x` w `range(6)`, ale tylko dla liczb **nieparzystych**.\n",
    "- Uruchom komórkę Sprawdź."
   ]
  },
  {
   "cell_type": "code",
   "id": "k48",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# TODO: uzupełnij"
   ]
  },
  {
   "cell_type": "code",
   "id": "k49",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# Sprawdź\n",
    "try:\n",
    "    assert set_comp == {0, 4, 16, 36, 64, 100}\n",
    "    assert dict_comp == {1: 1, 3: 9, 5: 25}\n",
    "except AssertionError:\n",
    "    print(\"❌ Testy nie przeszły — popraw rozwiązanie.\")\n",
    "except Exception as e:\n",
    "    print(\"❌ Kod kończy się błędem:\", type(e).__name__, e)\n",
    "else:\n",
    "    print(\"✅ Zadanie 3.3 zaliczone!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k50",
   "metadata": {},
   "source": [
    "## Zadanie 3.4 dla chętnych — anagramy\n",
    "\n",
    "Anagramy to słowa z tych samych liter: `kot`, `tok`, `okt`. Napisz funkcję `grupuj_anagramy(slowa)`, która zwraca\n",
    "słownik: kluczem jest **krotka** posortowanych liter (`tuple(sorted(slowo))`), wartością — lista słów z tej grupy,\n",
    "w kolejności z wejścia. Dlaczego kluczem jest krotka, a nie lista?"
   ]
  },
  {
   "cell_type": "code",
   "id": "k51",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "def grupuj_anagramy(slowa: list) -> dict:\n",
    "    \"\"\"Słowa z tych samych liter trafiają do jednej grupy.\"\"\"\n",
    "    # TODO: klucz = tuple(sorted(slowo))\n",
    "\n",
    "    return"
   ]
  },
  {
   "cell_type": "code",
   "id": "k52",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# Sprawdź\n",
    "try:\n",
    "    wynik = grupuj_anagramy(['kot', 'ala', 'tok', 'lala', 'okt'])\n",
    "    assert wynik[tuple(sorted('kot'))] == ['kot', 'tok', 'okt']\n",
    "    assert len(wynik) == 3\n",
    "    assert grupuj_anagramy([]) == {}\n",
    "except AssertionError:\n",
    "    print(\"❌ Testy nie przeszły — popraw rozwiązanie.\")\n",
    "except Exception as e:\n",
    "    print(\"❌ Kod kończy się błędem:\", type(e).__name__, e)\n",
    "else:\n",
    "    print(\"✅ Zadanie 3.4 zaliczone!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k53",
   "metadata": {},
   "source": [
    "## Zapamiętaj\n",
    "\n",
    "- **Słownik** znajduje wartość po kluczu w średnio stałym czasie, bo liczy hash i idzie prosto do szuflady.\n",
    "  Od Pythona 3.7 pamięta kolejność wstawiania.\n",
    "- **Krotka** jest niemutowalna i lżejsza od listy. Może być kluczem słownika, jeśli w środku nie ma list.\n",
    "- **Zbiór** trzyma unikatowe elementy bez kolejności i robi działania jak w matematyce. Pusty zbiór to `set()`, nie `{}`.\n",
    "\n",
    "## Zadania dodatkowe\n",
    "\n",
    "1. Dlaczego lista nie może być kluczem słownika, a krotka może?\n",
    "2. Masz listę 1 000 000 numerów albumów i często sprawdzasz, czy dany numer na niej jest. Jakiej struktury użyjesz i dlaczego?\n",
    "3. Znajdź w dokumentacji Pythona, co robi `dict.setdefault` i czym różni się od `get`."
   ]
  }
 ],
 "nbformat": 4,
 "nbformat_minor": 5,
 "metadata": {
  "kernelspec": {
   "name": "python3",
   "display_name": "Python 3",
   "language": "python"
  },
  "language_info": {
   "name": "python"
  }
 }
}
