{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "3b02fa24",
   "metadata": {},
   "source": [
    "# Лабораторная работа: регулярные выражения в Python\n",
    "\n",
    "\n",
    "\n",
    "## Правила\n",
    "\n",
    "- Шаблоны записывайте **сырыми строками**: `r\"\\d+\"`, а не `\"\\d+\"`.\n",
    "- `re.fullmatch(p, s)` — вся строка соответствует шаблону (объект совпадения или `None`).\n",
    "- `re.search(p, s)` — первый подходящий фрагмент (объект или `None`).\n",
    "- `re.sub(p, repl, s)` — новая строка, все совпадения заменены.\n",
    "- У объекта совпадения есть `.group()`, `.start()`, `.end()`.\n",
    "- **Нельзя** использовать `re.search` там, где нужна проверка всей строки, и наоборот.\n",
    "\n",
    "В каждой задаче приводите и подходящие, и неподходящие примеры (граничные случаи: пустая строка, лишний символ, мин/макс длина)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "19b9e2fa",
   "metadata": {},
   "outputs": [],
   "source": [
    "import re\n",
    "\n",
    "def check(pattern, strings, expected=None):\n",
    "    \"\"\"Печатает, какие строки целиком соответствуют шаблону.\n",
    "    Если expected передан — сравнивает и печатает OK/FAIL.\"\"\"\n",
    "    for s in strings:\n",
    "        got = bool(re.fullmatch(pattern, s))\n",
    "        mark = \"OK  \" if (expected is None or got == expected) else \"FAIL\"\n",
    "        print(f\"{mark} {s!r:20} -> {got}\")\n",
    "\n",
    "print(\"check() готов\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c591b2a6",
   "metadata": {},
   "source": [
    "---\n",
    "## Часть 1. `re.fullmatch` — вся строка соответствует шаблону"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e3f42bca",
   "metadata": {},
   "source": [
    "### Задача 1. IPv4-адрес\n",
    "\n",
    "Напишите `is_ipv4(s)` → `True`, если строка имеет вид `октет.октет.октет.октет`, где каждый октет — 1–3 цифры.\n",
    "\n",
    "**Подходят:** `\"192.168.0.1\"`, `\"8.8.8.8\"`, `\"10.0.0.255\"`  \n",
    "**Не подходят:** `\"256.1.1.1.1\"`, `\"192.168.1\"`, `\"192.168.1.\"`, `\"a.b.c.d\"`, `\"\"`\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0dc938fe",
   "metadata": {},
   "outputs": [],
   "source": [
    "def is_ipv4(s):\n",
    "    #вернуть True/False\n",
    "    \n",
    "\n",
    "good = [\"192.168.0.1\", \"8.8.8.8\", \"10.0.0.255\"]\n",
    "bad  = [\"256.1.1.1.1\", \"192.168.1\", \"192.168.1.\", \"a.b.c.d\", \"\"]\n",
    "\n",
    "for s in good:\n",
    "    print(f\"{s!r:20} -> {is_ipv4(s)}\")\n",
    "for s in bad:\n",
    "    print(f\"{s!r:20} -> {is_ipv4(s)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8952bacc",
   "metadata": {},
   "source": [
    "### Задача 2. HEX-цвет\n",
    "\n",
    "`is_hex_color(s)` → `True`, если строка — цвет в формате `#RRGGBB` (ровно 6 шестнадцатеричных цифр после `#`).\n",
    "\n",
    "**Подходят:** `\"#FF0000\"`, `\"#00ff7f\"`, `\"#AbCdEf\"`  \n",
    "**Не подходят:** `\"FF0000\"` (нет `#`), `\"#FFF\"` (3 цифры), `\"#GGGGGG\"`, `\"#1234567\"`, `\"\"`\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9b886fd0",
   "metadata": {},
   "outputs": [],
   "source": [
    "def is_hex_color(s):\n",
    "    # \n",
    "    \n",
    "\n",
    "good = [\"#FF0000\", \"#00ff7f\", \"#AbCdEf\"]\n",
    "bad  = [\"FF0000\", \"#FFF\", \"#GGGGGG\", \"#1234567\", \"\"]\n",
    "for s in good: print(f\"{s!r:20} -> {is_hex_color(s)}\")\n",
    "for s in bad:  print(f\"{s!r:20} -> {is_hex_color(s)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b67ecfd0",
   "metadata": {},
   "source": [
    "### Задача 3. Никнейм\n",
    "\n",
    "`is_nickname(s)` → `True`, если строка:\n",
    "\n",
    "- длина **от 3 до 16** символов;\n",
    "- состоит только из латинских букв, цифр и `_`;\n",
    "- **не начинается** с цифры.\n",
    "\n",
    "**Подходят:** `\"user_1\"`, `\"_abc\"`, `\"A123456789012345\"` (16 симв.)  \n",
    "**Не подходят:** `\"ab\"` (коротко), `\"1abc\"`, `\"abc-\"`, `\"\"`, `\"A1234567890123456\"` (17 симв.)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6bb355b8",
   "metadata": {},
   "outputs": [],
   "source": [
    "def is_nickname(s):\n",
    "    # \n",
    "    \n",
    "\n",
    "good = [\"user_1\", \"_abc\", \"A123456789012345\"]\n",
    "bad  = [\"ab\", \"1abc\", \"abc-\", \"\", \"A1234567890123456\"]\n",
    "for s in good: print(f\"{s!r:20} -> {is_nickname(s)}\")\n",
    "for s in bad:  print(f\"{s!r:20} -> {is_nickname(s)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3cb17ec1",
   "metadata": {},
   "source": [
    "### Задача 4. Время `ЧЧ:ММ` со смысловой проверкой\n",
    "\n",
    "`is_valid_time(s)` → `True`, если строка — корректное время в 24-часовом формате:\n",
    "\n",
    "- форма `ЧЧ:ММ` (по две цифры);\n",
    "- часы `00–23`, минуты `00–59`.\n",
    "\n",
    "**Подходят:** `\"00:00\"`, `\"09:45\"`, `\"23:59\"`  \n",
    "**Не подходят:** `\"24:00\"`, `\"9:45\"`, `\"12:60\"`, `\"99:99\"`, `\"\"`\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d123c15c",
   "metadata": {},
   "outputs": [],
   "source": [
    "def is_valid_time(s):\n",
    "    # \n",
    "\n",
    "good = [\"00:00\", \"09:45\", \"23:59\"]\n",
    "bad  = [\"24:00\", \"9:45\", \"12:60\", \"99:99\", \"\"]\n",
    "for s in good: print(f\"{s!r:20} -> {is_valid_time(s)}\")\n",
    "for s in bad:  print(f\"{s!r:20} -> {is_valid_time(s)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "88ab2c5a",
   "metadata": {},
   "source": [
    "### Задача 5. Индекс и «не часть числа»\n",
    "\n",
    "**(а)** `is_index(s)` → `True`, если строка — ровно 6 цифр (почтовый индекс).\n",
    "\n",
    "**(б)** Объясните в комментарии (и проверьте кодом), почему:\n",
    "\n",
    "```python\n",
    "re.fullmatch(r\"\\d{6}\", \"3440907\")  # → ?\n",
    "re.search(r\"\\d{6}\", \"3440907\")      # → ?\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9eacf8ea",
   "metadata": {},
   "outputs": [],
   "source": [
    "def is_index(s):\n",
    "    #\n",
    "    \n",
    "\n",
    "good = [\"344090\", \"123456\"]\n",
    "bad  = [\"34409\", \"3440900\", \"344 090\", \"abcdef\", \"\"]\n",
    "for s in good: print(f\"{s!r:20} -> {is_index(s)}\")\n",
    "for s in bad:  print(f\"{s!r:20} -> {is_index(s)}\")\n",
    "\n",
    "# проверьте и объясните\n",
    "print(\"fullmatch:\", re.fullmatch(r\"\\d{6}\", \"3440907\"))\n",
    "print(\"search:   \", re.search(r\"\\d{6}\", \"3440907\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1208f2b3",
   "metadata": {},
   "source": [
    "## Часть 2. `re.search` — первый подходящий фрагмент"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9294b7df",
   "metadata": {},
   "source": [
    "### Задача 6. Домен `.ru` / `.com`\n",
    "\n",
    "`find_domain(text)` → первое упоминание домена второго уровня в зоне `.ru` или `.com`, либо `None`.\n",
    "\n",
    "- `\"Сайт example.ru и test.com\"` → `\"example.ru\"`\n",
    "- `\"нет доменов\"` → `None`\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "b563b4ce",
   "metadata": {},
   "outputs": [],
   "source": [
    "def find_domain(text):\n",
    "    #\n",
    "\n",
    "print(find_domain(\"Сайт example.ru и test.com\"))\n",
    "print(find_domain(\"нет доменов\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6a661c97",
   "metadata": {},
   "source": [
    "### Задача 7. Первое число с плавающей точкой\n",
    "\n",
    "`first_float(text)` → первое число с плавающей точкой (с точкой и хотя бы одной цифрой после неё), либо `None`. Знак `+`/`-` учитывается.\n",
    "\n",
    "- `\"Температура -3.14 градуса\"` → `\"-3.14\"`\n",
    "- `\"Версия 1.2.3\"` → `\"1.2\"` (первое совпадение!)\n",
    "- `\"числа 42 нет\"` → `None`\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "73aaa3fd",
   "metadata": {},
   "outputs": [],
   "source": [
    "def first_float(text):\n",
    "    # \n",
    "    pass\n",
    "\n",
    "print(first_float(\"Температура -3.14 градуса\"))\n",
    "print(first_float(\"Версия 1.2.3\"))\n",
    "print(first_float(\"числа 42 нет\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eeded654",
   "metadata": {},
   "source": [
    "### Задача 8. Первый хэштег\n",
    "\n",
    "`first_hashtag(text)` → первый хэштег вида `#слово` (буквы, цифры, `_`; минимум один символ), либо `None`.\n",
    "\n",
    "- `\"Пост #python и #regex\"` → `\"#python\"`\n",
    "- `\"Без хэштегов\"` → `None`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0c1033d4",
   "metadata": {},
   "outputs": [],
   "source": [
    "def first_hashtag(text):\n",
    "    # \n",
    "\n",
    "\n",
    "print(first_hashtag(\"Пост #python и #regex\"))\n",
    "print(first_hashtag(\"Без хэштегов\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a5eb2a5a",
   "metadata": {},
   "source": [
    "### Задача 9. Первое упоминание `@username`\n",
    "\n",
    "`first_mention(text)` → первое упоминание вида `@username`, где `username` начинается с латинской буквы или `_`, затем 1–15 символов `[A-Za-z0-9_]` (итого 2–16), либо `None`.\n",
    "\n",
    "- `\"Привет, @anna_92!\"` → `\"@anna_92\"`\n",
    "- `\"@1user и @ok\"` → `\"@ok\"` (первое подходящее)\n",
    "- `\"нет упоминаний\"` → `None`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cbedd681",
   "metadata": {},
   "outputs": [],
   "source": [
    "def first_mention(text):\n",
    "    # \n",
    "\n",
    "\n",
    "print(first_mention(\"Привет, @anna_92!\"))\n",
    "print(first_mention(\"@1user и @ok\"))\n",
    "print(first_mention(\"нет упоминаний\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c55bae68",
   "metadata": {},
   "source": [
    "### Задача 10. Номер заказа с позицией\n",
    "\n",
    "Номер заказа имеет вид `ORD-XXXX-YYYY`, где `X` и `Y` — цифры.\n",
    "\n",
    "`find_order(text)` находит **первый** номер заказа и возвращает кортеж `(номер, start, end)`, либо `None`. Если заказа нет — `None`.\n",
    "\n",
    "- `\"Заказ ORD-1234-5678 принят\"` → `(\"ORD-1234-5678\", 6, 19)`\n",
    "- `\"ORD-12-34 и ORD-9999-0000\"` → `(\"ORD-9999-0000\", 12, 25)`\n",
    "- `\"Заказов нет\"` → `None`\n",
    "\n",
    "**Подсказка:** `ORD-\\d{4}-\\d{4}` и методы `.group()`, `.start()`, `.end()`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f080af04",
   "metadata": {},
   "outputs": [],
   "source": [
    "def find_order(text):\n",
    "    #\n",
    "    \n",
    "\n",
    "print(find_order(\"Заказ ORD-1234-5678 принят\"))\n",
    "print(find_order(\"ORD-12-34 и ORD-9999-0000\"))\n",
    "print(find_order(\"Заказов нет\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6539fdf9",
   "metadata": {},
   "source": [
    "## Часть 3. `re.sub` — замена фрагментов"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e717a15a",
   "metadata": {},
   "source": [
    "### Задача 11. Удаление HTML-тегов\n",
    "\n",
    "`strip_tags(html)` удаляет **все** HTML-теги вида `<тег>`, `</тег>`, `<тег attr=\"...\">`, оставляя только текст.\n",
    "\n",
    "```python\n",
    "html = '<p>Привет, <b>мир</b>!</p><br/>Конец'\n",
    "strip_tags(html)  # → 'Привет, мир!Конец'\n",
    "```\n",
    "\n",
    "Ещё примеры: `\"<a href='x'>ссылка</a>\"` → `\"ссылка\"`; `\"просто текст\"` → `\"просто текст\"`; `\"<b></b>\"` → `\"\"`.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c2045c35",
   "metadata": {},
   "outputs": [],
   "source": [
    "def strip_tags(html):\n",
    "    #\n",
    "\n",
    "\n",
    "print(strip_tags('<p>Привет, <b>мир</b>!</p><br/>Конец'))\n",
    "print(strip_tags(\"<a href='x'>ссылка</a>\"))\n",
    "print(strip_tags(\"просто текст\"))\n",
    "print(strip_tags(\"<b></b>\"))\n",
    "\n",
    "# Для сравнения — жадный вариант (раскомментируйте и посмотрите):\n",
    "# print(re.sub(r\"<.*>\", \"\", '<b>a</b>'))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7ebd3199",
   "metadata": {},
   "source": [
    "### Задача 12. Удаление повторяющихся слов\n",
    "\n",
    "`remove_duplicates(text)` удаляет **повторяющиеся подряд** слова (регистр не важен), оставляя одно.\n",
    "\n",
    "- `\"это очень очень хороший тест\"` → `\"это очень хороший тест\"`\n",
    "- `\"Привет привет мир\"` → `\"Привет мир\"` (сохраняется первое написание)\n",
    "\n",
    "**Подсказка:** используйте группировку и обратную ссылку: `\\b(\\w+)\\s+\\1\\b` с флагом `re.IGNORECASE`.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ab9149e9",
   "metadata": {},
   "outputs": [],
   "source": [
    "def remove_duplicates(text):\n",
    "    #\n",
    "   \n",
    "\n",
    "print(remove_duplicates(\"это очень очень хороший тест\"))\n",
    "print(remove_duplicates(\"Привет привет мир\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4a46036d",
   "metadata": {},
   "source": [
    "### Задача 13. Разделители только в датах\n",
    "\n",
    "`fix_dates(text)` заменяет `:` на `-` **только** внутри дат вида `ДД:ММ:ГГГГ`, не трогая другие двоеточия.\n",
    "\n",
    "- `\"Дата: 25:01:2027, время: 10:30\"` → `\"Дата: 25-01-2027, время: 10:30\"`\n",
    "- `\"Время 10:30 и 11:45\"` → без изменений (не даты)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8e1ca44c",
   "metadata": {},
   "outputs": [],
   "source": [
    "def fix_dates(text):\n",
    "    #\n",
    "\n",
    "print(fix_dates(\"Дата: 25:01:2027, время: 10:30\"))\n",
    "print(fix_dates(\"Время 10:30 и 11:45\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "96797cd5",
   "metadata": {},
   "source": [
    "### Задача 14. Комментарии `/* ... */` и жадность\n",
    "\n",
    "`remove_comments(code)` удаляет **все** блочные комментарии `/* ... */` из строки.\n",
    "\n",
    "- `\"a /* комментарий */ b\"` → `\"a  b\"`\n",
    "- `\"x = 1; /* c1 */ y = 2; /* c2 */\"` → `\"x = 1;  y = 2; \"`\n",
    "\n",
    "**Подсказка:** сравните два варианта и объясните разницу в комментарии:\n",
    "\n",
    "```python\n",
    "re.sub(r\"/\\*.*\\*/\", \"\", code)    # жадный\n",
    "re.sub(r\"/\\*.*?\\*/\", \"\", code)   # ленивый (non-greedy)\n",
    "```\n",
    "\n",
    "**Вопрос:** что вернёт жадный вариант для `\"a /* x */ b /* y */ c\"` и почему?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c0e57f9c",
   "metadata": {},
   "outputs": [],
   "source": [
    "def remove_comments(code):\n",
    "    # используйте ленивый вариант\n",
    "    pass\n",
    "\n",
    "print(repr(remove_comments(\"a /* комментарий */ b\")))\n",
    "print(repr(remove_comments(\"x = 1; /* c1 */ y = 2; /* c2 */\")))\n",
    "\n",
    "# Для сравнения — жадный вариант:\n",
    "# print(repr(re.sub(r\"/\\*.*\\*/\", \"\", \"a /* x */ b /* y */ c\")))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f5126c01",
   "metadata": {},
   "source": [
    "## Типичные ошибки\n",
    "\n",
    "- `re.search` вместо `re.fullmatch`: `re.search(r\"\\d+\", \"ab12\")` найдёт `\"12\"`, хотя строка — не число.\n",
    "- Неэкранированная точка: `r\"3.14\"` подходит и для `3x14`.\n",
    "- `*` вместо `+`: `r\"\\d*\"` подходит и для пустой строки.\n",
    "- Квантификатор не к тому элементу: `r\"ab+\"` повторяет только `b`, `r\"(ab)+\"` — группу.\n",
    "- Обычная строка вместо сырой: `\"\\b\"` — backspace, а не граница слова.\n",
    "- Жадность `.*` в задаче 14: `.*` съедает всё от первого `/*` до последнего `*/`.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d7a1b743",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.11.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
