{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "98e2455a",
   "metadata": {},
   "source": [
    "# Creating sets\n",
    "\n",
    "A set is created using curly braces `{}` with comma-separated values, or by using the `set()` constructor. \n",
    "\n",
    "Unlike lists, sets are unordered and do not allow duplicates - if you add the same value twice, it only appears once. This makes sets useful for storing **unique** items. One important gotcha: an empty {} creates a dict, not a set - you must use set() for an empty set.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8cc82333",
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "from pathlib import Path\n",
    "\n",
    "# Find project root by looking for _config.yml\n",
    "current = Path.cwd()\n",
    "for parent in [current, *current.parents]:\n",
    "    if (parent / '_config.yml').exists():\n",
    "        project_root = parent\n",
    "        break\n",
    "else:\n",
    "    project_root = Path.cwd().parent.parent\n",
    "\n",
    "# Add project root to path\n",
    "sys.path.insert(0, str(project_root))\n",
    "\n",
    "# Import shared teaching helpers and cell magics\n",
    "from shared import thinkpython, diagram, jupyturtle, structshape\n",
    "from shared.download import download\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "41d69110",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{1, 2, 3}\n",
      "{1, 2, 3}\n"
     ]
    }
   ],
   "source": [
    "s = {1, 2, 3, 3}       # duplicate 3 is ignored\n",
    "print(s)               # {1, 2, 3}\n",
    "\n",
    "s = set([1, 2, 2, 3])  # from a list\n",
    "print(s)               # {1, 2, 3}\n",
    "\n",
    "s = set()              # empty set\n",
    "d = {}                 # this is a dict, not a set!"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "54e54710",
   "metadata": {},
   "source": [
    "`set` elements must be **hashable**. Since sets use **hashing** internally to enforce uniqueness and enable fast lookup, all elements must be hashable. This means you can store integers, floats, strings, and tuples in a set, but not lists or dicts."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f488cd39",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%expect TypeError\n",
    "# Valid\n",
    "s = {1, \"hello\", (1, 2)}\n",
    "\n",
    "# Invalid\n",
    "s = {[1, 2], [3, 4]}    # TypeError: unhashable type: 'list'\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "17f4f5e2",
   "metadata": {},
   "source": [
    "Similarly, a `tuple` is hashable only if **all** of its elements are hashable."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "ab98e372",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "-3550055125485641917\n",
      "7267574591690527098\n",
      "unhashable type: 'list'\n"
     ]
    }
   ],
   "source": [
    "print(hash((1, 2)))          # ok\n",
    "print(hash((1, (2, 3))))     # ok\n",
    "\n",
    "try:\n",
    "    hash((1, [2, 3]))        # list inside tuple => unhashable\n",
    "except TypeError as e:\n",
    "    print(e)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0b005023",
   "metadata": {},
   "source": [
    "## Accessing Elements\n",
    "\n",
    "Sets have no indexing or positional access. Because sets are unordered, there's no concept of \"first\" or \"second\" element, so index access makes no sense. \n",
    "\n",
    "Compared with list and dictionary, `dict` is the only one with a built-in safe getter. A `list` requires a **guard clause**. \n",
    "\n",
    "| | List | Dict | Set |\n",
    "|---|---|---|---|\n",
    "| **Access** | by index `l[i]` | by key `d[key]` | no direct access |\n",
    "| **Safe get** | `l[i] if i < len(l) else default` | `d.get(key, default)` | `val if val in s else default` |\n",
    "| **Built-in safe method** | none | `.get()` | none |"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cf593cd9",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%expect IndexError\n",
    "d = {'x': 1, 'y': 2}\n",
    "d.get('z', 'not found')                 # 'not found' — no error\n",
    "\n",
    "l = [10, 20, 30]\n",
    "l[5]                                  # IndexError!   ### uncomment to see the error\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "78699f53",
   "metadata": {},
   "source": [
    "For `list`, a safe way to handle a possible out-of-range error is to use a guard clause. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "id": "4fa18963",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "None\n"
     ]
    }
   ],
   "source": [
    "l = [10, 20, 30]\n",
    "\n",
    "result = l[5] if 5 < len(l) else None   # None — safe\n",
    "print(result)                           # None"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eae58e00",
   "metadata": {},
   "source": [
    "To work with elements in a set, you can:\n",
    "\n",
    "| Method | Example | Notes |\n",
    "|---|---|---|\n",
    "| **Membership check** | `3 in s` | True/False only, no error |\n",
    "| **Iterate** | `for x in s:` | no guaranteed order |\n",
    "| **Convert to list** | `list(s)[0]` | order not guaranteed |\n",
    "| **`s.pop()`** | `s.pop()` | returns an arbitrary element |"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "id": "dfd96d28",
   "metadata": {},
   "outputs": [],
   "source": [
    "### check membership\n",
    "\n",
    "if \"apple\" in s:\n",
    "    print(\"found\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "4f9c90f9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "frozenset({3, 4})\n",
      "frozenset({1, 2})\n"
     ]
    }
   ],
   "source": [
    "### iterate over a set\n",
    "for element in s:\n",
    "    print(element)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "id": "cb1e1a08",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[frozenset({3, 4}), frozenset({1, 2})]\n"
     ]
    }
   ],
   "source": [
    "### conversion to list\n",
    "l = list(s)\n",
    "print(l)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "id": "288f4af5",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1"
      ]
     },
     "execution_count": 60,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "### s.pop() removes and returns an arbitrary element from the set. If the set is empty, it raises a KeyError.\n",
    "s = {1, 2, 3}\n",
    "s.pop()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
