ИУП: интенсив 7 дней + диагностический пакет (32 вопроса, 9 задач с автопроверкой)
This commit is contained in:
+10
@@ -0,0 +1,10 @@
|
||||
diag/solutions/
|
||||
diag/submissions/
|
||||
diag/dist/
|
||||
diag/results_*.json
|
||||
diag/tasks/*/t_0*
|
||||
diag/tasks/07_epoll/solution
|
||||
diag/tasks/09_gdb/crash_checked
|
||||
diag/tasks/08_bash/big.log
|
||||
diag/tasks/08_bash/empty.log
|
||||
*.o
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
# Индивидуальный учебный план — CONTEXT
|
||||
|
||||
Лейн: HQ (Kodlo group, -1003909355979), топик `🎓 Индивидуальный учебный план` (thread 1419).
|
||||
Создание/пересоздание: `~/.hermes/scripts/lane_topic_create.py ~/.hermes/eduplan_lane.json "🎓 Индивидуальный учебный план"`.
|
||||
Отправка в лейн: `~/.hermes/scripts/send_lane.py ~/.hermes/eduplan_lane.json "текст" [файл ...]`.
|
||||
Заведён 22.09.2026 по запросу Туры.
|
||||
|
||||
## Вводные (ответы Туры, 23.09.2026)
|
||||
|
||||
1. Ученик — сам Тура (человек в студии), не внешний пользователь.
|
||||
2. Предмет: программирование, приоритет — алгоритмы и структуры данных.
|
||||
Цель-ориентир: вакансия **«Разработчик C/C++», Eltex (Новосибирск), hh #136775547**.
|
||||
3. Формат результата: всё вместе — трек с контрольными точками, расписание,
|
||||
подборка материалов, проверка знаний.
|
||||
4. Критерий выполнения: тесты + выполненные практические задачи.
|
||||
5. Дополнительно: знания «обширные, но нужно плотно повторить и укрепиться перед
|
||||
собеседованием»; нужна диагностика текущего уровня.
|
||||
6. **Срок: собеседование максимум через неделю, подготовка фуллтаймом (~8 ч/день).**
|
||||
Поэтому основной рабочий документ — 7-дневный интенсив `WEEK7.md`, а `PLAN.md`
|
||||
(полный трек M1–M10) переведён в пост-офферный режим: хвост M2, M6, M7, M8, M9.
|
||||
|
||||
## Вакансия-ориентир (данные hh, вытащено 23.09.2026 через chromium --dump-dom)
|
||||
|
||||
- Работодатель: «Предприятие ЭЛТЕКС» (Eltex), Новосибирск. Опыт: 1–3 года.
|
||||
Формат: офис/гибрид (hh в тексте: «рассматриваем только офисный формат»). Полный день.
|
||||
- Ключевые навыки hh: `C/C++`, `Linux`, `TCP/IP`.
|
||||
- Продукт: телеком-оборудование (Ethernet-коммутаторы, сервисные маршрутизаторы,
|
||||
Wi-Fi, GPON, IoT), embedded C/C++.
|
||||
- Задачи: userspace-приложения/библиотеки/утилиты; bringup устройств (uboot → стабильная
|
||||
ОС); драйверы устройств и модули ядра Linux.
|
||||
- Требования:
|
||||
- C/C++ уверенно; **базовые алгоритмы и структуры данных**; многопоточные и сетевые
|
||||
приложения; bash-скрипты.
|
||||
- Коммерческая разработка под Linux.
|
||||
- L2/L3-сети, OSI, TCP/IP, работа с сетевым оборудованием и анализаторами трафика.
|
||||
- Железо/электроника (для аппаратных проектов): схемотехника, измерительное/паяльное,
|
||||
чтение принципиальных схем.
|
||||
- Git, Docker, GDB.
|
||||
- Плюс: опыт разработки устройств на SoC (uboot/linux, драйверы).
|
||||
|
||||
## Артефакты лейна
|
||||
|
||||
- `/home/pi/eduplan/PLAN.md` — трек подготовки (модули, контрольные точки, артефакты,
|
||||
материалы, ритм).
|
||||
- `/home/pi/eduplan/diag/` — диагностический пакет: квиз (32 вопроса, 4 домена) +
|
||||
9 машинно проверяемых практических задач + автооценка (`grade.py`).
|
||||
`diag/submissions/` (0777) — инбокс для файлов-ответов Туры.
|
||||
- `/home/pi/eduplan/diag/dist/eduplan_diag.tar.gz` — тот же пакет для скачивания в TG.
|
||||
- `/home/pi/eduplan/WEEK7.md` — интенсив на 7 дней (D0 23.09 — D7 30.09), три блока в дне,
|
||||
у каждого дня контрольная точка. Выдача дня в лейн: крон `d3af678622f3`
|
||||
(`eduplan-intensive-daily`, 09:00 NSK, no_agent, скрипт `~/.hermes/scripts/eduplan_day.py`,
|
||||
читает WEEK7.md, 8 срабатываний).
|
||||
|
||||
## Статус
|
||||
|
||||
23.09.2026: вводные получены, вакансия разобрана, план и диагностика собраны.
|
||||
Диагностика проверена на двух прогонах: заглушки → 0/9 задач PASS, эталонные решения
|
||||
(`diag/solutions/`, в руки не отдавать) → 9/9 PASS; квиз на верных ответах даёт 32/32.
|
||||
Отложенные прогоны: `python3 grade.py` (все), `python3 quiz.py --answers "1-3 2-4 ..."`.
|
||||
Ждём прохождения диагностики — по её результатам интенсив калибруется (какие дни усилить,
|
||||
что срезать). Срок известен: неделя, фуллтайм. Открытый вопрос: реальные часы в день и
|
||||
дата самого собеседования (для мок-сессий на D7).
|
||||
@@ -0,0 +1,160 @@
|
||||
# ИУП: подготовка под «Разработчик C/C++» (Eltex, Новосибирск)
|
||||
|
||||
> **Актуальный режим (23.09.2026):** собеседование ≤ 1 недели, подготовка фуллтаймом —
|
||||
> рабочий документ сейчас `WEEK7.md` (интенсив 7 дней). Этот файл — полный трек на
|
||||
> пост-офферный период и калибровка по диагностике.
|
||||
|
||||
Цель: пройти собеседование на вакансию hh #136775547 (Eltex, embedded C/C++, Linux, TCP/IP)
|
||||
и подтвердить уровень практикой, а не самооценкой.
|
||||
|
||||
Принципы плана:
|
||||
- каждая точка трека закрывается артефактом, у которого есть машинная или ревью-проверка;
|
||||
- материалы — первопартийные (официальные доки, man-страницы, книги авторов стека);
|
||||
- теории ровно столько, сколько нужно для практики: сначала задача, потом разбор;
|
||||
- ритм недельный, с обязательным «выходом» (код/разбор) каждую неделю;
|
||||
- ревизия плана каждые 4 недели по факту прохождения.
|
||||
|
||||
## Диагностика (вход)
|
||||
|
||||
`/home/pi/eduplan/diag` — квиз по 4 доменам (C/C++, алгоритмы, Linux/OS, сети) и
|
||||
9 практических задач (биты, связный список, ring buffer, разбор IPv4-пакета,
|
||||
ДП, потоки, epoll-сервер, bash, отладка gdb).
|
||||
Результат задаёт: с каких модулей стартовать, какие закрывать «по касательной»,
|
||||
а какие — с нуля. Пока уровня не знаем, все модули помечены как «полный».
|
||||
|
||||
## Трек модулей
|
||||
|
||||
Обозначения: **К** — контрольная точка, **A** — артефакт.
|
||||
Оценка «сдал»: артефакт собирается и проходит автопроверку/ревью; провал — точка не закрыта.
|
||||
|
||||
### M0. Диагностика — 1 день
|
||||
- A: `diag/results_quiz.json`, `diag/results_tasks.json`.
|
||||
- К: пройден квиз (≥ 24/32 верных, иначе модуль-дыра уходит в план) и 9 задач.
|
||||
|
||||
### M1. Системный C — 1 неделя
|
||||
- Память и владение: malloc/free, утечки, double free, dangling, переполнение буфера.
|
||||
- Указательная арифметика, выравнивание, структуры и padding, bitfields, union.
|
||||
- UB: переполнение знаковых, strict aliasing, неинициализированная память, порядок
|
||||
вычислений; как ловить — `-Wall -Wextra -fsanitize=address,undefined`.
|
||||
- Строки/буферы, `memcpy` vs `strcpy`, `restrict`, endianness, work with bytes.
|
||||
- A: 20 задач (массивы, битовые операции, парсинг бинарных структур) + конспект по UB.
|
||||
- К: A собирается с `-Wall -Wextra -Werror` и проходит автопроверку.
|
||||
- Материалы: cppreference (C), man-страницы 3 раздел, lkmpg (глава про кодинг-стиль
|
||||
и ошибки), OSTEP (глава про адресное пространство и malloc).
|
||||
|
||||
### M2. Алгоритмы и структуры данных — 4–6 недель (приоритет по запросу)
|
||||
Порядок — от «спрашивают почти всегда» к «глубина»:
|
||||
1. Сложность, амортизация, оценка на салфетке.
|
||||
2. Массив/строка: два указателя, скользящее окно, префиксные суммы, бинарный поиск
|
||||
(в т.ч. lower_bound руками).
|
||||
3. Сортировки: понимание всех, stability, компараторы; встроенная сортировка с ключом.
|
||||
4. Хеш-таблицы: коллизии, открытая адресация vs цепочки, нагрузка; unordered_map.
|
||||
5. Связные списки: разворот, поиск цикла (Флойд), пересечение, k-й с конца.
|
||||
6. Стек/очередь/ring buffer, монотонный стек/дек.
|
||||
7. Бинарные деревья и BST, обходы (рекурсия/итерация), LCA; куча и priority_queue
|
||||
(heapify O(n), top-K).
|
||||
8. Графы: представления, BFS/DFS, топосорт, компоненты, Дейкстра (неотрицательные веса),
|
||||
union-find.
|
||||
9. ДП: базовые состояния и переходы, задача о рюкзаке, LIS O(n log n), разбор «как
|
||||
доказывать корректность и считать сложность».
|
||||
10. Битовая арифметика, математика, строковые алгоритмы (хеш/префикс-функция — по остатку).
|
||||
- A: 60 задач (Яндекс-тренировки по алгоритмам + LeetCode/Codeforces-набор), 3 «живых»
|
||||
разбора вслух с замером времени, файл-трекер с датами и статусами.
|
||||
- К: 60% задач решены без подсказки за целевое время; 3 разбора приняты на ревью.
|
||||
- Материалы: официальные Яндекс Тренировки по алгоритмам (бесплатно), cppreference,
|
||||
OSTEP — как базис по структурам; Кормен — как справочник, не как старт.
|
||||
|
||||
### M3. C++ как язык — 2 недели
|
||||
- RAII, Rule of 0/3/5, move-семантика, `std::move` vs перемещение, noexcept.
|
||||
- Умные указатели: unique_ptr/shared_ptr/weak_ptr, циклы ссылок.
|
||||
- Шаблоны и STL: контейнеры, итераторы, алгоритмы, лямбды, `auto`, structured bindings.
|
||||
- Исключения и гарантии безопасности (basic/strong), RAII-обёртки.
|
||||
- Виртуальность, vtable, виртуальный деструктор, slicing.
|
||||
- A: своя реализация `vector`/`unique_ptr` + мини-библиотека утилит.
|
||||
- К: сборка с `-Wall -Wextra -Werror` под ASAN/UBSAN, тесты проходят; ревью кода.
|
||||
- Материалы: cppreference (эталон синтаксиса/поведения), Core Guidelines (C++ Core
|
||||
Guidelines — официальный свод), книги автора языка (Страуструп) — выборочно.
|
||||
|
||||
### M4. Многопоточность — 2 недели
|
||||
- std::thread, mutex/lock_guard, condition_variable, атомики и memory_order, барьеры.
|
||||
- Гонки, дедлоки, ложное пробуждение, паттерны: producer/consumer, пул потоков,
|
||||
thread-safe singleton, reader-writer.
|
||||
- Инструменты: ThreadSanitizer, `-pthread`, helgrind — по возможности.
|
||||
- A: bounded blocking queue + пул потоков с тестами, гонки ловятся TSan.
|
||||
- К: TSan чистый на 4×4 потоков/20k элементов, счётчики совпадают.
|
||||
- Материалы: cppreference (atomic/thread), документация компилятора по санитайзерам.
|
||||
|
||||
### M5. Linux: системное программирование — 3 недели
|
||||
- Процессы: fork/exec/wait, зомби, сигналы и маски, exit-коды.
|
||||
- Файловый ввод-вывод: open/read/write/lseek/mmap, буферизация, права.
|
||||
- IPC: pipes, FIFO, unix-сокеты, shared memory.
|
||||
- Мультиплексирование: select → poll → epoll (LT/ET), неблокирующие сокеты.
|
||||
- Сокеты: TCP/UDP клиент-сервер, SO_REUSEADDR, разбор ошибок errno.
|
||||
- /proc, /sys, ulimit, cgroups (обзор), systemd-юниты (обзор).
|
||||
- A: TCP-эхо-сервер на epoll с поддержкой многих соединений + клиент; конспект по
|
||||
системным вызовам с man-страницами.
|
||||
- К: автотест поднимает сервер, гоняет 3 соединения, проверяет эхо; ревью кода.
|
||||
- Материалы: man7.org (man-страницы, разделы 2/3/7), LKMPG, OSTEP (процессы/потоки),
|
||||
Beej's Guide to Network Programming.
|
||||
|
||||
### M6. Сети L2/L3 и анализ трафика — 3 недели
|
||||
- OSI/TCP-IP стеки; инкапсуляция; MTU/фрагментация.
|
||||
- L2: Ethernet, MAC, ARP, VLAN 802.1Q, STP (обзор), коммутация, learning/broadcast.
|
||||
- L3: IPv4-заголовок и контрольная сумма, маски/подсети, маршрутизация, ICMP, TTL.
|
||||
- L4: TCP (handshake, состояния, окно, ретрансмиссии, таймеры), UDP, порты, NAT.
|
||||
- Прикладные: DNS, DHCP, HTTP-минимум.
|
||||
- Инструменты: tcpdump, Wireshark, tcpreplay-разбор дампов, iperf.
|
||||
- A: 5 письменных разборов дампов (ARP-запрос, VLAN-кадр, TCP-handshake, ретрансмиссия,
|
||||
ICMP unreachable) + свой дамп, снятый своей программой (`tcpdump`).
|
||||
- К: разборы приняты на ревью; в каждом названы поля и вывод — что происходит в сети.
|
||||
- Материалы: RFC 791/793 (первоисточники), man-страницы сокетов, Wireshark wiki.
|
||||
|
||||
### M7. Инструменты и инженерная гигиена — параллельно с M1–M6
|
||||
- git: ветки, rebase, cherry-pick, bisect, stash, worktree, конфликты.
|
||||
- gdb: breakpoints/watch/backtrace, разбор падений, core dumps, отладка потоков.
|
||||
- Сборка: Makefile руками → CMake, флаги, санитайзеры, препроцессор.
|
||||
- Docker: образ под сборку/тесты, воспроизводимое окружение.
|
||||
- bash: пайплайны, регулярки, awk/sed на уровне «разобрать лог», set -euo pipefail.
|
||||
- A: скрипт `check.sh` (сборка+тесты+санитайзеры), разбор одного реального падения в gdb,
|
||||
Makefile+CMake для своих задач.
|
||||
- К: скрипт зелёный на чистом клоне; падение разобрано письменно.
|
||||
|
||||
### M8. Ядро и embedded (плюс к вакансии) — 2 недели
|
||||
- Модуль ядра: сборка, insmod/rmmod, printk, параметры, /proc.
|
||||
- Символьный драйвер: file_operations, копирование user↔kernel.
|
||||
- Device Tree (обзор), cross-compile (aarch64), uboot и bringup (обзор).
|
||||
- A: рабочий модуль + символьный драйвер, собранные под текущее ядро (или в QEMU).
|
||||
- К: модуль грузится и выгружается, драйвер отдаёт данные `cat`-ом.
|
||||
- Материалы: LKMPG (sysprog21.github.io/lkmpg), docs.kernel.org, эликсир (исходники ядра),
|
||||
kernel.org driver-api.
|
||||
|
||||
### M9. Электроника (если проект аппаратный) — 1–2 недели
|
||||
- Основы схемотехники: питание, делители, подтяжки, транзистор как ключ.
|
||||
- Интерфейсы: UART/I2C/SPI, уровни, осциллограмма.
|
||||
- Чтение принципиальной схемы, работа мультиметром/осциллографом, пайка.
|
||||
- A: письменный разбор одной схемы из открытого проекта + замеры (если есть доступ к
|
||||
железу студии).
|
||||
- К: разбор принят на ревью; термины и режимы интерфейсов объяснены без подглядывания.
|
||||
|
||||
### M10. Мок-интервью и выход — финальные 2 недели
|
||||
- 3 живые сессии: (1) алгоритмы — 2 задачи вслух с замером, (2) C/C++ глубина + Linux,
|
||||
(3) сети L2/L3 + анализ дампа.
|
||||
- Резюме под вакансию + сопроводительное с готовностью к офисному формату.
|
||||
- A: 3 записи/протокола сессий с разбором ошибок; резюме и cover под Eltex.
|
||||
- К: не больше 1 грубой ошибки на сессию; резюме отревьюено.
|
||||
|
||||
## Ритм
|
||||
|
||||
- Будни: 1.5–2 ч практики (задачи) + 30 мин разбора теории под сегодняшние задачи.
|
||||
- Выходные: 3 ч — «грязный» модуль недели (драйвер/сервер/дампы) с артефактом.
|
||||
- Каждую неделю — отчёт: что закрыто, что нет, где упёрся; каждые 4 недели — ревизия
|
||||
плана (пункты переехать/выбросить/добавить).
|
||||
- Правило: не начинать новый модуль, пока не закрыта контрольная точка предыдущего;
|
||||
исключение — M7 (идёт параллельно).
|
||||
|
||||
## Порядок запуска (после диагностики)
|
||||
|
||||
Старт — с M2 (приоритет Туры) с параллельным добором M1 там, где диагностика показала
|
||||
дыры; M3/M4/M5 — по мере закрытия M2; M6/M7/M8/M9/M10 — по срокам собеседования.
|
||||
Если собеседование ближе 6 недель — сжатый вариант: M2 (ядро) + M5/M6 + M7 + M10,
|
||||
остальное после оффера.
|
||||
@@ -0,0 +1,40 @@
|
||||
# ИУП: подготовка к собеседованию Eltex (C/C++, алгоритмы, Linux, сети)
|
||||
|
||||
Личный учебный план и диагностический пакет. Репозиторий обновляется по ходу недели.
|
||||
|
||||
## Что внутри
|
||||
|
||||
- `WEEK7.md` — **рабочий документ**: интенсив на 7 дней (D0 23.09 → D7 30.09), три блока
|
||||
в дне (алгоритмы / системное / практика+разбор), у каждого дня контрольная точка.
|
||||
- `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` — дневник: что решено, где встал, сколько минут.
|
||||
|
||||
## Как начать
|
||||
|
||||
```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 # выборочно
|
||||
```
|
||||
|
||||
Нужны `g++` (C++17), `gcc`, `python3`; для задачи 07 — только Linux-сокеты,
|
||||
для 08 — bash/awk/sort. Никаких внешних зависимостей.
|
||||
|
||||
## Правила честности
|
||||
|
||||
Эталонных решений в репозитории нет — они лежат только на машинке студии, чтобы
|
||||
автопроверка не протухла. Задача 09 (`answer.txt`) — часть оценки: важен ход разбора.
|
||||
|
||||
## Как отдавать результаты
|
||||
|
||||
- `python3 grade.py --submit` — складывает `results_*.json` и свои файлы решений рядом;
|
||||
- либо коммит в ветку `answers/<день>` и push (доступ на запись у аккаунта `tura`);
|
||||
- либо файлами в чат / в топик лейна.
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
# Трекер интенсива
|
||||
|
||||
Пиши по факту, не по плану. «Где встал» важнее «сколько сделал» — по этому я калибрую
|
||||
следующий день.
|
||||
|
||||
| День | Дата | Задач решено | Код дня | Задача вслух (время) | Где встал / что не понял |
|
||||
|------|------|--------------|---------|----------------------|--------------------------|
|
||||
| D0 | 23.09 | | | | |
|
||||
| D1 | 24.09 | | | | |
|
||||
| D2 | 25.09 | | | | |
|
||||
| D3 | 26.09 | | | | |
|
||||
| D4 | 27.09 | | | | |
|
||||
| D5 | 28.09 | | | | |
|
||||
| D6 | 29.09 | | | | |
|
||||
| D7 | 30.09 | | | | |
|
||||
|
||||
## Слабые темы (по диагностике и по ходу)
|
||||
|
||||
- ...
|
||||
|
||||
## Вопросы, которые хочу разобрать со студией
|
||||
|
||||
- ...
|
||||
@@ -0,0 +1,145 @@
|
||||
# ИУП — интенсив 7 дней (фуллтайм, собеседование ≤ 1 неделя)
|
||||
|
||||
Срок: собеседование максимум через неделю, подготовка — фуллтайм (~8 ч/день).
|
||||
Цель интенсива: закрыть ровно то, что спросят на входе в Eltex (C/C++, базовые алгоритмы и
|
||||
структуры данных, Linux, L2/L3-сети, инструменты), и не утонуть в глубине.
|
||||
|
||||
## Правила интенсива
|
||||
|
||||
1. Каждый день — три блока: **утро (3 ч) алгоритмы**, **день (3 ч) системное/язык**,
|
||||
**вечер (2 ч) практика + разбор**. Блок не начинается, пока не закрыта вчерашняя точка.
|
||||
2. День закрыт, только если: решены задачи дня **без подсказки**, написан/починен код дня,
|
||||
сделана **одна задача вслух на таймер** (10 мин: 2 мин на разбор + код + сложность).
|
||||
3. Всё пишем, а не читаем: теория — только под конкретную задачу, максимум 40 минут.
|
||||
4. Трекер: `/home/pi/eduplan/diag/submissions/` или файл у себя — что решено, где встал,
|
||||
сколько минут. Это вход для калибровки на следующий день.
|
||||
5. Что осознанно НЕ лезет в неделю: полноценное ядро (модуль+драйвер), электроника,
|
||||
Docker-продакшн. Ядро — обзорно на D6, электроника — после оффера (см. `PLAN.md`).
|
||||
6. Если день провален — не догоняем всё, а переносим хвост в D7 (день повтора) и режем
|
||||
второстепенное (M8-обзор, 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 открываются только в браузере).
|
||||
|
||||
---
|
||||
|
||||
## D0 — 23.09, диагностика (3–4 ч)
|
||||
|
||||
- Квиз: `python3 quiz.py` (или ответы строкой в чат), 32 вопроса, 4 домена.
|
||||
- Задачи: `python3 grade.py` — 9 задач с автопроверкой (ASAN/UBSAN).
|
||||
- Разбор промахов вместе со мной, калибровка: какие дни усилить, что срезать.
|
||||
- **Точка:** есть `results_quiz.json` и `results_tasks.json`, названы 3 слабейших темы.
|
||||
|
||||
## D1 — 24.09: массивы/строки + память в C + процессы Linux
|
||||
|
||||
- Утро (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 чистый; лог дня.
|
||||
|
||||
## D2 — 25.09: хеш-таблицы и сортировки + C++ (RAII, владение, STL) + git/Makefile
|
||||
|
||||
- Утро (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`,
|
||||
мультиплексирование `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+ задач.
|
||||
|
||||
## D5 — 28.09: графы + TCP глубоко + bash/proc
|
||||
|
||||
- Утро (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 минимум; работа с
|
||||
`tcpdump`/Wireshark. Практика: 3 письменных разбора дампов (ретрансмиссия, ICMP
|
||||
unreachable, DNS) + свой захват трафика своей же программой.
|
||||
- Вечер (2 ч) — Linux-быт: `/proc`, `ulimit`, systemd-юниты обзорно; bash (пайплайны,
|
||||
`set -euo pipefail`, awk/sed для логов). Практика: `tasks/08_bash` + свой скрипт
|
||||
диагностики сети/логов.
|
||||
- **Точка:** 8+ задач; 3 разбора дампов; скрипт работает на логе в 100k строк за секунды.
|
||||
|
||||
## D6 — 29.09: ДП + ядро/embedded обзорно + 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**
|
||||
(учесть: рассматривают офисный формат).
|
||||
- **Точка:** модуль грузится/выгружается; черновик резюме отревьюен.
|
||||
|
||||
## D7 — 30.09: повтор, два мок-интервью, выход
|
||||
|
||||
- 2 ч — утренний повтор: только слабые места из трекера (не всё подряд).
|
||||
- 3 ч — мок-интервью №1: алгоритмы, 2 задачи вслух с таймером, я веду и разбираю.
|
||||
- 2 ч — мок-интервью №2: C/C++ + Linux + сети (вопросы по кругу, включая разбор дампа).
|
||||
- 1 ч — вопросы работодателю, легенда по опыту, финальная вычитка резюме.
|
||||
- **Точка:** ≤ 1 грубая ошибка на сессию; резюме и сопроводительное готовы.
|
||||
|
||||
---
|
||||
|
||||
## После собеседования
|
||||
|
||||
Незакрытое (полный трек `PLAN.md`): M2 хвост (битовые/строковые алгоритмы, математика),
|
||||
M6 глубина (STP, маршрутизация, HTTP), M7 полностью (Docker в бою, bisect на реальном
|
||||
репозитории), M8 ядро (полноценный драйвер, uboot/bringup), M9 электроника.
|
||||
@@ -0,0 +1,54 @@
|
||||
# Диагностика уровня — пакет (C/C++, алгоритмы, Linux, сети)
|
||||
|
||||
Собран под вакансию Eltex «Разработчик C/C++» (Новосибирск, hh #136775547).
|
||||
Цель — не «проверить себя на ощупь», а получить машинную оценку по доменам и список дыр.
|
||||
|
||||
## Что внутри
|
||||
|
||||
- `quiz.json` + `quiz.py` — 32 вопроса по 4 доменам (C/C++ и память, алгоритмы и структуры
|
||||
данных, Linux/ОС, сети L2/L3 и TCP/IP). 24+ верных — уверенный вход.
|
||||
- `tasks/` — 9 практических задач, у каждой автопроверка:
|
||||
|
||||
| # | Задача | Что проверяется |
|
||||
|---|--------|-----------------|
|
||||
| 01 | `01_bits` | битовые операции, без встроенных popcount |
|
||||
| 02 | `02_list` | связный список: разворот, середина, цикл (Флойд) |
|
||||
| 03 | `03_ring` | кольцевой буфер, wrap-around, ASAN |
|
||||
| 04 | `04_ipv4` | разбор IPv4-заголовка из буфера + контрольная сумма |
|
||||
| 05 | `05_lis` | НВП за O(n log n), тест на 100 000 элементов |
|
||||
| 06 | `06_threads` | ограниченная очередь: mutex/condvar, ThreadSanitizer |
|
||||
| 07 | `07_epoll` | TCP-эхо-сервер на epoll: несколько соединений, SIGTERM |
|
||||
| 08 | `08_bash` | разбор лога доступа: TOTAL/TOP-3/5XX, 110k строк |
|
||||
| 09 | `09_gdb` | отладка ASAN-падения + письменный разбор в `answer.txt` |
|
||||
|
||||
- `grade.py` — прогон задач с автооценкой (`PASS/FAIL`, число проверок, время).
|
||||
- `solutions/` — **эталонные решения, в руки не отдавать**: они лежат здесь, чтобы
|
||||
автопроверка не «протухла» (эталон → все PASS, заглушка → FAIL).
|
||||
|
||||
## Как проходить
|
||||
|
||||
```bash
|
||||
cd diag
|
||||
python3 quiz.py # вопросы по одному; ответы — номер варианта 1-4
|
||||
python3 quiz.py --export # вывести вопросы текстом (можно отвечать в чате)
|
||||
python3 grade.py # все задачи (с ASAN/UBSAN/TSAN)
|
||||
python3 grade.py 01 04 # только выбранные
|
||||
python3 grade.py --fast # без санитайзеров, быстрее
|
||||
python3 grade.py --submit # результаты + свои файлы в submissions/
|
||||
```
|
||||
|
||||
Порядок: сначала квиз, потом задачи с 01 по 09. Зависание = FAIL (есть таймауты).
|
||||
Правила честности: без встроенных `popcount`/`reverse`-хелперов, без копирования чужих
|
||||
решений, `answer.txt` в задаче 09 — обязательная часть (оценивается ход разбора).
|
||||
|
||||
## Что считается результатом
|
||||
|
||||
- `results_quiz.json`: балл по каждому домену + разбор промахов с пояснениями.
|
||||
- `results_tasks.json`: по задаче — PASS/FAIL, число проверок, время, полный лог сборки.
|
||||
- Итог и решение по плану (с каких модулей стартовать) собираются по этим двум файлам.
|
||||
|
||||
## Публикация и доступ
|
||||
|
||||
- Пакет в каталоге: `/home/pi/eduplan/diag` (мир читает; каталог доступен на исполнение).
|
||||
- Инбокс для ответов: `/home/pi/eduplan/diag/submissions/` (0777).
|
||||
- Архив для скачивания: `/home/pi/eduplan/diag/dist/eduplan_diag.tar.gz`.
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Автопроверка практических задач диагностики.
|
||||
|
||||
Использование (из каталога diag):
|
||||
python3 grade.py # проверить все задачи (с санитайзерами)
|
||||
python3 grade.py 01 04 07 # только указанные
|
||||
python3 grade.py --fast # без ASAN/UBSAN/TSAN (быстрее, слабее)
|
||||
python3 grade.py --submit # после проверки скопировать ответы в submissions/
|
||||
|
||||
Что требуется от решающего: файл solution.cpp / solution.sh / crash.c + answer.txt,
|
||||
каждый в своём каталоге tasks/<NN_имя>/.
|
||||
Результат: results_tasks.json (по задаче: pass, сколько проверок, время, лог).
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
TASKS = os.path.join(HERE, "tasks")
|
||||
SUB = os.path.join(HERE, "submissions")
|
||||
|
||||
CXX = os.environ.get("CXX", "g++")
|
||||
SAN = "-fsanitize=address,undefined -fno-omit-frame-pointer"
|
||||
FAST = False
|
||||
|
||||
# имя задачи: (тип, что запускать, таймаут, флаги компиляции)
|
||||
SPEC = {
|
||||
"01_bits": ("cpp", "test_bits.cpp", 180),
|
||||
"02_list": ("cpp", "test_list.cpp", 180),
|
||||
"03_ring": ("cpp", "test_ring.cpp", 180),
|
||||
"04_ipv4": ("cpp", "test_ipv4.cpp", 180),
|
||||
"05_lis": ("cpp", "test_lis.cpp", 300),
|
||||
"06_threads": ("cpp", "test_threads.cpp", 600), # собирается с TSan, не с ASAN
|
||||
"07_epoll": ("bin", "test_epoll.py", 300),
|
||||
"08_bash": ("bash", "test_bash.py", 300),
|
||||
"09_gdb": ("py", "test_gdb.py", 300),
|
||||
}
|
||||
|
||||
|
||||
def sh(cmd, cwd, timeout):
|
||||
t0 = time.time()
|
||||
try:
|
||||
r = subprocess.run(cmd, cwd=cwd, capture_output=True, text=True, timeout=timeout)
|
||||
return r.returncode, r.stdout + r.stderr, time.time() - t0, False
|
||||
except subprocess.TimeoutExpired as e:
|
||||
out = (e.stdout or b"").decode(errors="replace") if isinstance(e.stdout, bytes) else (e.stdout or "")
|
||||
return None, out + "\n[TIMEOUT] превышено время %s c" % timeout, time.time() - t0, True
|
||||
|
||||
|
||||
def run_task(name, root):
|
||||
d = os.path.join(root, name)
|
||||
kind, test, timeout = SPEC[name]
|
||||
log = []
|
||||
rc = None
|
||||
timed_out = False
|
||||
if kind == "cpp":
|
||||
flags = ["-std=c++17", "-O2", "-Wall", "-Wextra", "-g"]
|
||||
extra = []
|
||||
if name == "06_threads":
|
||||
flags = ["-std=c++17", "-O1", "-Wall", "-Wextra", "-g", "-pthread"]
|
||||
extra = [] if FAST else ["-fsanitize=thread"]
|
||||
elif not FAST:
|
||||
extra = SAN.split()
|
||||
cmd = [CXX] + flags + (extra or []) + [test, "-o", "t_" + name]
|
||||
rc, out, secs, timed_out = sh(cmd, d, timeout)
|
||||
log.append("$ " + " ".join(cmd) + "\n" + out)
|
||||
if rc != 0:
|
||||
return False, log, secs, timed_out
|
||||
rc, out, secs2, timed_out = sh(["./t_" + name], d, timeout)
|
||||
log.append("$ ./t_" + name + "\n" + out)
|
||||
secs += secs2
|
||||
if "unsupported VMA" in out and not FAST:
|
||||
# ThreadSanitizer не поддерживает этот размер адресного пространства
|
||||
# (Raspberry Pi / ядро 6.18, 47-бит VMA) — откатываемся на ASAN.
|
||||
cmd2 = [CXX, "-std=c++17", "-O1", "-Wall", "-Wextra", "-g", "-pthread"] \
|
||||
+ SAN.split() + [test, "-o", "t_" + name]
|
||||
rc, out2, s, to = sh(cmd2, d, timeout)
|
||||
log.append("$ " + " ".join(cmd2) + "\n# TSan недоступен на этой платформе, откат на ASAN\n" + out2)
|
||||
if rc == 0:
|
||||
rc, out2, s2, to = sh(["./t_" + name], d, timeout)
|
||||
log.append("$ ./t_" + name + "\n" + out2)
|
||||
secs += s2
|
||||
elif kind == "bin":
|
||||
# epoll-сервер: solution.cpp -> бинарь solution, затем python-тест
|
||||
cmd = [CXX, "-std=c++17", "-O2", "-Wall", "-Wextra", "-g",
|
||||
"solution.cpp", "-o", "solution"]
|
||||
rc, out, secs, timed_out = sh(cmd, d, timeout)
|
||||
log.append("$ " + " ".join(cmd) + "\n" + out)
|
||||
if rc != 0:
|
||||
return False, log, secs, timed_out
|
||||
rc, out, secs2, timed_out = sh([sys.executable, test], d, timeout)
|
||||
log.append("$ python3 " + test + "\n" + out)
|
||||
secs += secs2
|
||||
else: # bash / py — тест сам всё делает
|
||||
if name == "08_bash":
|
||||
os.chmod(os.path.join(d, "solution.sh"), 0o755)
|
||||
rc, out, secs, timed_out = sh([sys.executable, test], d, timeout)
|
||||
log.append("$ python3 " + test + "\n" + out)
|
||||
|
||||
body = log[-1]
|
||||
passed = (rc == 0) and ("FAIL" not in body) and ("ALL PASS" in body)
|
||||
return passed, log, secs, timed_out
|
||||
|
||||
|
||||
def main():
|
||||
global FAST
|
||||
args = [a for a in sys.argv[1:]]
|
||||
FAST = "--fast" in args
|
||||
do_submit = "--submit" in args
|
||||
names = [a for a in args if a.isdigit() or a in SPEC]
|
||||
if names:
|
||||
names = [n for n in SPEC if n.split("_")[0] in names or n in names]
|
||||
else:
|
||||
names = list(SPEC)
|
||||
|
||||
results = {}
|
||||
print(f"{'задача':12s} {'итог':7s} {'проверок':9s} {'FAIL':5s} {'время':8s}")
|
||||
for n in names:
|
||||
ok, log, secs, to = run_task(n, TASKS)
|
||||
body = log[-1]
|
||||
n_ok = body.count("ok ")
|
||||
n_bad = body.count("FAIL ")
|
||||
results[n] = {
|
||||
"pass": ok, "checks_ok": n_ok, "checks_fail": n_bad,
|
||||
"seconds": round(secs, 2), "timeout": to, "log": log,
|
||||
}
|
||||
print(f"{n:12s} {'PASS' if ok else 'FAIL':7s} {n_ok:<9d} {n_bad:<5d} {secs:8.2f}")
|
||||
|
||||
total = len(names)
|
||||
done = sum(1 for n in names if results[n]["pass"])
|
||||
with open(os.path.join(HERE, "results_tasks.json"), "w", encoding="utf-8") as f:
|
||||
json.dump({"passed": done, "total": total, "tasks": results}, f,
|
||||
ensure_ascii=False, indent=2)
|
||||
print(f"\nИТОГ ЗАДАЧ: {done}/{total} PASS. Подробности: results_tasks.json")
|
||||
|
||||
if do_submit:
|
||||
os.makedirs(SUB, exist_ok=True)
|
||||
for fn in ("results_quiz.json", "results_tasks.json"):
|
||||
p = os.path.join(HERE, fn)
|
||||
if os.path.exists(p):
|
||||
shutil.copy2(p, os.path.join(SUB, fn))
|
||||
for n in SPEC:
|
||||
d = os.path.join(TASKS, n)
|
||||
for fn in ("solution.cpp", "solution.sh", "crash.c", "answer.txt"):
|
||||
p = os.path.join(d, fn)
|
||||
if os.path.exists(p):
|
||||
shutil.copy2(p, os.path.join(SUB, f"{n}__{fn}"))
|
||||
for fn in os.listdir(SUB):
|
||||
os.chmod(os.path.join(SUB, fn), 0o644)
|
||||
print(f"Ответы скопированы в {SUB}")
|
||||
sys.exit(0 if done == total else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,193 @@
|
||||
1. [C/C++ и системная память] Чему равен sizeof(struct S { char a; int b; char c; }) на 64-битной платформе без pack-атрибутов?
|
||||
1) 6
|
||||
2) 9
|
||||
3) 12
|
||||
4) 16
|
||||
|
||||
2. [C/C++ и системная память] Что означает undefined behavior (UB)?
|
||||
1) Программа гарантированно завершится с ошибкой
|
||||
2) Компилятор вправе сгенерировать любой код, программа не обязана иметь смысл
|
||||
3) Поведение определено стандартом, но зависит от платформы
|
||||
4) Это предупреждение компилятора, которое можно игнорировать
|
||||
|
||||
3. [C/C++ и системная память] std::move(x) — что он делает сам по себе?
|
||||
1) Перемещает объект и обнуляет исходный
|
||||
2) Вызывает конструктор перемещения
|
||||
3) Приводит x к rvalue-ссылке, перемещение делает уже вызванный конструктор/присваивание
|
||||
4) Освобождает память объекта
|
||||
|
||||
4. [C/C++ и системная память] Чем new отличается от malloc?
|
||||
1) Только синтаксисом
|
||||
2) malloc работает только со структурами
|
||||
3) new не может вернуть nullptr
|
||||
4) new вызывает конструктор, возвращает типизированный указатель и бросает исключение при неудаче
|
||||
|
||||
5. [C/C++ и системная память] Класс владеет ресурсом и имеет деструктор, освобождающий его. Копирующий конструктор/присваивание не объявлены. Что будет?
|
||||
1) Всё нормально, компилятор сам сделает глубокую копию
|
||||
2) Неявные копии дадут два владельца одного ресурса → double free/утечка
|
||||
3) Программа не скомпилируется
|
||||
4) Компилятор сгенерирует перемещение вместо копирования
|
||||
|
||||
6. [C/C++ и системная память] Для чего нужен виртуальный деструктор у базового класса?
|
||||
1) Чтобы ускорить разрушение
|
||||
2) Он не нужен, деструкторы всегда виртуальны
|
||||
3) Чтобы поля базы не копировались
|
||||
4) Чтобы при delete по указателю на базу вызвался деструктор производного класса
|
||||
|
||||
7. [C/C++ и системная память] Что делает ключевое слово const после сигнатуры метода (int size() const)?
|
||||
1) Делает метод виртуальным
|
||||
2) Запрещает копирование возвращаемого значения
|
||||
3) Обязывает не менять поля объекта и позволяет вызывать метод на const-объекте
|
||||
4) Ничего, это оформление
|
||||
|
||||
8. [C/C++ и системная память] Определён ли стандартом порядок вычисления аргументов функции (f(a(), b()))?
|
||||
1) Слева направо
|
||||
2) Справа налево
|
||||
3) Не определён (порядок не специфицирован, но вычисления не пересекаются)
|
||||
4) Зависит только от оптимизаций
|
||||
|
||||
9. [Алгоритмы и структуры данных] Средняя сложность поиска в хеш-таблице (при разумной нагрузке)?
|
||||
1) O(n log n)
|
||||
2) O(log n)
|
||||
3) O(n)
|
||||
4) O(1)
|
||||
|
||||
10. [Алгоритмы и структуры данных] Какая структура лучше подходит для задачи «поиск слов по префиксу»?
|
||||
1) Префиксное дерево (trie)
|
||||
2) Хеш-таблица
|
||||
3) Двоичная куча
|
||||
4) Связный список
|
||||
|
||||
11. [Алгоритмы и структуры данных] Сколько сравнений нужно бинарному поиску в худшем случае в отсортированном массиве из 1 000 000 элементов?
|
||||
1) 20
|
||||
2) 10
|
||||
3) 100
|
||||
4) 1 000
|
||||
|
||||
12. [Алгоритмы и структуры данных] При каком условии применим алгоритм Дейкстры?
|
||||
1) Граф обязательно связный
|
||||
2) Веса рёбер неотрицательны
|
||||
3) Граф неориентированный
|
||||
4) Граф разреженный
|
||||
|
||||
13. [Алгоритмы и структуры данных] Какова сложность построения кучи (heapify) из произвольного массива?
|
||||
1) O(n)
|
||||
2) O(log n)
|
||||
3) O(1)
|
||||
4) O(n log n)
|
||||
|
||||
14. [Алгоритмы и структуры данных] Какая из сортировок устойчивая (stable)?
|
||||
1) Быстрая (quicksort)
|
||||
2) Пирамидальная (heapsort)
|
||||
3) Слиянием (merge sort)
|
||||
4) Выбором (selection sort)
|
||||
|
||||
15. [Алгоритмы и структуры данных] Для какого графа возможна топологическая сортировка?
|
||||
1) Для ориентированного ациклического (DAG)
|
||||
2) Для любого связного
|
||||
3) Для двудольного
|
||||
4) Для полного
|
||||
|
||||
16. [Алгоритмы и структуры данных] Как найти цикл в связном списке за O(1) дополнительной памяти?
|
||||
1) Алгоритм Флойда: медленный и быстрый указатели
|
||||
2) Хранить посещённые узлы в хеш-таблице
|
||||
3) Отсортировать список
|
||||
4) Рекурсивно пройти до nullptr
|
||||
|
||||
17. [Linux / ОС] Что делает fork()?
|
||||
1) Заменяет образ процесса
|
||||
2) Создаёт поток в том же адресном пространстве
|
||||
3) Создаёт копию процесса; в родителе возвращает PID ребёнка, в ребёнке — 0
|
||||
4) Загружает библиотеку в процесс
|
||||
|
||||
18. [Linux / ОС] Чем поток отличается от процесса в Linux?
|
||||
1) Ничем, это синонимы
|
||||
2) Потоки делят адресное пространство и файловые дескрипторы, но имеют свои стеки
|
||||
3) Поток всегда быстрее
|
||||
4) Процессы не могут работать параллельно
|
||||
|
||||
19. [Linux / ОС] Что такое процесс-зомби?
|
||||
1) Процесс, съедающий CPU
|
||||
2) Завершившийся процесс, чей статус ещё не собран родителем через wait()
|
||||
3) Процесс без терминала
|
||||
4) Процесс, потерявший родителя
|
||||
|
||||
20. [Linux / ОС] Какой механизм в Linux штатно обслуживает десятки тысяч сетевых соединений в одном потоке?
|
||||
1) select()
|
||||
2) Неблокирующие сокеты в цикле опроса
|
||||
3) epoll
|
||||
4) fork на каждое соединение
|
||||
|
||||
21. [Linux / ОС] Что происходит со страницами памяти при fork()?
|
||||
1) Сразу полностью копируются
|
||||
2) Память обнуляется
|
||||
3) Копируются при первой записи (copy-on-write)
|
||||
4) Ребёнок получает те же физические страницы без защиты
|
||||
|
||||
22. [Linux / ОС] Процесс завершился с кодом 137. Что это значит?
|
||||
1) Ошибка сегментации
|
||||
2) Процесс убит сигналом 9 (SIGKILL) — типично OOM-killer или docker kill
|
||||
3) Не найден файл
|
||||
4) Процесс вышел штатно
|
||||
|
||||
23. [Linux / ОС] Что даёт вызов mmap() на файл?
|
||||
1) Полное чтение файла в кучу
|
||||
2) Отображение файла в адресное пространство: работа как с массивом в памяти
|
||||
3) Блокировку файла
|
||||
4) Копирование файла
|
||||
|
||||
24. [Linux / ОС] Зачем нужен waitpid() родителю?
|
||||
1) Чтобы приостановить ребёнка
|
||||
2) Чтобы сменить приоритет ребёнка
|
||||
3) Чтобы получить доступ к памяти ребёнка
|
||||
4) Чтобы собрать статус завершения и убрать зомби
|
||||
|
||||
25. [Сети L2/L3, TCP/IP] Сколько байт в Ethernet-заголовке кадра без VLAN-тега?
|
||||
1) 8
|
||||
2) 14
|
||||
3) 18
|
||||
4) 20
|
||||
|
||||
26. [Сети L2/L3, TCP/IP] На каком уровне OSI работает обычный коммутатор (switch)?
|
||||
1) L2
|
||||
2) L4
|
||||
3) L3
|
||||
4) L1
|
||||
|
||||
27. [Сети L2/L3, TCP/IP] Что делает протокол ARP?
|
||||
1) Раздаёт IP-адреса
|
||||
2) Проверяет целостность пакетов
|
||||
3) Разрешает доменные имена
|
||||
4) Сопоставляет IP-адрес и MAC-адрес в локальном сегменте
|
||||
|
||||
28. [Сети L2/L3, TCP/IP] Какое поле IPv4-заголовка уменьшается на единицу на каждом маршрутизаторе?
|
||||
1) Total Length
|
||||
2) IHL
|
||||
3) Checksum
|
||||
4) TTL
|
||||
|
||||
29. [Сети L2/L3, TCP/IP] Правильная последовательность установки TCP-соединения?
|
||||
1) SYN → SYN-ACK → ACK
|
||||
2) SYN → ACK → SYN-ACK
|
||||
3) SYN-ACK → SYN → ACK
|
||||
4) ACK → SYN → SYN-ACK
|
||||
|
||||
30. [Сети L2/L3, TCP/IP] Ключевое отличие TCP от UDP?
|
||||
1) TCP быстрее
|
||||
2) TCP работает только на L2
|
||||
3) UDP не поддерживает порты
|
||||
4) TCP даёт надёжный поток байт с подтверждениями, порядком и повторной передачей
|
||||
|
||||
31. [Сети L2/L3, TCP/IP] Что делает VLAN-тег 802.1Q?
|
||||
1) Добавляет в кадр 4 байта с 12-битным идентификатором VLAN
|
||||
2) Шифрует кадр
|
||||
3) Увеличивает MTU
|
||||
4) Превращает кадр в IP-пакет
|
||||
|
||||
32. [Сети L2/L3, TCP/IP] Сколько адресов узлов доступно в подсети /26?
|
||||
1) 32
|
||||
2) 126
|
||||
3) 64
|
||||
4) 62
|
||||
|
||||
Формат ответа: "1-2 2-4 3-1 ..." (номер вопроса - номер варианта)
|
||||
+398
@@ -0,0 +1,398 @@
|
||||
{
|
||||
"meta": {
|
||||
"title": "Диагностика уровня: C/C++, алгоритмы, Linux, сети",
|
||||
"pass_hint": "24+ из 32 верных — уверенный вход; 16–23 — база есть, нужен добор; <16 — модуль в план с нуля",
|
||||
"domains": {
|
||||
"c_cpp": "C/C++ и системная память",
|
||||
"algo": "Алгоритмы и структуры данных",
|
||||
"linux": "Linux / ОС",
|
||||
"net": "Сети L2/L3, TCP/IP"
|
||||
}
|
||||
},
|
||||
"questions": [
|
||||
{
|
||||
"d": "c_cpp",
|
||||
"q": "Чему равен sizeof(struct S { char a; int b; char c; }) на 64-битной платформе без pack-атрибутов?",
|
||||
"o": [
|
||||
"6",
|
||||
"9",
|
||||
"12",
|
||||
"16"
|
||||
],
|
||||
"a": 2,
|
||||
"e": "a = 1 байт + 3 паддинга, b = 4, c = 1 + 3 хвостового паддинга = 12 (выравнивание структуры по 4)."
|
||||
},
|
||||
{
|
||||
"d": "c_cpp",
|
||||
"q": "Что означает undefined behavior (UB)?",
|
||||
"o": [
|
||||
"Программа гарантированно завершится с ошибкой",
|
||||
"Компилятор вправе сгенерировать любой код, программа не обязана иметь смысл",
|
||||
"Поведение определено стандартом, но зависит от платформы",
|
||||
"Это предупреждение компилятора, которое можно игнорировать"
|
||||
],
|
||||
"a": 1,
|
||||
"e": "UB — стандарт не накладывает требований: компилятор может что угодно, вплоть до выкидывания проверок."
|
||||
},
|
||||
{
|
||||
"d": "c_cpp",
|
||||
"q": "std::move(x) — что он делает сам по себе?",
|
||||
"o": [
|
||||
"Перемещает объект и обнуляет исходный",
|
||||
"Вызывает конструктор перемещения",
|
||||
"Приводит x к rvalue-ссылке, перемещение делает уже вызванный конструктор/присваивание",
|
||||
"Освобождает память объекта"
|
||||
],
|
||||
"a": 2,
|
||||
"e": "move — это static_cast к rvalue; фактическое перемещение выполняет конструктор/оператор присваивания."
|
||||
},
|
||||
{
|
||||
"d": "c_cpp",
|
||||
"q": "Чем new отличается от malloc?",
|
||||
"o": [
|
||||
"Только синтаксисом",
|
||||
"malloc работает только со структурами",
|
||||
"new не может вернуть nullptr",
|
||||
"new вызывает конструктор, возвращает типизированный указатель и бросает исключение при неудаче"
|
||||
],
|
||||
"a": 3,
|
||||
"e": "new = выделение + конструктор + типизированный указатель + исключение; malloc возвращает void* и nullptr при неудаче."
|
||||
},
|
||||
{
|
||||
"d": "c_cpp",
|
||||
"q": "Класс владеет ресурсом и имеет деструктор, освобождающий его. Копирующий конструктор/присваивание не объявлены. Что будет?",
|
||||
"o": [
|
||||
"Всё нормально, компилятор сам сделает глубокую копию",
|
||||
"Неявные копии дадут два владельца одного ресурса → double free/утечка",
|
||||
"Программа не скомпилируется",
|
||||
"Компилятор сгенерирует перемещение вместо копирования"
|
||||
],
|
||||
"a": 1,
|
||||
"e": "Правило трёх/пяти: владеющий ресурсом класс должен определить копирование/перемещение или запретить их."
|
||||
},
|
||||
{
|
||||
"d": "c_cpp",
|
||||
"q": "Для чего нужен виртуальный деструктор у базового класса?",
|
||||
"o": [
|
||||
"Чтобы ускорить разрушение",
|
||||
"Он не нужен, деструкторы всегда виртуальны",
|
||||
"Чтобы поля базы не копировались",
|
||||
"Чтобы при delete по указателю на базу вызвался деструктор производного класса"
|
||||
],
|
||||
"a": 3,
|
||||
"e": "Без virtual-деструктора delete через Base* — UB, деструктор производного не вызовется."
|
||||
},
|
||||
{
|
||||
"d": "c_cpp",
|
||||
"q": "Что делает ключевое слово const после сигнатуры метода (int size() const)?",
|
||||
"o": [
|
||||
"Делает метод виртуальным",
|
||||
"Запрещает копирование возвращаемого значения",
|
||||
"Обязывает не менять поля объекта и позволяет вызывать метод на const-объекте",
|
||||
"Ничего, это оформление"
|
||||
],
|
||||
"a": 2,
|
||||
"e": "const-метод получает this как const-указатель; на изменении поля — ошибка компиляции."
|
||||
},
|
||||
{
|
||||
"d": "c_cpp",
|
||||
"q": "Определён ли стандартом порядок вычисления аргументов функции (f(a(), b()))?",
|
||||
"o": [
|
||||
"Слева направо",
|
||||
"Справа налево",
|
||||
"Не определён (порядок не специфицирован, но вычисления не пересекаются)",
|
||||
"Зависит только от оптимизаций"
|
||||
],
|
||||
"a": 2,
|
||||
"e": "Порядок аргументов не специфицирован; с C++17 вычисления не чередуются (indeterminately sequenced), но порядок всё равно не задан."
|
||||
},
|
||||
{
|
||||
"d": "algo",
|
||||
"q": "Средняя сложность поиска в хеш-таблице (при разумной нагрузке)?",
|
||||
"o": [
|
||||
"O(n log n)",
|
||||
"O(log n)",
|
||||
"O(n)",
|
||||
"O(1)"
|
||||
],
|
||||
"a": 3,
|
||||
"e": "Амортизированно O(1) в среднем; худший случай при всех коллизиях — O(n)."
|
||||
},
|
||||
{
|
||||
"d": "algo",
|
||||
"q": "Какая структура лучше подходит для задачи «поиск слов по префиксу»?",
|
||||
"o": [
|
||||
"Префиксное дерево (trie)",
|
||||
"Хеш-таблица",
|
||||
"Двоичная куча",
|
||||
"Связный список"
|
||||
],
|
||||
"a": 0,
|
||||
"e": "Trie даёт проход по префиксу за O(len) и общий префикс хранит один раз."
|
||||
},
|
||||
{
|
||||
"d": "algo",
|
||||
"q": "Сколько сравнений нужно бинарному поиску в худшем случае в отсортированном массиве из 1 000 000 элементов?",
|
||||
"o": [
|
||||
"20",
|
||||
"10",
|
||||
"100",
|
||||
"1 000"
|
||||
],
|
||||
"a": 0,
|
||||
"e": "log2(1e6) ≈ 19.9 → 20 итераций."
|
||||
},
|
||||
{
|
||||
"d": "algo",
|
||||
"q": "При каком условии применим алгоритм Дейкстры?",
|
||||
"o": [
|
||||
"Граф обязательно связный",
|
||||
"Веса рёбер неотрицательны",
|
||||
"Граф неориентированный",
|
||||
"Граф разреженный"
|
||||
],
|
||||
"a": 1,
|
||||
"e": "Отрицательные рёбра ломают инвариант «кратчайший путь найден»; тогда Беллман–Форд."
|
||||
},
|
||||
{
|
||||
"d": "algo",
|
||||
"q": "Какова сложность построения кучи (heapify) из произвольного массива?",
|
||||
"o": [
|
||||
"O(n)",
|
||||
"O(log n)",
|
||||
"O(1)",
|
||||
"O(n log n)"
|
||||
],
|
||||
"a": 0,
|
||||
"e": "Просеивание снизу вверх даёт сумму геометрической прогрессии — O(n), хотя каждый push по отдельности O(log n)."
|
||||
},
|
||||
{
|
||||
"d": "algo",
|
||||
"q": "Какая из сортировок устойчивая (stable)?",
|
||||
"o": [
|
||||
"Быстрая (quicksort)",
|
||||
"Пирамидальная (heapsort)",
|
||||
"Слиянием (merge sort)",
|
||||
"Выбором (selection sort)"
|
||||
],
|
||||
"a": 2,
|
||||
"e": "Merge sort сохраняет порядок равных элементов; quicksort/heapsort/selection — нет."
|
||||
},
|
||||
{
|
||||
"d": "algo",
|
||||
"q": "Для какого графа возможна топологическая сортировка?",
|
||||
"o": [
|
||||
"Для ориентированного ациклического (DAG)",
|
||||
"Для любого связного",
|
||||
"Для двудольного",
|
||||
"Для полного"
|
||||
],
|
||||
"a": 0,
|
||||
"e": "Цикл делает линейный порядок невозможным; топосорт — признак DAG."
|
||||
},
|
||||
{
|
||||
"d": "algo",
|
||||
"q": "Как найти цикл в связном списке за O(1) дополнительной памяти?",
|
||||
"o": [
|
||||
"Алгоритм Флойда: медленный и быстрый указатели",
|
||||
"Хранить посещённые узлы в хеш-таблице",
|
||||
"Отсортировать список",
|
||||
"Рекурсивно пройти до nullptr"
|
||||
],
|
||||
"a": 0,
|
||||
"e": "«Черепаха и заяц»: если цикл есть, указатели встретятся."
|
||||
},
|
||||
{
|
||||
"d": "linux",
|
||||
"q": "Что делает fork()?",
|
||||
"o": [
|
||||
"Заменяет образ процесса",
|
||||
"Создаёт поток в том же адресном пространстве",
|
||||
"Создаёт копию процесса; в родителе возвращает PID ребёнка, в ребёнке — 0",
|
||||
"Загружает библиотеку в процесс"
|
||||
],
|
||||
"a": 2,
|
||||
"e": "fork = новый процесс (свой PID, COW-копия памяти); execve меняет образ, clone — поток."
|
||||
},
|
||||
{
|
||||
"d": "linux",
|
||||
"q": "Чем поток отличается от процесса в Linux?",
|
||||
"o": [
|
||||
"Ничем, это синонимы",
|
||||
"Потоки делят адресное пространство и файловые дескрипторы, но имеют свои стеки",
|
||||
"Поток всегда быстрее",
|
||||
"Процессы не могут работать параллельно"
|
||||
],
|
||||
"a": 1,
|
||||
"e": "clone с CLONE_VM/CLONE_FILES — потоки; у каждого свой стек и регистры."
|
||||
},
|
||||
{
|
||||
"d": "linux",
|
||||
"q": "Что такое процесс-зомби?",
|
||||
"o": [
|
||||
"Процесс, съедающий CPU",
|
||||
"Завершившийся процесс, чей статус ещё не собран родителем через wait()",
|
||||
"Процесс без терминала",
|
||||
"Процесс, потерявший родителя"
|
||||
],
|
||||
"a": 1,
|
||||
"e": "Запись остаётся в таблице процессов до wait/waitpid; «потерявший родителя» — это сирота (переходит к init)."
|
||||
},
|
||||
{
|
||||
"d": "linux",
|
||||
"q": "Какой механизм в Linux штатно обслуживает десятки тысяч сетевых соединений в одном потоке?",
|
||||
"o": [
|
||||
"select()",
|
||||
"Неблокирующие сокеты в цикле опроса",
|
||||
"epoll",
|
||||
"fork на каждое соединение"
|
||||
],
|
||||
"a": 2,
|
||||
"e": "epoll — O(1) на готовое событие; select линейно по всем дескрипторам."
|
||||
},
|
||||
{
|
||||
"d": "linux",
|
||||
"q": "Что происходит со страницами памяти при fork()?",
|
||||
"o": [
|
||||
"Сразу полностью копируются",
|
||||
"Память обнуляется",
|
||||
"Копируются при первой записи (copy-on-write)",
|
||||
"Ребёнок получает те же физические страницы без защиты"
|
||||
],
|
||||
"a": 2,
|
||||
"e": "COW: страницы помечаются read-only и копируются по факту записи."
|
||||
},
|
||||
{
|
||||
"d": "linux",
|
||||
"q": "Процесс завершился с кодом 137. Что это значит?",
|
||||
"o": [
|
||||
"Ошибка сегментации",
|
||||
"Процесс убит сигналом 9 (SIGKILL) — типично OOM-killer или docker kill",
|
||||
"Не найден файл",
|
||||
"Процесс вышел штатно"
|
||||
],
|
||||
"a": 1,
|
||||
"e": "137 = 128 + 9: завершение по SIGKILL; код 139 = 128 + 11 (SIGSEGV)."
|
||||
},
|
||||
{
|
||||
"d": "linux",
|
||||
"q": "Что даёт вызов mmap() на файл?",
|
||||
"o": [
|
||||
"Полное чтение файла в кучу",
|
||||
"Отображение файла в адресное пространство: работа как с массивом в памяти",
|
||||
"Блокировку файла",
|
||||
"Копирование файла"
|
||||
],
|
||||
"a": 1,
|
||||
"e": "Страницы подгружаются по обращению; запись уходит в файл (или в MAP_SHARED-память)."
|
||||
},
|
||||
{
|
||||
"d": "linux",
|
||||
"q": "Зачем нужен waitpid() родителю?",
|
||||
"o": [
|
||||
"Чтобы приостановить ребёнка",
|
||||
"Чтобы сменить приоритет ребёнка",
|
||||
"Чтобы получить доступ к памяти ребёнка",
|
||||
"Чтобы собрать статус завершения и убрать зомби"
|
||||
],
|
||||
"a": 3,
|
||||
"e": "Без wait завершившиеся дети остаются зомби и держат запись в таблице процессов."
|
||||
},
|
||||
{
|
||||
"d": "net",
|
||||
"q": "Сколько байт в Ethernet-заголовке кадра без VLAN-тега?",
|
||||
"o": [
|
||||
"8",
|
||||
"14",
|
||||
"18",
|
||||
"20"
|
||||
],
|
||||
"a": 1,
|
||||
"e": "dst MAC (6) + src MAC (6) + EtherType (2) = 14; VLAN 802.1Q добавляет 4 байта."
|
||||
},
|
||||
{
|
||||
"d": "net",
|
||||
"q": "На каком уровне OSI работает обычный коммутатор (switch)?",
|
||||
"o": [
|
||||
"L2",
|
||||
"L4",
|
||||
"L3",
|
||||
"L1"
|
||||
],
|
||||
"a": 0,
|
||||
"e": "Коммутация по MAC-адресам и таблице обучения — канальный уровень (L2); маршрутизатор — L3."
|
||||
},
|
||||
{
|
||||
"d": "net",
|
||||
"q": "Что делает протокол ARP?",
|
||||
"o": [
|
||||
"Раздаёт IP-адреса",
|
||||
"Проверяет целостность пакетов",
|
||||
"Разрешает доменные имена",
|
||||
"Сопоставляет IP-адрес и MAC-адрес в локальном сегменте"
|
||||
],
|
||||
"a": 3,
|
||||
"e": "ARP-запрос broadcast, ответ unicast; DHCP раздаёт адреса, DNS — имена."
|
||||
},
|
||||
{
|
||||
"d": "net",
|
||||
"q": "Какое поле IPv4-заголовка уменьшается на единицу на каждом маршрутизаторе?",
|
||||
"o": [
|
||||
"Total Length",
|
||||
"IHL",
|
||||
"Checksum",
|
||||
"TTL"
|
||||
],
|
||||
"a": 3,
|
||||
"e": "TTL защищает от петель; при 0 пакет отбрасывается и уходит ICMP Time Exceeded. Контрольная сумма при этом пересчитывается, но не «уменьшается на 1»."
|
||||
},
|
||||
{
|
||||
"d": "net",
|
||||
"q": "Правильная последовательность установки TCP-соединения?",
|
||||
"o": [
|
||||
"SYN → SYN-ACK → ACK",
|
||||
"SYN → ACK → SYN-ACK",
|
||||
"SYN-ACK → SYN → ACK",
|
||||
"ACK → SYN → SYN-ACK"
|
||||
],
|
||||
"a": 0,
|
||||
"e": "Клиент SYN, сервер SYN-ACK, клиент ACK; номера подтверждения = seq+1."
|
||||
},
|
||||
{
|
||||
"d": "net",
|
||||
"q": "Ключевое отличие TCP от UDP?",
|
||||
"o": [
|
||||
"TCP быстрее",
|
||||
"TCP работает только на L2",
|
||||
"UDP не поддерживает порты",
|
||||
"TCP даёт надёжный поток байт с подтверждениями, порядком и повторной передачей"
|
||||
],
|
||||
"a": 3,
|
||||
"e": "UDP — дейтаграммы «как есть», без гарантий и порядка."
|
||||
},
|
||||
{
|
||||
"d": "net",
|
||||
"q": "Что делает VLAN-тег 802.1Q?",
|
||||
"o": [
|
||||
"Добавляет в кадр 4 байта с 12-битным идентификатором VLAN",
|
||||
"Шифрует кадр",
|
||||
"Увеличивает MTU",
|
||||
"Превращает кадр в IP-пакет"
|
||||
],
|
||||
"a": 0,
|
||||
"e": "Тег EtherType 0x8100 + PCP/DEI + 12 бит VID; логически разделяет L2-сегменты."
|
||||
},
|
||||
{
|
||||
"d": "net",
|
||||
"q": "Сколько адресов узлов доступно в подсети /26?",
|
||||
"o": [
|
||||
"32",
|
||||
"126",
|
||||
"64",
|
||||
"62"
|
||||
],
|
||||
"a": 3,
|
||||
"e": "/26 = 64 адреса, минус адрес сети и broadcast → 62 хоста."
|
||||
}
|
||||
]
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Диагностический квиз: C/C++, алгоритмы, Linux, сети.
|
||||
|
||||
Режимы:
|
||||
python3 quiz.py — пройти интерактивно (по одной строке на ответ: номер варианта 1-4)
|
||||
python3 quiz.py --export — вывести вопросы текстом (можно отвечать в чате в формате "1-2 2-4 ...")
|
||||
python3 quiz.py --answers "1-2 2-4 3-1" — проверить ответы, переданные строкой
|
||||
Результат пишется в results_quiz.json (там же разбор по доменам).
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
QUIZ = os.path.join(HERE, "quiz.json")
|
||||
|
||||
|
||||
def load():
|
||||
with open(QUIZ, encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def export(data):
|
||||
for i, q in enumerate(data["questions"], 1):
|
||||
print(f"{i}. [{data['meta']['domains'][q['d']]}] {q['q']}")
|
||||
for j, o in enumerate(q["o"], 1):
|
||||
print(f" {j}) {o}")
|
||||
print()
|
||||
print('Формат ответа: "1-2 2-4 3-1 ..." (номер вопроса - номер варианта)')
|
||||
|
||||
|
||||
def parse_answers(s):
|
||||
res = {}
|
||||
for tok in s.replace(",", " ").split():
|
||||
if "-" not in tok:
|
||||
continue
|
||||
k, v = tok.split("-", 1)
|
||||
try:
|
||||
res[int(k)] = int(v)
|
||||
except ValueError:
|
||||
continue
|
||||
return res
|
||||
|
||||
|
||||
def grade(data, answers):
|
||||
per_domain = {}
|
||||
details = []
|
||||
for i, q in enumerate(data["questions"], 1):
|
||||
d = q["d"]
|
||||
per_domain.setdefault(d, {"total": 0, "correct": 0})
|
||||
per_domain[d]["total"] += 1
|
||||
got = answers.get(i)
|
||||
ok = got == q["a"] + 1
|
||||
if ok:
|
||||
per_domain[d]["correct"] += 1
|
||||
details.append({
|
||||
"n": i, "domain": d, "question": q["q"],
|
||||
"your_answer": got, "correct_answer": q["a"] + 1,
|
||||
"ok": ok, "explain": q["e"],
|
||||
})
|
||||
return per_domain, details
|
||||
|
||||
|
||||
def report(data, per_domain, details):
|
||||
total = len(details)
|
||||
correct = sum(1 for x in details if x["ok"])
|
||||
print(f"\n=== ИТОГ КВИЗА: {correct}/{total} ===")
|
||||
for d, st in per_domain.items():
|
||||
pct = round(100 * st["correct"] / st["total"])
|
||||
if pct >= 80:
|
||||
verdict = "крепко"
|
||||
elif pct >= 60:
|
||||
verdict = "база есть, нужен добор"
|
||||
else:
|
||||
verdict = "дыра, идёт в план с нуля"
|
||||
print(f" {data['meta']['domains'][d]:38s} {st['correct']}/{st['total']} ({pct}%) — {verdict}")
|
||||
wrong = [x for x in details if not x["ok"]]
|
||||
if wrong:
|
||||
print("\nРазбор промахов:")
|
||||
for x in wrong:
|
||||
print(f" {x['n']}. {x['question']}")
|
||||
print(f" ты: {x['your_answer'] or '—'} | верно: {x['correct_answer']}. {x['explain']}")
|
||||
return correct, total
|
||||
|
||||
|
||||
def main():
|
||||
data = load()
|
||||
args = sys.argv[1:]
|
||||
if "--export" in args:
|
||||
export(data)
|
||||
return
|
||||
if "--answers" in args:
|
||||
s = args[args.index("--answers") + 1]
|
||||
answers = parse_answers(s)
|
||||
else:
|
||||
answers = {}
|
||||
for i, q in enumerate(data["questions"], 1):
|
||||
print(f"\n{i}. [{data['meta']['domains'][q['d']]}] {q['q']}")
|
||||
for j, o in enumerate(q["o"], 1):
|
||||
print(f" {j}) {o}")
|
||||
while True:
|
||||
raw = input("ответ (1-4, пусто = пропустить, q = выход): ").strip().lower()
|
||||
if raw == "q":
|
||||
break
|
||||
if raw == "":
|
||||
break
|
||||
if raw in {"1", "2", "3", "4"}:
|
||||
answers[i] = int(raw)
|
||||
break
|
||||
if raw == "q":
|
||||
break
|
||||
per_domain, details = grade(data, answers)
|
||||
correct, total = report(data, per_domain, details)
|
||||
out = {
|
||||
"correct": correct, "total": total,
|
||||
"per_domain": per_domain,
|
||||
"details": details,
|
||||
}
|
||||
with open(os.path.join(HERE, "results_quiz.json"), "w", encoding="utf-8") as f:
|
||||
json.dump(out, f, ensure_ascii=False, indent=2)
|
||||
print(f"\nРезультат сохранён: {os.path.join(HERE, 'results_quiz.json')}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,27 @@
|
||||
// Задача 01 — битовые операции. Заполни реализации.
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
// число единичных бит в 32-битном слове
|
||||
int popcount32(uint32_t x) {
|
||||
(void)x;
|
||||
return 0; // TODO
|
||||
}
|
||||
|
||||
// развернуть порядок следования всех 32 бит
|
||||
uint32_t reverse_bits32(uint32_t x) {
|
||||
(void)x;
|
||||
return 0; // TODO
|
||||
}
|
||||
|
||||
// true, если x — степень двойки; 0 — не степень двойки
|
||||
bool is_power_of_two(uint32_t x) {
|
||||
(void)x;
|
||||
return false; // TODO
|
||||
}
|
||||
|
||||
// поменять местами байты: 0x11223344 -> 0x44332211
|
||||
uint32_t bswap32(uint32_t x) {
|
||||
(void)x;
|
||||
return 0; // TODO
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
# Задача 01 — битовые операции (C)
|
||||
|
||||
Реализуй в `solution.cpp` четыре функции. Запрещено пользоваться встроенными
|
||||
`__builtin_popcount`, `std::popcount`, `std::byteswap` — считается любая корректная
|
||||
ручная реализация.
|
||||
|
||||
```c
|
||||
int popcount32(uint32_t x); // число единичных бит
|
||||
uint32_t reverse_bits32(uint32_t x); // развернуть порядок всех 32 бит
|
||||
bool is_power_of_two(uint32_t x); // x — степень двойки (0 не считается)
|
||||
uint32_t bswap32(uint32_t x); // поменять порядок байт местами
|
||||
```
|
||||
|
||||
Проверка: `python3 grade.py 01` из каталога `diag/`.
|
||||
Критерий: все проверки `ok`, сборка без предупреждений, ASAN/UBSAN чистые.
|
||||
|
||||
Подсказки по разбору после сдачи: `x & (x - 1)`; реверс — классический цикл на 32 итерации
|
||||
или пара «маска+сдвиг»; `bswap` — четыре маски.
|
||||
@@ -0,0 +1,41 @@
|
||||
#include <cstdio>
|
||||
#include <cstdint>
|
||||
#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() {
|
||||
// popcount
|
||||
CHECK(popcount32(0u) == 0, "popcount(0) == 0");
|
||||
CHECK(popcount32(1u) == 1, "popcount(1) == 1");
|
||||
CHECK(popcount32(0xFFFFFFFFu) == 32, "popcount(0xFFFFFFFF) == 32");
|
||||
CHECK(popcount32(0x80000000u) == 1, "popcount(1<<31) == 1");
|
||||
CHECK(popcount32(0x0000000Bu) == 3, "popcount(0b1011) == 3");
|
||||
CHECK(popcount32(0xA5A5A5A5u) == 16, "popcount(0xA5A5A5A5) == 16");
|
||||
|
||||
// reverse
|
||||
CHECK(reverse_bits32(1u) == 0x80000000u, "reverse(1) == 0x80000000");
|
||||
CHECK(reverse_bits32(0x80000000u) == 1u, "reverse(0x80000000) == 1");
|
||||
CHECK(reverse_bits32(0x12345678u) == 0x1E6A2C48u, "reverse(0x12345678) == 0x1E6A2C48");
|
||||
CHECK(reverse_bits32(0xFFFFFFFFu) == 0xFFFFFFFFu, "reverse(0xFFFFFFFF) == 0xFFFFFFFF");
|
||||
CHECK(reverse_bits32(0u) == 0u, "reverse(0) == 0");
|
||||
|
||||
// power of two
|
||||
CHECK(!is_power_of_two(0u), "pow2(0) == false");
|
||||
CHECK(is_power_of_two(1u), "pow2(1) == true");
|
||||
CHECK(is_power_of_two(2u), "pow2(2) == true");
|
||||
CHECK(!is_power_of_two(3u), "pow2(3) == false");
|
||||
CHECK(is_power_of_two(1024u), "pow2(1024) == true");
|
||||
CHECK(is_power_of_two(0x80000000u), "pow2(1<<31) == true");
|
||||
CHECK(!is_power_of_two(0x80000001u), "pow2(0x80000001) == false");
|
||||
|
||||
// bswap
|
||||
CHECK(bswap32(0x11223344u) == 0x44332211u, "bswap(0x11223344) == 0x44332211");
|
||||
CHECK(bswap32(0x000000FFu) == 0xFF000000u, "bswap(0x000000FF) == 0xFF000000");
|
||||
CHECK(bswap32(0xDEADBEEFu) == 0xEFBEADDEu, "bswap(0xDEADBEEF) == 0xEFBEADDE");
|
||||
|
||||
std::printf(failures ? "\nFAILURES: %d\n" : "\nALL PASS\n", failures);
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Задача 02 — связный список. Заполни реализации.
|
||||
#include <cstddef>
|
||||
|
||||
struct Node {
|
||||
int value;
|
||||
Node* next;
|
||||
};
|
||||
|
||||
// Развернуть список на месте. Вернуть новую голову.
|
||||
Node* reverse_list(Node* head) {
|
||||
(void)head;
|
||||
return nullptr; // TODO
|
||||
}
|
||||
|
||||
// Середина списка: для чётного количества — второй из двух средних узлов.
|
||||
Node* find_middle(Node* head) {
|
||||
(void)head;
|
||||
return nullptr; // TODO
|
||||
}
|
||||
|
||||
// Есть ли цикл в списке.
|
||||
bool has_cycle(Node* head) {
|
||||
(void)head;
|
||||
return false; // TODO
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
# Задача 02 — связный список (C++)
|
||||
|
||||
Даны функции над односвязным списком `Node{int value; Node* next;}`:
|
||||
|
||||
```c
|
||||
Node* reverse_list(Node* head); // развернуть список на месте, вернуть новую голову
|
||||
Node* find_middle(Node* head); // середина; для чётной длины — второй из двух средних
|
||||
bool has_cycle(Node* head); // есть ли цикл
|
||||
```
|
||||
|
||||
Требования:
|
||||
- никаких аллокаций, O(1) дополнительной памяти, один проход там, где это возможно;
|
||||
- `find_middle` и `has_cycle` — через два указателя (медленный/быстрый);
|
||||
- корректная работа с пустым списком и списком из одного узла.
|
||||
|
||||
Проверка: `python3 grade.py 02`. Критерий: все `ok`, ASAN/UBSAN чистые (утечки в тесте
|
||||
считаются ошибкой — тест сам освобождает память).
|
||||
@@ -0,0 +1,94 @@
|
||||
#include <cstdio>
|
||||
#include <vector>
|
||||
#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 Node* build(const std::vector<int>& v) {
|
||||
Node* head = nullptr; Node** tail = &head;
|
||||
for (int x : v) { *tail = new Node{x, nullptr}; tail = &(*tail)->next; }
|
||||
return head;
|
||||
}
|
||||
static std::vector<int> to_vec(Node* h, size_t limit = 1000) {
|
||||
std::vector<int> r; while (h && r.size() < limit) { r.push_back(h->value); h = h->next; } return r;
|
||||
}
|
||||
static void destroy(Node* h) { while (h) { Node* n = h->next; delete h; h = n; } }
|
||||
|
||||
int main() {
|
||||
// reverse
|
||||
{
|
||||
Node* h = build({1, 2, 3, 4, 5});
|
||||
Node* r = reverse_list(h);
|
||||
CHECK(to_vec(r) == std::vector<int>({5, 4, 3, 2, 1}), "reverse [1..5] -> [5..1]");
|
||||
destroy(r);
|
||||
}
|
||||
{
|
||||
Node* h = build({42});
|
||||
Node* r = reverse_list(h);
|
||||
CHECK(r && r->value == 42 && r->next == nullptr, "reverse одного узла");
|
||||
destroy(r);
|
||||
}
|
||||
CHECK(reverse_list(nullptr) == nullptr, "reverse пустого списка == nullptr");
|
||||
{
|
||||
Node* h = build({7, 8});
|
||||
Node* r = reverse_list(h);
|
||||
CHECK(to_vec(r) == std::vector<int>({8, 7}), "reverse двух узлов");
|
||||
destroy(r);
|
||||
}
|
||||
|
||||
// middle
|
||||
{
|
||||
Node* h = build({1, 2, 3, 4, 5, 6});
|
||||
Node* m = find_middle(h);
|
||||
CHECK(m && m->value == 4, "middle чётного [1..6] == 4 (второй из двух средних)");
|
||||
destroy(h);
|
||||
}
|
||||
{
|
||||
Node* h = build({1, 2, 3, 4, 5});
|
||||
Node* m = find_middle(h);
|
||||
CHECK(m && m->value == 3, "middle нечётного [1..5] == 3");
|
||||
destroy(h);
|
||||
}
|
||||
{
|
||||
Node* h = build({9});
|
||||
CHECK(find_middle(h) && find_middle(h)->value == 9, "middle одного узла == 9");
|
||||
destroy(h);
|
||||
}
|
||||
CHECK(find_middle(nullptr) == nullptr, "middle пустого списка == nullptr");
|
||||
|
||||
// cycle
|
||||
{
|
||||
Node* h = build({1, 2, 3, 4});
|
||||
CHECK(!has_cycle(h), "has_cycle(без цикла) == false");
|
||||
destroy(h);
|
||||
}
|
||||
{
|
||||
Node* h = build({1, 2, 3, 4, 5});
|
||||
Node* third = h->next->next;
|
||||
Node* last = h; while (last->next) last = last->next;
|
||||
last->next = third; // цикл 3->4->5->3
|
||||
CHECK(has_cycle(h), "has_cycle(цикл внутри) == true");
|
||||
last->next = nullptr;
|
||||
destroy(h);
|
||||
}
|
||||
{
|
||||
Node* h = build({1, 2});
|
||||
Node* last = h->next; last->next = h; // цикл на всю длину
|
||||
CHECK(has_cycle(h), "has_cycle(цикл на всю длину) == true");
|
||||
last->next = nullptr;
|
||||
destroy(h);
|
||||
}
|
||||
{
|
||||
Node* one = new Node{5, nullptr};
|
||||
one->next = one;
|
||||
CHECK(has_cycle(one), "has_cycle(сам на себя) == true");
|
||||
one->next = nullptr;
|
||||
delete one;
|
||||
}
|
||||
CHECK(!has_cycle(nullptr), "has_cycle(nullptr) == false");
|
||||
|
||||
std::printf(failures ? "\nFAILURES: %d\n" : "\nALL PASS\n", failures);
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Задача 03 — кольцевой буфер. Заполни реализации.
|
||||
#include <cstddef>
|
||||
|
||||
class RingBuffer {
|
||||
public:
|
||||
explicit RingBuffer(size_t capacity) : cap_(capacity), data_(new int[capacity ? capacity : 1]), size_(0) {}
|
||||
~RingBuffer() { delete[] data_; }
|
||||
|
||||
bool push(int v) {
|
||||
(void)v;
|
||||
return false; // TODO
|
||||
}
|
||||
|
||||
bool pop(int& out) {
|
||||
(void)out;
|
||||
return false; // TODO
|
||||
}
|
||||
|
||||
size_t size() const { return size_; }
|
||||
size_t capacity() const { return cap_; }
|
||||
bool empty() const { return size_ == 0; }
|
||||
bool full() const { return size_ == cap_; }
|
||||
|
||||
private:
|
||||
size_t cap_;
|
||||
int* data_;
|
||||
size_t size_;
|
||||
// добавь head_/tail_ по необходимости
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
# Задача 03 — кольцевой буфер (C++)
|
||||
|
||||
Кольцевой буфер фиксированной ёмкости — базовая структура для embedded и сетевого кода.
|
||||
|
||||
```c++
|
||||
class RingBuffer {
|
||||
public:
|
||||
explicit RingBuffer(size_t capacity);
|
||||
~RingBuffer();
|
||||
bool push(int v); // false, если буфер полон (перезапись НЕ делать)
|
||||
bool pop(int& out); // false, если буфер пуст
|
||||
size_t size() const;
|
||||
size_t capacity() const;
|
||||
bool empty() const;
|
||||
bool full() const;
|
||||
};
|
||||
```
|
||||
|
||||
Требования:
|
||||
- O(1) на push/pop, никаких сдвигов элементов;
|
||||
- память выделяется один раз в конструкторе, освобождается в деструкторе;
|
||||
- поведение после заворачивания индексов (wrap-around) корректное;
|
||||
- копирование запрещать не обязательно, но двойного освобождения быть не должно.
|
||||
|
||||
Проверка: `python3 grade.py 03`. Критерий: все `ok`, ASAN/UBSAN чистые.
|
||||
@@ -0,0 +1,46 @@
|
||||
#include <cstdio>
|
||||
#include <vector>
|
||||
#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() {
|
||||
{
|
||||
RingBuffer rb(4);
|
||||
CHECK(rb.capacity() == 4 && rb.size() == 0 && rb.empty() && !rb.full(), "старт: пуст, ёмкость 4");
|
||||
CHECK(rb.push(1) && rb.push(2) && rb.push(3) && rb.push(4), "4 push прошли");
|
||||
CHECK(rb.full() && rb.size() == 4, "после 4 push буфер полон");
|
||||
CHECK(!rb.push(5), "push в полный буфер -> false");
|
||||
int v = -1;
|
||||
CHECK(rb.pop(v) && v == 1, "FIFO: первый pop == 1");
|
||||
CHECK(rb.pop(v) && v == 2, "FIFO: второй pop == 2");
|
||||
CHECK(rb.size() == 2 && !rb.full(), "после двух pop размер 2");
|
||||
CHECK(rb.push(5) && rb.push(6), "push после pop (заворачивание)");
|
||||
int expect[] = {3, 4, 5, 6};
|
||||
bool ok = true;
|
||||
for (int i = 0; i < 4; ++i) { int x = -1; ok = ok && rb.pop(x) && x == expect[i]; }
|
||||
CHECK(ok, "wrap-around: порядок 3,4,5,6");
|
||||
CHECK(rb.empty() && !rb.pop(v), "пустой буфер: pop -> false");
|
||||
}
|
||||
{
|
||||
RingBuffer rb(1);
|
||||
int v = 0;
|
||||
CHECK(rb.push(11) && !rb.push(12), "ёмкость 1: второй push отклонён");
|
||||
CHECK(rb.pop(v) && v == 11, "ёмкость 1: pop вернул 11");
|
||||
CHECK(rb.pop(v) == false, "ёмкость 1: второй pop -> false");
|
||||
}
|
||||
{
|
||||
RingBuffer rb(8);
|
||||
int cnt = 0;
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
if (rb.push(i)) cnt++;
|
||||
if (i % 3 == 0) { int x = -1; if (rb.pop(x)) cnt--; }
|
||||
}
|
||||
CHECK(cnt == (int)rb.size(), "длинный прогон: счётчик push/pop сходится с size()");
|
||||
CHECK(rb.size() <= 8, "длинный прогон: размер не превышает ёмкость");
|
||||
}
|
||||
std::printf(failures ? "\nFAILURES: %d\n" : "\nALL PASS\n", failures);
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Задача 04 — разбор IPv4-пакета. Заполни реализации.
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
struct Ipv4Header {
|
||||
uint8_t version;
|
||||
uint8_t ihl;
|
||||
uint8_t protocol;
|
||||
uint16_t total_length;
|
||||
uint32_t src_ip;
|
||||
uint32_t dst_ip;
|
||||
uint16_t header_checksum;
|
||||
};
|
||||
|
||||
bool parse_ipv4(const uint8_t* buf, size_t len, Ipv4Header* out) {
|
||||
(void)buf; (void)len; (void)out;
|
||||
return false; // TODO
|
||||
}
|
||||
|
||||
uint16_t compute_checksum(const uint8_t* buf, size_t len) {
|
||||
(void)buf; (void)len;
|
||||
return 0; // TODO
|
||||
}
|
||||
|
||||
bool checksum_valid(const uint8_t* buf, size_t len) {
|
||||
(void)buf; (void)len;
|
||||
return false; // TODO
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
# Задача 04 — разбор IPv4-пакета и контрольная сумма (C++)
|
||||
|
||||
Это то, что реально делают сетевые железки: взять буфер из сокета и корректно разобрать
|
||||
заголовок, не выйдя за границы и не поверив «на слово» полям пакета.
|
||||
|
||||
```c++
|
||||
struct Ipv4Header {
|
||||
uint8_t version; // должно быть 4
|
||||
uint8_t ihl; // длина заголовка в 32-битных словах
|
||||
uint8_t protocol; // 6 = TCP, 17 = UDP, 1 = ICMP
|
||||
uint16_t total_length; // из заголовка (хост-порядок)
|
||||
uint32_t src_ip; // байты как на проводе: 10.0.0.1 -> 0x0A000001
|
||||
uint32_t dst_ip;
|
||||
uint16_t header_checksum; // поле из заголовка (хост-порядок)
|
||||
};
|
||||
|
||||
// false: буфер короче 20 байт, version != 4, ihl < 5, ihl*4 > len,
|
||||
// total_length < ihl*4 или total_length > len
|
||||
bool parse_ipv4(const uint8_t* buf, size_t len, Ipv4Header* out);
|
||||
|
||||
// сумма в дополнительном коде по len байтам, затем инверсия — значение для записи в поле
|
||||
// (вызывается на буфере, где поле контрольной суммы уже обнулено)
|
||||
uint16_t compute_checksum(const uint8_t* buf, size_t len);
|
||||
|
||||
// true, если контрольная сумма заголовка верна: сумма в дополнительном коде по ihl*4 байтам
|
||||
// заголовка (вместе с полем контрольной суммы) равна 0xFFFF; нагрузка не участвует
|
||||
bool checksum_valid(const uint8_t* buf, size_t len);
|
||||
```
|
||||
|
||||
Проверка: `python3 grade.py 04`. Критерий: все `ok`, ASAN/UBSAN чистые.
|
||||
Запрещено приводить буфер к структуре через `reinterpret_cast` и читать поля как есть —
|
||||
на невыровненных адресах и в другом порядке байт это UB.
|
||||
@@ -0,0 +1,100 @@
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#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 uint16_t ref_sum(const uint8_t* b, size_t n, uint32_t init) {
|
||||
uint32_t acc = init;
|
||||
for (size_t i = 0; i + 1 < n; i += 2) acc += (uint32_t(b[i]) << 8) | b[i + 1];
|
||||
if (n % 2) acc += uint32_t(b[n - 1]) << 8;
|
||||
while (acc >> 16) acc = (acc & 0xFFFF) + (acc >> 16);
|
||||
return uint16_t(acc);
|
||||
}
|
||||
|
||||
// Пакет: заголовок (ihl=5) + payload_len байт нагрузки.
|
||||
static std::vector<uint8_t> make_packet(size_t payload_len, uint8_t proto, bool with_options = false,
|
||||
bool corrupt_checksum = false, uint16_t force_total = 0) {
|
||||
size_t hlen = with_options ? 24 : 20;
|
||||
std::vector<uint8_t> p(hlen + payload_len, 0);
|
||||
p[0] = 0x40 | uint8_t(hlen / 4); // version 4, ihl
|
||||
p[1] = 0x00; // DSCP/ECN
|
||||
uint16_t total = force_total ? force_total : uint16_t(hlen + payload_len);
|
||||
p[2] = uint8_t(total >> 8); p[3] = uint8_t(total & 0xFF);
|
||||
p[4] = 0x12; p[5] = 0x34; // id
|
||||
p[6] = 0x40; p[7] = 0x00; // DF
|
||||
p[8] = 64; // TTL
|
||||
p[9] = proto;
|
||||
p[10] = 0; p[11] = 0; // checksum = 0 для расчёта
|
||||
p[12] = 0x0A; p[13] = 0x00; p[14] = 0x00; p[15] = 0x01; // 10.0.0.1
|
||||
p[16] = 0x0A; p[17] = 0x00; p[18] = 0x00; p[19] = 0x02; // 10.0.0.2
|
||||
if (with_options) { p[20] = 0x01; p[21] = 0x01; p[22] = 0x00; p[23] = 0x00; }
|
||||
for (size_t i = 0; i < payload_len; ++i) p[hlen + i] = uint8_t('A' + (i % 26));
|
||||
uint16_t cs = uint16_t(~ref_sum(p.data(), hlen, 0));
|
||||
p[10] = uint8_t(cs >> 8); p[11] = uint8_t(cs & 0xFF);
|
||||
if (corrupt_checksum) p[10] ^= 0xFF;
|
||||
return p;
|
||||
}
|
||||
|
||||
int main() {
|
||||
// parse: обычный пакет
|
||||
{
|
||||
auto p = make_packet(10, 6);
|
||||
Ipv4Header h{};
|
||||
bool ok = parse_ipv4(p.data(), p.size(), &h);
|
||||
CHECK(ok, "parse() обычного пакета -> true");
|
||||
CHECK(h.version == 4, "parse: version == 4");
|
||||
CHECK(h.ihl == 5, "parse: ihl == 5");
|
||||
CHECK(h.protocol == 6, "parse: protocol == 6 (TCP)");
|
||||
CHECK(h.total_length == 30, "parse: total_length == 30");
|
||||
CHECK(h.src_ip == 0x0A000001u, "parse: src_ip == 10.0.0.1 (порядок байт как на проводе)");
|
||||
CHECK(h.dst_ip == 0x0A000002u, "parse: dst_ip == 10.0.0.2");
|
||||
}
|
||||
// parse: с опциями (ihl=6)
|
||||
{
|
||||
auto p = make_packet(0, 17, true);
|
||||
Ipv4Header h{};
|
||||
bool ok = parse_ipv4(p.data(), p.size(), &h);
|
||||
CHECK(ok && h.ihl == 6 && h.total_length == 24, "parse: ihl=6 и total_length=24 (с опциями)");
|
||||
}
|
||||
// parse: отрицательные случаи
|
||||
{
|
||||
auto p = make_packet(10, 6);
|
||||
Ipv4Header h{};
|
||||
CHECK(!parse_ipv4(p.data(), 10, &h), "parse: обрезанный буфер (10 из 30) -> false");
|
||||
CHECK(!parse_ipv4(nullptr, 0, &h), "parse: пустой буфер -> false");
|
||||
auto v6 = p; v6[0] = 0x60;
|
||||
CHECK(!parse_ipv4(v6.data(), v6.size(), &h), "parse: version 6 -> false");
|
||||
auto bad_ihl = p; bad_ihl[0] = 0x43; // ihl=3
|
||||
CHECK(!parse_ipv4(bad_ihl.data(), bad_ihl.size(), &h), "parse: ihl < 5 -> false");
|
||||
auto too_big = make_packet(10, 6, false, false, 100); // total_length больше буфера
|
||||
CHECK(!parse_ipv4(too_big.data(), too_big.size(), &h), "parse: total_length > len -> false");
|
||||
auto small_total = make_packet(10, 6, false, false, 12); // меньше самого заголовка
|
||||
CHECK(!parse_ipv4(small_total.data(), small_total.size(), &h), "parse: total_length < ihl*4 -> false");
|
||||
}
|
||||
// checksum
|
||||
{
|
||||
auto p = make_packet(10, 6);
|
||||
auto pz = p; pz[10] = 0; pz[11] = 0; // поле обнулено — так считают контрольную сумму
|
||||
uint16_t cs = compute_checksum(pz.data(), 20);
|
||||
CHECK(cs == uint16_t(~ref_sum(pz.data(), 20, 0)), "compute_checksum: совпадает с эталонным расчётом");
|
||||
CHECK(cs == uint16_t((p[10] << 8) | p[11]), "compute_checksum: значение равно полю в корректном пакете");
|
||||
CHECK(checksum_valid(p.data(), p.size()), "checksum_valid(корректный пакет) -> true");
|
||||
auto bad = make_packet(10, 6, false, true);
|
||||
CHECK(!checksum_valid(bad.data(), bad.size()), "checksum_valid(испорченная сумма) -> false");
|
||||
auto bad_ttl = p; bad_ttl[8] ^= 0x01;
|
||||
CHECK(!checksum_valid(bad_ttl.data(), bad_ttl.size()), "checksum_valid(испорчен TTL) -> false");
|
||||
auto bad_payload = p; bad_payload[24] ^= 0xFF;
|
||||
CHECK(checksum_valid(bad_payload.data(), bad_payload.size()),
|
||||
"checksum_valid: нагрузка в контрольную сумму заголовка не входит -> true");
|
||||
// нечётная длина
|
||||
uint8_t odd[3] = {0x01, 0x02, 0x03};
|
||||
CHECK(compute_checksum(odd, 3) == uint16_t(~ref_sum(odd, 3, 0)), "compute_checksum: нечётная длина");
|
||||
}
|
||||
std::printf(failures ? "\nFAILURES: %d\n" : "\nALL PASS\n", failures);
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Задача 05 — длина НВП за O(n log n). Заполни реализацию.
|
||||
#include <vector>
|
||||
|
||||
int lis_length(const std::vector<int>& a) {
|
||||
(void)a;
|
||||
return 0; // TODO
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
# Задача 05 — длиннейшая возрастающая подпоследовательность (C++)
|
||||
|
||||
```c++
|
||||
int lis_length(const std::vector<int>& a); // длина НВП (строго возрастающей)
|
||||
```
|
||||
|
||||
Требования:
|
||||
- пустая последовательность → 0;
|
||||
- работа за O(n log n); наивное O(n²) не пройдёт проверку на 100 000 элементах
|
||||
(внутренний тест меряет время и роняет прогон при > 2 с);
|
||||
- подпоследовательность не обязана быть непрерывной.
|
||||
|
||||
Проверка: `python3 grade.py 05`. Критерий: все `ok`, сборка без предупреждений.
|
||||
Разбор после сдачи: «хвосты» — массив минимальных последних элементов + lower_bound.
|
||||
@@ -0,0 +1,47 @@
|
||||
#include <cstdio>
|
||||
#include <vector>
|
||||
#include <random>
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#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() {
|
||||
CHECK(lis_length({}) == 0, "пустая -> 0");
|
||||
CHECK(lis_length({5}) == 1, "один элемент -> 1");
|
||||
CHECK(lis_length({1, 2, 3}) == 3, "возрастающая -> вся длина");
|
||||
CHECK(lis_length({3, 2, 1}) == 1, "убывающая -> 1");
|
||||
CHECK(lis_length({10, 9, 2, 5, 3, 7, 101, 18}) == 4, "классика -> 4");
|
||||
CHECK(lis_length({7, 7, 7}) == 1, "равные элементы -> строго возрастающая длина 1");
|
||||
CHECK(lis_length({0, 1, 0, 3, 2, 3}) == 4, "лекод-пример -> 4");
|
||||
CHECK(lis_length({2, 2, 2, 3}) == 2, "дубли + рост -> 2");
|
||||
CHECK(lis_length({-5, -4, -3, -2}) == 4, "отрицательные -> 4");
|
||||
|
||||
// производительность: худший случай для наивного DP
|
||||
{
|
||||
std::vector<int> big(100000);
|
||||
for (int i = 0; i < 100000; ++i) big[i] = i;
|
||||
auto t0 = std::chrono::steady_clock::now();
|
||||
int r = lis_length(big);
|
||||
double sec = std::chrono::duration<double>(std::chrono::steady_clock::now() - t0).count();
|
||||
std::printf(" (100k возрастающих: result=%d, time=%.3fs)\n", r, sec);
|
||||
CHECK(r == 100000, "100k возрастающих -> 100000");
|
||||
CHECK(sec < 2.0, "100k за < 2 c (требуется O(n log n))");
|
||||
}
|
||||
{
|
||||
std::mt19937 rng(12345);
|
||||
std::vector<int> big(100000);
|
||||
for (auto& x : big) x = int(rng() % 100000);
|
||||
auto t0 = std::chrono::steady_clock::now();
|
||||
int r = lis_length(big);
|
||||
double sec = std::chrono::duration<double>(std::chrono::steady_clock::now() - t0).count();
|
||||
std::printf(" (100k случайных: result=%d, time=%.3fs)\n", r, sec);
|
||||
CHECK(r > 300 && r < 800, "100k случайных: результат в разумном диапазоне");
|
||||
CHECK(sec < 2.0, "100k случайных за < 2 c");
|
||||
}
|
||||
std::printf(failures ? "\nFAILURES: %d\n" : "\nALL PASS\n", failures);
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Задача 06 — потокобезопасная очередь. Заполни реализацию.
|
||||
#include <cstddef>
|
||||
#include <stdexcept>
|
||||
|
||||
class BlockingQueue {
|
||||
public:
|
||||
explicit BlockingQueue(size_t capacity) : cap_(capacity) {}
|
||||
~BlockingQueue() = default;
|
||||
|
||||
void push(int v) {
|
||||
(void)v;
|
||||
// TODO: должен блокироваться на полной очереди и бросать std::runtime_error после close()
|
||||
throw std::runtime_error("push: not implemented");
|
||||
}
|
||||
|
||||
bool pop(int& out) {
|
||||
(void)out;
|
||||
return false; // TODO
|
||||
}
|
||||
|
||||
void close() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
size_t size() const { return 0; }
|
||||
|
||||
private:
|
||||
size_t cap_;
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
# Задача 06 — потокобезопасная ограниченная очередь (C++)
|
||||
|
||||
Классический вопрос на собеседовании в embedded/сетевую разработку: не «знаешь ли ты
|
||||
std::thread», а «умеешь ли ты не сломать счётчик под нагрузкой».
|
||||
|
||||
```c++
|
||||
class BlockingQueue {
|
||||
public:
|
||||
explicit BlockingQueue(size_t capacity);
|
||||
~BlockingQueue();
|
||||
void push(int v); // блокируется, пока очередь полна
|
||||
bool pop(int& out); // блокируется, пока пуста; false — если закрыта и пуста
|
||||
void close(); // после close: pop() опустошает остаток и отдаёт false
|
||||
size_t size() const;
|
||||
};
|
||||
```
|
||||
|
||||
Требования:
|
||||
- push после close() бросает `std::runtime_error`;
|
||||
- закрытие разблокирует все ждущие потоки (никакого вечного ожидания и busy-wait);
|
||||
- размер очереди никогда не превышает capacity;
|
||||
- ни одной гонки: тест собирается и гоняется с ThreadSanitizer (в том числе `size()`
|
||||
вызывается из другого потока — он тоже должен быть потокобезопасным).
|
||||
|
||||
Проверка: `python3 grade.py 06`. Критерий: все `ok`, TSan чистый, нет зависания.
|
||||
Разбор после сдачи: почему нужен predicate-цикл вокруг wait, и как один condition_variable
|
||||
на оба события даёт ложные пробуждения.
|
||||
@@ -0,0 +1,73 @@
|
||||
#include <cstdio>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#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() {
|
||||
// Базовая дисциплина
|
||||
{
|
||||
BlockingQueue q(2);
|
||||
q.push(1);
|
||||
q.push(2);
|
||||
CHECK(q.size() == 2, "size() == 2 после двух push");
|
||||
int v = 0;
|
||||
CHECK(q.pop(v) && v == 1, "pop -> 1 (FIFO)");
|
||||
CHECK(q.pop(v) && v == 2, "pop -> 2 (FIFO)");
|
||||
q.close();
|
||||
CHECK(!q.pop(v), "после close и опустошения pop -> false");
|
||||
bool threw = false;
|
||||
try { q.push(3); } catch (const std::runtime_error&) { threw = true; }
|
||||
CHECK(threw, "push после close -> std::runtime_error");
|
||||
}
|
||||
// Продюсеры/потребители под нагрузкой
|
||||
{
|
||||
const int P = 4, C = 4, PER = 20000, C3 = 64;
|
||||
BlockingQueue q(C3);
|
||||
std::atomic<int> pushed{0};
|
||||
std::atomic<long long> sum{0};
|
||||
std::atomic<int> popped{0};
|
||||
std::atomic<int> over{0};
|
||||
|
||||
auto producer = [&](int pid) {
|
||||
for (int i = 0; i < PER; ++i) {
|
||||
q.push(pid * 1000000 + i);
|
||||
pushed.fetch_add(1, std::memory_order_relaxed);
|
||||
if (q.size() > C3) over.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
};
|
||||
auto consumer = [&](int) {
|
||||
int v = 0;
|
||||
while (q.pop(v)) {
|
||||
sum.fetch_add(v, std::memory_order_relaxed);
|
||||
popped.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<std::thread> ts;
|
||||
for (int i = 0; i < C; ++i) ts.emplace_back(consumer, i);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
for (int i = 0; i < P; ++i) ts.emplace_back(producer, i);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
while (pushed.load() < P * PER) std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
q.close();
|
||||
for (auto& t : ts) t.join();
|
||||
|
||||
long long expect = 0;
|
||||
for (int p = 0; p < P; ++p) for (int i = 0; i < PER; ++i) expect += (long long)p * 1000000 + i;
|
||||
std::printf(" (вставлено=%d, извлечено=%d, переполнений=%d)\n",
|
||||
pushed.load(), popped.load(), over.load());
|
||||
CHECK(pushed.load() == P * PER, "все вставки выполнены (нет потерь)");
|
||||
CHECK(popped.load() == P * PER, "извлечено ровно столько же (нет дублей/потерь)");
|
||||
CHECK(sum.load() == expect, "сумма значений совпадает с эталоном");
|
||||
CHECK(over.load() == 0, "size() не превышал capacity");
|
||||
}
|
||||
std::printf(failures ? "\nFAILURES: %d\n" : "\nALL PASS\n", failures);
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Задача 07 — TCP-эхо-сервер на epoll. Реализуй сервер.
|
||||
// Запуск: ./solution <порт>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 2) {
|
||||
std::fprintf(stderr, "usage: %s <port>\n", argv[0]);
|
||||
return 2;
|
||||
}
|
||||
long port = std::strtol(argv[1], nullptr, 10);
|
||||
std::fprintf(stderr, "TODO: сервер на порту %ld не реализован\n", port);
|
||||
return 1; // TODO: реализовать сервер на epoll
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
# Задача 07 — TCP-эхо-сервер на epoll (Linux, C++)
|
||||
|
||||
Написать сервер: принимает TCP-соединения на `127.0.0.1:<порт из argv[1]>`, эхо всех
|
||||
принятых байт обратно в то же соединение, обслуживает несколько клиентов одновременно,
|
||||
завершается по SIGTERM/SIGINT аккуратно (exit 0).
|
||||
|
||||
Требования:
|
||||
- мультиплексирование через `epoll` (не блокирующий `accept` в цикле, не поток на клиента);
|
||||
- сокеты неблокирующие, обработка `EAGAIN`/`EINTR`, частичные чтения и записи;
|
||||
- `SO_REUSEADDR`, bind на 127.0.0.1, прослушивание на нужном порту;
|
||||
- до 64 одновременных соединений, закрытие по EOF со стороны клиента;
|
||||
- никаких утечек дескрипторов (тест проверяет закрытие).
|
||||
|
||||
Запускается как самостоятельный бинарник: `./solution <порт>`.
|
||||
Проверка: `python3 grade.py 07` — тест сам поднимает сервер, открывает 3 соединения,
|
||||
льёт данные, проверяет эхо и корректное завершение по SIGTERM.
|
||||
Критерий: все `ok` + ревью кода (использование epoll, обработка ошибок).
|
||||
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Тест задачи 07: поднимает сервер, проверяет эхо на нескольких соединениях и SIGTERM."""
|
||||
import os
|
||||
import signal
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
BIN = os.path.join(HERE, "solution")
|
||||
failures = 0
|
||||
|
||||
|
||||
def check(cond, name):
|
||||
global failures
|
||||
if cond:
|
||||
print(f"ok {name}")
|
||||
else:
|
||||
print(f"FAIL {name}")
|
||||
failures += 1
|
||||
|
||||
|
||||
def free_port():
|
||||
s = socket.socket()
|
||||
s.bind(("127.0.0.1", 0))
|
||||
p = s.getsockname()[1]
|
||||
s.close()
|
||||
return p
|
||||
|
||||
|
||||
def main():
|
||||
port = free_port()
|
||||
proc = subprocess.Popen([BIN, str(port)], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
up = False
|
||||
for _ in range(100): # до 5 секунд на старт
|
||||
if proc.poll() is not None:
|
||||
break
|
||||
try:
|
||||
s = socket.create_connection(("127.0.0.1", port), timeout=0.3)
|
||||
s.close()
|
||||
up = True
|
||||
break
|
||||
except OSError:
|
||||
time.sleep(0.05)
|
||||
check(up, "сервер принял соединение и слушает порт")
|
||||
|
||||
if up:
|
||||
# три одновременных соединения, большие и разорванные сообщения
|
||||
socks = []
|
||||
for i in range(3):
|
||||
s = socket.create_connection(("127.0.0.1", port), timeout=2)
|
||||
s.settimeout(5)
|
||||
socks.append(s)
|
||||
payloads = [b"hello\n", b"x" * 70000, b"chunk-" + bytes(range(32, 127))]
|
||||
for s, p in zip(socks, payloads):
|
||||
s.sendall(p)
|
||||
ok = True
|
||||
for i, (s, p) in enumerate(zip(socks, payloads)):
|
||||
got = b""
|
||||
while len(got) < len(p):
|
||||
chunk = s.recv(65536)
|
||||
if not chunk:
|
||||
break
|
||||
got += chunk
|
||||
if got != p:
|
||||
ok = False
|
||||
print(f" соединение {i}: получено {len(got)} из {len(p)} байт")
|
||||
check(ok, "эхо на трёх одновременных соединениях (включая 70 КБ)")
|
||||
|
||||
# эхо в двух шагах по одному соединению
|
||||
s = socks[0]
|
||||
s.sendall(b"part1")
|
||||
first = s.recv(5)
|
||||
s.sendall(b"part2")
|
||||
second = b""
|
||||
while len(second) < 5:
|
||||
chunk = s.recv(5 - len(second))
|
||||
if not chunk:
|
||||
break
|
||||
second += chunk
|
||||
check(first == b"part1" and second == b"part2", "эхо по частям (несколько циклов read/write)")
|
||||
|
||||
# клиент закрывается — сервер не падает
|
||||
for s in socks:
|
||||
s.close()
|
||||
time.sleep(0.3)
|
||||
check(proc.poll() is None, "сервер жив после закрытия всех клиентов")
|
||||
|
||||
# новое соединение после закрытия старых
|
||||
try:
|
||||
s = socket.create_connection(("127.0.0.1", port), timeout=2)
|
||||
s.settimeout(3)
|
||||
s.sendall(b"again")
|
||||
check(s.recv(5) == b"again", "новое соединение после отключения старых")
|
||||
s.close()
|
||||
except OSError as e:
|
||||
check(False, f"новое соединение после отключения старых ({e})")
|
||||
|
||||
proc.send_signal(signal.SIGTERM)
|
||||
try:
|
||||
rc = proc.wait(timeout=5)
|
||||
check(rc == 0, f"аккуратное завершение по SIGTERM (exit={rc})")
|
||||
except subprocess.TimeoutExpired:
|
||||
proc.kill()
|
||||
check(False, "аккуратное завершение по SIGTERM (не завершился за 5 с)")
|
||||
|
||||
if failures:
|
||||
err = proc.stderr.read().decode(errors="replace")[:2000]
|
||||
if err:
|
||||
print("--- stderr сервера ---")
|
||||
print(err)
|
||||
print(f"\n{'FAILURES: %d' % failures if failures else 'ALL PASS'}")
|
||||
sys.exit(1 if failures else 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,12 @@
|
||||
10.0.0.1 - - [23/Sep/2026:10:00:01 +0700] "GET /index.html HTTP/1.1" 200 512
|
||||
10.0.0.2 - - [23/Sep/2026:10:00:02 +0700] "GET /a HTTP/1.1" 404 128
|
||||
10.0.0.1 - - [23/Sep/2026:10:00:03 +0700] "POST /api HTTP/1.1" 500 42
|
||||
10.0.0.3 - - [23/Sep/2026:10:00:04 +0700] "GET /b HTTP/1.1" 200 1024
|
||||
10.0.0.1 - - [23/Sep/2026:10:00:05 +0700] "GET /c HTTP/1.1" 503 0
|
||||
10.0.0.2 - - [23/Sep/2026:10:00:06 +0700] "GET /d HTTP/1.1" 200 33
|
||||
10.0.0.4 - - [23/Sep/2026:10:00:07 +0700] "GET /e HTTP/1.1" 200 10
|
||||
10.0.0.3 - - [23/Sep/2026:10:00:08 +0700] "GET /f HTTP/1.1" 200 11
|
||||
10.0.0.2 - - [23/Sep/2026:10:00:09 +0700] "GET /g HTTP/1.1" 200 12
|
||||
10.0.0.5 - - [23/Sep/2026:10:00:10 +0700] "GET /h HTTP/1.1" 500 13
|
||||
10.0.0.1 - - [23/Sep/2026:10:00:11 +0700] "GET /i HTTP/1.1" 200 14
|
||||
10.0.0.6 - - [23/Sep/2026:10:00:12 +0700] "GET /j HTTP/1.1" 200 15
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
# Задача 08 — разбор лога доступа. Реализуй.
|
||||
set -euo pipefail
|
||||
|
||||
log="${1:-}"
|
||||
if [[ -z "$log" ]]; then
|
||||
echo "usage: $0 <access.log>" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
echo "TOTAL 0"
|
||||
# TODO
|
||||
exit 0
|
||||
@@ -0,0 +1,25 @@
|
||||
# Задача 08 — bash: разбор лога доступа
|
||||
|
||||
Написать `solution.sh`, который принимает путь к файлу лога (формат nginx/apache access.log)
|
||||
и печатает ровно четыре строки:
|
||||
|
||||
```
|
||||
TOTAL <число строк в файле>
|
||||
TOP <ip> <число запросов>
|
||||
TOP <ip> <число запросов>
|
||||
TOP <ip> <число запросов>
|
||||
5XX <число ответов со статусом 500-599>
|
||||
```
|
||||
|
||||
Правила:
|
||||
- IP — первое поле строки;
|
||||
- TOP — три самых частых IP по убыванию числа запросов; при равенстве — по возрастанию
|
||||
строки IP (лексикографически);
|
||||
- если уникальных IP меньше трёх — печатать столько строк TOP, сколько есть;
|
||||
- файл может быть большим (миллион строк): построчный bash-цикл по строкам не пройдёт
|
||||
по времени — нужен awk/sort или один-два прохода;
|
||||
- пустой файл: `TOTAL 0`, `5XX 0`, строк TOP нет.
|
||||
|
||||
Запуск: `bash solution.sh access.log`
|
||||
Проверка: `python3 grade.py 08` (тест гоняет скрипт на фикстуре и на большом логе).
|
||||
Критерий: точное совпадение вывода, код возврата 0.
|
||||
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Тест задачи 08: сравнение вывода solution.sh с эталоном на фикстуре и большом логе."""
|
||||
import os
|
||||
import random
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
SCRIPT = os.path.join(HERE, "solution.sh")
|
||||
failures = 0
|
||||
|
||||
|
||||
def check(cond, name, extra=""):
|
||||
global failures
|
||||
if cond:
|
||||
print(f"ok {name}")
|
||||
else:
|
||||
print(f"FAIL {name} {extra}")
|
||||
failures += 1
|
||||
|
||||
|
||||
def run(path):
|
||||
r = subprocess.run(["bash", SCRIPT, path], capture_output=True, text=True, timeout=120)
|
||||
return r.returncode, r.stdout, r.stderr
|
||||
|
||||
|
||||
def expected(lines):
|
||||
from collections import Counter
|
||||
ips = [l.split()[0] for l in lines if l.strip()]
|
||||
total = len(lines)
|
||||
cnt = Counter(ips)
|
||||
top = sorted(cnt.items(), key=lambda kv: (-kv[1], kv[0]))[:3]
|
||||
five = 0
|
||||
for l in lines:
|
||||
parts = l.split('"')
|
||||
if len(parts) >= 3:
|
||||
codes = parts[2].split()
|
||||
if codes and codes[0].isdigit() and 500 <= int(codes[0]) <= 599:
|
||||
five += 1
|
||||
out = [f"TOTAL {total}"]
|
||||
for ip, c in top:
|
||||
out.append(f"TOP {ip} {c}")
|
||||
out.append(f"5XX {five}")
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
def compare(path, lines, label):
|
||||
rc, out, err = run(path)
|
||||
exp = expected(lines)
|
||||
got = out.strip("\n")
|
||||
if got != exp:
|
||||
print(f"--- ожидалось ({label}) ---\n{exp}\n--- получено ---\n{got}")
|
||||
check(rc == 0, f"{label}: код возврата 0 (получен {rc})", err[:300])
|
||||
check(got == exp, f"{label}: вывод совпадает с эталоном")
|
||||
|
||||
|
||||
def main():
|
||||
fixture = os.path.join(HERE, "access.log")
|
||||
with open(fixture, encoding="utf-8") as f:
|
||||
lines = f.read().splitlines()
|
||||
compare(fixture, lines, "фикстура")
|
||||
|
||||
# пустой файл
|
||||
empty = os.path.join(HERE, "empty.log")
|
||||
open(empty, "w").close()
|
||||
compare(empty, [], "пустой файл")
|
||||
|
||||
# большой лог: 200k строк, часть 5xx, один лидер, ничья между двумя IP
|
||||
rng = random.Random(7)
|
||||
big = os.path.join(HERE, "big.log")
|
||||
ips = ["10.0.0.1"] * 50000 + ["10.0.0.2"] * 30000 + ["10.0.0.3"] * 30000
|
||||
ips += [f"192.168.1.{i}" for i in range(1, 61)] * 10
|
||||
ips += [f"172.16.0.{i}" for i in range(1, 21)] * 5
|
||||
rng.shuffle(ips)
|
||||
big_lines = []
|
||||
for ip in ips:
|
||||
code = rng.choice([200, 200, 200, 301, 404, 500, 502, 503])
|
||||
big_lines.append(f'{ip} - - [23/Sep/2026:11:00:00 +0700] "GET /p HTTP/1.1" {code} 100')
|
||||
with open(big, "w", encoding="utf-8") as f:
|
||||
f.write("\n".join(big_lines) + "\n")
|
||||
compare(big, big_lines, "большой лог 110k строк")
|
||||
|
||||
print(f"\n{'FAILURES: %d' % failures if failures else 'ALL PASS'}")
|
||||
sys.exit(1 if failures else 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,20 @@
|
||||
Задача 09 — разбор падения.
|
||||
|
||||
1. Дефекты (сколько и какие):
|
||||
- ...
|
||||
- ...
|
||||
|
||||
2. Сборка и первые шаги:
|
||||
$ g++ -g -O0 -fsanitize=address,undefined crash.c -o crash
|
||||
$ ./crash world
|
||||
<что вывел ASAN — своими словами>
|
||||
|
||||
3. Команды gdb по шагам и что они показали:
|
||||
(gdb) ...
|
||||
...
|
||||
|
||||
4. Почему программа падала именно так (механизм):
|
||||
...
|
||||
|
||||
5. Что изменено в crash.c (минимальные правки):
|
||||
...
|
||||
@@ -0,0 +1,21 @@
|
||||
/* Задача 09: программа с дефектами. Починить минимально. */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char *word = argc > 1 ? argv[1] : "hello";
|
||||
size_t n = strlen(word);
|
||||
|
||||
char *buf = malloc(n);
|
||||
if (!buf) {
|
||||
return 2;
|
||||
}
|
||||
strcpy(buf, word);
|
||||
|
||||
printf("len=%zu\n", strlen(buf));
|
||||
|
||||
free(buf);
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
# Задача 09 — отладка: найти и исправить дефекты (gdb)
|
||||
|
||||
В `crash.c` лежит программа, которая падает на части входов и портит память.
|
||||
Задача — не переписать её с нуля, а **найти дефекты отладчиком и починить минимально**.
|
||||
|
||||
Ожидаемое поведение после починки:
|
||||
- `./crash` (без аргумента) печатает `len=5` и завершается с кодом 0;
|
||||
- `./crash <слово>` печатает `len=<длина слова>` и завершается с кодом 0;
|
||||
- `./crash ""` печатает `len=0` и завершается с кодом 0;
|
||||
- сборка с `-fsanitize=address,undefined` не даёт ни одного сообщения об ошибке.
|
||||
|
||||
Что сделать:
|
||||
1. Собрать с отладочной информацией и санитайзерами:
|
||||
`gcc -g -O0 -fsanitize=address,undefined crash.c -o crash`
|
||||
2. Разобраться, что именно портит память, а что приводит к падению при выходе.
|
||||
Полезно: `gdb ./crash`, `run`, `bt`, `frame`, `info locals`, `watch`.
|
||||
3. Исправить `crash.c` (минимальные правки, стиль сохранить).
|
||||
4. Заполнить `answer.txt`: сколько дефектов нашёл, какие именно, какими командами
|
||||
отладчика это подтвердил (по шагам), почему падало именно так.
|
||||
|
||||
Проверка: `python3 grade.py 09` — компилирует `crash.c` (компилятор C, gcc) с ASAN/UBSAN
|
||||
и прогоняет 5 входов.
|
||||
`answer.txt` проверяется ревью (это часть оценки: важен ход разбора, а не только результат).
|
||||
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Тест задачи 09: сборка crash.c с ASAN/UBSAN и прогон входов."""
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
SRC = os.path.join(HERE, "crash.c")
|
||||
BIN = os.path.join(HERE, "crash_checked")
|
||||
failures = 0
|
||||
|
||||
|
||||
def check(cond, name, extra=""):
|
||||
global failures
|
||||
if cond:
|
||||
print(f"ok {name}")
|
||||
else:
|
||||
print(f"FAIL {name} {extra}")
|
||||
failures += 1
|
||||
|
||||
|
||||
def main():
|
||||
r = subprocess.run(["gcc", "-g", "-O1", "-Wall", "-Wextra",
|
||||
"-fsanitize=address,undefined", "-fno-omit-frame-pointer",
|
||||
SRC, "-o", BIN], capture_output=True, text=True, timeout=180)
|
||||
check(r.returncode == 0, "сборка с ASAN/UBSAN прошла", r.stderr[:800])
|
||||
if r.returncode != 0:
|
||||
print("\nFAILURES: 1")
|
||||
sys.exit(1)
|
||||
|
||||
cases = [
|
||||
([], "len=5"),
|
||||
(["world"], "len=5"),
|
||||
(["a"], "len=1"),
|
||||
([""], "len=0"),
|
||||
(["x" * 300], "len=300"),
|
||||
]
|
||||
for args, expect in cases:
|
||||
try:
|
||||
run = subprocess.run([BIN] + args, capture_output=True, text=True, timeout=60,
|
||||
env={**os.environ, "ASAN_OPTIONS": "detect_leaks=1"})
|
||||
except subprocess.TimeoutExpired:
|
||||
check(False, f"вход {args or '<нет>'}: завершился за 60 c")
|
||||
continue
|
||||
out = run.stdout.strip()
|
||||
err = run.stderr
|
||||
label = f"вход {args[0][:12]!r}" if args else "вход без аргумента"
|
||||
check(run.returncode == 0, f"{label}: код возврата 0 (получен {run.returncode})")
|
||||
check(out == expect, f"{label}: вывод {expect!r} (получено {out!r})")
|
||||
check("AddressSanitizer" not in err and "runtime error" not in err,
|
||||
f"{label}: санитайзеры молчат", err[:400].replace("\n", " | "))
|
||||
|
||||
# answer.txt заполнен (проверка на «не оставил заготовку»)
|
||||
apath = os.path.join(HERE, "answer.txt")
|
||||
if os.path.exists(apath):
|
||||
txt = open(apath, encoding="utf-8").read()
|
||||
check("..." not in txt and len(txt) > 400, "answer.txt заполнен разбором (ревью отдельно)")
|
||||
else:
|
||||
check(False, "answer.txt: файл с разбором не найден")
|
||||
|
||||
print(f"\n{'FAILURES: %d' % failures if failures else 'ALL PASS'}")
|
||||
sys.exit(1 if failures else 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user