{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "k00",
   "metadata": {},
   "source": [
    "# Listy w Pythonie\n",
    "\n",
    "*Wersja notatnika: 2026-10-08 · na bazie wersji z 2025-10-07 · przygotowanie: dr inż. Szczepan Górtowski*\n",
    "\n",
    "Lista to najczęściej używana struktura danych w Pythonie. Po tym notatniku:\n",
    "- tworzysz listy, czytasz je indeksami i wycinkami (slicing),\n",
    "- zmieniasz listy i wiesz, kiedy **zmieniasz oryginał**, a kiedy **kopię**,\n",
    "- wiesz, co dzieje się w liście pod maską i dlaczego jedne operacje są tysiące razy szybsze od innych,\n",
    "- używasz listy jako **stosu** i wiesz, czego użyć jako **kolejki**,\n",
    "- piszesz pętle, warunki i krótkie testy z `assert`.\n",
    "\n",
    "### Jak korzystać\n",
    "1. Uruchamiaj komórki po kolei: `Ctrl + Enter` (albo `Shift + Enter` — uruchom i przejdź dalej).\n",
    "2. Przy ramkach **Przewidź, zanim uruchomisz** najpierw zgadnij wynik, potem uruchom i dopiero wtedy rozwiń odpowiedź.\n",
    "3. W zadaniach edytuj komórki z `TODO` i uruchom komórkę **Sprawdź**.\n",
    "4. Kod, którego nie rozumiesz, wklej do [Python Tutor](https://pythontutor.com) — narysuje ci strzałki od nazw do list."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k01",
   "metadata": {},
   "source": [
    "## Spis treści\n",
    "1. Listy i tablice — pojęcia\n",
    "2. Deklaracja i przykłady list\n",
    "3. Długość listy: `len`\n",
    "4. Indeksowanie i ujemne indeksy\n",
    "5. Zagnieżdżone listy\n",
    "6. Wycinki (slicing) i krok\n",
    "7. Zmiany w liście: `append`, `pop`, przypisanie — i dwie pułapki\n",
    "8. Kopia czy referencja?\n",
    "9. **Nowe:** lista pod maską — pamięć i czas\n",
    "10. **Nowe:** stos i kolejka\n",
    "11. **Nowe:** sortowanie i Powersort\n",
    "12. Pętle `for` i `while`, `range`, `enumerate`\n",
    "13. Warunki i operatory logiczne\n",
    "14. Importy i moduł `math`\n",
    "15. `assert` i testy w pigułce\n",
    "16. Zadania 2.0–2.3 i zadanie 2.4 dla chętnych\n",
    "17. Zapamiętaj i zadania dodatkowe"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k02",
   "metadata": {},
   "source": [
    "## 1) Listy i tablice — pojęcia\n",
    "\n",
    "| | Tablica | Lista jednokierunkowa |\n",
    "|---|---|---|\n",
    "| jak leży w pamięci | elementy jeden obok drugiego | elementy rozrzucone, każdy wskazuje następny |\n",
    "| dostęp do elementu nr `i` | od razu, w stałym czasie | trzeba przejść `i` kroków od początku |\n",
    "| typowy język | C, C++, Java | C, C++ (struktura budowana ręcznie) |\n",
    "\n",
    "**Lista w Pythonie to w środku tablica** — dokładniej tablica wskaźników do obiektów, która sama rośnie,\n",
    "gdy brakuje miejsca. Dlatego `lista[i]` jest szybkie, a wstawianie na początek — wolne (zobaczysz to w sekcji 9).\n",
    "W C/C++ rozróżnienie lista/tablica jest ostrzejsze, bo strukturę wybierasz sam."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k03",
   "metadata": {},
   "source": [
    "## 2) Deklaracja i przykłady list\n",
    "\n",
    "Lista przechowuje wiele elementów w jednej zmiennej i może zawierać różne typy — także inne listy.\n",
    "Pozostałe kolekcje w Pythonie to **krotki** (`tuple`), **zbiory** (`set`) i **słowniki** (`dict`)."
   ]
  },
  {
   "cell_type": "code",
   "id": "k04",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "lista = [2, 4, 5, 6, 9, -1, 477, 34668]\n",
    "lista_studentow = [\"Anna Nowak\", \"Jan Kowalski\"]\n",
    "lista_przyklad = [[232, 4, \"test\"], \"Basia\", -1, True]\n",
    "lista_macierz = [\n",
    "    [5, 8, 9, 0],\n",
    "    [1, 5, 7, 7],\n",
    "    [0, 9, 1, 0],\n",
    "]\n",
    "print(lista)\n",
    "print(lista_studentow)\n",
    "print(lista_przyklad)\n",
    "print(lista_macierz)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k05",
   "metadata": {},
   "source": [
    "## 3) Długość listy: `len`\n",
    "\n",
    "`len(lista)` zwraca liczbę elementów. Pusta lista ma długość 0."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k06",
   "metadata": {},
   "source": [
    "> **Przewidź, zanim uruchomisz:** ile wypisze `len(lista_przyklad)`? Lista w środku ma trzy elementy.\n",
    ">\n",
    "> <details><summary>Odpowiedź</summary>\n",
    ">\n",
    "> 4 — lista w liście to **jeden** element zewnętrznej listy.\n",
    ">\n",
    "> </details>"
   ]
  },
  {
   "cell_type": "code",
   "id": "k07",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "print(len(lista), len(lista_studentow), len(lista_przyklad))\n",
    "print(len([]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k08",
   "metadata": {},
   "source": [
    "## 4) Indeksowanie i ujemne indeksy\n",
    "\n",
    "Elementy liczysz od 0. Ujemne indeksy liczą **od końca**: `-1` to ostatni element.\n",
    "\n",
    "```\n",
    "indeks:    0    1    2    3    4    5    6      7\n",
    "lista:  [  2,   4,   5,   6,   9,  -1,  477, 34668 ]\n",
    "od końca: -8   -7   -6   -5   -4   -3   -2     -1\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "id": "k09",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "print(lista_studentow[0])\n",
    "print(lista_studentow[1])\n",
    "print(lista[-1])\n",
    "\n",
    "try:\n",
    "    print(lista[999])\n",
    "except IndexError as e:\n",
    "    print(\"IndexError:\", e)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k10",
   "metadata": {},
   "source": [
    "## 5) Zagnieżdżone listy\n",
    "\n",
    "Każda warstwa ma własne indeksy. Czytasz je od lewej: najpierw zewnętrzna lista, potem wewnętrzna.\n",
    "Lista list to też najprostsza **macierz**: `macierz[wiersz][kolumna]`."
   ]
  },
  {
   "cell_type": "code",
   "id": "k11",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "print(lista_przyklad[0][2])   # w liście nr 0 element nr 2\n",
    "print(lista_macierz[1][2])    # wiersz 1, kolumna 2"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k12",
   "metadata": {},
   "source": [
    "## 6) Wycinki (slicing) i krok\n",
    "\n",
    "`lista[a:b]` wybiera elementy od indeksu `a` do `b - 1` (koniec **nie wchodzi**). `lista[a:b:c]` dodaje **krok**.\n",
    "Brak `a` to „od początku”, brak `b` to „do końca”, `[::-1]` odwraca listę."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k13",
   "metadata": {},
   "source": [
    "> **Przewidź, zanim uruchomisz:** co wypisze każda linia poniżej? Zapisz na kartce, potem uruchom.\n",
    ">\n",
    "> <details><summary>Odpowiedź</summary>\n",
    ">\n",
    "> `[5, 6, 9, -1]` · `[9, -1, 477]` · `[2, 4, 5, 6]` · `[5, 6, 9, -1, 477, 34668]` · `[2, 5, 9, 477]` · `[5, 9]` · `[34668, 477, -1, 9, 6, 5, 4, 2]`\n",
    ">\n",
    "> </details>"
   ]
  },
  {
   "cell_type": "code",
   "id": "k14",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "print(lista[2:6])\n",
    "print(lista[-4:-1])\n",
    "print(lista[:4])\n",
    "print(lista[2:])\n",
    "print(lista[::2])\n",
    "print(lista[2:6:2])\n",
    "print(lista[::-1])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k15",
   "metadata": {},
   "source": [
    "## 7) Zmiany w liście: `append`, `pop`, przypisanie\n",
    "\n",
    "Najczęstsze operacje: dodanie na koniec, usunięcie i podmiana elementu. `pop()` **zwraca** usunięty element.\n",
    "\n",
    "| Operacja | Co robi |\n",
    "|---|---|\n",
    "| `lista.append(x)` | dodaje `x` na koniec |\n",
    "| `lista.insert(i, x)` | wstawia `x` pod indeks `i`, reszta przesuwa się w prawo |\n",
    "| `lista.pop()` / `lista.pop(i)` | usuwa i zwraca ostatni / `i`-ty element |\n",
    "| `lista.remove(x)` | usuwa pierwsze wystąpienie wartości `x` |\n",
    "| `lista[i] = x` | podmienia element pod indeksem `i` |\n",
    "| `lista.extend(inna)` | dokleja wszystkie elementy innej listy |"
   ]
  },
  {
   "cell_type": "code",
   "id": "k16",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "lista = [2, 4, 5, 6, 9, -1, 477, 34668]\n",
    "lista.append(555)\n",
    "print(lista)\n",
    "usuniety = lista.pop()\n",
    "print(\"pop() zwrócił:\", usuniety)\n",
    "lista.pop(2)\n",
    "print(lista)\n",
    "lista[3] = -986\n",
    "print(lista)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k17",
   "metadata": {},
   "source": [
    "### Pułapka 1: po `pop` indeksy się przesuwają\n",
    "\n",
    "Chcesz usunąć elementy, które **teraz** stoją pod indeksami 1 i 4. Piszesz `pop(1)`, potem `pop(4)`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k18",
   "metadata": {},
   "source": [
    "> **Przewidź, zanim uruchomisz:** które dwie wartości naprawdę znikną?\n",
    ">\n",
    "> <details><summary>Odpowiedź</summary>\n",
    ">\n",
    "> `4` i `-1`. Po `pop(1)` wszystko na prawo przesuwa się o jedno miejsce, więc pod indeksem 4 stoi już `-1`, a nie `9`. Rozwiązanie: usuwaj **od największego indeksu**: `pop(4)`, potem `pop(1)`.\n",
    ">\n",
    "> </details>"
   ]
  },
  {
   "cell_type": "code",
   "id": "k19",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "lista = [2, 4, 5, 6, 9, -1, 477, 34668]\n",
    "print(\"usunięto:\", lista.pop(1), \"i\", lista.pop(4))\n",
    "print(lista)\n",
    "\n",
    "lista = [2, 4, 5, 6, 9, -1, 477, 34668]\n",
    "print(\"od końca:\", lista.pop(4), \"i\", lista.pop(1))\n",
    "print(lista)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k20",
   "metadata": {},
   "source": [
    "### Pułapka 2: metody, które zmieniają listę, zwracają `None`\n",
    "\n",
    "`sort()`, `append()`, `insert()`, `remove()` zmieniają listę **w miejscu** i zwracają `None` — to zasada dla wszystkich\n",
    "mutowalnych struktur w Pythonie (dokumentacja Pythona, *Data Structures*). Jeśli chcesz nową, posortowaną listę, użyj `sorted()`."
   ]
  },
  {
   "cell_type": "code",
   "id": "k21",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "liczby = [5, 2, 9, 1]\n",
    "wynik = liczby.sort()\n",
    "print(\"wynik =\", wynik, \"| liczby =\", liczby)\n",
    "\n",
    "liczby = [5, 2, 9, 1]\n",
    "nowa = sorted(liczby)\n",
    "print(\"nowa =\", nowa, \"| liczby =\", liczby)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k22",
   "metadata": {},
   "source": [
    "## 8) Kopia czy referencja?\n",
    "\n",
    "Zmienna w Pythonie to **etykieta** przyklejona do obiektu. Przypisanie `b = a` **nie kopiuje** obiektu —\n",
    "przykleja drugą etykietę do tego samego obiektu. Różnica między liczbą a listą bierze się z tego, że:\n",
    "- `int` jest **niemutowalny**: `b += 1` nie zmienia liczby, tylko przykleja `b` do nowej liczby,\n",
    "- `list` jest **mutowalna**: `b.append(17)` zmienia ten sam obiekt, który widzi też `a`.\n",
    "\n",
    "Operator `is` sprawdza, czy dwie nazwy wskazują **ten sam obiekt**."
   ]
  },
  {
   "cell_type": "code",
   "id": "k23",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "a = 10\n",
    "b = a\n",
    "print(\"przed: b is a ->\", b is a)\n",
    "b += 1\n",
    "print(\"a =\", a, \"| b =\", b, \"| b is a ->\", b is a)\n",
    "\n",
    "a = [2, 5, 7, 8, 9]\n",
    "b = a            # ta sama lista, dwie etykiety\n",
    "b.append(17)\n",
    "print(\"a =\", a, \"| b =\", b, \"| b is a ->\", b is a)\n",
    "\n",
    "a = [2, 5, 7, 8, 9]\n",
    "c = a.copy()     # nowa lista z tymi samymi elementami\n",
    "c.append(17)\n",
    "print(\"a =\", a, \"| c =\", c, \"| c is a ->\", c is a)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k24",
   "metadata": {},
   "source": [
    "### Płytka kopia: `copy()` kopiuje tylko pierwszą warstwę\n",
    "\n",
    "Przy liście list `copy()` tworzy nową zewnętrzną listę, ale **wewnętrzne listy są wspólne**.\n",
    "Pełną kopię wszystkich warstw robi `copy.deepcopy()`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k25",
   "metadata": {},
   "source": [
    "> **Przewidź, zanim uruchomisz:** co wypisze `print(oryginal)` po zmianie `plytka[0][0] = 99`?\n",
    ">\n",
    "> <details><summary>Odpowiedź</summary>\n",
    ">\n",
    "> `[[99, 2], [3, 4]]` — wewnętrzna lista jest wspólna dla oryginału i płytkiej kopii. Głęboka kopia jej nie dzieli, więc `gleboka` zostaje bez zmian.\n",
    ">\n",
    "> </details>"
   ]
  },
  {
   "cell_type": "code",
   "id": "k26",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "import copy\n",
    "\n",
    "oryginal = [[1, 2], [3, 4]]\n",
    "plytka = oryginal.copy()\n",
    "gleboka = copy.deepcopy(oryginal)\n",
    "\n",
    "plytka[0][0] = 99\n",
    "print(\"oryginal:\", oryginal)\n",
    "print(\"plytka:  \", plytka)\n",
    "print(\"gleboka: \", gleboka)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k27",
   "metadata": {},
   "source": [
    "### Pułapka 3: `[[0] * 3] * 3`\n",
    "\n",
    "Mnożenie listy powiela **referencje**. Trzy wiersze takiej „macierzy” to trzy etykiety na **jeden** wiersz.\n",
    "Bezpieczny sposób: utwórz każdy wiersz osobno w pętli."
   ]
  },
  {
   "cell_type": "code",
   "id": "k28",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "zla = [[0] * 3] * 3\n",
    "zla[0][0] = 1\n",
    "print(\"zła:   \", zla)\n",
    "\n",
    "dobra = []\n",
    "for _ in range(3):\n",
    "    dobra.append([0] * 3)\n",
    "dobra[0][0] = 1\n",
    "print(\"dobra: \", dobra)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k29",
   "metadata": {},
   "source": [
    "## 9) Nowe: lista pod maską — pamięć i czas\n",
    "\n",
    "Lista Pythona rezerwuje pamięć **na zapas**. Gdy miejsce się kończy, przenosi elementy do większego bloku.\n",
    "Dzięki temu `append` jest średnio bardzo szybkie, choć co jakiś czas lista musi „się przeprowadzić”.\n",
    "\n",
    "`sys.getsizeof(lista)` mówi, ile bajtów zajmuje sama lista (bez elementów). Każde miejsce to wskaźnik: 8 bajtów\n",
    "w 64-bitowym Pythonie. Zobacz, kiedy lista rośnie."
   ]
  },
  {
   "cell_type": "code",
   "id": "k30",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "import sys\n",
    "\n",
    "lista = []\n",
    "rozmiary = []\n",
    "for n in range(65):\n",
    "    rozmiary.append(sys.getsizeof(lista))\n",
    "    lista.append(n)\n",
    "\n",
    "pusta = rozmiary[0]\n",
    "miejsca = [(r - pusta) // 8 for r in rozmiary]\n",
    "for n in range(1, 65):\n",
    "    if miejsca[n] != miejsca[n - 1]:\n",
    "        print(f\"elementów: {n:>2} → miejsc w liście: {miejsca[n]:>2}\")"
   ]
  },
  {
   "cell_type": "code",
   "id": "k31",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "plt.figure(figsize=(8, 4))\n",
    "plt.step(range(65), miejsca, where=\"post\",\n",
    "         label=\"miejsca zarezerwowane\", color=\"#18480C\")\n",
    "plt.plot(range(65), range(65), label=\"elementy w liście\", color=\"#C6803B\")\n",
    "plt.xlabel(\"liczba elementów\")\n",
    "plt.ylabel(\"liczba miejsc\")\n",
    "plt.title(\"Lista rośnie skokami: rezerwuje miejsce na zapas\")\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k32",
   "metadata": {},
   "source": [
    "### Ile kosztuje operacja?\n",
    "\n",
    "Operacje na **końcu** listy nie ruszają reszty elementów. Wstawienie albo usunięcie na **początku** przesuwa\n",
    "wszystkie pozostałe. Sprawdzenie `x in lista` przegląda listę element po elemencie, a zbiór (`set`) znajduje\n",
    "element od razu.\n",
    "\n",
    "| Operacja | Złożoność (średnio) |\n",
    "|---|---|\n",
    "| `lista[i]`, `lista[i] = x`, `len(lista)` | O(1) |\n",
    "| `append(x)`, `pop()` | O(1) |\n",
    "| `insert(0, x)`, `pop(0)` | O(n) |\n",
    "| `x in lista` | O(n) |\n",
    "| `x in zbior` | O(1) |\n",
    "\n",
    "Źródło: Python Wiki, *TimeComplexity* (wiki.python.org/moin/TimeComplexity)."
   ]
  },
  {
   "cell_type": "code",
   "id": "k33",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "import timeit\n",
    "\n",
    "N = 100_000\n",
    "przygotuj = f\"lista = list(range({N})); zbior = set(lista)\"\n",
    "\n",
    "def czas(polecenie, powtorzen):\n",
    "    # najkrótszy z 5 pomiarów, w mikrosekundach na jedno wykonanie\n",
    "    pomiary = timeit.repeat(polecenie, przygotuj, number=powtorzen, repeat=5)\n",
    "    return min(pomiary) / powtorzen * 1e6\n",
    "\n",
    "koniec = czas(\"lista.append(1); lista.pop()\", 10_000)\n",
    "poczatek = czas(\"lista.insert(0, 1); lista.pop(0)\", 500)\n",
    "w_liscie = czas(f\"{N - 1} in lista\", 200)\n",
    "w_zbiorze = czas(f\"{N - 1} in zbior\", 10_000)\n",
    "\n",
    "print(f\"koniec:    {koniec:10.3f} µs  (append + pop)\")\n",
    "print(f\"początek:  {poczatek:10.3f} µs  ({poczatek / koniec:,.0f}× wolniej)\")\n",
    "print(f\"in lista:  {w_liscie:10.3f} µs  (100 000 elementów)\")\n",
    "razy = w_liscie / w_zbiorze\n",
    "print(f\"in zbior:  {w_zbiorze:10.3f} µs  ({razy:,.0f}× szybciej)\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k34",
   "metadata": {},
   "source": [
    "## 10) Nowe: stos i kolejka\n",
    "\n",
    "- **Stos** (LIFO — ostatni przyszedł, pierwszy wychodzi): jak stos talerzy. Lista nadaje się idealnie:\n",
    "  `append()` kładzie na wierzch, `pop()` zdejmuje z wierzchu.\n",
    "- **Kolejka** (FIFO — pierwszy przyszedł, pierwszy wychodzi): jak kolejka do kasy. Lista się **nie** nadaje,\n",
    "  bo `pop(0)` przesuwa wszystkie elementy. Dokumentacja Pythona poleca `collections.deque`, które szybko\n",
    "  dodaje i zdejmuje z obu końców."
   ]
  },
  {
   "cell_type": "code",
   "id": "k35",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "from collections import deque\n",
    "\n",
    "stos = []\n",
    "for talerz in [\"biały\", \"zielony\", \"niebieski\"]:\n",
    "    stos.append(talerz)\n",
    "print(\"zdejmuję ze stosu:\", stos.pop())          # ostatni położony\n",
    "\n",
    "kolejka = deque()\n",
    "for klient in [\"Ola\", \"Jan\", \"Ewa\"]:\n",
    "    kolejka.append(klient)\n",
    "print(\"obsługuję z kolejki:\", kolejka.popleft())  # pierwszy, który przyszedł"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k36",
   "metadata": {},
   "source": [
    "## 11) Nowe: sortowanie i Powersort\n",
    "\n",
    "`sorted(lista)` i `lista.sort()` sortują rosnąco. `reverse=True` odwraca kolejność, a `key=` mówi, **po czym** sortować.\n",
    "Sortowanie jest **stabilne**: elementy o tym samym kluczu zostają w kolejności z wejścia — zobacz Olę, Jana i Ewę\n",
    "przy `key=len`.\n",
    "\n",
    "**Ciekawostka z algorytmiki:** od Pythona 3.11 (październik 2022) sortowanie list używa reguły scalania z algorytmu\n",
    "**Powersort** (J. I. Munro, S. Wild, ESA 2018). Zastąpiła ona regułę z Timsorta, którego Python używał od wersji 2.3.\n",
    "Wdrożył to Tim Peters — autor Timsorta. Dla większości list różnica jest niewielka, ale w trudnych przypadkach\n",
    "nowa reguła scala serie wyraźnie lepiej. Algorytmy sortowania omówimy w module o algorytmach."
   ]
  },
  {
   "cell_type": "code",
   "id": "k37",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "studenci = [\"Ola\", \"Bartłomiej\", \"Jan\", \"Ewa\", \"Zuzanna\"]\n",
    "print(sorted(studenci))\n",
    "print(sorted(studenci, key=len))\n",
    "print(sorted(studenci, key=len, reverse=True))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k38",
   "metadata": {},
   "source": [
    "## 12) Pętle `for` i `while`, `range`, `enumerate`\n",
    "\n",
    "`while` powtarza blok, dopóki warunek jest prawdziwy. `for` przechodzi po elementach kolekcji.\n",
    "`range(start, stop, krok)` generuje liczby od `start` do `stop - 1`. `enumerate()` daje jednocześnie indeks i element."
   ]
  },
  {
   "cell_type": "code",
   "id": "k39",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# while\n",
    "n = 1\n",
    "while n < 4:\n",
    "    print(n)\n",
    "    n = n + 1\n",
    "\n",
    "# for po liście\n",
    "for element in [2, 4, 8, 10]:\n",
    "    print(element)\n",
    "\n",
    "# range\n",
    "for n in range(5):\n",
    "    print(n)"
   ]
  },
  {
   "cell_type": "code",
   "id": "k40",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "for i, student in enumerate([\"Anna Nowak\", \"Jan Kowalski\"]):\n",
    "    print(i, student)\n",
    "\n",
    "print(list(range(2, 11, 2)))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k41",
   "metadata": {},
   "source": [
    "## 13) Warunki: `if` / `elif` / `else` i operatory logiczne\n",
    "\n",
    "Porównania: `==`, `!=`, `>`, `>=`, `<`, `<=`. Operatory logiczne: `and` (oba warunki), `or` (choć jeden), `not` (zaprzeczenie)."
   ]
  },
  {
   "cell_type": "code",
   "id": "k42",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "ocena = 4\n",
    "if ocena >= 3:\n",
    "    print(\"zdane\")\n",
    "else:\n",
    "    print(\"niezdane\")\n",
    "\n",
    "liczba = 0\n",
    "if liczba > 0:\n",
    "    print(\"liczba większa od zera\")\n",
    "elif liczba == 0:\n",
    "    print(\"liczba równa zero\")\n",
    "else:\n",
    "    print(\"liczba mniejsza od zera\")\n",
    "\n",
    "ocena_test = 3\n",
    "obecnosc = 0.7\n",
    "if ocena_test >= 3 and obecnosc > 0.5:\n",
    "    print(\"zaliczono\")\n",
    "else:\n",
    "    print(\"niezaliczono\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k43",
   "metadata": {},
   "source": [
    "## 14) Importy i moduł `math`\n",
    "\n",
    "Moduły rozszerzają Pythona. Importujesz cały moduł (`import math`) albo wybrane nazwy (`from math import pi`)."
   ]
  },
  {
   "cell_type": "code",
   "id": "k44",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "import math\n",
    "from math import sqrt\n",
    "\n",
    "print(math.pi)\n",
    "print(sqrt(16))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k45",
   "metadata": {},
   "source": [
    "## 15) `assert` i testy w pigułce\n",
    "\n",
    "`assert warunek, \"komunikat\"` sprawdza założenie. Gdy warunek jest fałszywy, Python podnosi `AssertionError`.\n",
    "Na `assert` opierają się komórki **Sprawdź** w zadaniach."
   ]
  },
  {
   "cell_type": "code",
   "id": "k46",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "def dzielenie(a, b):\n",
    "    assert b != 0, \"Dzielenie przez zero!\"\n",
    "    return a / b\n",
    "\n",
    "print(dzielenie(6, 3))\n",
    "\n",
    "try:\n",
    "    dzielenie(1, 0)\n",
    "except AssertionError as e:\n",
    "    print(\"AssertionError:\", e)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k47",
   "metadata": {},
   "source": [
    "## 16) Zadanie 2.0 — operacje na listach\n",
    "\n",
    "**Instrukcje:**\n",
    "- Zadeklaruj listę `a` z co najmniej 10 elementami (mieszane typy mile widziane).\n",
    "- Wyświetl jej długość; wydrukuj elementy pod indeksami 3 i 7 oraz `-1` i `-8`.\n",
    "- Wydrukuj wycinek `a[2:6]` (elementy o indeksach 2, 3, 4 i 5) i co trzeci element `a[::3]`.\n",
    "- Dodaj 4 nowe elementy; usuń elementy pod indeksami 2 i 8, następnie wydrukuj listę.\n",
    "\n",
    "*Wskazówka:* pamiętasz pułapkę 1? Zastanów się, który element usunąć najpierw."
   ]
  },
  {
   "cell_type": "code",
   "id": "k48",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# TODO: zrealizuj polecenia"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k49",
   "metadata": {},
   "source": [
    "## Zadanie 2.1 — kopiowanie i mutowalność\n",
    "\n",
    "**Instrukcje:**\n",
    "- Utwórz `b = a` i **zmodyfikuj** `b` (np. dodaj element). Zaobserwuj zmianę w `a`. Sprawdź `b is a`.\n",
    "- Utwórz `c = a.copy()` i **zmodyfikuj** `c`; pokaż, że `a` i `c` są różne."
   ]
  },
  {
   "cell_type": "code",
   "id": "k50",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# TODO: dokończ eksperyment z kopiowaniem"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k51",
   "metadata": {},
   "source": [
    "## Zadanie 2.2 — pętle i warunki\n",
    "\n",
    "**Instrukcje:**\n",
    "- Wydrukuj liczby od 1 do 10 w pętli `while`.\n",
    "- Wydrukuj **tylko parzyste** liczby od 1 do 10 w pętli `for` (z użyciem `range`).\n",
    "- Zaimportuj `math` i wydrukuj wartość `pi` z dokładnością do 5 miejsc po przecinku."
   ]
  },
  {
   "cell_type": "code",
   "id": "k52",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# TODO: uzupełnij"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k53",
   "metadata": {},
   "source": [
    "## Zadanie 2.3 — mini-test jednostkowy\n",
    "\n",
    "Napisz funkcję `parzyste_zakres(a, b)`, która zwraca listę liczb **parzystych** z zakresu domkniętego `[a, b]`.\n",
    "Użyj instrukcji warunkowych i `range`. Następnie uruchom komórkę **Sprawdź**."
   ]
  },
  {
   "cell_type": "code",
   "id": "k54",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "def parzyste_zakres(a: int, b: int) -> list:\n",
    "    \"\"\"Zwróć listę liczb parzystych z domkniętego zakresu [a, b].\n",
    "    Jeśli a > b, zwróć pustą listę.\n",
    "    \"\"\"\n",
    "    # TODO: zaimplementuj\n",
    "\n",
    "    return"
   ]
  },
  {
   "cell_type": "code",
   "id": "k55",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# Sprawdź\n",
    "try:\n",
    "    assert parzyste_zakres(1, 10) == [2, 4, 6, 8, 10]\n",
    "    assert parzyste_zakres(2, 2) == [2]\n",
    "    assert parzyste_zakres(3, 3) == []\n",
    "    assert parzyste_zakres(10, 1) == []\n",
    "except AssertionError:\n",
    "    print(\"❌ Testy nie przeszły — popraw implementację.\")\n",
    "except Exception as e:\n",
    "    print(\"❌ Funkcja kończy się błędem:\", type(e).__name__, e)\n",
    "else:\n",
    "    print(\"✅ Wszystkie testy zaliczone!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k56",
   "metadata": {},
   "source": [
    "## Zadanie 2.4 dla chętnych — unikalne elementy\n",
    "\n",
    "Napisz funkcję `unikalne(lista)`, która zwraca **nową** listę bez powtórzeń, w kolejności pierwszego wystąpienia.\n",
    "Oryginalna lista ma zostać bez zmian. Przykład: `[3, 1, 3, 2, 1]` → `[3, 1, 2]`.\n",
    "\n",
    "*Do przemyślenia:* ile porównań wykona twoja funkcja dla listy 100 000 różnych liczb? Na zajęciach 3 zobaczysz,\n",
    "jak słownik robi to samo w jednej linii — i dużo szybciej."
   ]
  },
  {
   "cell_type": "code",
   "id": "k57",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "def unikalne(lista: list) -> list:\n",
    "    \"\"\"Elementy bez powtórzeń, w kolejności pierwszego wystąpienia.\"\"\"\n",
    "    # TODO: dokładaj do wyniku tylko elementy, których w nim jeszcze nie ma\n",
    "\n",
    "    return"
   ]
  },
  {
   "cell_type": "code",
   "id": "k58",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# Sprawdź\n",
    "try:\n",
    "    assert unikalne([3, 1, 3, 2, 1]) == [3, 1, 2]\n",
    "    assert unikalne([\"a\", \"a\"]) == [\"a\"]\n",
    "    assert unikalne([]) == []\n",
    "    wejscie = [1, 1, 2]\n",
    "    unikalne(wejscie)\n",
    "    assert wejscie == [1, 1, 2], \"funkcja zmieniła listę wejściową\"\n",
    "except AssertionError:\n",
    "    print(\"❌ Testy nie przeszły — popraw implementację.\")\n",
    "except Exception as e:\n",
    "    print(\"❌ Funkcja kończy się błędem:\", type(e).__name__, e)\n",
    "else:\n",
    "    print(\"✅ Wszystkie testy zaliczone!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k59",
   "metadata": {},
   "source": [
    "## 17) Zapamiętaj\n",
    "\n",
    "Lista (`list`) w Pythonie:\n",
    "- przechowuje wiele wartości w jednej zmiennej i **zachowuje kolejność**,\n",
    "- pozwala na **duplikaty** i różne typy elementów,\n",
    "- jest **mutowalna**: możesz dodawać, usuwać i podmieniać elementy,\n",
    "- w środku jest **tablicą wskaźników**: szybka na końcu, wolna na początku.\n",
    "\n",
    "`b = a` to druga etykieta na tę samą listę. Kopię robi `a.copy()`, a kopię wszystkich warstw — `copy.deepcopy(a)`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k60",
   "metadata": {},
   "source": [
    "## Zadania dodatkowe\n",
    "\n",
    "**Odpowiedz na pytania:**\n",
    "1. Co znaczy mutowalny obiekt, a co niemutowalny? Podaj po dwa przykłady typów.\n",
    "2. Co to jest referencja, a co kopiowanie? Czym różni się kopia płytka od głębokiej?\n",
    "3. Jakie operacje można wykonywać na listach? Które są szybkie, a które wolne — i dlaczego?\n",
    "\n",
    "**Wyszukaj:**\n",
    "4. Co to jest kolejka, a co stos? Podaj przykład z życia dla każdego i powiedz, której struktury Pythona użyjesz.\n",
    "\n",
    "**Zobacz:**\n",
    "- [Python Tutor](https://pythontutor.com) — wklej kod z sekcji 8 i obejrzyj strzałki.\n",
    "- Dokumentacja Pythona, *Data Structures* — rozdział 5.1 o listach (docs.python.org/3/tutorial/datastructures.html)."
   ]
  }
 ],
 "nbformat": 4,
 "nbformat_minor": 5,
 "metadata": {
  "kernelspec": {
   "name": "python3",
   "display_name": "Python 3",
   "language": "python"
  },
  "language_info": {
   "name": "python"
  }
 }
}
