{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Working with tuples\n",
    "\n",
    "In this tutorial, you will learn how to create and use tuples — Python's immutable sequences.\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)\n",
    "\n",
    "## Learning objectives\n",
    "\n",
    "By the end of this tutorial, you will be able to:\n",
    "\n",
    "- Create tuples and access items by index\n",
    "- Unpack tuples into individual variables\n",
    "- Use tuples as return values from functions\n",
    "- Understand when to choose tuples over lists"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## What is a tuple?\n",
    "\n",
    "A **tuple** is an ordered, immutable sequence of items. Like lists, tuples can hold any type of item — numbers, strings, or even other tuples. The key difference is that once you create a tuple, you cannot change its contents.\n",
    "\n",
    "Tuples are created using parentheses `()`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "colours = (\"red\", \"green\", \"blue\")\n",
    "print(colours)\n",
    "print(type(colours))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can also create a tuple without parentheses — Python recognises the commas:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "coordinates = 51.5074, -0.1278\n",
    "print(coordinates)\n",
    "print(type(coordinates))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Single-item tuples\n",
    "\n",
    "To create a tuple with a single item, you must include a trailing comma. Without the comma, Python treats the parentheses as grouping, not as a tuple:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "not_a_tuple = (\"hello\")\n",
    "print(type(not_a_tuple))  # This is a string\n",
    "\n",
    "a_tuple = (\"hello\",)\n",
    "print(type(a_tuple))  # This is a tuple"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Empty tuples\n",
    "\n",
    "You can create an empty tuple with `()` or `tuple()`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "empty1 = ()\n",
    "empty2 = tuple()\n",
    "print(len(empty1), len(empty2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Accessing items by index\n",
    "\n",
    "Tuples support the same indexing as lists. Use square brackets with the index position:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "colours = (\"red\", \"green\", \"blue\")\n",
    "\n",
    "print(colours[0])   # First item\n",
    "print(colours[-1])  # Last item\n",
    "print(colours[1:])  # Slicing works too"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Tuple unpacking\n",
    "\n",
    "One of the most useful features of tuples is **unpacking** — assigning each item to a separate variable in a single statement:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "coordinates = (51.5074, -0.1278)\n",
    "latitude, longitude = coordinates\n",
    "\n",
    "print(f\"Latitude: {latitude}\")\n",
    "print(f\"Longitude: {longitude}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can use the `*` operator to collect remaining items into a list:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scores = (95, 87, 92, 78, 88)\n",
    "first, second, *rest = scores\n",
    "\n",
    "print(f\"First: {first}\")\n",
    "print(f\"Second: {second}\")\n",
    "print(f\"Rest: {rest}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Unpacking also provides an elegant way to swap two variables:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = 10\n",
    "b = 20\n",
    "\n",
    "a, b = b, a\n",
    "\n",
    "print(f\"a = {a}, b = {b}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Tuples as return values\n",
    "\n",
    "Functions often return tuples when they need to send back multiple values. This is one of the most common uses of tuples in Python:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def min_max(numbers):\n",
    "    \"\"\"Return the minimum and maximum of a list of numbers.\"\"\"\n",
    "    return min(numbers), max(numbers)\n",
    "\n",
    "lowest, highest = min_max([4, 7, 2, 9, 1])\n",
    "print(f\"Lowest: {lowest}\")\n",
    "print(f\"Highest: {highest}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Named tuples\n",
    "\n",
    "When a tuple represents a record with named fields, you can use `namedtuple` from the `collections` module. This makes your code more readable because you can access items by name instead of index:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from collections import namedtuple\n",
    "\n",
    "Point = namedtuple(\"Point\", [\"x\", \"y\"])\n",
    "\n",
    "origin = Point(0, 0)\n",
    "destination = Point(3, 4)\n",
    "\n",
    "print(f\"Origin: ({origin.x}, {origin.y})\")\n",
    "print(f\"Destination: ({destination.x}, {destination.y})\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Named tuples are still tuples — they support indexing, unpacking, and all other tuple operations:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x, y = destination\n",
    "print(f\"Unpacked: x={x}, y={y}\")\n",
    "print(f\"By index: {destination[0]}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## When to use tuples versus lists\n",
    "\n",
    "Use a **tuple** when:\n",
    "\n",
    "- The data should not change after creation (for example, coordinates, dates, or configuration)\n",
    "- You need a hashable value (tuples can be dictionary keys or set members, but lists cannot)\n",
    "- You are returning multiple values from a function\n",
    "\n",
    "Use a **list** when:\n",
    "\n",
    "- You need to add, remove, or change items\n",
    "- The collection will grow or shrink over time\n",
    "- The order of items may need to change"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Immutability in practice\n",
    "\n",
    "Because tuples are immutable, any attempt to modify them raises a `TypeError`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "colours = (\"red\", \"green\", \"blue\")\n",
    "\n",
    "try:\n",
    "    colours[0] = \"yellow\"\n",
    "except TypeError as error:\n",
    "    print(f\"Error: {error}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This immutability is a feature, not a limitation. It means you can trust that a tuple's contents will not change unexpectedly, which makes your code safer and easier to reason about."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise: describe a circle\n",
    "\n",
    "Write a function called `describe_circle` that takes a radius and returns a tuple containing the circumference and area. Then unpack the result into two variables and print them.\n",
    "\n",
    "Use the formulas:\n",
    "\n",
    "- Circumference = 2 × π × radius\n",
    "- Area = π × radius²\n",
    "\n",
    "You can use `math.pi` for the value of π."
   ]
  },
  {
   "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": [
    "import math\n",
    "\n",
    "def describe_circle(radius):\n",
    "    \"\"\"Return the circumference and area of a circle.\"\"\"\n",
    "    circumference = 2 * math.pi * radius\n",
    "    area = math.pi * radius ** 2\n",
    "    return circumference, area\n",
    "\n",
    "circumference, area = describe_circle(5)\n",
    "print(f\"Circumference: {circumference:.2f} metres\")\n",
    "print(f\"Area: {area:.2f} square metres\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Summary\n",
    "\n",
    "In this tutorial, you learned how to:\n",
    "\n",
    "- Create tuples using parentheses `()` or commas\n",
    "- Access items using positive and negative indices\n",
    "- Unpack tuples into individual variables\n",
    "- Use the `*` operator for extended unpacking\n",
    "- Return multiple values from functions using tuples\n",
    "- Create named tuples for readable record-like structures\n",
    "- Understand when to choose tuples over lists\n",
    "\n",
    "## What is next\n",
    "\n",
    "In the next tutorial, you will explore:\n",
    "\n",
    "- **Dictionaries** — key-value pairs for fast lookups and structured data"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.12.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}