{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Comprehensions\n",
    "\n",
    "In this tutorial, you will learn how to use comprehensions — a concise and expressive way to create lists, dictionaries, and sets in Python.\n",
    "\n",
    "**Time commitment:** 15–20 minutes\n",
    "\n",
    "**Prerequisites:**\n",
    "\n",
    "- Python 3.12 or later installed on your machine\n",
    "- Completed tutorials 01 through 04 (lists, tuples, dictionaries, and sets)\n",
    "\n",
    "## Learning objectives\n",
    "\n",
    "By the end of this tutorial, you will be able to:\n",
    "\n",
    "- Create lists using list comprehensions\n",
    "- Filter items with conditional comprehensions\n",
    "- Build dictionaries and sets with comprehensions\n",
    "- Decide when to use a comprehension versus a loop"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## What are comprehensions?\n",
    "\n",
    "A **comprehension** is a compact way to create a new collection by transforming and filtering items from an existing iterable. Instead of writing a multi-line loop, you can express the same logic in a single line."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## List comprehensions\n",
    "\n",
    "The basic syntax for a list comprehension is:\n",
    "\n",
    "```python\n",
    "[expression for item in iterable]\n",
    "```\n",
    "\n",
    "Here is a simple example — creating a list of squares:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "squares = [x ** 2 for x in range(1, 6)]\n",
    "print(squares)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is equivalent to the following loop:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "squares = []\n",
    "for x in range(1, 6):\n",
    "    squares.append(x ** 2)\n",
    "print(squares)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Transformations\n",
    "\n",
    "You can apply any expression to each item. For example, converting strings to uppercase:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "words = [\"hello\", \"world\", \"python\"]\n",
    "upper_words = [word.upper() for word in words]\n",
    "print(upper_words)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Filtering with conditions\n",
    "\n",
    "Add an `if` clause to include only items that meet a condition:\n",
    "\n",
    "```python\n",
    "[expression for item in iterable if condition]\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n",
    "evens = [n for n in numbers if n % 2 == 0]\n",
    "print(evens)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can combine transformations and filtering in the same comprehension:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Squares of even numbers only\n",
    "even_squares = [n ** 2 for n in range(1, 11) if n % 2 == 0]\n",
    "print(even_squares)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Dictionary comprehensions\n",
    "\n",
    "Dictionary comprehensions use a similar syntax with curly braces and a colon separating keys from values:\n",
    "\n",
    "```python\n",
    "{key: value for item in iterable}\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "words = [\"apple\", \"banana\", \"cherry\"]\n",
    "word_lengths = {word: len(word) for word in words}\n",
    "print(word_lengths)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can add conditions to dictionary comprehensions too:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scores = {\"Alice\": 85, \"Bob\": 42, \"Charlie\": 91, \"Diana\": 67}\n",
    "\n",
    "# Only students who scored above 60\n",
    "passing = {name: score for name, score in scores.items() if score > 60}\n",
    "print(passing)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Set comprehensions\n",
    "\n",
    "Set comprehensions use curly braces without the colon:\n",
    "\n",
    "```python\n",
    "{expression for item in iterable}\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "words = [\"hello\", \"HELLO\", \"Hello\", \"world\", \"WORLD\"]\n",
    "unique_lower = {word.lower() for word in words}\n",
    "print(unique_lower)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Nested comprehensions\n",
    "\n",
    "You can nest `for` clauses to work with multiple iterables. For example, creating all pairs from two lists:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "colours = [\"red\", \"blue\"]\n",
    "sizes = [\"small\", \"large\"]\n",
    "\n",
    "combinations = [(colour, size) for colour in colours for size in sizes]\n",
    "print(combinations)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Keep nested comprehensions simple. If the logic becomes hard to read, a regular loop is usually clearer."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## When to use comprehensions versus loops\n",
    "\n",
    "Comprehensions are best when:\n",
    "\n",
    "- The logic is simple and fits comfortably on one or two lines\n",
    "- You are creating a new collection from an existing one\n",
    "- The transformation or filter is straightforward\n",
    "\n",
    "Use a regular loop when:\n",
    "\n",
    "- The logic involves multiple steps or side effects\n",
    "- The comprehension would be difficult to read\n",
    "- You need to handle exceptions or use `break` and `continue`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Generator expressions\n",
    "\n",
    "If you replace the square brackets with parentheses, you get a **generator expression**. Generators produce items one at a time instead of building the entire collection in memory, which is useful for large datasets:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# List comprehension — builds the entire list in memory\n",
    "total_list = sum([x ** 2 for x in range(1000)])\n",
    "\n",
    "# Generator expression — computes values one at a time\n",
    "total_gen = sum(x ** 2 for x in range(1000))\n",
    "\n",
    "print(total_list == total_gen)  # Same result, different memory usage"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise: word length dictionary\n",
    "\n",
    "Given the following list of words:\n",
    "\n",
    "```python\n",
    "words = [\"the\", \"quick\", \"brown\", \"fox\", \"jumps\", \"over\", \"the\", \"lazy\", \"dog\"]\n",
    "```\n",
    "\n",
    "Using comprehensions:\n",
    "\n",
    "1. Create a dictionary mapping each word to its length, but only for words longer than three characters\n",
    "2. Create a set of all unique word lengths\n",
    "3. Create a list of words sorted by length (shortest first), with duplicates removed"
   ]
  },
  {
   "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": [
    "words = [\"the\", \"quick\", \"brown\", \"fox\", \"jumps\", \"over\", \"the\", \"lazy\", \"dog\"]\n",
    "\n",
    "# 1. Dictionary of words longer than three characters\n",
    "long_words = {word: len(word) for word in words if len(word) > 3}\n",
    "print(f\"Long words: {long_words}\")\n",
    "\n",
    "# 2. Set of unique word lengths\n",
    "lengths = {len(word) for word in words}\n",
    "print(f\"Unique lengths: {lengths}\")\n",
    "\n",
    "# 3. Sorted unique words by length\n",
    "unique_words = list(dict.fromkeys(words))  # Remove duplicates, keep order\n",
    "sorted_by_length = sorted(unique_words, key=len)\n",
    "print(f\"Sorted by length: {sorted_by_length}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Summary\n",
    "\n",
    "In this tutorial, you learned how to:\n",
    "\n",
    "- Create lists with list comprehensions using `[expression for item in iterable]`\n",
    "- Filter items with conditional comprehensions using `if`\n",
    "- Build dictionaries with `{key: value for item in iterable}`\n",
    "- Build sets with `{expression for item in iterable}`\n",
    "- Use nested comprehensions for combinations\n",
    "- Choose between comprehensions and regular loops\n",
    "- Use generator expressions for memory efficiency\n",
    "\n",
    "## What is next\n",
    "\n",
    "In the next tutorial, you will explore:\n",
    "\n",
    "- **Slicing and unpacking** — advanced techniques for extracting and selecting data from sequences"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.12.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
