Порядок выполнения модулей
Страница загружает один модуль JavaScript, а он тянет за собой остальные. По спецификации ECMAScript модуль выполняется после модулей, которые он импортирует, и они выполняются в том порядке, в каком записаны его строки import. Каждый модуль выполняется один раз. Если модули импортируют друг друга по кругу, браузер не ждёт модуль, который уже начал выполнять, и идёт дальше.
Точнее, браузер поступает так. Чтобы выполнить модуль a, он помечает его как начатый, затем по порядку для каждого импорта b: если b ещё не начат, выполняет b тем же способом, а если начат — пропускает. Когда импорты кончились, выполняет код самого a. Напечатайте модули в том порядке, в каком выполнится их код. Один из тестов — модули этого сайта: что выполнит браузер, если страница загрузит движок символьной математики.
Вход
В первой строке число n — сколько модулей (1 ≤ n ≤ 100 000). Дальше n строк, по одной на модуль: имя, число k его импортов и k имён в порядке строк import в файле. Имена — строки до 60 символов без пробелов; каждый упомянутый модуль описан своей строкой; всего импортов не больше 200 000. В последней строке — имя модуля, который загружает страница.
Выход
Имена модулей, которые выполнятся, в порядке выполнения, по одному в строке.
Примеры
Пример 1
7 solve.js 6 rational.js ast.js print.js simplify.js expand.js poly.js poly.js 4 rational.js ast.js expand.js print.js expand.js 4 rational.js ast.js simplify.js poly.js simplify.js 2 rational.js ast.js print.js 2 rational.js ast.js ast.js 1 rational.js rational.js 0 solve.js
rational.js ast.js print.js simplify.js poly.js expand.js solve.js
Пример 2
5 main 3 a b a a 1 c b 2 c main c 1 a lonely 1 main main
c a b main
Ограничения
- Время · Python
- 4000 мс
- Время · Go
- 1000 мс
- Память
- 256 МБ
- Проверка
- 10 тестов
Разбор
Открывайте по ступеням: каждая следующая подсказывает больше. Можно сразу к решению — но задача, решённая с одной подсказкой, запоминается лучше.
1Подсказка 1
Перечитайте правило браузера и сравните его с обходом в глубину. Когда именно выполняется код модуля: при входе в него или при выходе?
2Подсказка 2
Рекурсивное решение пишется в шесть строк. Сколько уровней рекурсии понадобится в тесте, где сто тысяч модулей выстроены в цепочку?
3Подсказка 3
Если вместо рекурсии положить в стек все импорты модуля сразу, модули достанутся не в том порядке. Что нужно хранить в стеке, чтобы он повторял рекурсию шаг в шаг?
4Идея
Правило браузера — это поиск в глубину из модуля страницы, где соседи перебираются в порядке строк import, а модуль печатается в момент выхода из него: обратный порядок обхода, post-order. «Начат» — это серый или чёрный цвет, поэтому круговой импорт к серому модулю просто пропускается, как в спецификации.
5Разбор
Рекурсия переводится на свой стек так: в стеке лежат пары «модуль, номер следующего импорта». На каждом шаге смотрим на верхнюю пару. Если импорты кончились — печатаем модуль и снимаем пару. Иначе сдвигаем номер и, если очередной импорт ещё не начат, помечаем его и кладём пару «импорт, 0». Ровно это и делает рекурсия: пара — это кадр стека вызовов с номером строки, на которой он остановился.
Почему не годится стек модулей. Цикл «достать модуль, положить все его импорты, отметить их» обходит те же модули, но импорт, который лежит в стеке давно, может быть достан позже модулей, которые его импортируют. В первом примере так expand.js выполнился бы раньше poly.js, а в движке математики этого сайта при загрузке index.js модуль simplify.js выполнился бы раньше evaluate.js, который он импортирует. Это wrong.py; в статье есть сцена, где видно все такие нарушения.
В примере с кругом c выполняется первым, хотя импортирует a, а a ещё не выполнен: браузер дошёл до c из a и не стал ждать. Поэтому при круговых импортах один из модулей обязательно видит другой недоделанным.
5+Решение
6Сложность
Каждый модуль кладётся в стек один раз, каждый импорт просматривается один раз: O(n + m), где m — число импортов. Плюс словарь имён.
7Типичные ошибки
- Стек модулей вместо стека кадров. Порядок получается не тот —
wrong.py, падает на первом примере. - Печать при входе. Это порядок, в котором браузер начинает модули, а не выполняет их.
- Пометка только при выходе. Тогда круговой импорт уводит в бесконечную рекурсию: модуль, который уже начат, нужно пропускать сразу.
- Глубина рекурсии. На цепочке из ста тысяч модулей рекурсивное решение на Python без
sys.setrecursionlimitпадает сRecursionError. - Сортировка импортов. Порядок строк
importважен: от него зависит ответ.