/Yndx-Shri-Stories-2

Решение второго задания Яндекс ШРИ 2021

Primary LanguageJavaScript

README.md

Особенности реализации

Решения, принятые в ходе разработки

Как работает сама функция.

Функция prepareData получает массив сущностей для обработки и объект с полем sprintId. Далее функция проходится циклом по всем сущностям, проверяя уникальность каждой (проверка на уникальность добавлена для устойчивости и требует лишь дополнительной памяти на хранение id пройденных сущностей).

Здесь важно отметить, что в задании было сказано о том, что данные могут быть n-ого уровня вложенности, а также сказано, что работа функции не должна зависеть от платформы (Node.js или браузер), поэтому я решил использовать связанный список, так как у рекурсии в браузерах есть максимальная глубина - 10 000 (в некоторых 100 000).

Работа со связанным списком обстоит следующим образом: при обработке каждой сущности создаётся экземпляр связанного списка, и если во время обработки встречается вложенная сущность, то она добавляется в конец данного списка. Список обходится до конца (если встречаются вложенности, то список увеличивается) и происходит переход к следующей сущности.

Обработка сущности

Для каждой сущности, которую необходимо сохранить выделяется отдельный обработчик, что удобно при тестировании. Всё хранение данных вынесено в отдельный объект - экземпляр класса Storage. Класс используется, так как с помощью него можно при каждом вызове функции создавать экземпляр хранилища (если использовать статичное хранилище, то его придётся чистить, что неудобно).

likes, users и summaries сохраняются в Map структуры, как ключ используется свойство id сущности (для likes используется не id комментария, а id автора комментария). Используется именно Map структуры, так как это упростит доступ к likes конкретного пользователя, к конкретному user'у и к конкретной summary, соответственно. Без Map структур при необходимости получить конкретного пользователя приходилось бы перебирать всех пользователей, но такой подход имел бы линейную асимптотику, а использование Map (так как Map является хэш-таблицей) имеет константную асимптотику.

Что касается сущностей sprints и commits, то они сохраняются в массивы, так как перебирать их придётся всех в любом случае. У commits есть ещё дополнительно хранилище - Map структура для связывания commit.id и массива принадлежащих коммиту summary.id's.

Пометка: во время обработки спринтов дополнительно сохраняется активный спринт в отдельную переменную для удобства.

Работа с сущностями после обработки

После обработки всех сущностей можно подготавливать данные дальше.

Здесь используется функция sortByProperty, которая использует метод массива .sort() с определённой функцией (в случае равенства сравниваемых значений будет использована сортировка по id - данная функция используется только для сущностей). Сначала, данная функция используется для сортировки спринтов по id, что необходимо для распределения коммитов по спринтам.

Распределение коммитов происходит с помощью бинарного поиска. Бинарный поиск выгоднее использовать, чем стандартный перебор всех возможных спринтов (асимптотика бинарного поиска - логарифмическая, а перебора - линейная).

Связка коммитов со спринтами нам нужна для 3 последних слайдов (chart, diagram и activity) - для подсчёта общего количества за спринт, для сбора характеристик для активного и прошлого спринта, для составления тепловой карты (карты активности) по активному спринту. Поэтому во время распределения коммитов по спринтам дополнительно сохраняются ссылки на коммиты активного спринта и на коммиты прошлого спринта.

Вычисление статистики происходит с помощью ранее сохранённых ссылок на коммиты активного и прошлого спринтов. По commit.id получаем массив принадлежащих ему summary (Map структура commitSummaries) и далее берётся сам объект summary по его id (Map структура summaries).

Определения времени коммита происходит с помощью объекта Date. Время не UTC, используется локальное время.

Тестирование

Во время тестирования использовались вспомогательные 'генераторы', что упрощало процесс написания тестов.

В папке ./examples есть дополнительные тесты - ./examples/heatMapTest.js и ./examples/bigTest.js, которые нужны были для проверки результатов работы функции с приведённым примером (данные на вход - ./examples/input.json, ожидаемый результат - ./examples/output.json; полученный результат сравнивается с помощью assert.deepStrictEqual и дополнительно сохраняется в отдельный файл - ./examples/output.dev.json).

Использованные технологии

  • webpack Я использовал Webpack, так как это популярный сборщик с большим количеством удобных плагинов и лоадеров. Также, у меня есть опыт работы с ним, поэтому не нужно перед разработкой что-то дополнительно изучать.
  • Jest При выборе фреймворка для тестирования, я сравнивал их популярность и смотрел на возможности. Больше всего мне понравился фреймворк Jest, который имеет очень большую популярность (11 млн скачиваний в месяц с npm), подробную документацию и удобен в использовании.
  • ESLint (с конфигом - airbnb-base) Для отслеживания качества кода был использован ESLint вместе с конфигом от airbnb.
  • husky Данный пакет удобно использовать с ESLint - в репозиторий не сможет попасть некачественный код.
  • Uglify.js Uglify.js сильно уменьшает размер итоговой сборки, что полезно.

Способы подключения функции prepareData:

  1. Для получения доступа к функции в окружении Node.js нужно:

    const { prepareData } = require('./index');
    // можно что-то делать с этой функций далее
  2. Для доступа в браузере нужно импортировать index.js и далее глобально будет доступна функция prepareData. Пример HTML:

    <script src="index.js"></script>
    <!-- Функция может быть вызвана так: -->
    <script>
        prepareData(slides, { sprintId: 1 });
    </script>
  3. Функция, также, доступна для импорта в стиле AMD.