План v2 по диагностике (квиз 14/32) + 4 новые задачи (hash, heap, dijkstra, subnet) + тренажёр 60 карточек
This commit is contained in:
+29
-2
@@ -21,6 +21,19 @@
|
||||
| 08 | `08_bash` | разбор лога доступа: TOTAL/TOP-3/5XX, 110k строк |
|
||||
| 09 | `09_gdb` | отладка ASAN-падения + письменный разбор в `answer.txt` |
|
||||
|
||||
Добор под слабые домены (добавлено 23.09 после диагностики — квиз 14/32):
|
||||
|
||||
| # | Задача | Что проверяется |
|
||||
|---|--------|-----------------|
|
||||
| 10 | `10_hash` | хеш-таблица с открытой адресацией: пробирование, перехеширование, erase |
|
||||
| 11 | `11_heap` | куча: `heapify` снизу вверх O(n), `kth_largest`, `top_k` на 3M |
|
||||
| 12 | `12_dijkstra` | Дейкстра на `priority_queue`: неотрицательные веса, 100k вершин |
|
||||
| 13 | `13_subnet` | арифметика IPv4: сеть/broadcast/узлы, `/31`, `/32`, подбор префикса |
|
||||
|
||||
- `facts.json` + `facts_drill.py` — тренажёр «числа и факты»: 60 карточек
|
||||
(algo 20, linux 14, net 14, c_cpp 12) под конкретные промахи квиза: сложности, байты
|
||||
заголовков, `/26 = 62`, TTL, порядок SYN/SYN-ACK/ACK, паддинг структур, правило трёх/пяти.
|
||||
Маркеры итога: `>=80%` ок, `50–79%` добор, `<50%` дыра.
|
||||
- `grade.py` — прогон задач с автооценкой (`PASS/FAIL`, число проверок, время).
|
||||
- `solutions/` — **эталонные решения, в руки не отдавать**: они лежат здесь, чтобы
|
||||
автопроверка не «протухла» (эталон → все PASS, заглушка → FAIL).
|
||||
@@ -35,9 +48,13 @@ python3 grade.py # все задачи (с ASAN/UBSAN/TSAN)
|
||||
python3 grade.py 01 04 # только выбранные
|
||||
python3 grade.py --fast # без санитайзеров, быстрее
|
||||
python3 grade.py --submit # результаты + свои файлы в submissions/
|
||||
python3 facts_drill.py # 20 карточек, интерактивно
|
||||
python3 facts_drill.py --all # все 60
|
||||
python3 facts_drill.py --domain algo # только алгоритмы
|
||||
python3 facts_drill.py --answers "20|62|ttl|..." # проверить строку ответов разом
|
||||
```
|
||||
|
||||
Порядок: сначала квиз, потом задачи с 01 по 09. Зависание = FAIL (есть таймауты).
|
||||
Порядок: сначала квиз, потом задачи с 01 по 09 (диагностика), затем 10–13 как добор. Зависание = FAIL (есть таймауты).
|
||||
Правила честности: без встроенных `popcount`/`reverse`-хелперов, без копирования чужих
|
||||
решений, `answer.txt` в задаче 09 — обязательная часть (оценивается ход разбора).
|
||||
|
||||
@@ -45,10 +62,20 @@ python3 grade.py --submit # результаты + свои файлы в subm
|
||||
|
||||
- `results_quiz.json`: балл по каждому домену + разбор промахов с пояснениями.
|
||||
- `results_tasks.json`: по задаче — PASS/FAIL, число проверок, время, полный лог сборки.
|
||||
- Итог и решение по плану (с каких модулей стартовать) собираются по этим двум файлам.
|
||||
- `results_facts.json`: тренажёр карточек — балл по доменам и список промахов.
|
||||
- Итог и решение по плану (с каких модулей стартовать) собираются по этим файлам.
|
||||
- Рабочий план недели: `WEEK7.md` в корне репозитория; трекер дня — `TRACKER.md`.
|
||||
|
||||
## Публикация и доступ
|
||||
|
||||
Репозиторий: https://git.kodlo.art/Kodlo/eduplan-cpp-eltex — клонируется без пароля,
|
||||
обновляется по ходу недели. Результаты можно присылать веткой `answers/<день>`, файлами
|
||||
в чат или через `grade.py --submit`.
|
||||
|
||||
Репозиторий: https://git.kodlo.art/Kodlo/eduplan-cpp-eltex (клонируется без пароля,
|
||||
обновляется по ходу недели). Результаты можно присылать веткой `answers/<день>`,
|
||||
файлами в чат или через `grade.py --submit`.
|
||||
|
||||
- Пакет в каталоге: `/home/pi/eduplan/diag` (мир читает; каталог доступен на исполнение).
|
||||
- Инбокс для ответов: `/home/pi/eduplan/diag/submissions/` (0777).
|
||||
- Архив для скачивания: `/home/pi/eduplan/diag/dist/eduplan_diag.tar.gz`.
|
||||
|
||||
+591
@@ -0,0 +1,591 @@
|
||||
{
|
||||
"version": 1,
|
||||
"note": "Карточки под промахи квиза 23.09: точные числа, сложности и условия.",
|
||||
"facts": [
|
||||
{
|
||||
"domain": "algo",
|
||||
"q": "Средняя сложность поиска в хеш-таблице при разумной загрузке?",
|
||||
"a": "амортизированное O(1)",
|
||||
"accept": [
|
||||
"o(1)",
|
||||
"амортизированное o(1)",
|
||||
"o(1) амортизированное",
|
||||
"амортизированно o(1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "algo",
|
||||
"q": "Худшая сложность поиска в хеш-таблице (все ключи в одну корзину)?",
|
||||
"a": "O(n)",
|
||||
"accept": [
|
||||
"o(n)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "algo",
|
||||
"q": "Сколько сравнений в худшем случае у бинарного поиска в массиве из 1 000 000 элементов?",
|
||||
"a": "20",
|
||||
"accept": [
|
||||
"20",
|
||||
"log2 1e6 = 20",
|
||||
"около 20"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "algo",
|
||||
"q": "При каком условии применим алгоритм Дейкстры?",
|
||||
"a": "веса рёбер неотрицательны",
|
||||
"accept": [
|
||||
"неотрицательные веса",
|
||||
"все веса >= 0",
|
||||
"веса неотрицательны",
|
||||
"нет отрицательных рёбер"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "algo",
|
||||
"q": "Чем заменить Дейкстру при отрицательных рёбрах?",
|
||||
"a": "Беллман–Форд",
|
||||
"accept": [
|
||||
"беллман-форд",
|
||||
"беллман форд",
|
||||
"bellman-ford"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "algo",
|
||||
"q": "Сложность построения кучи (heapify) из произвольного массива?",
|
||||
"a": "O(n)",
|
||||
"accept": [
|
||||
"o(n)",
|
||||
"о(n)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "algo",
|
||||
"q": "Сложность вставки одного элемента в кучу?",
|
||||
"a": "O(log n)",
|
||||
"accept": [
|
||||
"o(log n)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "algo",
|
||||
"q": "Какая сортировка устойчивая: merge, quick, heap или selection?",
|
||||
"a": "merge sort",
|
||||
"accept": [
|
||||
"merge",
|
||||
"merge sort",
|
||||
"слиянием",
|
||||
"сортировка слиянием"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "algo",
|
||||
"q": "Худшая сложность quicksort?",
|
||||
"a": "O(n²)",
|
||||
"accept": [
|
||||
"o(n^2)",
|
||||
"o(n²)",
|
||||
"o(n2)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "algo",
|
||||
"q": "Средняя сложность quicksort и mergesort?",
|
||||
"a": "O(n log n)",
|
||||
"accept": [
|
||||
"o(n log n)",
|
||||
"o(n*logn)",
|
||||
"o(nlogn)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "algo",
|
||||
"q": "Для какого графа возможна топологическая сортировка?",
|
||||
"a": "для ациклического (DAG)",
|
||||
"accept": [
|
||||
"dag",
|
||||
"ациклический",
|
||||
"без циклов",
|
||||
"dag (ациклический)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "algo",
|
||||
"q": "Как найти цикл в связном списке за O(1) дополнительной памяти?",
|
||||
"a": "алгоритм Флойда: черепаха и заяц",
|
||||
"accept": [
|
||||
"флойд",
|
||||
"черепаха и заяц",
|
||||
"алгоритм флойда"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "algo",
|
||||
"q": "BFS или DFS даёт кратчайший путь в невзвешенном графе?",
|
||||
"a": "BFS",
|
||||
"accept": [
|
||||
"bfs",
|
||||
"обход в ширину"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "algo",
|
||||
"q": "Сложность Дейкстры на priority_queue (двоичная куча)?",
|
||||
"a": "O((V+E) log V)",
|
||||
"accept": [
|
||||
"o((v+e) log v)",
|
||||
"o((v+e)logv)",
|
||||
"o(e log v)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "algo",
|
||||
"q": "За какую сложность ищется НВП (LIS) в оптимальном решении?",
|
||||
"a": "O(n log n)",
|
||||
"accept": [
|
||||
"o(n log n)",
|
||||
"o(nlogn)",
|
||||
"o(n*log n)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "algo",
|
||||
"q": "Сложность задачи о рюкзаке 0/1 через ДП по вместимости?",
|
||||
"a": "O(n * W)",
|
||||
"accept": [
|
||||
"o(n*w)",
|
||||
"o(nw)",
|
||||
"o(n*w)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "algo",
|
||||
"q": "Насколько быстро работают операции union-find с эвристиками?",
|
||||
"a": "почти O(1), O(α(n))",
|
||||
"accept": [
|
||||
"почти o(1)",
|
||||
"o(alpha(n))",
|
||||
"o(α(n))",
|
||||
"обратная функция аккермана"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "algo",
|
||||
"q": "Какова сложность по памяти рекурсивного DFS в графе в худшем случае?",
|
||||
"a": "O(V) — глубина стека рекурсии",
|
||||
"accept": [
|
||||
"o(v)",
|
||||
"o(h)",
|
||||
"o(v) в худшем случае"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "algo",
|
||||
"q": "Какова нижняя оценка сложности сортировки сравнениями?",
|
||||
"a": "O(n log n)",
|
||||
"accept": [
|
||||
"o(n log n)",
|
||||
"o(nlogn)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "algo",
|
||||
"q": "Структура для поиска слов по префиксу за O(len)?",
|
||||
"a": "trie (префиксное дерево)",
|
||||
"accept": [
|
||||
"trie",
|
||||
"префиксное дерево",
|
||||
"бор"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "linux",
|
||||
"q": "Что делает fork()?",
|
||||
"a": "создаёт новый процесс (свой PID, COW-копия памяти)",
|
||||
"accept": [
|
||||
"создаёт новый процесс",
|
||||
"создает новый процесс",
|
||||
"новый процесс"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "linux",
|
||||
"q": "Что происходит со страницами памяти при fork()?",
|
||||
"a": "copy-on-write: копируются при первой записи",
|
||||
"accept": [
|
||||
"cow",
|
||||
"copy-on-write",
|
||||
"копирование при записи"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "linux",
|
||||
"q": "Какой механизм держит десятки тысяч соединений в одном потоке?",
|
||||
"a": "epoll",
|
||||
"accept": [
|
||||
"epoll",
|
||||
"эполл"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "linux",
|
||||
"q": "Сложность select против epoll на большом числе дескрипторов?",
|
||||
"a": "select — O(n) по всем, epoll — O(1) на готовое событие",
|
||||
"accept": [
|
||||
"select o(n), epoll o(1)",
|
||||
"epoll o(1)",
|
||||
"o(1) на готовое"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "linux",
|
||||
"q": "Что даёт mmap() на файл?",
|
||||
"a": "отображает файл в память; страницы подгружаются по обращению",
|
||||
"accept": [
|
||||
"отображает файл в память",
|
||||
"mmap файла в память",
|
||||
"отображение файла в память"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "linux",
|
||||
"q": "Зачем родителю waitpid()?",
|
||||
"a": "чтобы забрать код возврата и не оставлять зомби",
|
||||
"accept": [
|
||||
"забрать код возврата",
|
||||
"убрать зомби",
|
||||
"чтобы не остались зомби"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "linux",
|
||||
"q": "Что такое процесс-зомби?",
|
||||
"a": "завершившийся процесс, чья запись ждёт wait/waitpid",
|
||||
"accept": [
|
||||
"завершившийся процесс без wait",
|
||||
"ждёт wait",
|
||||
"запись ждёт wait"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "linux",
|
||||
"q": "Код возврата 137 — что это?",
|
||||
"a": "SIGKILL (128+9) — убит",
|
||||
"accept": [
|
||||
"sigkill",
|
||||
"128+9",
|
||||
"убит sigkill"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "linux",
|
||||
"q": "Код возврата 139 — что это?",
|
||||
"a": "SIGSEGV (128+11) — падение по памяти",
|
||||
"accept": [
|
||||
"sigsegv",
|
||||
"128+11",
|
||||
"сегфолт"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "linux",
|
||||
"q": "Что означает errno EAGAIN на неблокирующем сокете?",
|
||||
"a": "данных сейчас нет, повторить позже",
|
||||
"accept": [
|
||||
"повторить позже",
|
||||
"нет данных, попробовать снова",
|
||||
"данных нет"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "linux",
|
||||
"q": "Что такое TID (Thread ID) в /proc?",
|
||||
"a": "номер задачи в /proc/<pid>/task/",
|
||||
"accept": [
|
||||
"номер задачи",
|
||||
"task",
|
||||
"в /proc/<pid>/task"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "linux",
|
||||
"q": "Что делает ulimit -n?",
|
||||
"a": "показывает/меняет лимит открытых файловых дескрипторов",
|
||||
"accept": [
|
||||
"лимит дескрипторов",
|
||||
"лимит открытых файлов",
|
||||
"число открытых файлов"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "linux",
|
||||
"q": "Сигнал с номером 15?",
|
||||
"a": "SIGTERM — запрос на завершение",
|
||||
"accept": [
|
||||
"sigterm",
|
||||
"sigterm (мягкое завершение)",
|
||||
"запрос на завершение"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "linux",
|
||||
"q": "Сколько байт в заголовке TCP без опций?",
|
||||
"a": "20",
|
||||
"accept": [
|
||||
"20",
|
||||
"20 байт"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "net",
|
||||
"q": "Сколько байт в Ethernet-заголовке без VLAN?",
|
||||
"a": "14",
|
||||
"accept": [
|
||||
"14",
|
||||
"14 байт"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "net",
|
||||
"q": "Сколько байт добавляет VLAN-тег 802.1Q?",
|
||||
"a": "4",
|
||||
"accept": [
|
||||
"4",
|
||||
"4 байта"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "net",
|
||||
"q": "Сколько адресов узлов в подсети /26?",
|
||||
"a": "62",
|
||||
"accept": [
|
||||
"62",
|
||||
"62 узла"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "net",
|
||||
"q": "Сколько адресов узлов в подсети /24?",
|
||||
"a": "254",
|
||||
"accept": [
|
||||
"254"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "net",
|
||||
"q": "Какое поле IPv4-заголовка уменьшается на 1 на каждом маршрутизаторе?",
|
||||
"a": "TTL",
|
||||
"accept": [
|
||||
"ttl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "net",
|
||||
"q": "Правильная последовательность установки TCP-соединения?",
|
||||
"a": "SYN -> SYN-ACK -> ACK",
|
||||
"accept": [
|
||||
"syn, syn-ack, ack",
|
||||
"syn syn-ack ack",
|
||||
"syn -> syn-ack -> ack"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "net",
|
||||
"q": "Что делает ARP?",
|
||||
"a": "находит MAC по IP в локальном сегменте",
|
||||
"accept": [
|
||||
"mac по ip",
|
||||
"ip -> mac",
|
||||
"находит mac по ip"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "net",
|
||||
"q": "Размер MTU Ethernet по умолчанию?",
|
||||
"a": "1500 байт",
|
||||
"accept": [
|
||||
"1500"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "net",
|
||||
"q": "На каком уровне OSI работает обычный коммутатор?",
|
||||
"a": "L2 (канальный)",
|
||||
"accept": [
|
||||
"l2",
|
||||
"канальный",
|
||||
"2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "net",
|
||||
"q": "На каком уровне OSI работает маршрутизатор?",
|
||||
"a": "L3 (сетевой)",
|
||||
"accept": [
|
||||
"l3",
|
||||
"сетевой",
|
||||
"3"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "net",
|
||||
"q": "Сколько байт в минимальном IPv4-заголовке?",
|
||||
"a": "20",
|
||||
"accept": [
|
||||
"20"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "net",
|
||||
"q": "По какому протоколу и порту работает DNS?",
|
||||
"a": "UDP/53 (при больших ответах TCP/53)",
|
||||
"accept": [
|
||||
"udp 53",
|
||||
"53",
|
||||
"udp/53"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "net",
|
||||
"q": "Что означает маска /30 в адресах?",
|
||||
"a": "4 адреса: сеть, 2 узла, broadcast",
|
||||
"accept": [
|
||||
"4 адреса, 2 узла",
|
||||
"2 узла",
|
||||
"сеть+2 узла+broadcast"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "net",
|
||||
"q": "Что делает ICMP Time Exceeded?",
|
||||
"a": "сообщает, что TTL истёк (используется в traceroute)",
|
||||
"accept": [
|
||||
"ttl истёк",
|
||||
"истёк ttl",
|
||||
"сообщает об истечении ttl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "c_cpp",
|
||||
"q": "Чему равен sizeof(struct S { char a; int b; char c; }) на x86-64 без pack?",
|
||||
"a": "12 (паддинг до выравнивания)",
|
||||
"accept": [
|
||||
"12",
|
||||
"12 байт"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "c_cpp",
|
||||
"q": "Что такое UB?",
|
||||
"a": "undefined behavior: стандарт не накладывает требований",
|
||||
"accept": [
|
||||
"undefined behavior",
|
||||
"неопределённое поведение",
|
||||
"неопределенное поведение"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "c_cpp",
|
||||
"q": "Что делает std::move сам по себе?",
|
||||
"a": "static_cast к rvalue-ссылке; перемещение делает конструктор/присваивание",
|
||||
"accept": [
|
||||
"static_cast к rvalue",
|
||||
"ничего, это каст",
|
||||
"это каст к rvalue"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "c_cpp",
|
||||
"q": "Чем new отличается от malloc?",
|
||||
"a": "new вызывает конструктор и возвращает типизированный указатель",
|
||||
"accept": [
|
||||
"вызывает конструктор",
|
||||
"конструктор",
|
||||
"конструктор + типизированный указатель"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "c_cpp",
|
||||
"q": "Класс владеет ресурсом и имеет деструктор, копирование не объявлено. Что будет?",
|
||||
"a": "сгенерируется поверхностное копирование — двойное освобождение",
|
||||
"accept": [
|
||||
"двойное освобождение",
|
||||
"двойное free",
|
||||
"поверхностное копирование"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "c_cpp",
|
||||
"q": "Зачем нужен виртуальный деструктор у базового класса?",
|
||||
"a": "иначе delete через Base* — UB, деструктор наследника не вызовется",
|
||||
"accept": [
|
||||
"деструктор наследника не вызовется",
|
||||
"иначе ub",
|
||||
"чтобы вызвался деструктор наследника"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "c_cpp",
|
||||
"q": "Что делает const после сигнатуры метода?",
|
||||
"a": "делает this const, запрещает менять поля",
|
||||
"accept": [
|
||||
"this const",
|
||||
"запрещает менять поля",
|
||||
"метод не меняет объект"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "c_cpp",
|
||||
"q": "Определён ли стандартом порядок вычисления аргументов f(a(), b())?",
|
||||
"a": "нет, порядок не задан",
|
||||
"accept": [
|
||||
"нет",
|
||||
"не определён",
|
||||
"не задан",
|
||||
"неопределён"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "c_cpp",
|
||||
"q": "Что проверяет ASAN?",
|
||||
"a": "выходы за границы, use-after-free, утечки",
|
||||
"accept": [
|
||||
"выходы за границы",
|
||||
"use-after-free",
|
||||
"память: границы и use-after-free"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "c_cpp",
|
||||
"q": "Что проверяет UBSAN?",
|
||||
"a": "неопределённое поведение: overflow, сдвиги, выравнивание",
|
||||
"accept": [
|
||||
"undefined behavior",
|
||||
"неопределённое поведение",
|
||||
"overflow"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "c_cpp",
|
||||
"q": "Правило трёх/пяти — в чём суть?",
|
||||
"a": "владеющий ресурсом класс задаёт деструктор, копирование и перемещение",
|
||||
"accept": [
|
||||
"деструктор + копирование + перемещение",
|
||||
"деструктор, копирование, перемещение",
|
||||
"нужно определить деструктор и копирование/перемещение"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "c_cpp",
|
||||
"q": "Чем unique_ptr отличается от shared_ptr?",
|
||||
"a": "unique_ptr — единственный владелец без счётчика; shared_ptr считает ссылки",
|
||||
"accept": [
|
||||
"единственный владелец",
|
||||
"нет счётчика ссылок",
|
||||
"счётчик ссылок у shared_ptr"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Тренажёр «числа и факты» — добор по промахам квиза (22.09: algo 1/8, linux 3/8, net 3/8).
|
||||
|
||||
Использование (из каталога diag):
|
||||
python3 facts_drill.py # интерактивно, 20 случайных карточек
|
||||
python3 facts_drill.py --count 60 --all # все 60
|
||||
python3 facts_drill.py --domain algo # только алгоритмы
|
||||
python3 facts_drill.py --list # вывести все вопросы с ответами (читать)
|
||||
python3 facts_drill.py --answers "20|62|ttl|..." # проверить строку ответов
|
||||
python3 facts_drill.py --answers "..." --count 60 --seed 42 # тот же порядок, что в прогоне
|
||||
python3 facts_drill.py --selftest # проверить сам набор карточек
|
||||
|
||||
Ответы сравниваются нестрого: регистр, «ё», тире и лишние пробелы не важны.
|
||||
Результат пишется в results_facts.json.
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
FACTS_PATH = os.path.join(HERE, "facts.json")
|
||||
|
||||
|
||||
def norm(s: str) -> str:
|
||||
s = (s or "").lower().replace("ё", "е")
|
||||
s = s.replace("–", "-").replace("—", "-").replace("−", "-")
|
||||
s = re.sub(r"[^0-9a-zа-я\-\s]", " ", s)
|
||||
return re.sub(r"\s+", " ", s).strip()
|
||||
|
||||
|
||||
def load():
|
||||
with open(FACTS_PATH, encoding="utf-8") as f:
|
||||
return json.load(f)["facts"]
|
||||
|
||||
|
||||
def check(fact, answer: str) -> bool:
|
||||
a = norm(answer)
|
||||
if not a:
|
||||
return False
|
||||
return any(a == norm(x) for x in fact.get("accept", []))
|
||||
|
||||
|
||||
def selftest(facts) -> int:
|
||||
problems = []
|
||||
for i, f in enumerate(facts, 1):
|
||||
for key in ("domain", "q", "a", "accept"):
|
||||
if not f.get(key):
|
||||
problems.append(f"карточка {i}: пустое поле {key}")
|
||||
if f.get("accept") and not any(norm(x) for x in f["accept"]):
|
||||
problems.append(f"карточка {i}: варианты ответа нормализуются в пустоту")
|
||||
if not check(f, f["accept"][0]):
|
||||
problems.append(f"карточка {i}: канонический ответ не проходит проверку")
|
||||
doms = {}
|
||||
for f in facts:
|
||||
doms[f["domain"]] = doms.get(f["domain"], 0) + 1
|
||||
print(f"карточек: {len(facts)}; по доменам: {doms}")
|
||||
if problems:
|
||||
print("ПРОБЛЕМЫ:")
|
||||
for p in problems:
|
||||
print(" -", p)
|
||||
return 1
|
||||
print("SELFTEST OK")
|
||||
return 0
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--answers", default=None, help="ответы через | (тот же порядок, что в интерактиве)")
|
||||
ap.add_argument("--count", type=int, default=20)
|
||||
ap.add_argument("--all", action="store_true", help="взять все карточки")
|
||||
ap.add_argument("--domain", default=None, choices=["algo", "linux", "net", "c_cpp"])
|
||||
ap.add_argument("--seed", type=int, default=42)
|
||||
ap.add_argument("--list", action="store_true")
|
||||
ap.add_argument("--selftest", action="store_true")
|
||||
args = ap.parse_args()
|
||||
|
||||
facts = load()
|
||||
if args.selftest:
|
||||
sys.exit(selftest(facts))
|
||||
if args.domain:
|
||||
facts = [f for f in facts if f["domain"] == args.domain]
|
||||
if not facts:
|
||||
print("нет карточек под фильтр")
|
||||
sys.exit(1)
|
||||
|
||||
if args.list:
|
||||
for i, f in enumerate(facts, 1):
|
||||
print(f"{i:2d}. [{f['domain']}] {f['q']}")
|
||||
print(f" -> {f['a']}")
|
||||
print(f"\nвсего: {len(facts)} (полный ответ в facts.json: {FACTS_PATH})")
|
||||
sys.exit(0)
|
||||
|
||||
rng = random.Random(args.seed)
|
||||
pool = facts[:]
|
||||
rng.shuffle(pool)
|
||||
picked = pool if args.all else pool[:min(args.count, len(pool))]
|
||||
|
||||
results = []
|
||||
if args.answers is not None:
|
||||
given = [x.strip() for x in args.answers.split("|")]
|
||||
if len(given) < len(picked):
|
||||
print(f"ответов меньше, чем карточек: {len(given)} < {len(picked)} (проверяю сколько есть)")
|
||||
for i, f in enumerate(picked):
|
||||
ans = given[i] if i < len(given) else ""
|
||||
results.append({"n": i + 1, "domain": f["domain"], "q": f["q"], "answer": ans,
|
||||
"ok": check(f, ans), "correct": f["a"]})
|
||||
else:
|
||||
print(f"Карточек в прогоне: {len(picked)}. Пустой ввод = не знаю, 'q' = выход.\n")
|
||||
for i, f in enumerate(picked, 1):
|
||||
print(f"[{i}/{len(picked)}] ({f['domain']}) {f['q']}")
|
||||
try:
|
||||
ans = input(" > ").strip()
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
print()
|
||||
break
|
||||
if ans.lower() in ("q", "quit", "выход"):
|
||||
break
|
||||
ok = check(f, ans)
|
||||
print(" ok" if ok else " нет. верно: " + f["a"])
|
||||
results.append({"n": len(results) + 1, "domain": f["domain"], "q": f["q"],
|
||||
"answer": ans, "ok": ok, "correct": f["a"]})
|
||||
|
||||
ok = sum(1 for r in results if r["ok"])
|
||||
total = len(results)
|
||||
per = {}
|
||||
for r in results:
|
||||
d = per.setdefault(r["domain"], {"total": 0, "ok": 0})
|
||||
d["total"] += 1
|
||||
d["ok"] += 1 if r["ok"] else 0
|
||||
with open(os.path.join(HERE, "results_facts.json"), "w", encoding="utf-8") as fh:
|
||||
json.dump({"seed": args.seed, "correct": ok, "total": total,
|
||||
"per_domain": per, "details": results}, fh, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"\n=== ИТОГ КАРТОЧЕК: {ok}/{total} ===")
|
||||
for d, v in sorted(per.items()):
|
||||
share = 100 * v["ok"] / v["total"] if v["total"] else 0
|
||||
mark = "ок" if share >= 80 else ("добор" if share >= 50 else "дыра")
|
||||
print(f" {d:8s} {v['ok']}/{v['total']} ({share:.0f}%) — {mark}")
|
||||
print("Подробности: results_facts.json")
|
||||
sys.exit(0 if total and ok == total else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -37,6 +37,10 @@ SPEC = {
|
||||
"07_epoll": ("bin", "test_epoll.py", 300),
|
||||
"08_bash": ("bash", "test_bash.py", 300),
|
||||
"09_gdb": ("py", "test_gdb.py", 300),
|
||||
"10_hash": ("cpp", "test_hash.cpp", 300),
|
||||
"11_heap": ("cpp", "test_heap.cpp", 600),
|
||||
"12_dijkstra":("cpp", "test_dijkstra.cpp",600),
|
||||
"13_subnet": ("cpp", "test_subnet.cpp", 120),
|
||||
}
|
||||
|
||||
|
||||
@@ -151,6 +155,7 @@ def main():
|
||||
for fn in os.listdir(SUB):
|
||||
os.chmod(os.path.join(SUB, fn), 0o644)
|
||||
print(f"Ответы скопированы в {SUB}")
|
||||
print("Пакет: 01-09 диагностика, 10-13 добор по слабым доменам (см. WEEK7.md).")
|
||||
sys.exit(0 if done == total else 1)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// Задача 10 — хеш-таблица с открытой адресацией.
|
||||
// Реализуй методы ниже. Заглушка намеренно не проходит проверку.
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
class HashTable {
|
||||
public:
|
||||
explicit HashTable(std::size_t initial_capacity = 16) {
|
||||
(void)initial_capacity;
|
||||
}
|
||||
|
||||
void put(int key, int value) {
|
||||
(void)key;
|
||||
(void)value;
|
||||
}
|
||||
|
||||
bool get(int key, int& out) const {
|
||||
(void)key;
|
||||
(void)out;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool erase(int key) {
|
||||
(void)key;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t size() const { return 0; }
|
||||
|
||||
std::size_t capacity() const { return 16; }
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
# Задача 10 — хеш-таблица с открытой адресацией (C++)
|
||||
|
||||
Свой контейнер, без `std::unordered_map`. Разрешение коллизий — **линейное зондирование**
|
||||
(linear probing), ёмкость — степень двойки, при загрузке > 0.7 таблица **перехешируется**
|
||||
вдвое. Индексация — через маску `idx & (capacity - 1)`, а не `%`.
|
||||
|
||||
```c++
|
||||
class HashTable {
|
||||
public:
|
||||
explicit HashTable(std::size_t initial_capacity = 16);
|
||||
void put(int key, int value); // обновляет значение, если ключ уже есть
|
||||
bool get(int key, int& out) const; // false — ключа нет
|
||||
bool erase(int key); // false — ключа не было
|
||||
std::size_t size() const;
|
||||
std::size_t capacity() const; // степень двойки, >= 16
|
||||
};
|
||||
```
|
||||
|
||||
Требования:
|
||||
- `put` 100 000 ключей — суммарно быстрее 2 секунд (в среднем O(1));
|
||||
- ключи с совпадающими младшими битами (например, все кратные 16) обязаны находиться —
|
||||
это проверка зондирования, а не «повезло с хешем»;
|
||||
- после 100 000 вставок `capacity()` растёт (степень двойки, загрузка ≤ 0.7), а `size()`
|
||||
честно считает элементы, включая обновления существующих ключей;
|
||||
- удаление должно работать: после `erase` ключ не находится, а поиск другого ключа,
|
||||
стоявшего за ним в цепочке зондирования, по-прежнему работает.
|
||||
|
||||
Проверка: `python3 grade.py 10`. Критерий: все `ok`, сборка без предупреждений,
|
||||
ASAN/UBSAN чистые.
|
||||
|
||||
Разбор после сдачи: почему ёмкость — степень двойки; чем линейное зондирование лучше
|
||||
цепочек по кэшу и хуже по кластеризации; как tombstone-метки спасают поиск после удаления.
|
||||
@@ -0,0 +1,78 @@
|
||||
#include <cstdio>
|
||||
#include <cstdint>
|
||||
#include <random>
|
||||
#include <chrono>
|
||||
#include <unordered_map>
|
||||
#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() {
|
||||
{
|
||||
HashTable t;
|
||||
CHECK(t.size() == 0, "новая таблица пуста");
|
||||
CHECK(t.capacity() >= 16 && (t.capacity() & (t.capacity() - 1)) == 0,
|
||||
"capacity — степень двойки и >= 16");
|
||||
int v = -1;
|
||||
CHECK(t.get(42, v) == false, "get по отсутствующему ключу -> false");
|
||||
CHECK(t.erase(42) == false, "erase отсутствующего -> false");
|
||||
}
|
||||
{
|
||||
HashTable t;
|
||||
for (int i = 0; i < 1000; ++i) t.put(i, i * 7);
|
||||
CHECK(t.size() == 1000, "1000 ключей: size == 1000");
|
||||
bool all = true;
|
||||
for (int i = 0; i < 1000; ++i) { int v = -1; if (!t.get(i, v) || v != i * 7) all = false; }
|
||||
CHECK(all, "1000 ключей: все значения читаются");
|
||||
t.put(500, 999);
|
||||
int v = -1;
|
||||
CHECK(t.get(500, v) && v == 999, "повторный put обновляет значение");
|
||||
CHECK(t.size() == 1000, "повторный put не увеличивает size");
|
||||
}
|
||||
{
|
||||
// ключи с одинаковыми младшими битами — проверка зондирования
|
||||
HashTable t;
|
||||
for (int i = 0; i < 512; ++i) t.put(i * 16, i);
|
||||
bool all = true;
|
||||
for (int i = 0; i < 512; ++i) { int v = -1; if (!t.get(i * 16, v) || v != i) all = false; }
|
||||
CHECK(all, "512 ключей, кратных 16: все находятся (линейное зондирование)");
|
||||
int missing = -1;
|
||||
CHECK(t.get(15, missing) == false, "нечётный отсутствующий ключ -> false");
|
||||
}
|
||||
{
|
||||
// удаление посреди цепочки зондирования
|
||||
HashTable t;
|
||||
for (int i = 0; i < 200; ++i) t.put(i * 16, i);
|
||||
CHECK(t.erase(0) == true, "erase существующего -> true");
|
||||
int v = -1;
|
||||
CHECK(t.get(0, v) == false, "удалённый ключ не найден");
|
||||
bool rest = true;
|
||||
for (int i = 1; i < 200; ++i) { int x = -1; if (!t.get(i * 16, x) || x != i) rest = false; }
|
||||
CHECK(rest, "остальные ключи из цепочки живы после удаления");
|
||||
CHECK(t.size() == 199, "size после удаления == 199");
|
||||
}
|
||||
{
|
||||
const int N = 100000;
|
||||
HashTable t;
|
||||
std::mt19937 rng(4242);
|
||||
std::vector<int> keys(N);
|
||||
for (auto& k : keys) k = int(rng() % 1000000);
|
||||
auto t0 = std::chrono::steady_clock::now();
|
||||
for (int i = 0; i < N; ++i) t.put(keys[i], i);
|
||||
double sec = std::chrono::duration<double>(std::chrono::steady_clock::now() - t0).count();
|
||||
std::printf(" (100k вставок: time=%.3fs, capacity=%zu, size=%zu)\n", sec, t.capacity(), t.size());
|
||||
CHECK(sec < 2.0, "100k вставок за < 2 c");
|
||||
CHECK(t.size() >= 95000 && t.size() <= 100000, "size в разумных пределах");
|
||||
CHECK(t.capacity() >= 131072 && t.capacity() <= 262144, "capacity выросла (загрузка <= 0.7)");
|
||||
bool all = true;
|
||||
for (int i = 0; i < N; ++i) { int v = -1; if (!t.get(keys[i], v)) all = false; }
|
||||
CHECK(all, "все 100k ключей читаются");
|
||||
int v = -1;
|
||||
CHECK(t.get(-12345, v) == false, "отсутствующий ключ -> false");
|
||||
}
|
||||
std::printf(failures ? "\nFAILURES: %d\n" : "\nALL PASS\n", failures);
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Задача 11 — куча: heapify за O(n), kth_largest, top_k.
|
||||
// Реализуй функции ниже. Заглушка намеренно не проходит проверку.
|
||||
#include <vector>
|
||||
|
||||
void heapify(std::vector<int>& a) {
|
||||
(void)a;
|
||||
}
|
||||
|
||||
int kth_largest(const std::vector<int>& a, int k) {
|
||||
(void)a;
|
||||
(void)k;
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<int> top_k(const std::vector<int>& a, int k) {
|
||||
(void)a;
|
||||
(void)k;
|
||||
return {};
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
# Задача 11 — куча: построение за O(n) и top-K (C++)
|
||||
|
||||
Куча **max-heap** в массиве (для элемента `i` дети — `2i+1`, `2i+2`), без `std::priority_queue`.
|
||||
|
||||
```c++
|
||||
void heapify(std::vector<int>& a); // перестроить массив в кучу
|
||||
int kth_largest(const std::vector<int>& a, int k); // k >= 1
|
||||
std::vector<int> top_k(const std::vector<int>& a, int k); // k наибольших, по убыванию
|
||||
```
|
||||
|
||||
Требования:
|
||||
- `heapify` — **просеивание снизу вверх (bottom-up), O(n)**, а не `push` в цикле (это O(n log n));
|
||||
проверка свойств кучи машинная, требование по сложности я сверяю глазами по коду — на
|
||||
собеседовании спросят именно это;
|
||||
- `heapify` обязан сохранить мультимножество элементов (ничего не терять и не дублировать);
|
||||
- `k` в границах `1..n`; при `k > n` — `top_k` возвращает все по убыванию, `kth_largest` при `k > n`
|
||||
возвращает значение минимального элемента (то есть не падает);
|
||||
- 3 000 000 элементов: `heapify` быстрее 2 секунд, `kth_largest` с `k = 1000` быстрее 2 секунд.
|
||||
|
||||
Проверка: `python3 grade.py 11`. Критерий: все `ok`, сборка без предупреждений,
|
||||
ASAN/UBSAN чистые.
|
||||
|
||||
Разбор после сдачи: почему снизу вверх выходит сумма геометрической прогрессии; когда
|
||||
нужен min-heap размера k (top-K на потоке); чем `nth_element` отличается от кучи.
|
||||
@@ -0,0 +1,74 @@
|
||||
#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)
|
||||
|
||||
static bool is_max_heap(const std::vector<int>& a) {
|
||||
for (std::size_t i = 1; i < a.size(); ++i)
|
||||
if (a[(i - 1) / 2] < a[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
int main() {
|
||||
{
|
||||
std::vector<int> a = {3, 1, 4, 1, 5, 9, 2, 6};
|
||||
std::vector<int> before = a;
|
||||
heapify(a);
|
||||
CHECK(is_max_heap(a), "маленький массив: свойство max-heap держится");
|
||||
std::vector<int> s1 = before, s2 = a;
|
||||
std::sort(s1.begin(), s1.end()); std::sort(s2.begin(), s2.end());
|
||||
CHECK(s1 == s2, "мультимножество сохранено");
|
||||
std::vector<int> empty_a;
|
||||
heapify(empty_a);
|
||||
CHECK(empty_a.empty(), "пустой массив -> пустой");
|
||||
std::vector<int> one = {7};
|
||||
heapify(one);
|
||||
CHECK(one.size() == 1 && one[0] == 7, "один элемент не теряется");
|
||||
}
|
||||
{
|
||||
std::vector<int> a = {1, 2, 3, 4, 5};
|
||||
CHECK(kth_largest(a, 1) == 5, "kth_largest k=1 -> максимум");
|
||||
CHECK(kth_largest(a, 5) == 1, "kth_largest k=n -> минимум");
|
||||
CHECK(kth_largest(a, 3) == 3, "kth_largest k=3 -> медиана");
|
||||
std::vector<int> z = {4, 4, 4};
|
||||
CHECK(kth_largest(z, 2) == 4, "дубликаты: k=2 -> 4");
|
||||
std::vector<int> dup = {5, 5, 1, 1, 3};
|
||||
CHECK(kth_largest(dup, 4) == 1, "дубликаты: k=4 -> 1");
|
||||
std::vector<int> big = top_k(a, 3);
|
||||
CHECK(big.size() == 3 && big[0] == 5 && big[1] == 4 && big[2] == 3, "top_k(3) по убыванию");
|
||||
std::vector<int> all = top_k(a, 10);
|
||||
CHECK(all.size() == 5 && all.front() == 5 && all.back() == 1, "top_k(k>n) -> все по убыванию");
|
||||
std::vector<int> neg = {-5, -1, -9};
|
||||
CHECK(kth_largest(neg, 1) == -1, "отрицательные: максимум -1");
|
||||
CHECK(kth_largest(a, 100) == 1, "k>n не падает и даёт минимум");
|
||||
}
|
||||
{
|
||||
const int N = 3000000;
|
||||
std::mt19937 rng(777);
|
||||
std::vector<int> a(N);
|
||||
for (auto& x : a) x = int(rng() % 1000000);
|
||||
std::vector<int> ref = a;
|
||||
auto t0 = std::chrono::steady_clock::now();
|
||||
heapify(a);
|
||||
double sec = std::chrono::duration<double>(std::chrono::steady_clock::now() - t0).count();
|
||||
std::printf(" (3M heapify: time=%.3fs, top=%d)\n", sec, a.empty() ? -1 : a[0]);
|
||||
CHECK(is_max_heap(a), "3M: свойство max-heap держится");
|
||||
CHECK(a[0] == *std::max_element(ref.begin(), ref.end()), "3M: корень == максимум");
|
||||
CHECK(sec < 2.0, "3M heapify за < 2 c");
|
||||
t0 = std::chrono::steady_clock::now();
|
||||
int k = kth_largest(ref, 1000);
|
||||
double sec2 = std::chrono::duration<double>(std::chrono::steady_clock::now() - t0).count();
|
||||
std::sort(ref.begin(), ref.end(), std::greater<int>());
|
||||
std::printf(" (3M kth_largest k=1000: time=%.3fs, value=%d)\n", sec2, k);
|
||||
CHECK(k == ref[999], "3M: kth_largest(k=1000) совпадает с сортировкой");
|
||||
CHECK(sec2 < 3.0, "3M kth_largest за < 3 c");
|
||||
}
|
||||
std::printf(failures ? "\nFAILURES: %d\n" : "\nALL PASS\n", failures);
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Задача 12 — кратчайший путь (Дейкстра).
|
||||
// Реализуй функцию ниже. Заглушка намеренно не проходит проверку.
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
|
||||
long long shortest_path(int n,
|
||||
const std::vector<std::tuple<int, int, int>>& edges,
|
||||
int src, int dst) {
|
||||
(void)n;
|
||||
(void)edges;
|
||||
(void)src;
|
||||
(void)dst;
|
||||
return -1;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
# Задача 12 — Дейкстра на priority_queue (C++)
|
||||
|
||||
```c++
|
||||
long long shortest_path(int n,
|
||||
const std::vector<std::tuple<int,int,int>>& edges, // (u, v, w)
|
||||
int src, int dst);
|
||||
```
|
||||
|
||||
Требования:
|
||||
- рёбра **ориентированные**, вес `w >= 0`, self-loop допустим; вершины `0..n-1`;
|
||||
- вернуть длину кратчайшего пути `src -> dst` (`long long`, веса суммируются до 10^9);
|
||||
- недостижимость → `-1`; `src == dst` → `0`;
|
||||
- вес рёбер 0 допустим (условие применимости Дейкстры — **неотрицательные** веса);
|
||||
- 100 000 вершин и 200 000 рёбер: быстрее 2 секунд. Значит нужна `std::priority_queue`
|
||||
(O((V+E) log V)), а не O(V²) перебор минимума.
|
||||
|
||||
Подсказки по разбору (после сдачи): «ленивое» удаление устаревших записей из очереди
|
||||
(`if (d != dist[v]) continue;`), почему нельзя Дейкстру с отрицательными рёбрами и когда
|
||||
нужен Беллман–Форд.
|
||||
|
||||
Проверка: `python3 grade.py 12`. Критерий: все `ok`, сборка без предупреждений.
|
||||
@@ -0,0 +1,58 @@
|
||||
#include <cstdio>
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
#include <random>
|
||||
#include <chrono>
|
||||
#include "solution.cpp"
|
||||
|
||||
static int failures = 0;
|
||||
#define CHECK(cond, name) do { if (cond) std::printf("ok %s\n", name); \
|
||||
else { std::printf("FAIL %s (line %d)\n", name, __LINE__); ++failures; } } while (0)
|
||||
|
||||
using E = std::tuple<int, int, int>;
|
||||
|
||||
int main() {
|
||||
{
|
||||
std::vector<E> e = {{0, 1, 4}, {0, 2, 1}, {2, 1, 1}, {1, 3, 1}};
|
||||
CHECK(shortest_path(4, e, 0, 3) == 3, "0->3 через 2: 1+1+1 = 3");
|
||||
CHECK(shortest_path(4, e, 0, 0) == 0, "src == dst -> 0");
|
||||
CHECK(shortest_path(4, e, 3, 0) == -1, "обратного пути нет -> -1");
|
||||
CHECK(shortest_path(4, e, 0, 2) == 1, "прямой путь 0->2 = 1");
|
||||
}
|
||||
{
|
||||
std::vector<E> e = {{0, 1, 5}, {0, 2, 1}, {2, 1, 1}};
|
||||
CHECK(shortest_path(3, e, 0, 1) == 2, "длинное ребро не должно побеждать короткий путь");
|
||||
std::vector<E> z = {{0, 1, 0}, {1, 2, 0}};
|
||||
CHECK(shortest_path(3, z, 0, 2) == 0, "нулевые веса допустимы");
|
||||
std::vector<E> self = {{0, 0, 7}, {0, 1, 2}};
|
||||
CHECK(shortest_path(2, self, 0, 1) == 2, "self-loop не ломает поиск");
|
||||
std::vector<E> par = {{0, 1, 3}, {0, 1, 7}};
|
||||
CHECK(shortest_path(2, par, 0, 1) == 3, "параллельные рёбра: берём минимальное");
|
||||
std::vector<E> none;
|
||||
CHECK(shortest_path(2, none, 0, 1) == -1, "без рёбер -> -1");
|
||||
}
|
||||
{
|
||||
// цепочка 0-1-2-...-(n-1) по 1000, плюс «ловушки»: обратные рёбра по 100000.
|
||||
// Ответ детерминирован: идти по цепочке дешевле, чем прыгать назад и снова вперёд.
|
||||
const int n = 100000;
|
||||
std::vector<E> e;
|
||||
e.reserve(200000);
|
||||
for (int i = 0; i + 1 < n; ++i) e.emplace_back(i, i + 1, 1000);
|
||||
for (int i = 1; i < n; ++i) e.emplace_back(i, i - 1, 100000);
|
||||
e.emplace_back(0, 0, 99999999);
|
||||
auto t0 = std::chrono::steady_clock::now();
|
||||
long long d = shortest_path(n, e, 0, n - 1);
|
||||
double sec = std::chrono::duration<double>(std::chrono::steady_clock::now() - t0).count();
|
||||
std::printf(" (100k вершин, 200k рёбер: dist=%lld, time=%.3fs)\n", d, sec);
|
||||
CHECK(d == (long long)(n - 1) * 1000, "цепочка: кратчайший путь по цепочке");
|
||||
CHECK(sec < 2.0, "100k вершин за < 2 c");
|
||||
}
|
||||
{
|
||||
// большие веса: сумма должна влезать в long long
|
||||
std::vector<E> e;
|
||||
for (int i = 0; i + 1 < 2000; ++i) e.emplace_back(i, i + 1, 1000000);
|
||||
CHECK(shortest_path(2000, e, 0, 1999) == 1999LL * 1000000LL, "большие веса: 1999 * 1e6");
|
||||
}
|
||||
std::printf(failures ? "\nFAILURES: %d\n" : "\nALL PASS\n", failures);
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Задача 13 — арифметика подсетей IPv4.
|
||||
// Реализуй функции ниже. Заглушка намеренно не проходит проверку.
|
||||
#include <cstdint>
|
||||
|
||||
struct Subnet {
|
||||
uint32_t network = 0;
|
||||
uint32_t broadcast = 0;
|
||||
uint32_t first_host = 0;
|
||||
uint32_t last_host = 0;
|
||||
long long host_count = 0;
|
||||
};
|
||||
|
||||
Subnet subnet_of(uint32_t ip, int prefix) {
|
||||
(void)ip;
|
||||
(void)prefix;
|
||||
return Subnet{};
|
||||
}
|
||||
|
||||
bool same_subnet(uint32_t a, uint32_t b, int prefix) {
|
||||
(void)a;
|
||||
(void)b;
|
||||
(void)prefix;
|
||||
return false;
|
||||
}
|
||||
|
||||
int prefix_for_hosts(int hosts) {
|
||||
(void)hosts;
|
||||
return -1;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
# Задача 13 — арифметика подсетей IPv4 (C++)
|
||||
|
||||
Адреса передаются в **host byte order** (`0x0A000001` = 10.0.0.1), маска — длиной префикса.
|
||||
|
||||
```c++
|
||||
struct Subnet {
|
||||
uint32_t network; // адрес сети
|
||||
uint32_t broadcast; // широковещательный адрес
|
||||
uint32_t first_host; // первый адрес для узла
|
||||
uint32_t last_host; // последний адрес для узла
|
||||
long long host_count; // сколько адресов узлов доступно (для /0 не влезает в int)
|
||||
};
|
||||
|
||||
Subnet subnet_of(uint32_t ip, int prefix); // 0 <= prefix <= 32
|
||||
bool same_subnet(uint32_t a, uint32_t b, int prefix);
|
||||
int prefix_for_hosts(int hosts); // самая узкая подсеть, куда влезает hosts
|
||||
```
|
||||
|
||||
Определения, которые нужно соблюсти:
|
||||
- для `prefix <= 30`: `host_count = 2^(32-prefix) - 2`, `first_host = network + 1`,
|
||||
`last_host = broadcast - 1`;
|
||||
- `/31` (RFC 3021): `host_count = 2`, `first_host = network`, `last_host = broadcast`;
|
||||
- `/32`: `host_count = 1`, `first_host = last_host = network = broadcast = ip`;
|
||||
- `prefix_for_hosts(h)`: **наибольший** `prefix` из диапазона `0..30` (то есть самая узкая
|
||||
подсеть), при котором `host_count >= h`. `h < 1` или `h > 2^32-2` → `-1`.
|
||||
Исключения `/31` и `/32` в подборе не участвуют — они для линков и одиночных адресов.
|
||||
|
||||
Эталонные примеры, которые обязаны сойтись: `/26` → 62 узла; `/24` → 254; `/30` → 2;
|
||||
`10.0.1.130/26` → сеть `10.0.1.128`, broadcast `10.0.1.191`, узлы `129..190`;
|
||||
`prefix_for_hosts(62)` → 26, `prefix_for_hosts(63)` → 25, `prefix_for_hosts(254)` → 24,
|
||||
`prefix_for_hosts(1)` → 30.
|
||||
|
||||
Проверка: `python3 grade.py 13`. Критерий: все `ok`, сборка без предупреждений.
|
||||
|
||||
Разбор после сдачи: как считать префикс по числу узлов за O(1) (`32 - ceil(log2(h+2))`),
|
||||
почему `/31` — исключение, что такое маска в бинарном виде.
|
||||
@@ -0,0 +1,66 @@
|
||||
#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)
|
||||
|
||||
static uint32_t ip4(int a, int b, int c, int d) {
|
||||
return (uint32_t(a) << 24) | (uint32_t(b) << 16) | (uint32_t(c) << 8) | uint32_t(d);
|
||||
}
|
||||
|
||||
int main() {
|
||||
{
|
||||
Subnet s = subnet_of(ip4(10, 0, 1, 130), 26);
|
||||
CHECK(s.network == ip4(10, 0, 1, 128), "/26: адрес сети 10.0.1.128");
|
||||
CHECK(s.broadcast == ip4(10, 0, 1, 191), "/26: broadcast 10.0.1.191");
|
||||
CHECK(s.first_host == ip4(10, 0, 1, 129), "/26: первый узел 10.0.1.129");
|
||||
CHECK(s.last_host == ip4(10, 0, 1, 190), "/26: последний узел 10.0.1.190");
|
||||
CHECK(s.host_count == 62, "/26: 62 узла (не 64)");
|
||||
}
|
||||
{
|
||||
Subnet s = subnet_of(ip4(192, 168, 5, 200), 24);
|
||||
CHECK(s.network == ip4(192, 168, 5, 0), "/24: сеть 192.168.5.0");
|
||||
CHECK(s.broadcast == ip4(192, 168, 5, 255), "/24: broadcast 192.168.5.255");
|
||||
CHECK(s.host_count == 254, "/24: 254 узла");
|
||||
Subnet t = subnet_of(ip4(172, 16, 4, 9), 30);
|
||||
CHECK(t.network == ip4(172, 16, 4, 8) && t.broadcast == ip4(172, 16, 4, 11),
|
||||
"/30: сеть 172.16.4.8, broadcast 172.16.4.11");
|
||||
CHECK(t.host_count == 2 && t.first_host == ip4(172, 16, 4, 9) && t.last_host == ip4(172, 16, 4, 10),
|
||||
"/30: 2 узла, 9 и 10");
|
||||
Subnet z = subnet_of(ip4(10, 0, 0, 5), 32);
|
||||
CHECK(z.network == ip4(10, 0, 0, 5) && z.broadcast == ip4(10, 0, 0, 5) && z.host_count == 1,
|
||||
"/32: один адрес, сеть == broadcast == узел");
|
||||
Subnet p31 = subnet_of(ip4(10, 0, 0, 1), 31);
|
||||
CHECK(p31.host_count == 2 && p31.first_host == ip4(10, 0, 0, 0) && p31.last_host == ip4(10, 0, 0, 1),
|
||||
"/31: RFC 3021 — 2 узла без вычета");
|
||||
Subnet eight = subnet_of(ip4(10, 200, 30, 40), 8);
|
||||
CHECK(eight.network == ip4(10, 0, 0, 0) && eight.broadcast == ip4(10, 255, 255, 255),
|
||||
"/8: сеть 10.0.0.0, broadcast 10.255.255.255");
|
||||
Subnet zero = subnet_of(ip4(8, 8, 8, 8), 0);
|
||||
CHECK(zero.network == 0 && zero.broadcast == 0xFFFFFFFFu && zero.host_count == 4294967294LL,
|
||||
"/0: вся сеть, узлов 2^32-2");
|
||||
}
|
||||
{
|
||||
CHECK(same_subnet(ip4(10, 0, 1, 130), ip4(10, 0, 1, 150), 26), "/26: 130 и 150 в одной подсети");
|
||||
CHECK(!same_subnet(ip4(10, 0, 1, 130), ip4(10, 0, 1, 200), 26), "/26: 130 и 200 — разные подсети");
|
||||
CHECK(!same_subnet(ip4(10, 0, 1, 130), ip4(10, 0, 1, 127), 26), "/26: 130 и 127 — разные подсети");
|
||||
CHECK(same_subnet(ip4(10, 0, 1, 1), ip4(10, 0, 2, 1), 16), "/16: разные третьи октеты — одна сеть");
|
||||
CHECK(!same_subnet(ip4(10, 0, 1, 1), ip4(10, 1, 1, 1), 16), "/16: 10.0.x и 10.1.x — разные");
|
||||
CHECK(same_subnet(ip4(10, 0, 0, 0), ip4(10, 0, 0, 1), 31), "/31: соседи по RFC 3021");
|
||||
}
|
||||
{
|
||||
CHECK(prefix_for_hosts(62) == 26, "prefix_for_hosts(62) -> 26");
|
||||
CHECK(prefix_for_hosts(63) == 25, "prefix_for_hosts(63) -> 25 (63 в /26 не влезает)");
|
||||
CHECK(prefix_for_hosts(254) == 24, "prefix_for_hosts(254) -> 24");
|
||||
CHECK(prefix_for_hosts(255) == 23, "prefix_for_hosts(255) -> 23");
|
||||
CHECK(prefix_for_hosts(2) == 30, "prefix_for_hosts(2) -> 30");
|
||||
CHECK(prefix_for_hosts(1) == 30, "prefix_for_hosts(1) -> 30 (узкая подсеть общего вида)");
|
||||
CHECK(prefix_for_hosts(0) == -1, "prefix_for_hosts(0) -> -1");
|
||||
CHECK(prefix_for_hosts(4294967295) == -1, "prefix_for_hosts(2^32-1) -> -1 (не влезает)");
|
||||
CHECK(prefix_for_hosts(1000000) == 12, "prefix_for_hosts(1000000) -> 12");
|
||||
}
|
||||
std::printf(failures ? "\nFAILURES: %d\n" : "\nALL PASS\n", failures);
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
Reference in New Issue
Block a user