Цель задания — реализовать алгоритм работы «умного дома», который будет производить расчёт стоимости потребляемой электроэнергии в день и возвращать рекомендованное расписание использования электроприборов, оптимизируя денежные затраты.
На вход подаются данные о тарифах, электроприборах и их максимальной потребляемой мощности.
Тарифы — это периоды в сутках, для которых задана отдельная стоимость киловатт-часа.
Приборы — это набор подключенных к «умному дому» электроприборов, для которых известна потребляемая мощность, длительность цикла работы, а также время дня, когда они используется. Каждый прибор должен отработать один цикл в сутки. Максимально потребляемая мощность указывается в ватт-часах.
На выходе должно получиться суточное расписание включения электроприборов. Каждый прибор за сутки должен отработать один цикл, а суммарная стоимость потраченной электроэнергии должна быть минимальной.
При значении mode:
- day период с 07:00 до 21:00.
- night период с 21:00 до 07:00 следующего дня.
- undefined период отсутствует, прибор может работать в любой промежуток времени.
Примеры входных и выходных данных находятся в ./test/data
.
В качестве необязательного задания предлагаем продумать и спроектировать сценарии обработки некорректных входных данных и системных ошибок.
Вы можете использовать любые технологии, фреймворки и библиотеки. Для каждого выбранного инструмента напишите небольшое обоснование — зачем он нужен в вашем проекте и почему именно он.
Мы будем оценивать реализацию функциональности по следующим критериям:
- Правильность алгоритма на разнообразных входных данных.
- Оформление кода.
- Производительность.
- Наличие и качество тестов.
Для реализации единого оформления кода установил дополнительно пакеты:
для реализации автоматизированного приведения к единому стилю с помощью запуска Prettier
в precommit hook
.
Для тестов решил использовать Jest
, так как он является zero configuration
, легок в использовании и имеет достаточный функционал без подключения дополнительных библиотек.
Все функции хранятся в файле index.js
, основная функция называется getScheduleDevices
.
Тесты хранятся в папке ./test
и имеют следующую структуру:
test/
|__ data/
| |
| |__ input.json // входные данные для основной функции
| |__ output.json // выходные данные для основной функции
| ...
|
|__ someFunction/
| |__ data/
| | |__ input.json // входные данные для вспомогательной функции
| | |__ output.json // выходные данные для вспомогательной функции
| |
| |__ someFunction.test.js // файл с Jest тестом для вспомогательной функции
...
|__ getScheduleDevices.test.js // файл с Jest тестом для основной функции
Установка:
npm install
Для того чтобы запустить тесты:
npm test
Посмотреть покрытие кода тестами:
npm run-script test-coverage
Авто запуск тестов при разработке:
npm run-script dev
Алгоритм действий:
- Преобразуем диапазоны тарифов в массив, где каждому индексу соответствует конкретный час работы, и сдвигаем его так чтобы начало утреннего периода было в 0-м индексе. Сдвиг делается для удобства работы с диапазонами.
- Получаем массив сумм тарифов,
sumRate[i] = sumRate[i-1] + rate[i]
, для удобства расчета тарифа в диапазоне. То есть, чтобы найти сумму тарифов на определенном отрезке понадобится:rangeRate = sumRate[range.finish] - sumRate[range.start]
; - Сразу расставляем устройства которые работают круглосуточно и которые работают только в определенном периоде имея продолжительность равную длительности этого периода.
- Находим все возможные положения оставшихся устройств на временной шкале с их суммарным тарифом и хешируем их:
- Положения ищутся исходя из продолжительности работы.
- Если положения для определенной продолжительности работы были рассчитаны - второй раз расчёт не запускается.
- К тому же если продолжительность работы больше 12 часов положения для устройсв можно не рассчитывать, если уже положения найдены для длительности равной
24 - duration
. Так как оптимальное положение для продолжительности в 23 часа можно найти просто взяв самый неоптимальный период для продолжительности в 1 час, и инвертировать период. (time1h.from = time23h.to; time1h.to = time23h.from;
). Данный прием позволит не делать лишних вычислений, а так же более точно подбирать оптимальные значения для больших диапазонов не добавляя дополнительной логики работы с временными периодами.
- Сортируем оставшиеся устройства исходя из мощности, чтобы более выгодно разместить самые энергозатратные устройства.
- Расставляем устройства в расписание:
- Выбираем нужные позиции из Хеша(созданного в п. 4) позиций, учитывая мод и продолжительность.
- Сортируем возможные позиции по величине суммарного тарифа, от меньшего к большему, чтобы саме оптимальные положения оказались в начале массива.
- Берем первый элемент массива (самый оптимальный вариант положения) и проверяем хватает ли мощности для данного устройства в каждом часе на выбранном периоде.
- Если хватает - устанавливаем устройство в расписание.
- Если не хватает - переходим к следующему варианту.
- Если не возможно установить устройство - запускаем функцию которая ищет варианты перестановки устройств местами.
- Находим часы в которые не укладывается устройство
- Находим устройства которые можно подвинуть, исходя из высвобождаемой мощности
- Сортируем устройства которые хотим переставить по возрастанию мощности
- Проверяем можно ли переставить устройства, путем удаления из расписания и массива доступной мощности, устройства которое хотим сдвинуть, и запуская функцию расставления устройств, с приоритетом на установку устройства которое не удалось установить ранее.
- если устройства можно переставить то функция сделает это и обновит календарь
- если нет - выкинет ошибку
- Формируем ответ из state, убирая смещение времени которое было сделано в п.1
-
Реализован алгоритм поиска оптимального расположения для устройств.
-
Реализована проверка типов всех входных данных в распределенные на несколько функций, чтобы дополнительно не создавать циклы только для валидации данных
-
Написаны тесты на все функции, которые используются в данном алгоритме.
-
Реализованы проверки:
- Корректного задания периодов
- Проверка корректности мода
- Возможности установки всех устройств имеющих продолжительность в "полный" период. (undefined = 24h, day - 14h, night - 10h)
- Возможности установки всех устройств с продолжительностью в "неполные" периоды.
-
Покрытие кода тестами: