{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "19982422",
   "metadata": {},
   "source": [
    "# Set Methods\n",
    "\n",
    "Python sets have methods for adding, removing, and checking elements.\n",
    "\n",
    "| Method             | Description                                   |\n",
    "|--------------------|-----------------------------------------------|\n",
    "| `.add(x)`          | Adds a single element                         |\n",
    "| `.update(iterable)`| Adds multiple elements                        |\n",
    "| `.copy()`       | Returns a shallow copy of the set  |\n",
    "| `.remove(x)`       | Removes element, raises `KeyError` if missing |\n",
    "| `.discard(x)`      | Removes element, silent if missing            |\n",
    "| `.pop()`           | Removes and returns an arbitrary element      |\n",
    "| `.clear()`         | Empties the set                               |\n",
    "| `.issubset(s)`     | Returns `True` if all elements are in `s`     |\n",
    "| `.issuperset(s)`   | Returns `True` if set contains all of `s`     |\n",
    "| `.isdisjoint(s)`   | Returns `True` if no elements in common       |"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8d770e1f",
   "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": "markdown",
   "id": "02f8779f",
   "metadata": {},
   "source": [
    "## Adding elements:\n",
    "\n",
    "- `add()`\n",
    "- `update()`: adds multiple elements, similar to `dict.update()`. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 87,
   "id": "5c7aafe4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{1, 2, 3, 4, 5, 6}"
      ]
     },
     "execution_count": 87,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s = {1, 2, 3}\n",
    "s.add(4)            # {1, 2, 3, 4}\n",
    "s.update([5, 6])    # {1, 2, 3, 4, 5, 6}\n",
    "s"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4898cd7e",
   "metadata": {},
   "source": [
    "## Copy vs aliasing\n",
    "\n",
    "Sets are mutable, so assignment shares the same object. Use `.copy()` when you need an independent set."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 88,
   "id": "64de2dc2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a: {1, 2, 3}\n",
      "b: {1, 2, 3}\n",
      "c: {1, 2}\n"
     ]
    }
   ],
   "source": [
    "a = {1, 2}\n",
    "b = a               # alias (same object)\n",
    "c = a.copy()        # separate object\n",
    "\n",
    "a.add(3)\n",
    "print(\"a:\", a)\n",
    "print(\"b:\", b)\n",
    "print(\"c:\", c)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0a7e4012",
   "metadata": {},
   "source": [
    "## Removing elements:\n",
    "\n",
    "- `remove()`\n",
    "- `discard()`\n",
    "- `pop()`\n",
    "- `clear()`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 108,
   "id": "32830bae",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{1, 2}\n",
      "{1, 2}\n",
      "{2}\n",
      "set()\n"
     ]
    }
   ],
   "source": [
    "s = {1, 2, 3}\n",
    "\n",
    "s.remove(3)     # raises KeyError if not found\n",
    "print(s)        # {1, 2}\n",
    "\n",
    "s.discard(3)    # silent if not found — safer\n",
    "print(s)        # {1, 2}\n",
    "\n",
    "s.pop()         # removes and returns an arbitrary element\n",
    "print(s)        # which item is removed is arbitrary\n",
    "\n",
    "s.clear()       # empties the set\n",
    "print(s)        # set()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "43cdb10f",
   "metadata": {},
   "source": [
    "If you use `remove()` in a `for` loop for a set, you may run into an error because Python creates an internal iterator that tracks position inside the set. The moment you call `s.remove()`, the set's structure changes and the iterator breaks and raises an error.\n",
    "\n",
    "The solution to this problem is to iterate over a copy or just use a `while` loop."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8712a534",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%expect RuntimeError\n",
    "s = {1, 2, 3,}\n",
    "for item in s:\n",
    "    s.remove(item)\n",
    "    print(f\"Processing {item}\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 119,
   "id": "074240f6",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Processing 2\n",
      "Processing 3\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "set()"
      ]
     },
     "execution_count": 119,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "### iterate through copy of the set while modifying the original\n",
    "\n",
    "for item in s.copy():   # iterate over copy\n",
    "    s.remove(item)      # modify original safely\n",
    "    print(f\"Processing {item}\")\n",
    "s"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 120,
   "id": "ce404de4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Processing 1\n",
      "Processing 2\n",
      "Processing 3\n"
     ]
    }
   ],
   "source": [
    "# process all items, consuming the set\n",
    "\n",
    "s = {1, 2, 3}\n",
    "while s:\n",
    "    item = s.pop()\n",
    "    print(f\"Processing {item}\")\n",
    "# s is now empty"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8929f271",
   "metadata": {},
   "source": [
    "`s.pop()` is useful when you want to process and consume a set one element at a time, and you don't care which element you get. This behavior is different from `pop()` in `list` and `dict`.\n",
    "\n",
    "| | `list.pop()` | `dict.pop()` | `set.pop()` |\n",
    "|---|---|---|---|\n",
    "| **Removes** | by index | by key | arbitrary element |\n",
    "| **Returns** | removed value | removed value | removed value |\n",
    "| **Default arg** | index (default `-1`) | key + optional default | none |\n",
    "| **If not found** | `IndexError` | `KeyError` (or default) | `KeyError` |\n",
    "| **Predictable?** | yes (by index) | yes (by key) | no (arbitrary item) |"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e5e935c7",
   "metadata": {},
   "source": [
    "## Checking membership:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "d26aa7e7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n",
      "False\n"
     ]
    }
   ],
   "source": [
    "s = {1, 2, 3}\n",
    "\n",
    "print(3 in s)         # True\n",
    "print(3 not in s)     # False"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f97d7ed3",
   "metadata": {},
   "source": [
    "## Subset & Superset:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 121,
   "id": "fa4c9d3d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 121,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "a = {1, 2}\n",
    "b = {1, 2, 3}\n",
    "\n",
    "a.issubset(b)    # True — all of a is in b\n",
    "b.issuperset(a)  # True — b contains all of a\n",
    "a.isdisjoint(b)  # False — they share elements"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a8576fdd",
   "metadata": {},
   "source": [
    "Operator equivalents for subset/superset are often used in real code:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 122,
   "id": "1165718f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n",
      "True\n",
      "True\n",
      "True\n",
      "True\n",
      "False\n"
     ]
    }
   ],
   "source": [
    "a = {1, 2}\n",
    "b = {1, 2, 3}\n",
    "\n",
    "print(a < b)    # proper subset\n",
    "print(a <= b)   # subset\n",
    "print(b > a)    # proper superset\n",
    "print(b >= a)   # superset\n",
    "print(a <= a)   # True (same set is subset of itself)\n",
    "print(a < a)    # False (not a proper subset)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6619fd2a",
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Update and Membership Check\n",
    "# 1. Start with fruits = {\"apple\", \"banana\"}.\n",
    "# 2. Add \"cherry\" and update with [\"banana\", \"mango\"].\n",
    "# 3. Remove \"apple\" safely.\n",
    "# 4. Print whether \"mango\" is in fruits.\n",
    "# 5. Print whether {\"banana\", \"cherry\"} is a subset of fruits.\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4b8d7159",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "fruits = {\"apple\", \"banana\"}\n",
    "fruits.add(\"cherry\")\n",
    "fruits.update([\"banana\", \"mango\"])\n",
    "fruits.discard(\"apple\")\n",
    "\n",
    "print(\"mango\" in fruits)\n",
    "print({\"banana\", \"cherry\"}.issubset(fruits))"
   ]
  }
 ],
 "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
}
