Анимации алгоритмов часто показывают неверные шаги. Узнайте, как генерировать их из реального кода и тестировать. Начните доверять своим визуализациям!
Вы когда-нибудь смотрели анимацию бинарного поиска и замечали, что она показывает неверный шаг? Я потратил месяцы на создание пошаговых визуализаций для структур данных и алгоритмов, и в итоге обнаружил, что мои анимации лгали. В этой статье я расскажу, как я это исправил, сделав анимации точными и тестируемыми.
Первая версия моих анимаций содержала жестко заданные кадры. Например, для бинарного поиска я вручную прописывал каждый шаг:
steps = [
{ lo: 0, hi: 9, mid: 4, note: "Проверяем середину" },
{ lo: 5, hi: 9, mid: 7, note: "Цель больше, идем вправо" },
// ...
]Это работает до тех пор, пока вы не измените входной массив. Тогда каждый кадр после первого становится неверным, и ничего об этом не сообщает. Анимация все еще воспроизводится, выглядит убедительно, но учит неправильному. Я обнаружил это, когда страница обхода дерева показывала анимацию, не соответствующую дереву рядом с ней. Картинка говорила одно, анимация — другое, и оба были взяты из разных источников.
Решение состояло в том, чтобы перестать писать кадры вручную и начать их записывать. Теперь каждая страница запускает реальный алгоритм и логирует снимок состояния на каждом значимом изменении:
function trace(arr, target) {
const steps = [];
let lo = 0, hi = arr.length - 1;
while (lo <= hi) {
const mid = (lo + hi) >> 1;
steps.push({ lo, hi, mid, note: `Сравниваем ${arr[mid]} с ${target}` });
if (arr[mid] === target) return steps;
if (arr[mid] < target) lo = mid + 1;
else hi = mid - 1;
}
return steps;
}Теперь анимация — это побочный продукт алгоритма, а не параллельное его описание. Измените входные данные — и все кадры обновятся. Сломайте алгоритм — и анимация сломается наглядно, так же как и сам код.
Настоящий выигрыш в том, что анимации теперь можно тестировать. Раз шаги генерируются из выполняемого кода, можно проверять последний шаг:
const steps = trace([1, 3, 5, 7, 9, 11], 7);
assert.equal(steps.at(-1).mid, 3);У меня теперь 1096 таких проверок для всех анимаций на сайте. Они запускаются при каждой сборке. Тесты поймали ошибки, которые я бы никогда не заметил, просто наблюдая:
Последний случай — мой любимый провал. Каждая из этих страниц выглядела законченной.
Если визуальное объяснение не порождено тем, что оно объясняет, оно будет расходиться с реальностью. Документация расходится с кодом по той же причине, и мы давно приняли, что сгенерированная документация лучше рукописной. Анимации ничем не отличаются — это просто движущаяся документация.
Тестируйте результат, а не рендеринг. Я не делаю скриншот-тесты SVG. Я проверяю конечное состояние трассировки. Это ловит важные ошибки (неправильный алгоритм) и игнорирует неважные (узел сдвинулся на три пикселя).
Убедительное неправильное объяснение хуже, чем отсутствие объяснения. Учащийся, читающий статичное неверное предложение, часто его замечает. Учащийся, смотрящий плавную анимацию, предполагает, что машина знает лучше. Сделать это правильно казалось не полировкой, а обязанностью.
Сайт называется SolveLog — разобранные задачи LeetCode и уроки по структурам данных, каждая с собственной сгенерированной анимацией. Вот несколько примеров, где подход с трассировкой оправдывает себя:
Сайт бесплатный, без регистрации. Я построил его, чтобы самому как следует разобраться в материале, и только дисциплина тестирования — причина, по которой я теперь ему доверяю.
Прямо сейчас откройте свою любимую анимацию алгоритма и спросите себя: генерируется ли она из реального кода? Если нет — замените её на трассировку. Начните с одного алгоритма, добавьте тесты на конечное состояние и убедитесь, что анимация обновляется при изменении входных данных. Это займёт вечер, но сэкономит часы отладки и убережёт ваших учеников от ложных знаний.
Хочешь закрепить знания на практике?
Решай задачи на Algolit — интерактивная платформа для обучения
Начать бесплатно →