{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Working with sets\n",
    "\n",
    "In this tutorial, you will learn how to create and use sets — Python's collection for storing unique items.\n",
    "\n",
    "**Time commitment:** 15–20 minutes\n",
    "\n",
    "**Prerequisites:**\n",
    "\n",
    "- Python 3.12 or later installed on your machine\n",
    "- Completion of [Working with lists](https://agilearn.co.uk/guides/data-structures/learn/01-lists), [Working with tuples](https://agilearn.co.uk/guides/data-structures/learn/02-tuples), and [Working with dictionaries](https://agilearn.co.uk/guides/data-structures/learn/03-dictionaries)\n",
    "\n",
    "## Learning objectives\n",
    "\n",
    "By the end of this tutorial, you will be able to:\n",
    "\n",
    "- Create sets and add or remove items\n",
    "- Perform set operations such as union, intersection, and difference\n",
    "- Test for subsets and supersets\n",
    "- Use sets for practical tasks like removing duplicates"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## What is a set?\n",
    "\n",
    "A **set** is an unordered, mutable collection of unique items. Sets automatically eliminate duplicates and provide fast membership testing.\n",
    "\n",
    "Sets are created using curly braces `{}`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fruits = {\"apple\", \"banana\", \"cherry\"}\n",
    "print(fruits)\n",
    "print(type(fruits))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can also create a set from a list using `set()`. Notice how duplicates are removed automatically:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "numbers = set([1, 2, 2, 3, 3, 3])\n",
    "print(numbers)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Important:** An empty `{}` creates an empty dictionary, not an empty set. Use `set()` to create an empty set:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "empty_dict = {}\n",
    "empty_set = set()\n",
    "\n",
    "print(type(empty_dict))  # dict\n",
    "print(type(empty_set))   # set"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Adding items\n",
    "\n",
    "Use `add()` to add a single item to a set:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fruits = {\"apple\", \"banana\"}\n",
    "fruits.add(\"cherry\")\n",
    "print(fruits)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Adding an item that already exists has no effect — sets guarantee uniqueness:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fruits.add(\"apple\")\n",
    "print(fruits)  # \"apple\" is not duplicated"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Use `update()` to add multiple items at once:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fruits = {\"apple\"}\n",
    "fruits.update([\"banana\", \"cherry\", \"date\"])\n",
    "print(fruits)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Removing items\n",
    "\n",
    "Use `remove()` to remove an item. This raises a `KeyError` if the item does not exist:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fruits = {\"apple\", \"banana\", \"cherry\"}\n",
    "fruits.remove(\"banana\")\n",
    "print(fruits)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Use `discard()` to remove an item without raising an error if it is missing:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fruits = {\"apple\", \"cherry\"}\n",
    "fruits.discard(\"grape\")  # No error, even though \"grape\" is not in the set\n",
    "print(fruits)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Set operations\n",
    "\n",
    "Sets support mathematical set operations. Let us define two sets to work with:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "maths_students = {\"Alice\", \"Bob\", \"Charlie\", \"Diana\"}\n",
    "science_students = {\"Charlie\", \"Diana\", \"Eve\", \"Frank\"}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Union\n",
    "\n",
    "The **union** combines all items from both sets (removing duplicates). Use `|` or `union()`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "all_students = maths_students | science_students\n",
    "print(f\"All students: {all_students}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Intersection\n",
    "\n",
    "The **intersection** finds items common to both sets. Use `&` or `intersection()`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "both = maths_students & science_students\n",
    "print(f\"In both classes: {both}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Difference\n",
    "\n",
    "The **difference** finds items in one set but not the other. Use `-` or `difference()`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "maths_only = maths_students - science_students\n",
    "print(f\"Maths only: {maths_only}\")\n",
    "\n",
    "science_only = science_students - maths_students\n",
    "print(f\"Science only: {science_only}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Symmetric difference\n",
    "\n",
    "The **symmetric difference** finds items in either set, but not both. Use `^` or `symmetric_difference()`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "exclusive = maths_students ^ science_students\n",
    "print(f\"In one class only: {exclusive}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Subset and superset checks\n",
    "\n",
    "Use `issubset()` to check whether all items in one set are contained in another:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "small = {\"Alice\", \"Bob\"}\n",
    "large = {\"Alice\", \"Bob\", \"Charlie\", \"Diana\"}\n",
    "\n",
    "print(small.issubset(large))    # True\n",
    "print(large.issuperset(small))  # True"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Use `isdisjoint()` to check whether two sets have no items in common:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "evens = {2, 4, 6}\n",
    "odds = {1, 3, 5}\n",
    "\n",
    "print(evens.isdisjoint(odds))  # True — no items in common"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Frozen sets\n",
    "\n",
    "A **frozenset** is an immutable version of a set. You cannot add or remove items from a frozenset, but you can use it as a dictionary key or as a member of another set:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vowels = frozenset({\"a\", \"e\", \"i\", \"o\", \"u\"})\n",
    "print(vowels)\n",
    "\n",
    "# Frozensets support set operations\n",
    "consonants = frozenset({\"b\", \"c\", \"d\"})\n",
    "print(vowels | consonants)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Practical uses\n",
    "\n",
    "### Removing duplicates\n",
    "\n",
    "Converting a list to a set removes all duplicates:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "names = [\"Alice\", \"Bob\", \"Alice\", \"Charlie\", \"Bob\"]\n",
    "unique_names = set(names)\n",
    "print(unique_names)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you need to preserve the original order, use `dict.fromkeys()` instead:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "unique_ordered = list(dict.fromkeys(names))\n",
    "print(unique_ordered)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Fast membership testing\n",
    "\n",
    "Checking whether an item exists in a set is much faster than checking a list, especially for large collections:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "allowed_users = {\"alice\", \"bob\", \"charlie\"}\n",
    "\n",
    "username = \"alice\"\n",
    "if username in allowed_users:\n",
    "    print(f\"Welcome, {username}!\")\n",
    "else:\n",
    "    print(\"Access denied.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise: compare class lists\n",
    "\n",
    "You have two lists of student names from different classes:\n",
    "\n",
    "```python\n",
    "art_class = [\"Alice\", \"Bob\", \"Charlie\", \"Diana\", \"Eve\"]\n",
    "music_class = [\"Charlie\", \"Eve\", \"Frank\", \"Grace\"]\n",
    "```\n",
    "\n",
    "Using sets, find:\n",
    "\n",
    "1. Students who take both art and music\n",
    "2. Students who take art but not music\n",
    "3. All unique students across both classes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Write your code here\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Solution\n",
    "\n",
    "Here is one way to complete the exercise:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "art_class = [\"Alice\", \"Bob\", \"Charlie\", \"Diana\", \"Eve\"]\n",
    "music_class = [\"Charlie\", \"Eve\", \"Frank\", \"Grace\"]\n",
    "\n",
    "art_set = set(art_class)\n",
    "music_set = set(music_class)\n",
    "\n",
    "# 1. Students in both classes\n",
    "both = art_set & music_set\n",
    "print(f\"Both classes: {both}\")\n",
    "\n",
    "# 2. Art but not music\n",
    "art_only = art_set - music_set\n",
    "print(f\"Art only: {art_only}\")\n",
    "\n",
    "# 3. All unique students\n",
    "all_students = art_set | music_set\n",
    "print(f\"All students: {all_students}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Summary\n",
    "\n",
    "In this tutorial, you learned how to:\n",
    "\n",
    "- Create sets using curly braces `{}` and `set()`\n",
    "- Add items with `add()` and `update()`\n",
    "- Remove items with `remove()` and `discard()`\n",
    "- Perform union, intersection, difference, and symmetric difference\n",
    "- Check for subsets, supersets, and disjoint sets\n",
    "- Use frozen sets for immutable collections\n",
    "- Remove duplicates and test membership efficiently\n",
    "\n",
    "## What is next\n",
    "\n",
    "In the next tutorial, you will explore:\n",
    "\n",
    "- **Comprehensions** — concise ways to build lists, dictionaries, and sets"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.12.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}