{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "39fee41e",
   "metadata": {},
   "source": [
    "# String Methods"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5856ea6e",
   "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": "4337712d",
   "metadata": {},
   "source": [
    "Python provides strings methods that perform a variety of useful operations. A method is similar to a function, it usually takes arguments and returns a value. But the syntax for methods is different from that of functions. A method belongs to an object, so, for example, the method `upper()` that returns a new all uppercase string has to come after a string object with a `.` (**dot notation**), which makes the method syntax like`'banana'.upper()` to output 'BANANA', instead of what a function would look like `upper('banana')`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "dbddc05b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'BANANA'"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "word = 'banana'\n",
    "new_word = word.upper()\n",
    "new_word"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "80eb36d3",
   "metadata": {},
   "source": [
    "This use of the dot operator specifies the name of the method, `upper`, and the name of the string to apply the method to, `word`.\n",
    "The empty parentheses indicate that this method takes no arguments.\n",
    "\n",
    "A method call is called an **invocation**; in this case, we would say that we are invoking `upper` on `word`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "3d1aea57",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "47\n",
      "['capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'removeprefix', 'removesuffix', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']\n"
     ]
    }
   ],
   "source": [
    "methods = [m for m in dir(str) if not m.startswith('_')]\n",
    "num_str_methods = len(methods)\n",
    "print(num_str_methods)  # 47\n",
    "print(methods)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "ed9271a3",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "47"
      ]
     },
     "metadata": {
      "scrapbook": {
       "mime_prefix": "",
       "name": "num_str_methods"
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "from myst_nb import glue\n",
    "glue(\"num_str_methods\", num_str_methods)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4f3919a5",
   "metadata": {},
   "source": [
    "Python offers {glue:}`num_str_methods` string methods. Here below is a collection of some of the commonly used ones.\n",
    "\n",
    "| Category | Method | Description |\n",
    "|---|---|---|\n",
    "| Case | `.upper()` | All uppercase |\n",
    "| Case | `.lower()` | All lowercase |\n",
    "| Search | `.find(x)` | Index of first match, -1 if missing |\n",
    "| Search | `.index(x)` | Index of first match, raises error if missing |\n",
    "| Search | `.count(x)` | Count occurrences |\n",
    "| Whitespace | `.strip()` | Remove leading/trailing whitespace |\n",
    "| Split | `.split(x)` | Split on delimiter |\n",
    "| Join | `.join(lst)` | Join list into string |\n",
    "| Replace | `.replace(a, b)` | Replace all occurrences |\n",
    "| Check | `.isspace()` | All whitespace |\n",
    "| Check | `.isupper()` | All uppercase |\n",
    "| Check | `.islower()` | All lowercase |"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "3c6a212d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--- Case ---\n",
      "  HELLO, WORLD!  \n",
      "  hello, world!  \n",
      "\n",
      "--- Search ---\n",
      "4\n",
      "16\n",
      "2\n",
      "\n",
      "--- Whitespace ---\n",
      "'Hello, World!'\n",
      "\n",
      "--- Split ---\n",
      "['the', 'quick', 'brown', 'fox']\n",
      "\n",
      "--- Join ---\n",
      "apple, banana, cherry\n",
      "\n",
      "--- Replace ---\n",
      "the quick brown cat\n",
      "\n",
      "--- Check ---\n",
      "True\n",
      "True\n",
      "True\n"
     ]
    }
   ],
   "source": [
    "s = \"  Hello, World!  \"\n",
    "words = \"the quick brown fox\"\n",
    "\n",
    "# Case\n",
    "print(\"--- Case ---\")\n",
    "print(s.upper())\n",
    "print(s.lower())\n",
    "\n",
    "# Search\n",
    "print(\"\\n--- Search ---\")\n",
    "print(words.find(\"quick\"))\n",
    "print(words.index(\"fox\"))\n",
    "print(words.count(\"o\"))\n",
    "\n",
    "# Whitespace\n",
    "print(\"\\n--- Whitespace ---\")\n",
    "print(repr(s.strip()))\n",
    "\n",
    "# Split\n",
    "print(\"\\n--- Split ---\")\n",
    "print(words.split(\" \"))\n",
    "\n",
    "# Join\n",
    "print(\"\\n--- Join ---\")\n",
    "print(\", \".join([\"apple\", \"banana\", \"cherry\"]))\n",
    "\n",
    "# Replace\n",
    "print(\"\\n--- Replace ---\")\n",
    "print(words.replace(\"fox\", \"cat\"))\n",
    "\n",
    "# Check\n",
    "print(\"\\n--- Check ---\")\n",
    "print(\"   \".isspace())\n",
    "print(\"HELLO\".isupper())\n",
    "print(\"hello\".islower())\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4a4172ce",
   "metadata": {},
   "source": [
    "## Case Methods\n",
    "\n",
    "Python provides several methods for changing the case of a string. These are useful for normalizing text before comparison or display."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "2aa5cab8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "HELLO, WORLD!\n",
      "hello, world!\n",
      "Hello, World!\n",
      "Hello, world!\n",
      "HELLO, WORLD!\n"
     ]
    }
   ],
   "source": [
    "s = 'hello, world!'\n",
    "\n",
    "print(s.upper())        # 'HELLO, WORLD!'  — all uppercase\n",
    "print(s.lower())        # 'hello, world!'  — all lowercase\n",
    "print(s.title())        # 'Hello, World!'  — first letter of each word capitalized\n",
    "print(s.capitalize())   # 'Hello, world!'  — first letter of string capitalized\n",
    "print(s.swapcase())     # 'HELLO, WORLD!'  — swap upper and lower"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2b622a25",
   "metadata": {},
   "source": [
    "Case methods are often used to make comparisons case-insensitive. For example, you might want to turn a username or email address all uppercase in the case of user login. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "10dd7834",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "False\n",
      "True\n"
     ]
    }
   ],
   "source": [
    "user_input = 'Alice'\n",
    "username    = 'alice'\n",
    "\n",
    "print(user_input == username)                    # False\n",
    "print(user_input.lower() == username.lower())    # True"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "id": "22d2eefa",
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### EXERCISE: Case Methods\n",
    "# Difficulty: Basic\n",
    "user_input = 'PyThOn'\n",
    "target = 'python'\n",
    "# 1. Print user_input in upper, lower, and title case\n",
    "# 2. Print whether user_input matches target case-insensitively\n",
    "### Your code starts here:\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "id": "1389ad4e",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "PYTHON\n",
      "python\n",
      "Python\n",
      "True\n"
     ]
    }
   ],
   "source": [
    "# Solution\n",
    "user_input = 'PyThOn'\n",
    "target = 'python'\n",
    "print(user_input.upper())\n",
    "print(user_input.lower())\n",
    "print(user_input.title())\n",
    "print(user_input.casefold() == target.casefold())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9383f003",
   "metadata": {},
   "source": [
    "## Searching and Testing\n",
    "\n",
    "### Finding a Substring\n",
    "\n",
    "`find(sub)` returns the index of the first occurrence of `sub`, or `-1` if not found. `index(sub)` works the same way but raises a `ValueError` if the substring is not found."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "id": "e465895e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0\n",
      "17\n",
      "-1\n",
      "17\n"
     ]
    }
   ],
   "source": [
    "s = 'data science and data engineering'\n",
    "\n",
    "print(s.find('data'))     # 0  — first occurrence\n",
    "print(s.find('data', 5))  # 17 — search starting at index 5\n",
    "print(s.find('math'))     # -1 — not found\n",
    "\n",
    "print(s.rfind('data'))    # 17 — last occurrence"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bece8efc",
   "metadata": {},
   "source": [
    "### Counting Occurrences\n",
    "\n",
    "`count(sub)` returns the number of non-overlapping occurrences of a substring."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "e363d5c3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3\n",
      "2\n"
     ]
    }
   ],
   "source": [
    "s = 'banana'\n",
    "print(s.count('a'))    # 3\n",
    "print(s.count('an'))   # 2"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4099eafd",
   "metadata": {},
   "source": [
    "### Starts and Ends With\n",
    "\n",
    "`startswith(prefix)` and `endswith(suffix)` test whether a string begins or ends with a given substring. Both return `True` or `False`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "id": "ec42a9a9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n",
      "True\n",
      "False\n"
     ]
    }
   ],
   "source": [
    "filename = 'report_2025.csv'\n",
    "\n",
    "print(filename.startswith('report'))   # True\n",
    "print(filename.endswith('.csv'))       # True\n",
    "print(filename.endswith('.xlsx'))      # False"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4b95c6a5",
   "metadata": {},
   "source": [
    "### The `in` Operator\n",
    "\n",
    "The `in` operator tests whether a substring appears anywhere in a string. It is the most readable way to check for membership."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "id": "af0c59e2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n",
      "False\n",
      "True\n"
     ]
    }
   ],
   "source": [
    "s = 'machine learning'\n",
    "\n",
    "print('learning' in s)    # True\n",
    "print('deep' in s)        # False\n",
    "print('deep' not in s)    # True"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "id": "aea3cc37",
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### EXERCISE: Searching and Testing\n",
    "# Difficulty: Intermediate\n",
    "sentence = 'data science uses data pipelines'\n",
    "# 1. Find the index of the first \"data\"\n",
    "# 2. Find the index of \"data\" starting from position 5\n",
    "# 3. Count how many times \"data\" appears\n",
    "# 4. Check if \"science\" is in sentence\n",
    "### Your code starts here:\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "id": "671c764d",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0\n",
      "18\n",
      "2\n",
      "True\n"
     ]
    }
   ],
   "source": [
    "# Solution\n",
    "sentence = 'data science uses data pipelines'\n",
    "print(sentence.find('data'))\n",
    "print(sentence.find('data', 5))\n",
    "print(sentence.count('data'))\n",
    "print('science' in sentence)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "407bbda6",
   "metadata": {},
   "source": [
    "## Cleaning\n",
    "\n",
    "Real-world text data often contains extra whitespace or unwanted characters. Python provides several methods for cleaning strings.\n",
    "\n",
    "### Stripping Whitespace\n",
    "\n",
    "- `strip()` removes leading and trailing whitespace. \n",
    "- `lstrip()` (left strip) removes only leading whitespace.\n",
    "- `rstrip()` (right strip) removes only trailing whitespace."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "id": "300fa0a3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "'hello, world!'\n",
      "'hello, world!   '\n",
      "'   hello, world!'\n"
     ]
    }
   ],
   "source": [
    "s = '   hello, world!   '\n",
    "\n",
    "print(repr(s.strip()))    # 'hello, world!'\n",
    "print(repr(s.lstrip()))   # 'hello, world!   '\n",
    "print(repr(s.rstrip()))   # '   hello, world!'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4f7c8686",
   "metadata": {},
   "source": [
    "You can also pass a character to strip. For example, `s.strip('.')` removes leading and trailing periods."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "id": "c47fcaef",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "hello\n"
     ]
    }
   ],
   "source": [
    "s = '...hello...'\n",
    "print(s.strip('.'))    # 'hello'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c59d0674",
   "metadata": {},
   "source": [
    "### Replacing Substrings\n",
    "\n",
    "`replace(old, new)` returns a new string with all occurrences of `old` replaced by `new`. An optional third argument limits the number of replacements."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "id": "b5c7470e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "I like dogs. Cats are great.\n",
      "I like dogs. Cats are great.\n",
      "hello world\n"
     ]
    }
   ],
   "source": [
    "s = 'I like cats. Cats are great.'\n",
    "\n",
    "print(s.replace('cats', 'dogs'))        # replace all\n",
    "print(s.replace('cats', 'dogs', 1))     # replace first occurrence only\n",
    "\n",
    "# Useful for removing characters\n",
    "s2 = 'hello, world!'\n",
    "print(s2.replace(',', '').replace('!', ''))   # 'hello world'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "id": "b6383085",
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### EXERCISE: Cleaning Strings\n",
    "# Difficulty: Intermediate\n",
    "raw = '...  Hello, Python!  ...'\n",
    "# 1. Strip leading/trailing dots\n",
    "# 2. Strip leading/trailing whitespace from the result\n",
    "# 3. Replace \"Python\" with \"Data Science\"\n",
    "# 4. Print the cleaned string\n",
    "### Your code starts here:\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "id": "dadf077e",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Hello, Data Science!\n"
     ]
    }
   ],
   "source": [
    "# Solution\n",
    "raw = '...  Hello, Python!  ...'\n",
    "clean = raw.strip('.').strip().replace('Python', 'Data Science')\n",
    "print(clean)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b576468c",
   "metadata": {},
   "source": [
    "## Splitting and Joining\n",
    "\n",
    "### Splitting\n",
    "\n",
    "`split(sep)` breaks a string into a list of substrings at each occurrence of the separator `sep`. If no separator is given, it splits on any whitespace and removes empty strings."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "id": "cf66c128",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['Python', 'R', 'SQL', 'Julia']\n",
      "['one', 'two', 'three']\n",
      "['a', '', 'b', '', 'c']\n",
      "['2025', '08-26']\n"
     ]
    }
   ],
   "source": [
    "s = 'Python,R,SQL,Julia'\n",
    "print(s.split(','))           # ['Python', 'R', 'SQL', 'Julia']\n",
    "\n",
    "s2 = 'one two   three'\n",
    "print(s2.split())             # ['one', 'two', 'three']\n",
    "\n",
    "# Split on a specific delimiter, keeping empty strings\n",
    "s3 = 'a,,b,,c'\n",
    "print(s3.split(','))          # ['a', '', 'b', '', 'c']\n",
    "\n",
    "# Limit the number of splits\n",
    "s4 = '2025-08-26'\n",
    "print(s4.split('-', 1))       # ['2025', '08-26']"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f77438ca",
   "metadata": {},
   "source": [
    "### Joining\n",
    "\n",
    "`join(iterable)` is the inverse of `split()`. It concatenates a list of strings into one string, inserting the separator between each element."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "id": "1864509f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Python is fun\n",
      "Python-is-fun\n",
      "Pythonisfun\n",
      "too many spaces\n"
     ]
    }
   ],
   "source": [
    "words = ['Python', 'is', 'fun']\n",
    "\n",
    "print(' '.join(words))     # 'Python is fun'\n",
    "print('-'.join(words))     # 'Python-is-fun'\n",
    "print(''.join(words))      # 'Pythonisfun'\n",
    "\n",
    "# Practical: reassemble a cleaned sentence\n",
    "sentence = '  too   many   spaces  '\n",
    "cleaned  = ' '.join(sentence.split())\n",
    "print(cleaned)             # 'too many spaces'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "id": "19f6b87a",
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### EXERCISE: Splitting and Joining\n",
    "# Difficulty: Intermediate\n",
    "record = 'alice,bob,charlie'\n",
    "# 1. Split the record into a list of names\n",
    "# 2. Join names with \" - \"\n",
    "# 3. Print both the list and joined string\n",
    "### Your code starts here:\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "id": "fbbb9ca1",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['alice', 'bob', 'charlie']\n",
      "alice - bob - charlie\n"
     ]
    }
   ],
   "source": [
    "# Solution\n",
    "record = 'alice,bob,charlie'\n",
    "names = record.split(',')\n",
    "joined = ' - '.join(names)\n",
    "print(names)\n",
    "print(joined)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a9941bad",
   "metadata": {},
   "source": [
    "## String Formatting\n",
    "\n",
    "String formatting inserts values into a string template. Python offers three approaches: f-strings (modern, recommended), `str.format()`, and `%` formatting (legacy).\n",
    "\n",
    "### f-Strings\n",
    "\n",
    "An **f-string** is prefixed with `f` and uses `{}` to embed expressions directly inside the string. F-strings are the most readable and most commonly used approach."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "id": "6fddace4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Student: Alice\n",
      "Score: 95.68\n",
      "Score:      95.68\n",
      "ALICE\n",
      "Double score: 191.356\n"
     ]
    }
   ],
   "source": [
    "name  = 'Alice'\n",
    "score = 95.678\n",
    "\n",
    "print(f'Student: {name}')\n",
    "print(f'Score: {score:.2f}')        # 2 decimal places\n",
    "print(f'Score: {score:>10.2f}')     # right-aligned, width 10\n",
    "print(f'{name.upper()}')              # apply conversion (capitalize)\n",
    "print(f'Double score: {score * 2}') # expressions work inside {}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f748acd0",
   "metadata": {},
   "source": [
    "### Format Specification Mini-Language\n",
    "\n",
    "Inside `{}`, a colon `:` introduces a **format spec** that controls how the value is displayed.\n",
    "\n",
    "| Spec | Meaning | Example |\n",
    "|------|---------|---------|\n",
    "| `.2f` | 2 decimal places (float) | `3.14` |\n",
    "| `d` | integer | `42` |\n",
    "| `e` | scientific notation | `3.14e+00` |\n",
    "| `%` | percentage | `75.00%` |\n",
    "| `>10` | right-align, width 10 | `      3.14` |\n",
    "| `<10` | left-align, width 10 | `3.14      ` |\n",
    "| `^10` | center, width 10 | `   3.14   ` |\n",
    "| `,` | thousands separator | `1,000,000` |"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "id": "bf25eaea",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3.1416\n",
      "3.141593e+00\n",
      "1,000,000\n",
      "75.6%\n",
      "   3.14   \n"
     ]
    }
   ],
   "source": [
    "pi = 3.14159265\n",
    "n  = 1000000\n",
    "r  = 0.756\n",
    "\n",
    "print(f'{pi:.4f}')      # '3.1416'\n",
    "print(f'{pi:e}')        # '3.141593e+00'\n",
    "print(f'{n:,}')         # '1,000,000'\n",
    "print(f'{r:.1%}')       # '75.6%'\n",
    "print(f'{pi:^10.2f}')   # '   3.14   '"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "89cbe6c4",
   "metadata": {},
   "source": [
    "### `str.format()`\n",
    "\n",
    "`str.format()` is an older but still widely used formatting approach. Values are passed as arguments and inserted into `{}` placeholders."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "id": "a7dc9b5b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Name: Bob, Grade: 88.5\n",
      "Name: Bob, Grade: 88.5\n",
      "Name: Bob, Grade: 88.5\n"
     ]
    }
   ],
   "source": [
    "name  = 'Bob'\n",
    "grade = 88.5\n",
    "\n",
    "print('Name: {}, Grade: {:.1f}'.format(name, grade))\n",
    "print('Name: {0}, Grade: {1:.1f}'.format(name, grade))   # positional\n",
    "print('Name: {n}, Grade: {g:.1f}'.format(n=name, g=grade))  # keyword"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "id": "dc2bda0e",
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### EXERCISE: String Formatting\n",
    "# Difficulty: Intermediate\n",
    "name = 'Alice'\n",
    "score = 92.456\n",
    "# 1. Print name and score with score rounded to 1 decimal place using f-string\n",
    "# 2. Print score as a percentage with 1 decimal place (assume score/100)\n",
    "# 3. Print name right-aligned in width 10\n",
    "### Your code starts here:\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "id": "81baaead",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Name: Alice, Score: 92.5\n",
      "Percent: 92.5%\n",
      "     Alice\n"
     ]
    }
   ],
   "source": [
    "# Solution\n",
    "name = 'Alice'\n",
    "score = 92.456\n",
    "print(f'Name: {name}, Score: {score:.1f}')\n",
    "print(f'Percent: {score/100:.1%}')\n",
    "print(f'{name:>10}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1ca40a76",
   "metadata": {},
   "source": [
    "## Type-Checking Methods\n",
    "\n",
    "Python strings have a family of `is*()` methods that test the character composition of a string. Each returns `True` or `False`.\n",
    "\n",
    "| Method | Returns `True` if... |\n",
    "|--------|----------------------|\n",
    "| `isdigit()` | all characters are digits (0–9) |\n",
    "| `isalpha()` | all characters are letters |\n",
    "| `isalnum()` | all characters are letters or digits |\n",
    "| `isspace()` | all characters are whitespace |\n",
    "| `isupper()` | all cased characters are uppercase |\n",
    "| `islower()` | all cased characters are lowercase |\n",
    "| `istitle()` | string is in title case |"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "id": "9c2bdc48",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n",
      "True\n",
      "True\n",
      "True\n",
      "True\n",
      "True\n",
      "True\n",
      "False\n",
      "False\n"
     ]
    }
   ],
   "source": [
    "print('12345'.isdigit())     # True\n",
    "print('abc'.isalpha())       # True\n",
    "print('abc123'.isalnum())    # True\n",
    "print('   '.isspace())       # True\n",
    "print('HELLO'.isupper())     # True\n",
    "print('hello'.islower())     # True\n",
    "print('Hello World'.istitle()) # True\n",
    "\n",
    "# Mixed cases return False\n",
    "print('abc123!'.isalnum())   # False — '!' is not alphanumeric\n",
    "print(''.isdigit())          # False — empty string"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f57a48a5",
   "metadata": {},
   "source": [
    "These methods are useful for input validation:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "id": "86f545b8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Valid year: 2025\n"
     ]
    }
   ],
   "source": [
    "user_input = '2025'\n",
    "\n",
    "if user_input.isdigit():\n",
    "    year = int(user_input)\n",
    "    print(f'Valid year: {year}')\n",
    "else:\n",
    "    print('Please enter a number.')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "id": "34986e8c",
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### EXERCISE: Type-Checking Methods\n",
    "# Difficulty: Intermediate\n",
    "samples = ['123', 'abc', 'abc123', '   ', 'Hello World']\n",
    "# 1. For each sample, print isdigit, isalpha, and isalnum results\n",
    "# 2. For \"Hello World\", print istitle result\n",
    "### Your code starts here:\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "id": "68640dab",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "123 True False True\n",
      "abc False True True\n",
      "abc123 False False True\n",
      "    False False False\n",
      "Hello World False False False\n",
      "True\n"
     ]
    }
   ],
   "source": [
    "# Solution\n",
    "samples = ['123', 'abc', 'abc123', '   ', 'Hello World']\n",
    "for s in samples:\n",
    "    print(s, s.isdigit(), s.isalpha(), s.isalnum())\n",
    "print('Hello World'.istitle())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "60b7f8f0",
   "metadata": {},
   "source": [
    "## Methods Reference\n",
    "\n",
    "Python provides a number of function and methods for string operations. The commonly used methods are: \n",
    "\n",
    "| Operation | Syntax | Description |\n",
    "|-----------|--------|-------------|\n",
    "| Length | `len(s)` | Number of characters |\n",
    "| Indexing | `s[i]` | Character at position `i` |\n",
    "| Slicing | `s[start:stop:step]` | Extract substring |\n",
    "| Concatenation | `s1 + s2` | Join two strings |\n",
    "| Repetition | `s * n` | Repeat string `n` times |\n",
    "| Uppercase | `s.upper()` | All uppercase |\n",
    "| Lowercase | `s.lower()` | All lowercase |\n",
    "| Title case | `s.title()` | Capitalize each word |\n",
    "| Find | `s.find(sub)` | Index of first match, or `-1` |\n",
    "| Count | `s.count(sub)` | Number of occurrences |\n",
    "| Membership | `sub in s` | Test if substring present |\n",
    "| Strip | `s.strip()` | Remove leading/trailing whitespace |\n",
    "| Replace | `s.replace(old, new)` | Substitute substring |\n",
    "| Split | `s.split(sep)` | String → list |\n",
    "| Join | `sep.join(list)` | List → string |\n",
    "| f-string | `f'{var:.2f}'` | Formatted string literal |\n",
    "| Type check | `s.isdigit()`, etc. | Test character composition |"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "id": "e3dbb7c4",
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### EXERCISE: Methods Reference Practice\n",
    "# Difficulty: Intermediate\n",
    "s = '  banana split  '\n",
    "# Use at least 4 methods from this section to:\n",
    "# 1. Remove outer spaces\n",
    "# 2. Replace \"split\" with \"bread\"\n",
    "# 3. Convert to uppercase\n",
    "# 4. Check whether \"BANANA\" is in the final string\n",
    "### Your code starts here:\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "id": "41b70f67",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "BANANA BREAD\n",
      "True\n"
     ]
    }
   ],
   "source": [
    "# Solution\n",
    "s = '  banana split  '\n",
    "t = s.strip().replace('split', 'bread').upper()\n",
    "print(t)\n",
    "print('BANANA' in t)"
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Tags",
  "kernelspec": {
   "display_name": ".venv (3.13.7)",
   "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
}
