From 165dd5619dc286bbae61da34089676d5ad0bcbc4 Mon Sep 17 00:00:00 2001 From: Kodlo-chan Date: Wed, 23 Sep 2026 17:15:36 +0700 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BB=D0=B0=D0=BD=20v2=20=D0=BF=D0=BE=20?= =?UTF-8?q?=D0=B4=D0=B8=D0=B0=D0=B3=D0=BD=D0=BE=D1=81=D1=82=D0=B8=D0=BA?= =?UTF-8?q?=D0=B5=20(=D0=BA=D0=B2=D0=B8=D0=B7=2014/32)=20+=204=20=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D1=8B=D0=B5=20=D0=B7=D0=B0=D0=B4=D0=B0=D1=87=D0=B8?= =?UTF-8?q?=20(hash,=20heap,=20dijkstra,=20subnet)=20+=20=D1=82=D1=80?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=D0=B6=D1=91=D1=80=2060=20=D0=BA=D0=B0=D1=80?= =?UTF-8?q?=D1=82=D0=BE=D1=87=D0=B5=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 +- README.md | 54 ++- WEEK7.md | 253 +++++----- diag/README.md | 31 +- diag/facts.json | 591 +++++++++++++++++++++++ diag/facts_drill.py | 147 ++++++ diag/grade.py | 5 + diag/tasks/10_hash/solution.cpp | 31 ++ diag/tasks/10_hash/task.md | 32 ++ diag/tasks/10_hash/test_hash.cpp | 78 +++ diag/tasks/11_heap/solution.cpp | 19 + diag/tasks/11_heap/task.md | 24 + diag/tasks/11_heap/test_heap.cpp | 74 +++ diag/tasks/12_dijkstra/solution.cpp | 14 + diag/tasks/12_dijkstra/task.md | 21 + diag/tasks/12_dijkstra/test_dijkstra.cpp | 58 +++ diag/tasks/13_subnet/solution.cpp | 29 ++ diag/tasks/13_subnet/task.md | 36 ++ diag/tasks/13_subnet/test_subnet.cpp | 66 +++ 19 files changed, 1429 insertions(+), 136 deletions(-) create mode 100644 diag/facts.json create mode 100644 diag/facts_drill.py create mode 100644 diag/tasks/10_hash/solution.cpp create mode 100644 diag/tasks/10_hash/task.md create mode 100644 diag/tasks/10_hash/test_hash.cpp create mode 100644 diag/tasks/11_heap/solution.cpp create mode 100644 diag/tasks/11_heap/task.md create mode 100644 diag/tasks/11_heap/test_heap.cpp create mode 100644 diag/tasks/12_dijkstra/solution.cpp create mode 100644 diag/tasks/12_dijkstra/task.md create mode 100644 diag/tasks/12_dijkstra/test_dijkstra.cpp create mode 100644 diag/tasks/13_subnet/solution.cpp create mode 100644 diag/tasks/13_subnet/task.md create mode 100644 diag/tasks/13_subnet/test_subnet.cpp diff --git a/.gitignore b/.gitignore index 1491a59..2acf892 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ diag/solutions/ diag/submissions/ diag/dist/ -diag/tasks/*/t_0* +diag/tasks/*/t_* diag/tasks/07_epoll/solution diag/tasks/09_gdb/crash_checked diag/tasks/08_bash/big.log diff --git a/README.md b/README.md index a6d58b3..55f1f8f 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,52 @@ # ИУП: подготовка к собеседованию Eltex (C/C++, алгоритмы, Linux, сети) -Личный учебный план и диагностический пакет. Репозиторий обновляется по ходу недели. +Личный учебный план и пакет задач с автопроверкой. Обновляется по ходу недели. ## Что внутри -- `WEEK7.md` — **рабочий документ**: интенсив на 7 дней (D0 23.09 → D7 30.09), три блока - в дне (алгоритмы / системное / практика+разбор), у каждого дня контрольная точка. +- `WEEK7.md` — **рабочий документ**: интенсив на 7 дней (D0 23.09 → D7 30.09), пересобранный + по результату диагностики (квиз 14/32: алгоритмы 1/8, Linux 3/8, сети 3/8, C/C++ 7/8). + Каждый день: карточки 30 мин → алгоритмы 3 ч → системное 3 ч → разбор и код 1,5 ч. - `PLAN.md` — полный трек M1–M10 на пост-офферный период (модули, артефакты, материалы). -- `CONTEXT.md` — вводные, разбор вакансии (Eltex, hh #136775547), статус лейна. -- `diag/` — диагностический пакет: - - `quiz.py` + `quiz.json` — 32 вопроса по 4 доменам (C/C++, алгоритмы, Linux, сети); - - `tasks/01..09` — 9 практических задач с автопроверкой (ASAN/UBSAN, python-драйверы); - - `grade.py` — прогон задач с автооценкой; - - `questions_export.txt` — вопросы квиза текстом (можно отвечать строкой в чате). -- `TRACKER.md` — дневник: что решено, где встал, сколько минут. +- `TRACKER.md` — дневник: что решено, где встал, сколько минут. Это вход для калибровки. +- `diag/` — пакет: + - `quiz.py` + `quiz.json` — 32 вопроса по 4 доменам; + - `tasks/01..09` — диагностика: биты, список, ring buffer, IPv4-заголовок, НВП, потоки, + epoll-сервер, bash по логу, разбор падения в gdb; + - `tasks/10..13` — добор под слабые домены: хеш-таблица с открытой адресацией, куча + (`heapify` O(n), top-K), Дейкстра, арифметика подсетей; + - `facts_drill.py` + `facts.json` — 60 карточек «числа и факты» (сложности, байты + заголовков, `/26 = 62`, TTL, порядок TCP-хендшейка, паддинг структур); + - `grade.py` — автопроверка задач (ASAN/UBSAN, таймауты, PASS/FAIL + полный лог); + - `questions_export.txt` — вопросы квиза текстом. ## Как начать ```bash git clone https://git.kodlo.art/Kodlo/eduplan-cpp-eltex.git cd eduplan-cpp-eltex/diag -python3 quiz.py # квиз, ответы — номер варианта -python3 grade.py # все 9 задач (с санитайзерами) -python3 grade.py 01 04 # выборочно +python3 quiz.py # квиз (или --answers "1-3 2-4 ...") +python3 grade.py # все 13 задач с санитайзерами +python3 grade.py 10 11 13 # выборочно +python3 grade.py --fast # без санитайзеров +python3 facts_drill.py # 20 карточек интерактивно +python3 facts_drill.py --all # все 60 ``` -Нужны `g++` (C++17), `gcc`, `python3`; для задачи 07 — только Linux-сокеты, -для 08 — bash/awk/sort. Никаких внешних зависимостей. +Нужны `g++` (C++17), `gcc`, `python3`; для `07_epoll` — Linux-сокеты, для `08_bash` — +bash/awk/sort. Внешних зависимостей нет. На Windows задачи 07 и 08 удобнее гонять в WSL. ## Правила честности -Эталонных решений в репозитории нет — они лежат только на машинке студии, чтобы -автопроверка не протухла. Задача 09 (`answer.txt`) — часть оценки: важен ход разбора. +Эталонных решений в репозитории нет (лежат только на машинке студии) — иначе автопроверка +превращается в шпаргалку. Требования по сложности, которые тест не ловит (например +`heapify` за O(n)), проверяются на разборе по коду. `answer.txt` в задаче 09 — обязательная +часть оценки. -## Как отдавать результаты +## Куда отдавать результаты -- `python3 grade.py --submit` — складывает `results_*.json` и свои файлы решений рядом; -- либо коммит в ветку `answers/<день>` и push (доступ на запись у аккаунта `tura`); -- либо файлами в чат / в топик лейна. +- ветка `answers/<день>` в этом репозитории; +- либо файлы/`results_*.json` в чат; +- либо `python3 grade.py --submit` — складывает результаты и решения рядом. + +Ежедневный блок плана приходит в топик лейна в 09:00 (НСК). diff --git a/WEEK7.md b/WEEK7.md index 2fe0e72..4469bbb 100644 --- a/WEEK7.md +++ b/WEEK7.md @@ -1,138 +1,167 @@ # ИУП — интенсив 7 дней (фуллтайм, собеседование ≤ 1 неделя) -Срок: собеседование максимум через неделю, подготовка — фуллтайм (~8 ч/день). -Цель интенсива: закрыть ровно то, что спросят на входе в Eltex (C/C++, базовые алгоритмы и -структуры данных, Linux, L2/L3-сети, инструменты), и не утонуть в глубине. +**Версия 2, 23.09** — пересобрана по результату диагностики (квиз 14/32). +Срок: собеседование максимум через неделю, подготовка фуллтайм (~8 ч/день). +Цель: закрыть ровно то, что спросят на входе в Eltex (C/C++, базовые алгоритмы и структуры +данных, Linux, L2/L3-сети, инструменты), и не утонуть в глубине. + +## Что показала диагностика (это и определило план) + +| Домен | Результат | Вывод | +|---|---|---| +| C/C++ | 7/8 | сильная сторона, **времени почти не даём** | +| Алгоритмы | **1/8** | провал: формальные свойства (сложности, условия применимости) | +| Linux | 3/8 | пропущены fork/COW, epoll, mmap, waitpid — «не был уверен» | +| Сети | 3/8 | пропущены числа и байты (14/4/62/TTL), порядок TCP-хендшейка | + +Отдельно: из 32 вопросов **11 пропущены**, и это почти все «точные факты» — числа, байты, +сложности. То есть база есть, а не хватает заученных формулировок и уверенности в них. +Поэтому в плане появился ежедневный блок карточек (`facts_drill.py`), а C++ урезан: +за него отвечает сильная сторона, забирать у неё часы смысла нет. ## Правила интенсива -1. Каждый день — три блока: **утро (3 ч) алгоритмы**, **день (3 ч) системное/язык**, - **вечер (2 ч) практика + разбор**. Блок не начинается, пока не закрыта вчерашняя точка. +1. Каждый день — три блока: **карточки (30 мин) → алгоритмы (3 ч) → системное (3 ч) → + разбор и код (1,5 ч)**. Блок не начинается, пока не закрыта вчерашняя точка. 2. День закрыт, только если: решены задачи дня **без подсказки**, написан/починен код дня, - сделана **одна задача вслух на таймер** (10 мин: 2 мин на разбор + код + сложность). -3. Всё пишем, а не читаем: теория — только под конкретную задачу, максимум 40 минут. -4. Трекер: `/home/pi/eduplan/diag/submissions/` или файл у себя — что решено, где встал, - сколько минут. Это вход для калибровки на следующий день. -5. Что осознанно НЕ лезет в неделю: полноценное ядро (модуль+драйвер), электроника, - Docker-продакшн. Ядро — обзорно на D6, электроника — после оффера (см. `PLAN.md`). -6. Если день провален — не догоняем всё, а переносим хвост в D7 (день повтора) и режем - второстепенное (M8-обзор, docker). + сделана **одна задача вслух на таймер** (10 мин: разбор + код + сложность), заполнен + трекер. +3. Теория — только под конкретную задачу, максимум 40 минут. Всё остальное — код и разбор + своих ошибок по трекеру. +4. Требования по сложности, которые тест не ловит (например `heapify` за O(n)) — проверяю + глазами по коду при разборе. Если делаешь «на оценку» не то, что заявлено, увижу. +5. Что осознанно НЕ лезет в неделю: полноценное ядро (модуль+драйвер как продукт), + электроника и схемотехника, Docker в продакшне. Ядро — обзорно на D6, электроника — + после оффера (полный трек `PLAN.md`, модули M8–M9). +6. Провалил день — не догоняю всё, переношу хвост в D7 и режу второстепенное (ядро, docker). -Материалы: cppreference (C и C++), man7.org (разделы 2/3/7), LKMPG, docs.kernel.org, -OSTEP (главы про процессы/память), Beej's Guide to Network Programming, RFC 791/793, -Wireshark wiki. Задачи: Codeforces с фильтром по тегам и рейтингу 800–1400 -(`https://codeforces.com/problemset?tags=two+pointers,800-1400` — менять тег), -Яндекс Тренировки по алгоритмам (бесплатные контесты с разборами, -`https://yandex.ru/yaintern/training/algorithm-training`), классика LeetCode — по названию -(ссылки на leetcode.com открываются только в браузере). +Материалы: cppreference (C и C++), man7.org (разделы 2/3/7), Beej's Guide to Network +Programming, RFC 791/793, Wireshark wiki, OSTEP (процессы/память), LKMPG, docs.kernel.org. +Задачи: Codeforces с фильтром по тегу и рейтингу 800–1400 +(`https://codeforces.com/problemset?tags=two+pointers,800-1400` — тег менять), +Яндекс Тренировки по алгоритмам (`https://yandex.ru/yaintern/training/algorithm-training`), +классика LeetCode — по названию (ссылки на leetcode.com открываются только в браузере). + +## Пакет задач (каталог `diag/`) + +- `01..09` — **диагностика**: битовые операции, список, ring buffer, IPv4-заголовок, НВП, + потоки, epoll-сервер, bash по логу, разбор падения в gdb. +- `10_hash`, `11_heap`, `12_dijkstra`, `13_subnet` — **добор под слабые домены**: своя + хеш-таблица с открытой адресацией, куча (`heapify` за O(n), top-K), Дейкстра на + `priority_queue`, арифметика подсетей. +- `python3 grade.py` — все 13 с автопроверкой (ASAN/UBSAN); `python3 grade.py 10 13` — + выборочно; `--fast` — без санитайзеров. +- `python3 facts_drill.py` — 60 карточек «числа и факты» (algo 20, linux 14, net 14, + c_cpp 12). Маркеры: `>=80%` ок, `50–79%` добор, `<50%` дыра. +- Трекер дня: `TRACKER.md` в корне репозитория. --- -## D0 — 23.09, диагностика (3–4 ч) +## D0 — 23.09, диагностика (квиз пройден, 14/32) -- Квиз: `python3 quiz.py` (или ответы строкой в чат), 32 вопроса, 4 домена. -- Задачи: `python3 grade.py` — 9 задач с автопроверкой (ASAN/UBSAN). -- Разбор промахов вместе со мной, калибровка: какие дни усилить, что срезать. +- Квиз: **пройден** — 14/32, слабейшие домены: алгоритмы, Linux, сети (см. таблицу выше). +- Осталось: `cd diag && python3 grade.py 01 02 03 04 05 06 07 08 09` — 9 практических задач. +- Разбор промахов квиза вместе со мной, калибровка дней (уже сделана в этой версии плана). - **Точка:** есть `results_quiz.json` и `results_tasks.json`, названы 3 слабейших темы. -## D1 — 24.09: массивы/строки + память в C + процессы Linux +## D1 — 24.09: инварианты сложности + хеш-таблицы | fork/COW/сигналы -- Утро (3 ч) — алгоритмы: два указателя, скользящее окно, префиксные суммы, бинарный - поиск и `lower_bound` руками. Задачи: Two Sum, Valid Palindrome, Container With Most - Water, Longest Substring Without Repeating Characters, Binary Search + 6–8 задач с - Codeforces по тегам `two pointers` и `binary search`, рейтинг 800–1400. -- День (3 ч) — C и память: указатели и арифметика, выравнивание и padding, UB - (signed overflow, strict aliasing, неинициализированная память), malloc/free, - переполнение буфера; сборка с `-Wall -Wextra -Werror -fsanitize=address,undefined`. - Практика: переписать `tasks/01_bits` и `tasks/03_ring` на чистом C (без C++-обёрток), - прогнать под санитайзерами. -- Вечер (2 ч) — Linux: `fork/exec/wait`, зомби, сигналы, коды возврата. Практика: свой - мини-шелл (fork + exec + wait, обработка `exit`) — это прямой вопрос на собеседовании. -- **Точка:** 10+ задач решены; мини-шелл запускает `ls`/`cat`; ASAN чистый; лог дня. +- Карточки (30 мин) — домен `algo`: бинарный поиск в 1e6, хеш O(1)/worst O(n), heapify O(n), + устойчивость сортировок, Дейкстра и условие применимости. +- Утро, алгоритмы (3 ч) — **формальные свойства, а не «умение решать»**: O-оценки и + амортизация, хеш-таблицы (цепочки vs открытая адресация, загрузка, tombstone, worst O(n)), + сортировки и устойчивость, бинарный поиск и `lower_bound` руками. + Практика: **`tasks/10_hash`** — своя таблица на линейном зондировании; 6–8 задач + Codeforces по тегам `hashing` и `sortings`, рейтинг 800–1400. +- День, системное (3 ч) — Linux: `fork/exec/wait`, **COW-страницы**, зомби и `waitpid`, + сигналы и коды возврата (137 = 128+9 SIGKILL, 139 = 128+11 SIGSEGV), `errno`. + Практика: мини-шелл (fork + exec + wait + `exit` + пайплайн по желанию) — прямой вопрос + на собеседовании; прогнать под ASAN. +- Разбор и код (1,5 ч) — `tasks/01_bits` и `tasks/03_ring` **на чистом C** (без C++-обёрток), + сборка `-Wall -Wextra -Werror -fsanitize=address,undefined`; одна задача вслух; трекер. +- **Точка:** 10_hash PASS; мини-шелл запускает `ls`/`cat`; ASAN чистый; карточки `algo` ≥ 80%. -## D2 — 25.09: хеш-таблицы и сортировки + C++ (RAII, владение, STL) + git/Makefile +## D2 — 25.09: деревья и куча | epoll и неблокирующие сокеты -- Утро (3 ч) — алгоритмы: хеш-таблицы и множества (коллизии, открытая адресация vs - цепочки), сортировки и компараторы, stability. Задачи: Group Anagrams, Top K Frequent - Elements, Two Sum II, Merge Intervals, Sort Colors + 6–8 задач с тегами `hashing`, - `sortings`. -- День (3 ч) — C++: RAII, правило 0/3/5, move-семантика и `std::move`, умные указатели - (unique/shared/weak, циклы ссылок), контейнеры STL и итераторы, лямбды, исключения и - гарантии безопасности. Практика: свой `unique_ptr` и свой `vector` (упрощённый), класс- - владелец с правилом пяти; сборка с `-Werror`. -- Вечер (2 ч) — git (ветки, rebase, конфликты, `bisect`) и сборка: Makefile руками, затем - CMake для своих задач. Практика: репозиторий с 3 задачами, где сборка/тесты — одной - командой. -- **Точка:** мини-библиотека собирается с `-Werror`; git-сценарии (rebase, bisect) пройдены - руками; 10+ задач. - -## D3 — 26.09: списки/стек + многопоточность + gdb - -- Утро (3 ч) — алгоритмы: связные списки (разворот, цикл Флойда, k-й с конца), стек/ - очередь/ring buffer, монотонный стек. Задачи: Reverse Linked List, Merge Two Sorted - Lists, Linked List Cycle, Valid Parentheses, Daily Temperatures + 6–8 задач с тегами - `linked list`, `stack`. -- День (3 ч) — многопоточность: `std::thread`, mutex/`lock_guard`, `condition_variable` - и predicate-цикл, атомики и `memory_order`, гонки, дедлоки, ложные пробуждения; - паттерны producer/consumer и пул потоков. Практика: довести `tasks/06_threads` до - продакшн-вида + написать пул потоков. TSan на этой машине недоступен (VMA) — проверяем - инвариантами + ASAN, в логе это помечено. -- Вечер (2 ч) — gdb: breakpoints, `watch`, `bt`, `frame`, отладка потоков, разбор - падения. Практика: `tasks/09_gdb` + своё падение (переполнение буфера) с письменным - разбором. -- **Точка:** 10+ задач; пул потоков проходит счётчики 4×4×20k; падение разобрано в gdb - письменно. - -## D4 — 27.09: деревья/куча + Linux-системное (epoll) + L2/L3 - -- Утро (3 ч) — алгоритмы: бинарные деревья и BST, обходы (рекурсия и итерация), куча и - `priority_queue`, heapify за O(n), top-K, LCA. Задачи: Invert Binary Tree, Validate - Binary Search Tree, Binary Tree Level Order Traversal, Kth Largest Element, Lowest - Common Ancestor + 6–8 задач с тегами `trees`, `heaps`. -- День (3 ч) — Linux: файловый ввод-вывод (`open/read/write/lseek`), `mmap`, +- Карточки (30 мин) — `algo`: heap insert O(log n), top-K, обходы деревьев, сложность DFS + по памяти. +- Утро, алгоритмы (3 ч) — бинарные деревья и BST: обходы рекурсией и итерацией, + валидация BST, LCA; куча: **`heapify` снизу вверх за O(n)**, `priority_queue`, top-K, + `nth_element`. Практика: **`tasks/11_heap`** + задачи Invert Binary Tree, Validate BST, + Kth Largest Element, Top K Frequent Elements. +- День, системное (3 ч) — Linux: файловый ввод-вывод (`open/read/write/lseek`), **`mmap`**, мультиплексирование `select → poll → epoll` (LT/ET), неблокирующие сокеты, - `SO_REUSEADDR`, обработка `EAGAIN/EINTR`. Практика: `tasks/07_epoll` — эхо-сервер - довести до вида «не падает под нагрузкой» + клиент, нагрузочный тест своими руками. -- Вечер (2 ч) — сети L2/L3: OSI и TCP/IP, инкапсуляция, Ethernet (14 байт заголовка), - MAC, ARP, VLAN 802.1Q, IPv4-заголовок и контрольная сумма, маски и подсети, ICMP. - Практика: `tasks/04_ipv4` + два письменных разбора дампов (`tcpdump`: ARP-обмен и - TCP-handshake). -- **Точка:** сервер держит 3+ одновременных соединения и не течёт по дескрипторам; - 2 разбора дампов письменно; 10+ задач. + `SO_REUSEADDR`, `EAGAIN`/`EINTR`. Практика: **`tasks/07_epoll`** — эхо-сервер держит + нагрузку, плюс нагрузочный тест своими руками. +- Разбор и код (1,5 ч) — C++ **ровно по промахам**: выравнивание и padding (sizeof 12), + правило 0/3/5, virtual-деструктор, `std::move` как каст, UB — 45 минут, не больше; + одна задача вслух; трекер. +- **Точка:** 11_heap и 07_epoll PASS; сервер держит 3+ соединений и не течёт по дескрипторам; + карточки `c_cpp` = 100%. -## D5 — 28.09: графы + TCP глубоко + bash/proc +## D3 — 26.09: списки/стек | многопоточность и gdb -- Утро (3 ч) — алгоритмы: представления графа, BFS/DFS, топологическая сортировка, - компоненты связности, Дейкстра (неотрицательные веса), union-find. Задачи: Number of - Islands, Course Schedule, Clone Graph, Network Delay Time, Redundant Connection + 6–8 - задач с тегами `graphs`, `dfs and similar`, `dsu`. -- День (3 ч) — сети глубже: TCP-автомат состояний, окно и подтверждения, ретрансмиссии и - таймеры, закрытие соединения, UDP, порты, NAT, DNS/DHCP минимум; работа с +- Карточки (30 мин) — `linux`: epoll O(1) против select O(n), mmap, waitpid, 137/139. +- Утро, алгоритмы (3 ч) — связные списки (разворот, **цикл Флойда**, k-й с конца), + стек/очередь/ring buffer, монотонный стек. Практика: `tasks/02_list`, `tasks/03_ring` + (если ещё не сданы) + Valid Parentheses, Daily Temperatures, Reverse Linked List. +- День, системное (3 ч) — многопоточность: `std::thread`, mutex/`lock_guard`, + `condition_variable` и predicate-цикл, атомики и `memory_order`, гонки, дедлоки, + ложные пробуждения; паттерны producer/consumer и пул потоков. Практика: + `tasks/06_threads` до продакшн-вида + свой пул потоков. (TSan на этой машине может + отвалиться по VMA — тогда инварианты + ASAN, в логе это помечено.) +- Разбор и код (1,5 ч) — gdb: breakpoints, `watch`, `bt`, `frame`, отладка потоков; + `tasks/09_gdb` + **своё падение** с письменным разбором; одна задача вслух; трекер. +- **Точка:** пул потоков проходит счётчики 4×4×20k; падение разобрано письменно; + карточки `linux` ≥ 80%. + +## D4 — 27.09: графы | L2/L3 и подсети + +- Карточки (30 мин) — `net`: 14 байт Ethernet, VLAN +4, /26 = 62, TTL, SYN/SYN-ACK/ACK. +- Утро, алгоритмы (3 ч) — графы: представления, BFS/DFS, топологическая сортировка + (только для DAG), компоненты связности, **Дейкстра (веса ≥ 0)**, union-find. + Практика: **`tasks/12_dijkstra`** + Number of Islands, Course Schedule, Network Delay + Time, Redundant Connection + 6–8 задач с тегами `graphs`, `dsu`. +- День, системное (3 ч) — сети L2/L3: OSI и TCP/IP, инкапсуляция, Ethernet (14 байт), + MAC, ARP, VLAN 802.1Q (+4 байта), IPv4-заголовок (20 байт) и контрольная сумма, **TTL**, + маски и подсети, ICMP. Практика: `tasks/04_ipv4` + **`tasks/13_subnet`** + два письменных + разбора дампов (`tcpdump`: ARP-обмен и TCP-handshake). +- Разбор и код (1,5 ч) — задача вслух (граф или подсеть); разбор промахов дня; трекер. +- **Точка:** 12_dijkstra и 13_subnet PASS; 2 разбора дампов письменно; карточки `net` ≥ 80%. + +## D5 — 28.09: ДП | TCP глубоко, bash и /proc + +- Карточки (30 мин) — `net` + `c_cpp` (20 байт IPv4/TCP, MTU 1500, DNS 53, ASAN/UBSAN). +- Утро, алгоритмы (3 ч) — ДП: состояния и переходы, рюкзак 0/1, **НВП за O(n log n)**, + строки (Edit Distance, Word Break). Практика: `tasks/05_lis` + Climbing Stairs, + House Robber, Coin Change, Edit Distance + 5–6 задач с тегом `dp`. +- День, системное (3 ч) — TCP глубоко: автомат состояний, окно и подтверждения, + ретрансмиссии и таймеры, закрытие, UDP, порты, NAT, DNS/DHCP минимум; работа с `tcpdump`/Wireshark. Практика: 3 письменных разбора дампов (ретрансмиссия, ICMP - unreachable, DNS) + свой захват трафика своей же программой. -- Вечер (2 ч) — Linux-быт: `/proc`, `ulimit`, systemd-юниты обзорно; bash (пайплайны, - `set -euo pipefail`, awk/sed для логов). Практика: `tasks/08_bash` + свой скрипт - диагностики сети/логов. -- **Точка:** 8+ задач; 3 разбора дампов; скрипт работает на логе в 100k строк за секунды. + unreachable, DNS) + захват трафика своей программой. +- Разбор и код (1,5 ч) — bash и Linux-быт: пайплайны, `set -euo pipefail`, awk/sed, + `/proc`, `ulimit`; `tasks/08_bash` (лог в 1e6 строк — только awk/sort, не bash-цикл); + одна задача вслух; трекер. +- **Точка:** 05_lis и 08_bash PASS; 3 разбора дампов; скрипт держит лог 100k строк за секунды. -## D6 — 29.09: ДП + ядро/embedded обзорно + docker + резюме +## D6 — 29.09: закрытие слабых мест | ядро обзорно, docker, резюме -- Утро (3 ч) — алгоритмы: ДП — состояния и переходы, рюкзак, LIS за O(n log n), строковые - задачи. Задачи: Climbing Stairs, House Robber, Coin Change, Longest Increasing - Subsequence, Edit Distance, Word Break + 5–6 задач с тегом `dp`, рейтинг 800–1400. -- День (3 ч) — ядро и embedded обзорно: модуль ядра (сборка, insmod/rmmod, printk, - параметры, `/proc`), символьный драйвер (`file_operations`), device tree и cross-compile - — по касательной, ровно чтобы отвечать словами. Практика: hello-модуль + простейший - символьный драйвер (в QEMU, если на этой машине сборка не встанет). -- Вечер (2 ч) — docker (образ под сборку/тесты) и **резюме + сопроводительное под Eltex** - (учесть: рассматривают офисный формат). -- **Точка:** модуль грузится/выгружается; черновик резюме отревьюен. +- Карточки (30 мин) — все домены, полный прогон 60 карточек. +- Утро, алгоритмы (3 ч) — **только слабые места по трекеру**: добить `10..13` до PASS, + повторить hash/BST/heap/Дейкстра/ДП там, где были FAIL или «не знаю». +- День, системное (3 ч) — ядро и embedded **обзорно**: модуль (сборка, insmod/rmmod, + printk, параметры, `/proc`), символьный драйвер (`file_operations`), device tree, + cross-compile, uboot/bringup — ровно чтобы отвечать словами; docker (образ под + сборку/тесты) — 45 минут. +- Разбор и код (1,5 ч) — **резюме + сопроводительное под Eltex** (учесть офисный формат), + вопросы работодателю, легенда по опыту. +- **Точка:** все 13 задач PASS; карточки ≥ 80% во всех доменах; резюме готово к отправке. -## D7 — 30.09: повтор, два мок-интервью, выход +## D7 — 30.09: повтор и два мок-интервью -- 2 ч — утренний повтор: только слабые места из трекера (не всё подряд). -- 3 ч — мок-интервью №1: алгоритмы, 2 задачи вслух с таймером, я веду и разбираю. -- 2 ч — мок-интервью №2: C/C++ + Linux + сети (вопросы по кругу, включая разбор дампа). +- 2 ч — повтор: только карточки с промахами и слабые темы из трекера. +- 3 ч — мок-интервью №1: алгоритмы, 2 задачи вслух с таймером (я веду и разбираю). +- 2 ч — мок-интервью №2: C/C++ + Linux + сети, включая разбор дампа. - 1 ч — вопросы работодателю, легенда по опыту, финальная вычитка резюме. - **Точка:** ≤ 1 грубая ошибка на сессию; резюме и сопроводительное готовы. @@ -142,4 +171,4 @@ Wireshark wiki. Задачи: Codeforces с фильтром по тегам и Незакрытое (полный трек `PLAN.md`): M2 хвост (битовые/строковые алгоритмы, математика), M6 глубина (STP, маршрутизация, HTTP), M7 полностью (Docker в бою, bisect на реальном -репозитории), M8 ядро (полноценный драйвер, uboot/bringup), M9 электроника. +репозитории), M8 ядро (полноценный драйвер, uboot/bringup), M9 электроника и схемотехника. diff --git a/diag/README.md b/diag/README.md index 319966d..48478ce 100644 --- a/diag/README.md +++ b/diag/README.md @@ -21,6 +21,19 @@ | 08 | `08_bash` | разбор лога доступа: TOTAL/TOP-3/5XX, 110k строк | | 09 | `09_gdb` | отладка ASAN-падения + письменный разбор в `answer.txt` | + Добор под слабые домены (добавлено 23.09 после диагностики — квиз 14/32): + + | # | Задача | Что проверяется | + |---|--------|-----------------| + | 10 | `10_hash` | хеш-таблица с открытой адресацией: пробирование, перехеширование, erase | + | 11 | `11_heap` | куча: `heapify` снизу вверх O(n), `kth_largest`, `top_k` на 3M | + | 12 | `12_dijkstra` | Дейкстра на `priority_queue`: неотрицательные веса, 100k вершин | + | 13 | `13_subnet` | арифметика IPv4: сеть/broadcast/узлы, `/31`, `/32`, подбор префикса | + +- `facts.json` + `facts_drill.py` — тренажёр «числа и факты»: 60 карточек + (algo 20, linux 14, net 14, c_cpp 12) под конкретные промахи квиза: сложности, байты + заголовков, `/26 = 62`, TTL, порядок SYN/SYN-ACK/ACK, паддинг структур, правило трёх/пяти. + Маркеры итога: `>=80%` ок, `50–79%` добор, `<50%` дыра. - `grade.py` — прогон задач с автооценкой (`PASS/FAIL`, число проверок, время). - `solutions/` — **эталонные решения, в руки не отдавать**: они лежат здесь, чтобы автопроверка не «протухла» (эталон → все PASS, заглушка → FAIL). @@ -35,9 +48,13 @@ python3 grade.py # все задачи (с ASAN/UBSAN/TSAN) python3 grade.py 01 04 # только выбранные python3 grade.py --fast # без санитайзеров, быстрее python3 grade.py --submit # результаты + свои файлы в submissions/ +python3 facts_drill.py # 20 карточек, интерактивно +python3 facts_drill.py --all # все 60 +python3 facts_drill.py --domain algo # только алгоритмы +python3 facts_drill.py --answers "20|62|ttl|..." # проверить строку ответов разом ``` -Порядок: сначала квиз, потом задачи с 01 по 09. Зависание = FAIL (есть таймауты). +Порядок: сначала квиз, потом задачи с 01 по 09 (диагностика), затем 10–13 как добор. Зависание = FAIL (есть таймауты). Правила честности: без встроенных `popcount`/`reverse`-хелперов, без копирования чужих решений, `answer.txt` в задаче 09 — обязательная часть (оценивается ход разбора). @@ -45,10 +62,20 @@ python3 grade.py --submit # результаты + свои файлы в subm - `results_quiz.json`: балл по каждому домену + разбор промахов с пояснениями. - `results_tasks.json`: по задаче — PASS/FAIL, число проверок, время, полный лог сборки. -- Итог и решение по плану (с каких модулей стартовать) собираются по этим двум файлам. +- `results_facts.json`: тренажёр карточек — балл по доменам и список промахов. +- Итог и решение по плану (с каких модулей стартовать) собираются по этим файлам. +- Рабочий план недели: `WEEK7.md` в корне репозитория; трекер дня — `TRACKER.md`. ## Публикация и доступ +Репозиторий: https://git.kodlo.art/Kodlo/eduplan-cpp-eltex — клонируется без пароля, +обновляется по ходу недели. Результаты можно присылать веткой `answers/<день>`, файлами +в чат или через `grade.py --submit`. + +Репозиторий: https://git.kodlo.art/Kodlo/eduplan-cpp-eltex (клонируется без пароля, +обновляется по ходу недели). Результаты можно присылать веткой `answers/<день>`, +файлами в чат или через `grade.py --submit`. + - Пакет в каталоге: `/home/pi/eduplan/diag` (мир читает; каталог доступен на исполнение). - Инбокс для ответов: `/home/pi/eduplan/diag/submissions/` (0777). - Архив для скачивания: `/home/pi/eduplan/diag/dist/eduplan_diag.tar.gz`. diff --git a/diag/facts.json b/diag/facts.json new file mode 100644 index 0000000..078ecd0 --- /dev/null +++ b/diag/facts.json @@ -0,0 +1,591 @@ +{ + "version": 1, + "note": "Карточки под промахи квиза 23.09: точные числа, сложности и условия.", + "facts": [ + { + "domain": "algo", + "q": "Средняя сложность поиска в хеш-таблице при разумной загрузке?", + "a": "амортизированное O(1)", + "accept": [ + "o(1)", + "амортизированное o(1)", + "o(1) амортизированное", + "амортизированно o(1)" + ] + }, + { + "domain": "algo", + "q": "Худшая сложность поиска в хеш-таблице (все ключи в одну корзину)?", + "a": "O(n)", + "accept": [ + "o(n)" + ] + }, + { + "domain": "algo", + "q": "Сколько сравнений в худшем случае у бинарного поиска в массиве из 1 000 000 элементов?", + "a": "20", + "accept": [ + "20", + "log2 1e6 = 20", + "около 20" + ] + }, + { + "domain": "algo", + "q": "При каком условии применим алгоритм Дейкстры?", + "a": "веса рёбер неотрицательны", + "accept": [ + "неотрицательные веса", + "все веса >= 0", + "веса неотрицательны", + "нет отрицательных рёбер" + ] + }, + { + "domain": "algo", + "q": "Чем заменить Дейкстру при отрицательных рёбрах?", + "a": "Беллман–Форд", + "accept": [ + "беллман-форд", + "беллман форд", + "bellman-ford" + ] + }, + { + "domain": "algo", + "q": "Сложность построения кучи (heapify) из произвольного массива?", + "a": "O(n)", + "accept": [ + "o(n)", + "о(n)" + ] + }, + { + "domain": "algo", + "q": "Сложность вставки одного элемента в кучу?", + "a": "O(log n)", + "accept": [ + "o(log n)" + ] + }, + { + "domain": "algo", + "q": "Какая сортировка устойчивая: merge, quick, heap или selection?", + "a": "merge sort", + "accept": [ + "merge", + "merge sort", + "слиянием", + "сортировка слиянием" + ] + }, + { + "domain": "algo", + "q": "Худшая сложность quicksort?", + "a": "O(n²)", + "accept": [ + "o(n^2)", + "o(n²)", + "o(n2)" + ] + }, + { + "domain": "algo", + "q": "Средняя сложность quicksort и mergesort?", + "a": "O(n log n)", + "accept": [ + "o(n log n)", + "o(n*logn)", + "o(nlogn)" + ] + }, + { + "domain": "algo", + "q": "Для какого графа возможна топологическая сортировка?", + "a": "для ациклического (DAG)", + "accept": [ + "dag", + "ациклический", + "без циклов", + "dag (ациклический)" + ] + }, + { + "domain": "algo", + "q": "Как найти цикл в связном списке за O(1) дополнительной памяти?", + "a": "алгоритм Флойда: черепаха и заяц", + "accept": [ + "флойд", + "черепаха и заяц", + "алгоритм флойда" + ] + }, + { + "domain": "algo", + "q": "BFS или DFS даёт кратчайший путь в невзвешенном графе?", + "a": "BFS", + "accept": [ + "bfs", + "обход в ширину" + ] + }, + { + "domain": "algo", + "q": "Сложность Дейкстры на priority_queue (двоичная куча)?", + "a": "O((V+E) log V)", + "accept": [ + "o((v+e) log v)", + "o((v+e)logv)", + "o(e log v)" + ] + }, + { + "domain": "algo", + "q": "За какую сложность ищется НВП (LIS) в оптимальном решении?", + "a": "O(n log n)", + "accept": [ + "o(n log n)", + "o(nlogn)", + "o(n*log n)" + ] + }, + { + "domain": "algo", + "q": "Сложность задачи о рюкзаке 0/1 через ДП по вместимости?", + "a": "O(n * W)", + "accept": [ + "o(n*w)", + "o(nw)", + "o(n*w)" + ] + }, + { + "domain": "algo", + "q": "Насколько быстро работают операции union-find с эвристиками?", + "a": "почти O(1), O(α(n))", + "accept": [ + "почти o(1)", + "o(alpha(n))", + "o(α(n))", + "обратная функция аккермана" + ] + }, + { + "domain": "algo", + "q": "Какова сложность по памяти рекурсивного DFS в графе в худшем случае?", + "a": "O(V) — глубина стека рекурсии", + "accept": [ + "o(v)", + "o(h)", + "o(v) в худшем случае" + ] + }, + { + "domain": "algo", + "q": "Какова нижняя оценка сложности сортировки сравнениями?", + "a": "O(n log n)", + "accept": [ + "o(n log n)", + "o(nlogn)" + ] + }, + { + "domain": "algo", + "q": "Структура для поиска слов по префиксу за O(len)?", + "a": "trie (префиксное дерево)", + "accept": [ + "trie", + "префиксное дерево", + "бор" + ] + }, + { + "domain": "linux", + "q": "Что делает fork()?", + "a": "создаёт новый процесс (свой PID, COW-копия памяти)", + "accept": [ + "создаёт новый процесс", + "создает новый процесс", + "новый процесс" + ] + }, + { + "domain": "linux", + "q": "Что происходит со страницами памяти при fork()?", + "a": "copy-on-write: копируются при первой записи", + "accept": [ + "cow", + "copy-on-write", + "копирование при записи" + ] + }, + { + "domain": "linux", + "q": "Какой механизм держит десятки тысяч соединений в одном потоке?", + "a": "epoll", + "accept": [ + "epoll", + "эполл" + ] + }, + { + "domain": "linux", + "q": "Сложность select против epoll на большом числе дескрипторов?", + "a": "select — O(n) по всем, epoll — O(1) на готовое событие", + "accept": [ + "select o(n), epoll o(1)", + "epoll o(1)", + "o(1) на готовое" + ] + }, + { + "domain": "linux", + "q": "Что даёт mmap() на файл?", + "a": "отображает файл в память; страницы подгружаются по обращению", + "accept": [ + "отображает файл в память", + "mmap файла в память", + "отображение файла в память" + ] + }, + { + "domain": "linux", + "q": "Зачем родителю waitpid()?", + "a": "чтобы забрать код возврата и не оставлять зомби", + "accept": [ + "забрать код возврата", + "убрать зомби", + "чтобы не остались зомби" + ] + }, + { + "domain": "linux", + "q": "Что такое процесс-зомби?", + "a": "завершившийся процесс, чья запись ждёт wait/waitpid", + "accept": [ + "завершившийся процесс без wait", + "ждёт wait", + "запись ждёт wait" + ] + }, + { + "domain": "linux", + "q": "Код возврата 137 — что это?", + "a": "SIGKILL (128+9) — убит", + "accept": [ + "sigkill", + "128+9", + "убит sigkill" + ] + }, + { + "domain": "linux", + "q": "Код возврата 139 — что это?", + "a": "SIGSEGV (128+11) — падение по памяти", + "accept": [ + "sigsegv", + "128+11", + "сегфолт" + ] + }, + { + "domain": "linux", + "q": "Что означает errno EAGAIN на неблокирующем сокете?", + "a": "данных сейчас нет, повторить позже", + "accept": [ + "повторить позже", + "нет данных, попробовать снова", + "данных нет" + ] + }, + { + "domain": "linux", + "q": "Что такое TID (Thread ID) в /proc?", + "a": "номер задачи в /proc//task/", + "accept": [ + "номер задачи", + "task", + "в /proc//task" + ] + }, + { + "domain": "linux", + "q": "Что делает ulimit -n?", + "a": "показывает/меняет лимит открытых файловых дескрипторов", + "accept": [ + "лимит дескрипторов", + "лимит открытых файлов", + "число открытых файлов" + ] + }, + { + "domain": "linux", + "q": "Сигнал с номером 15?", + "a": "SIGTERM — запрос на завершение", + "accept": [ + "sigterm", + "sigterm (мягкое завершение)", + "запрос на завершение" + ] + }, + { + "domain": "linux", + "q": "Сколько байт в заголовке TCP без опций?", + "a": "20", + "accept": [ + "20", + "20 байт" + ] + }, + { + "domain": "net", + "q": "Сколько байт в Ethernet-заголовке без VLAN?", + "a": "14", + "accept": [ + "14", + "14 байт" + ] + }, + { + "domain": "net", + "q": "Сколько байт добавляет VLAN-тег 802.1Q?", + "a": "4", + "accept": [ + "4", + "4 байта" + ] + }, + { + "domain": "net", + "q": "Сколько адресов узлов в подсети /26?", + "a": "62", + "accept": [ + "62", + "62 узла" + ] + }, + { + "domain": "net", + "q": "Сколько адресов узлов в подсети /24?", + "a": "254", + "accept": [ + "254" + ] + }, + { + "domain": "net", + "q": "Какое поле IPv4-заголовка уменьшается на 1 на каждом маршрутизаторе?", + "a": "TTL", + "accept": [ + "ttl" + ] + }, + { + "domain": "net", + "q": "Правильная последовательность установки TCP-соединения?", + "a": "SYN -> SYN-ACK -> ACK", + "accept": [ + "syn, syn-ack, ack", + "syn syn-ack ack", + "syn -> syn-ack -> ack" + ] + }, + { + "domain": "net", + "q": "Что делает ARP?", + "a": "находит MAC по IP в локальном сегменте", + "accept": [ + "mac по ip", + "ip -> mac", + "находит mac по ip" + ] + }, + { + "domain": "net", + "q": "Размер MTU Ethernet по умолчанию?", + "a": "1500 байт", + "accept": [ + "1500" + ] + }, + { + "domain": "net", + "q": "На каком уровне OSI работает обычный коммутатор?", + "a": "L2 (канальный)", + "accept": [ + "l2", + "канальный", + "2" + ] + }, + { + "domain": "net", + "q": "На каком уровне OSI работает маршрутизатор?", + "a": "L3 (сетевой)", + "accept": [ + "l3", + "сетевой", + "3" + ] + }, + { + "domain": "net", + "q": "Сколько байт в минимальном IPv4-заголовке?", + "a": "20", + "accept": [ + "20" + ] + }, + { + "domain": "net", + "q": "По какому протоколу и порту работает DNS?", + "a": "UDP/53 (при больших ответах TCP/53)", + "accept": [ + "udp 53", + "53", + "udp/53" + ] + }, + { + "domain": "net", + "q": "Что означает маска /30 в адресах?", + "a": "4 адреса: сеть, 2 узла, broadcast", + "accept": [ + "4 адреса, 2 узла", + "2 узла", + "сеть+2 узла+broadcast" + ] + }, + { + "domain": "net", + "q": "Что делает ICMP Time Exceeded?", + "a": "сообщает, что TTL истёк (используется в traceroute)", + "accept": [ + "ttl истёк", + "истёк ttl", + "сообщает об истечении ttl" + ] + }, + { + "domain": "c_cpp", + "q": "Чему равен sizeof(struct S { char a; int b; char c; }) на x86-64 без pack?", + "a": "12 (паддинг до выравнивания)", + "accept": [ + "12", + "12 байт" + ] + }, + { + "domain": "c_cpp", + "q": "Что такое UB?", + "a": "undefined behavior: стандарт не накладывает требований", + "accept": [ + "undefined behavior", + "неопределённое поведение", + "неопределенное поведение" + ] + }, + { + "domain": "c_cpp", + "q": "Что делает std::move сам по себе?", + "a": "static_cast к rvalue-ссылке; перемещение делает конструктор/присваивание", + "accept": [ + "static_cast к rvalue", + "ничего, это каст", + "это каст к rvalue" + ] + }, + { + "domain": "c_cpp", + "q": "Чем new отличается от malloc?", + "a": "new вызывает конструктор и возвращает типизированный указатель", + "accept": [ + "вызывает конструктор", + "конструктор", + "конструктор + типизированный указатель" + ] + }, + { + "domain": "c_cpp", + "q": "Класс владеет ресурсом и имеет деструктор, копирование не объявлено. Что будет?", + "a": "сгенерируется поверхностное копирование — двойное освобождение", + "accept": [ + "двойное освобождение", + "двойное free", + "поверхностное копирование" + ] + }, + { + "domain": "c_cpp", + "q": "Зачем нужен виртуальный деструктор у базового класса?", + "a": "иначе delete через Base* — UB, деструктор наследника не вызовется", + "accept": [ + "деструктор наследника не вызовется", + "иначе ub", + "чтобы вызвался деструктор наследника" + ] + }, + { + "domain": "c_cpp", + "q": "Что делает const после сигнатуры метода?", + "a": "делает this const, запрещает менять поля", + "accept": [ + "this const", + "запрещает менять поля", + "метод не меняет объект" + ] + }, + { + "domain": "c_cpp", + "q": "Определён ли стандартом порядок вычисления аргументов f(a(), b())?", + "a": "нет, порядок не задан", + "accept": [ + "нет", + "не определён", + "не задан", + "неопределён" + ] + }, + { + "domain": "c_cpp", + "q": "Что проверяет ASAN?", + "a": "выходы за границы, use-after-free, утечки", + "accept": [ + "выходы за границы", + "use-after-free", + "память: границы и use-after-free" + ] + }, + { + "domain": "c_cpp", + "q": "Что проверяет UBSAN?", + "a": "неопределённое поведение: overflow, сдвиги, выравнивание", + "accept": [ + "undefined behavior", + "неопределённое поведение", + "overflow" + ] + }, + { + "domain": "c_cpp", + "q": "Правило трёх/пяти — в чём суть?", + "a": "владеющий ресурсом класс задаёт деструктор, копирование и перемещение", + "accept": [ + "деструктор + копирование + перемещение", + "деструктор, копирование, перемещение", + "нужно определить деструктор и копирование/перемещение" + ] + }, + { + "domain": "c_cpp", + "q": "Чем unique_ptr отличается от shared_ptr?", + "a": "unique_ptr — единственный владелец без счётчика; shared_ptr считает ссылки", + "accept": [ + "единственный владелец", + "нет счётчика ссылок", + "счётчик ссылок у shared_ptr" + ] + } + ] +} diff --git a/diag/facts_drill.py b/diag/facts_drill.py new file mode 100644 index 0000000..dcaf51e --- /dev/null +++ b/diag/facts_drill.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 +"""Тренажёр «числа и факты» — добор по промахам квиза (22.09: algo 1/8, linux 3/8, net 3/8). + +Использование (из каталога diag): + python3 facts_drill.py # интерактивно, 20 случайных карточек + python3 facts_drill.py --count 60 --all # все 60 + python3 facts_drill.py --domain algo # только алгоритмы + python3 facts_drill.py --list # вывести все вопросы с ответами (читать) + python3 facts_drill.py --answers "20|62|ttl|..." # проверить строку ответов + python3 facts_drill.py --answers "..." --count 60 --seed 42 # тот же порядок, что в прогоне + python3 facts_drill.py --selftest # проверить сам набор карточек + +Ответы сравниваются нестрого: регистр, «ё», тире и лишние пробелы не важны. +Результат пишется в results_facts.json. +""" +import argparse +import json +import os +import random +import re +import sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +FACTS_PATH = os.path.join(HERE, "facts.json") + + +def norm(s: str) -> str: + s = (s or "").lower().replace("ё", "е") + s = s.replace("–", "-").replace("—", "-").replace("−", "-") + s = re.sub(r"[^0-9a-zа-я\-\s]", " ", s) + return re.sub(r"\s+", " ", s).strip() + + +def load(): + with open(FACTS_PATH, encoding="utf-8") as f: + return json.load(f)["facts"] + + +def check(fact, answer: str) -> bool: + a = norm(answer) + if not a: + return False + return any(a == norm(x) for x in fact.get("accept", [])) + + +def selftest(facts) -> int: + problems = [] + for i, f in enumerate(facts, 1): + for key in ("domain", "q", "a", "accept"): + if not f.get(key): + problems.append(f"карточка {i}: пустое поле {key}") + if f.get("accept") and not any(norm(x) for x in f["accept"]): + problems.append(f"карточка {i}: варианты ответа нормализуются в пустоту") + if not check(f, f["accept"][0]): + problems.append(f"карточка {i}: канонический ответ не проходит проверку") + doms = {} + for f in facts: + doms[f["domain"]] = doms.get(f["domain"], 0) + 1 + print(f"карточек: {len(facts)}; по доменам: {doms}") + if problems: + print("ПРОБЛЕМЫ:") + for p in problems: + print(" -", p) + return 1 + print("SELFTEST OK") + return 0 + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--answers", default=None, help="ответы через | (тот же порядок, что в интерактиве)") + ap.add_argument("--count", type=int, default=20) + ap.add_argument("--all", action="store_true", help="взять все карточки") + ap.add_argument("--domain", default=None, choices=["algo", "linux", "net", "c_cpp"]) + ap.add_argument("--seed", type=int, default=42) + ap.add_argument("--list", action="store_true") + ap.add_argument("--selftest", action="store_true") + args = ap.parse_args() + + facts = load() + if args.selftest: + sys.exit(selftest(facts)) + if args.domain: + facts = [f for f in facts if f["domain"] == args.domain] + if not facts: + print("нет карточек под фильтр") + sys.exit(1) + + if args.list: + for i, f in enumerate(facts, 1): + print(f"{i:2d}. [{f['domain']}] {f['q']}") + print(f" -> {f['a']}") + print(f"\nвсего: {len(facts)} (полный ответ в facts.json: {FACTS_PATH})") + sys.exit(0) + + rng = random.Random(args.seed) + pool = facts[:] + rng.shuffle(pool) + picked = pool if args.all else pool[:min(args.count, len(pool))] + + results = [] + if args.answers is not None: + given = [x.strip() for x in args.answers.split("|")] + if len(given) < len(picked): + print(f"ответов меньше, чем карточек: {len(given)} < {len(picked)} (проверяю сколько есть)") + for i, f in enumerate(picked): + ans = given[i] if i < len(given) else "" + results.append({"n": i + 1, "domain": f["domain"], "q": f["q"], "answer": ans, + "ok": check(f, ans), "correct": f["a"]}) + else: + print(f"Карточек в прогоне: {len(picked)}. Пустой ввод = не знаю, 'q' = выход.\n") + for i, f in enumerate(picked, 1): + print(f"[{i}/{len(picked)}] ({f['domain']}) {f['q']}") + try: + ans = input(" > ").strip() + except (EOFError, KeyboardInterrupt): + print() + break + if ans.lower() in ("q", "quit", "выход"): + break + ok = check(f, ans) + print(" ok" if ok else " нет. верно: " + f["a"]) + results.append({"n": len(results) + 1, "domain": f["domain"], "q": f["q"], + "answer": ans, "ok": ok, "correct": f["a"]}) + + ok = sum(1 for r in results if r["ok"]) + total = len(results) + per = {} + for r in results: + d = per.setdefault(r["domain"], {"total": 0, "ok": 0}) + d["total"] += 1 + d["ok"] += 1 if r["ok"] else 0 + with open(os.path.join(HERE, "results_facts.json"), "w", encoding="utf-8") as fh: + json.dump({"seed": args.seed, "correct": ok, "total": total, + "per_domain": per, "details": results}, fh, ensure_ascii=False, indent=2) + + print(f"\n=== ИТОГ КАРТОЧЕК: {ok}/{total} ===") + for d, v in sorted(per.items()): + share = 100 * v["ok"] / v["total"] if v["total"] else 0 + mark = "ок" if share >= 80 else ("добор" if share >= 50 else "дыра") + print(f" {d:8s} {v['ok']}/{v['total']} ({share:.0f}%) — {mark}") + print("Подробности: results_facts.json") + sys.exit(0 if total and ok == total else 1) + + +if __name__ == "__main__": + main() diff --git a/diag/grade.py b/diag/grade.py index 3eb97bc..c4caa5e 100644 --- a/diag/grade.py +++ b/diag/grade.py @@ -37,6 +37,10 @@ SPEC = { "07_epoll": ("bin", "test_epoll.py", 300), "08_bash": ("bash", "test_bash.py", 300), "09_gdb": ("py", "test_gdb.py", 300), + "10_hash": ("cpp", "test_hash.cpp", 300), + "11_heap": ("cpp", "test_heap.cpp", 600), + "12_dijkstra":("cpp", "test_dijkstra.cpp",600), + "13_subnet": ("cpp", "test_subnet.cpp", 120), } @@ -151,6 +155,7 @@ def main(): for fn in os.listdir(SUB): os.chmod(os.path.join(SUB, fn), 0o644) print(f"Ответы скопированы в {SUB}") + print("Пакет: 01-09 диагностика, 10-13 добор по слабым доменам (см. WEEK7.md).") sys.exit(0 if done == total else 1) diff --git a/diag/tasks/10_hash/solution.cpp b/diag/tasks/10_hash/solution.cpp new file mode 100644 index 0000000..c4e8073 --- /dev/null +++ b/diag/tasks/10_hash/solution.cpp @@ -0,0 +1,31 @@ +// Задача 10 — хеш-таблица с открытой адресацией. +// Реализуй методы ниже. Заглушка намеренно не проходит проверку. +#include +#include + +class HashTable { +public: + explicit HashTable(std::size_t initial_capacity = 16) { + (void)initial_capacity; + } + + void put(int key, int value) { + (void)key; + (void)value; + } + + bool get(int key, int& out) const { + (void)key; + (void)out; + return false; + } + + bool erase(int key) { + (void)key; + return false; + } + + std::size_t size() const { return 0; } + + std::size_t capacity() const { return 16; } +}; diff --git a/diag/tasks/10_hash/task.md b/diag/tasks/10_hash/task.md new file mode 100644 index 0000000..9568a91 --- /dev/null +++ b/diag/tasks/10_hash/task.md @@ -0,0 +1,32 @@ +# Задача 10 — хеш-таблица с открытой адресацией (C++) + +Свой контейнер, без `std::unordered_map`. Разрешение коллизий — **линейное зондирование** +(linear probing), ёмкость — степень двойки, при загрузке > 0.7 таблица **перехешируется** +вдвое. Индексация — через маску `idx & (capacity - 1)`, а не `%`. + +```c++ +class HashTable { +public: + explicit HashTable(std::size_t initial_capacity = 16); + void put(int key, int value); // обновляет значение, если ключ уже есть + bool get(int key, int& out) const; // false — ключа нет + bool erase(int key); // false — ключа не было + std::size_t size() const; + std::size_t capacity() const; // степень двойки, >= 16 +}; +``` + +Требования: +- `put` 100 000 ключей — суммарно быстрее 2 секунд (в среднем O(1)); +- ключи с совпадающими младшими битами (например, все кратные 16) обязаны находиться — + это проверка зондирования, а не «повезло с хешем»; +- после 100 000 вставок `capacity()` растёт (степень двойки, загрузка ≤ 0.7), а `size()` + честно считает элементы, включая обновления существующих ключей; +- удаление должно работать: после `erase` ключ не находится, а поиск другого ключа, + стоявшего за ним в цепочке зондирования, по-прежнему работает. + +Проверка: `python3 grade.py 10`. Критерий: все `ok`, сборка без предупреждений, +ASAN/UBSAN чистые. + +Разбор после сдачи: почему ёмкость — степень двойки; чем линейное зондирование лучше +цепочек по кэшу и хуже по кластеризации; как tombstone-метки спасают поиск после удаления. diff --git a/diag/tasks/10_hash/test_hash.cpp b/diag/tasks/10_hash/test_hash.cpp new file mode 100644 index 0000000..185f70a --- /dev/null +++ b/diag/tasks/10_hash/test_hash.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include +#include +#include "solution.cpp" + +static int failures = 0; +#define CHECK(cond, name) do { if (cond) std::printf("ok %s\n", name); \ + else { std::printf("FAIL %s (line %d)\n", name, __LINE__); ++failures; } } while (0) + +int main() { + { + HashTable t; + CHECK(t.size() == 0, "новая таблица пуста"); + CHECK(t.capacity() >= 16 && (t.capacity() & (t.capacity() - 1)) == 0, + "capacity — степень двойки и >= 16"); + int v = -1; + CHECK(t.get(42, v) == false, "get по отсутствующему ключу -> false"); + CHECK(t.erase(42) == false, "erase отсутствующего -> false"); + } + { + HashTable t; + for (int i = 0; i < 1000; ++i) t.put(i, i * 7); + CHECK(t.size() == 1000, "1000 ключей: size == 1000"); + bool all = true; + for (int i = 0; i < 1000; ++i) { int v = -1; if (!t.get(i, v) || v != i * 7) all = false; } + CHECK(all, "1000 ключей: все значения читаются"); + t.put(500, 999); + int v = -1; + CHECK(t.get(500, v) && v == 999, "повторный put обновляет значение"); + CHECK(t.size() == 1000, "повторный put не увеличивает size"); + } + { + // ключи с одинаковыми младшими битами — проверка зондирования + HashTable t; + for (int i = 0; i < 512; ++i) t.put(i * 16, i); + bool all = true; + for (int i = 0; i < 512; ++i) { int v = -1; if (!t.get(i * 16, v) || v != i) all = false; } + CHECK(all, "512 ключей, кратных 16: все находятся (линейное зондирование)"); + int missing = -1; + CHECK(t.get(15, missing) == false, "нечётный отсутствующий ключ -> false"); + } + { + // удаление посреди цепочки зондирования + HashTable t; + for (int i = 0; i < 200; ++i) t.put(i * 16, i); + CHECK(t.erase(0) == true, "erase существующего -> true"); + int v = -1; + CHECK(t.get(0, v) == false, "удалённый ключ не найден"); + bool rest = true; + for (int i = 1; i < 200; ++i) { int x = -1; if (!t.get(i * 16, x) || x != i) rest = false; } + CHECK(rest, "остальные ключи из цепочки живы после удаления"); + CHECK(t.size() == 199, "size после удаления == 199"); + } + { + const int N = 100000; + HashTable t; + std::mt19937 rng(4242); + std::vector keys(N); + for (auto& k : keys) k = int(rng() % 1000000); + auto t0 = std::chrono::steady_clock::now(); + for (int i = 0; i < N; ++i) t.put(keys[i], i); + double sec = std::chrono::duration(std::chrono::steady_clock::now() - t0).count(); + std::printf(" (100k вставок: time=%.3fs, capacity=%zu, size=%zu)\n", sec, t.capacity(), t.size()); + CHECK(sec < 2.0, "100k вставок за < 2 c"); + CHECK(t.size() >= 95000 && t.size() <= 100000, "size в разумных пределах"); + CHECK(t.capacity() >= 131072 && t.capacity() <= 262144, "capacity выросла (загрузка <= 0.7)"); + bool all = true; + for (int i = 0; i < N; ++i) { int v = -1; if (!t.get(keys[i], v)) all = false; } + CHECK(all, "все 100k ключей читаются"); + int v = -1; + CHECK(t.get(-12345, v) == false, "отсутствующий ключ -> false"); + } + std::printf(failures ? "\nFAILURES: %d\n" : "\nALL PASS\n", failures); + return failures ? 1 : 0; +} diff --git a/diag/tasks/11_heap/solution.cpp b/diag/tasks/11_heap/solution.cpp new file mode 100644 index 0000000..c72ce9f --- /dev/null +++ b/diag/tasks/11_heap/solution.cpp @@ -0,0 +1,19 @@ +// Задача 11 — куча: heapify за O(n), kth_largest, top_k. +// Реализуй функции ниже. Заглушка намеренно не проходит проверку. +#include + +void heapify(std::vector& a) { + (void)a; +} + +int kth_largest(const std::vector& a, int k) { + (void)a; + (void)k; + return 0; +} + +std::vector top_k(const std::vector& a, int k) { + (void)a; + (void)k; + return {}; +} diff --git a/diag/tasks/11_heap/task.md b/diag/tasks/11_heap/task.md new file mode 100644 index 0000000..6243cd1 --- /dev/null +++ b/diag/tasks/11_heap/task.md @@ -0,0 +1,24 @@ +# Задача 11 — куча: построение за O(n) и top-K (C++) + +Куча **max-heap** в массиве (для элемента `i` дети — `2i+1`, `2i+2`), без `std::priority_queue`. + +```c++ +void heapify(std::vector& a); // перестроить массив в кучу +int kth_largest(const std::vector& a, int k); // k >= 1 +std::vector top_k(const std::vector& a, int k); // k наибольших, по убыванию +``` + +Требования: +- `heapify` — **просеивание снизу вверх (bottom-up), O(n)**, а не `push` в цикле (это O(n log n)); + проверка свойств кучи машинная, требование по сложности я сверяю глазами по коду — на + собеседовании спросят именно это; +- `heapify` обязан сохранить мультимножество элементов (ничего не терять и не дублировать); +- `k` в границах `1..n`; при `k > n` — `top_k` возвращает все по убыванию, `kth_largest` при `k > n` + возвращает значение минимального элемента (то есть не падает); +- 3 000 000 элементов: `heapify` быстрее 2 секунд, `kth_largest` с `k = 1000` быстрее 2 секунд. + +Проверка: `python3 grade.py 11`. Критерий: все `ok`, сборка без предупреждений, +ASAN/UBSAN чистые. + +Разбор после сдачи: почему снизу вверх выходит сумма геометрической прогрессии; когда +нужен min-heap размера k (top-K на потоке); чем `nth_element` отличается от кучи. diff --git a/diag/tasks/11_heap/test_heap.cpp b/diag/tasks/11_heap/test_heap.cpp new file mode 100644 index 0000000..b900784 --- /dev/null +++ b/diag/tasks/11_heap/test_heap.cpp @@ -0,0 +1,74 @@ +#include +#include +#include +#include +#include +#include "solution.cpp" + +static int failures = 0; +#define CHECK(cond, name) do { if (cond) std::printf("ok %s\n", name); \ + else { std::printf("FAIL %s (line %d)\n", name, __LINE__); ++failures; } } while (0) + +static bool is_max_heap(const std::vector& a) { + for (std::size_t i = 1; i < a.size(); ++i) + if (a[(i - 1) / 2] < a[i]) return false; + return true; +} + +int main() { + { + std::vector a = {3, 1, 4, 1, 5, 9, 2, 6}; + std::vector before = a; + heapify(a); + CHECK(is_max_heap(a), "маленький массив: свойство max-heap держится"); + std::vector s1 = before, s2 = a; + std::sort(s1.begin(), s1.end()); std::sort(s2.begin(), s2.end()); + CHECK(s1 == s2, "мультимножество сохранено"); + std::vector empty_a; + heapify(empty_a); + CHECK(empty_a.empty(), "пустой массив -> пустой"); + std::vector one = {7}; + heapify(one); + CHECK(one.size() == 1 && one[0] == 7, "один элемент не теряется"); + } + { + std::vector a = {1, 2, 3, 4, 5}; + CHECK(kth_largest(a, 1) == 5, "kth_largest k=1 -> максимум"); + CHECK(kth_largest(a, 5) == 1, "kth_largest k=n -> минимум"); + CHECK(kth_largest(a, 3) == 3, "kth_largest k=3 -> медиана"); + std::vector z = {4, 4, 4}; + CHECK(kth_largest(z, 2) == 4, "дубликаты: k=2 -> 4"); + std::vector dup = {5, 5, 1, 1, 3}; + CHECK(kth_largest(dup, 4) == 1, "дубликаты: k=4 -> 1"); + std::vector big = top_k(a, 3); + CHECK(big.size() == 3 && big[0] == 5 && big[1] == 4 && big[2] == 3, "top_k(3) по убыванию"); + std::vector all = top_k(a, 10); + CHECK(all.size() == 5 && all.front() == 5 && all.back() == 1, "top_k(k>n) -> все по убыванию"); + std::vector neg = {-5, -1, -9}; + CHECK(kth_largest(neg, 1) == -1, "отрицательные: максимум -1"); + CHECK(kth_largest(a, 100) == 1, "k>n не падает и даёт минимум"); + } + { + const int N = 3000000; + std::mt19937 rng(777); + std::vector a(N); + for (auto& x : a) x = int(rng() % 1000000); + std::vector ref = a; + auto t0 = std::chrono::steady_clock::now(); + heapify(a); + double sec = std::chrono::duration(std::chrono::steady_clock::now() - t0).count(); + std::printf(" (3M heapify: time=%.3fs, top=%d)\n", sec, a.empty() ? -1 : a[0]); + CHECK(is_max_heap(a), "3M: свойство max-heap держится"); + CHECK(a[0] == *std::max_element(ref.begin(), ref.end()), "3M: корень == максимум"); + CHECK(sec < 2.0, "3M heapify за < 2 c"); + t0 = std::chrono::steady_clock::now(); + int k = kth_largest(ref, 1000); + double sec2 = std::chrono::duration(std::chrono::steady_clock::now() - t0).count(); + std::sort(ref.begin(), ref.end(), std::greater()); + std::printf(" (3M kth_largest k=1000: time=%.3fs, value=%d)\n", sec2, k); + CHECK(k == ref[999], "3M: kth_largest(k=1000) совпадает с сортировкой"); + CHECK(sec2 < 3.0, "3M kth_largest за < 3 c"); + } + std::printf(failures ? "\nFAILURES: %d\n" : "\nALL PASS\n", failures); + return failures ? 1 : 0; +} diff --git a/diag/tasks/12_dijkstra/solution.cpp b/diag/tasks/12_dijkstra/solution.cpp new file mode 100644 index 0000000..1934fc4 --- /dev/null +++ b/diag/tasks/12_dijkstra/solution.cpp @@ -0,0 +1,14 @@ +// Задача 12 — кратчайший путь (Дейкстра). +// Реализуй функцию ниже. Заглушка намеренно не проходит проверку. +#include +#include + +long long shortest_path(int n, + const std::vector>& edges, + int src, int dst) { + (void)n; + (void)edges; + (void)src; + (void)dst; + return -1; +} diff --git a/diag/tasks/12_dijkstra/task.md b/diag/tasks/12_dijkstra/task.md new file mode 100644 index 0000000..e2709c4 --- /dev/null +++ b/diag/tasks/12_dijkstra/task.md @@ -0,0 +1,21 @@ +# Задача 12 — Дейкстра на priority_queue (C++) + +```c++ +long long shortest_path(int n, + const std::vector>& edges, // (u, v, w) + int src, int dst); +``` + +Требования: +- рёбра **ориентированные**, вес `w >= 0`, self-loop допустим; вершины `0..n-1`; +- вернуть длину кратчайшего пути `src -> dst` (`long long`, веса суммируются до 10^9); +- недостижимость → `-1`; `src == dst` → `0`; +- вес рёбер 0 допустим (условие применимости Дейкстры — **неотрицательные** веса); +- 100 000 вершин и 200 000 рёбер: быстрее 2 секунд. Значит нужна `std::priority_queue` + (O((V+E) log V)), а не O(V²) перебор минимума. + +Подсказки по разбору (после сдачи): «ленивое» удаление устаревших записей из очереди +(`if (d != dist[v]) continue;`), почему нельзя Дейкстру с отрицательными рёбрами и когда +нужен Беллман–Форд. + +Проверка: `python3 grade.py 12`. Критерий: все `ok`, сборка без предупреждений. diff --git a/diag/tasks/12_dijkstra/test_dijkstra.cpp b/diag/tasks/12_dijkstra/test_dijkstra.cpp new file mode 100644 index 0000000..2c15765 --- /dev/null +++ b/diag/tasks/12_dijkstra/test_dijkstra.cpp @@ -0,0 +1,58 @@ +#include +#include +#include +#include +#include +#include "solution.cpp" + +static int failures = 0; +#define CHECK(cond, name) do { if (cond) std::printf("ok %s\n", name); \ + else { std::printf("FAIL %s (line %d)\n", name, __LINE__); ++failures; } } while (0) + +using E = std::tuple; + +int main() { + { + std::vector e = {{0, 1, 4}, {0, 2, 1}, {2, 1, 1}, {1, 3, 1}}; + CHECK(shortest_path(4, e, 0, 3) == 3, "0->3 через 2: 1+1+1 = 3"); + CHECK(shortest_path(4, e, 0, 0) == 0, "src == dst -> 0"); + CHECK(shortest_path(4, e, 3, 0) == -1, "обратного пути нет -> -1"); + CHECK(shortest_path(4, e, 0, 2) == 1, "прямой путь 0->2 = 1"); + } + { + std::vector e = {{0, 1, 5}, {0, 2, 1}, {2, 1, 1}}; + CHECK(shortest_path(3, e, 0, 1) == 2, "длинное ребро не должно побеждать короткий путь"); + std::vector z = {{0, 1, 0}, {1, 2, 0}}; + CHECK(shortest_path(3, z, 0, 2) == 0, "нулевые веса допустимы"); + std::vector self = {{0, 0, 7}, {0, 1, 2}}; + CHECK(shortest_path(2, self, 0, 1) == 2, "self-loop не ломает поиск"); + std::vector par = {{0, 1, 3}, {0, 1, 7}}; + CHECK(shortest_path(2, par, 0, 1) == 3, "параллельные рёбра: берём минимальное"); + std::vector none; + CHECK(shortest_path(2, none, 0, 1) == -1, "без рёбер -> -1"); + } + { + // цепочка 0-1-2-...-(n-1) по 1000, плюс «ловушки»: обратные рёбра по 100000. + // Ответ детерминирован: идти по цепочке дешевле, чем прыгать назад и снова вперёд. + const int n = 100000; + std::vector e; + e.reserve(200000); + for (int i = 0; i + 1 < n; ++i) e.emplace_back(i, i + 1, 1000); + for (int i = 1; i < n; ++i) e.emplace_back(i, i - 1, 100000); + e.emplace_back(0, 0, 99999999); + auto t0 = std::chrono::steady_clock::now(); + long long d = shortest_path(n, e, 0, n - 1); + double sec = std::chrono::duration(std::chrono::steady_clock::now() - t0).count(); + std::printf(" (100k вершин, 200k рёбер: dist=%lld, time=%.3fs)\n", d, sec); + CHECK(d == (long long)(n - 1) * 1000, "цепочка: кратчайший путь по цепочке"); + CHECK(sec < 2.0, "100k вершин за < 2 c"); + } + { + // большие веса: сумма должна влезать в long long + std::vector e; + for (int i = 0; i + 1 < 2000; ++i) e.emplace_back(i, i + 1, 1000000); + CHECK(shortest_path(2000, e, 0, 1999) == 1999LL * 1000000LL, "большие веса: 1999 * 1e6"); + } + std::printf(failures ? "\nFAILURES: %d\n" : "\nALL PASS\n", failures); + return failures ? 1 : 0; +} diff --git a/diag/tasks/13_subnet/solution.cpp b/diag/tasks/13_subnet/solution.cpp new file mode 100644 index 0000000..68f521e --- /dev/null +++ b/diag/tasks/13_subnet/solution.cpp @@ -0,0 +1,29 @@ +// Задача 13 — арифметика подсетей IPv4. +// Реализуй функции ниже. Заглушка намеренно не проходит проверку. +#include + +struct Subnet { + uint32_t network = 0; + uint32_t broadcast = 0; + uint32_t first_host = 0; + uint32_t last_host = 0; + long long host_count = 0; +}; + +Subnet subnet_of(uint32_t ip, int prefix) { + (void)ip; + (void)prefix; + return Subnet{}; +} + +bool same_subnet(uint32_t a, uint32_t b, int prefix) { + (void)a; + (void)b; + (void)prefix; + return false; +} + +int prefix_for_hosts(int hosts) { + (void)hosts; + return -1; +} diff --git a/diag/tasks/13_subnet/task.md b/diag/tasks/13_subnet/task.md new file mode 100644 index 0000000..c274d51 --- /dev/null +++ b/diag/tasks/13_subnet/task.md @@ -0,0 +1,36 @@ +# Задача 13 — арифметика подсетей IPv4 (C++) + +Адреса передаются в **host byte order** (`0x0A000001` = 10.0.0.1), маска — длиной префикса. + +```c++ +struct Subnet { + uint32_t network; // адрес сети + uint32_t broadcast; // широковещательный адрес + uint32_t first_host; // первый адрес для узла + uint32_t last_host; // последний адрес для узла + long long host_count; // сколько адресов узлов доступно (для /0 не влезает в int) +}; + +Subnet subnet_of(uint32_t ip, int prefix); // 0 <= prefix <= 32 +bool same_subnet(uint32_t a, uint32_t b, int prefix); +int prefix_for_hosts(int hosts); // самая узкая подсеть, куда влезает hosts +``` + +Определения, которые нужно соблюсти: +- для `prefix <= 30`: `host_count = 2^(32-prefix) - 2`, `first_host = network + 1`, + `last_host = broadcast - 1`; +- `/31` (RFC 3021): `host_count = 2`, `first_host = network`, `last_host = broadcast`; +- `/32`: `host_count = 1`, `first_host = last_host = network = broadcast = ip`; +- `prefix_for_hosts(h)`: **наибольший** `prefix` из диапазона `0..30` (то есть самая узкая + подсеть), при котором `host_count >= h`. `h < 1` или `h > 2^32-2` → `-1`. + Исключения `/31` и `/32` в подборе не участвуют — они для линков и одиночных адресов. + +Эталонные примеры, которые обязаны сойтись: `/26` → 62 узла; `/24` → 254; `/30` → 2; +`10.0.1.130/26` → сеть `10.0.1.128`, broadcast `10.0.1.191`, узлы `129..190`; +`prefix_for_hosts(62)` → 26, `prefix_for_hosts(63)` → 25, `prefix_for_hosts(254)` → 24, +`prefix_for_hosts(1)` → 30. + +Проверка: `python3 grade.py 13`. Критерий: все `ok`, сборка без предупреждений. + +Разбор после сдачи: как считать префикс по числу узлов за O(1) (`32 - ceil(log2(h+2))`), +почему `/31` — исключение, что такое маска в бинарном виде. diff --git a/diag/tasks/13_subnet/test_subnet.cpp b/diag/tasks/13_subnet/test_subnet.cpp new file mode 100644 index 0000000..ab39816 --- /dev/null +++ b/diag/tasks/13_subnet/test_subnet.cpp @@ -0,0 +1,66 @@ +#include +#include +#include "solution.cpp" + +static int failures = 0; +#define CHECK(cond, name) do { if (cond) std::printf("ok %s\n", name); \ + else { std::printf("FAIL %s (line %d)\n", name, __LINE__); ++failures; } } while (0) + +static uint32_t ip4(int a, int b, int c, int d) { + return (uint32_t(a) << 24) | (uint32_t(b) << 16) | (uint32_t(c) << 8) | uint32_t(d); +} + +int main() { + { + Subnet s = subnet_of(ip4(10, 0, 1, 130), 26); + CHECK(s.network == ip4(10, 0, 1, 128), "/26: адрес сети 10.0.1.128"); + CHECK(s.broadcast == ip4(10, 0, 1, 191), "/26: broadcast 10.0.1.191"); + CHECK(s.first_host == ip4(10, 0, 1, 129), "/26: первый узел 10.0.1.129"); + CHECK(s.last_host == ip4(10, 0, 1, 190), "/26: последний узел 10.0.1.190"); + CHECK(s.host_count == 62, "/26: 62 узла (не 64)"); + } + { + Subnet s = subnet_of(ip4(192, 168, 5, 200), 24); + CHECK(s.network == ip4(192, 168, 5, 0), "/24: сеть 192.168.5.0"); + CHECK(s.broadcast == ip4(192, 168, 5, 255), "/24: broadcast 192.168.5.255"); + CHECK(s.host_count == 254, "/24: 254 узла"); + Subnet t = subnet_of(ip4(172, 16, 4, 9), 30); + CHECK(t.network == ip4(172, 16, 4, 8) && t.broadcast == ip4(172, 16, 4, 11), + "/30: сеть 172.16.4.8, broadcast 172.16.4.11"); + CHECK(t.host_count == 2 && t.first_host == ip4(172, 16, 4, 9) && t.last_host == ip4(172, 16, 4, 10), + "/30: 2 узла, 9 и 10"); + Subnet z = subnet_of(ip4(10, 0, 0, 5), 32); + CHECK(z.network == ip4(10, 0, 0, 5) && z.broadcast == ip4(10, 0, 0, 5) && z.host_count == 1, + "/32: один адрес, сеть == broadcast == узел"); + Subnet p31 = subnet_of(ip4(10, 0, 0, 1), 31); + CHECK(p31.host_count == 2 && p31.first_host == ip4(10, 0, 0, 0) && p31.last_host == ip4(10, 0, 0, 1), + "/31: RFC 3021 — 2 узла без вычета"); + Subnet eight = subnet_of(ip4(10, 200, 30, 40), 8); + CHECK(eight.network == ip4(10, 0, 0, 0) && eight.broadcast == ip4(10, 255, 255, 255), + "/8: сеть 10.0.0.0, broadcast 10.255.255.255"); + Subnet zero = subnet_of(ip4(8, 8, 8, 8), 0); + CHECK(zero.network == 0 && zero.broadcast == 0xFFFFFFFFu && zero.host_count == 4294967294LL, + "/0: вся сеть, узлов 2^32-2"); + } + { + CHECK(same_subnet(ip4(10, 0, 1, 130), ip4(10, 0, 1, 150), 26), "/26: 130 и 150 в одной подсети"); + CHECK(!same_subnet(ip4(10, 0, 1, 130), ip4(10, 0, 1, 200), 26), "/26: 130 и 200 — разные подсети"); + CHECK(!same_subnet(ip4(10, 0, 1, 130), ip4(10, 0, 1, 127), 26), "/26: 130 и 127 — разные подсети"); + CHECK(same_subnet(ip4(10, 0, 1, 1), ip4(10, 0, 2, 1), 16), "/16: разные третьи октеты — одна сеть"); + CHECK(!same_subnet(ip4(10, 0, 1, 1), ip4(10, 1, 1, 1), 16), "/16: 10.0.x и 10.1.x — разные"); + CHECK(same_subnet(ip4(10, 0, 0, 0), ip4(10, 0, 0, 1), 31), "/31: соседи по RFC 3021"); + } + { + CHECK(prefix_for_hosts(62) == 26, "prefix_for_hosts(62) -> 26"); + CHECK(prefix_for_hosts(63) == 25, "prefix_for_hosts(63) -> 25 (63 в /26 не влезает)"); + CHECK(prefix_for_hosts(254) == 24, "prefix_for_hosts(254) -> 24"); + CHECK(prefix_for_hosts(255) == 23, "prefix_for_hosts(255) -> 23"); + CHECK(prefix_for_hosts(2) == 30, "prefix_for_hosts(2) -> 30"); + CHECK(prefix_for_hosts(1) == 30, "prefix_for_hosts(1) -> 30 (узкая подсеть общего вида)"); + CHECK(prefix_for_hosts(0) == -1, "prefix_for_hosts(0) -> -1"); + CHECK(prefix_for_hosts(4294967295) == -1, "prefix_for_hosts(2^32-1) -> -1 (не влезает)"); + CHECK(prefix_for_hosts(1000000) == 12, "prefix_for_hosts(1000000) -> 12"); + } + std::printf(failures ? "\nFAILURES: %d\n" : "\nALL PASS\n", failures); + return failures ? 1 : 0; +}