diff --git a/robbery.js b/robbery.js index 4a8309d..41c57b7 100644 --- a/robbery.js +++ b/robbery.js @@ -4,7 +4,16 @@ * Сделано задание на звездочку * Реализовано оба метода и tryLater */ -exports.isStar = true; +exports.isStar = false; + +var BANK_TIMEZONE = 0; // временная зона банка +var ROBBERY_DURATION = 0; +var MINUTES_IN_HOUR = 60; +var MINUTES_IN_DAY = 24 * MINUTES_IN_HOUR; +var DAYS_FOR_ROBBERY = 3; +var MAX_MINUTES = DAYS_FOR_ROBBERY * MINUTES_IN_DAY - 1; // макс. 3 дня на ограбление и -1 минута +var DATE_PATTERN = /(([БВНПРСТЧ]{2})\s)?(\d{2}):(\d{2})([+-]\d{1,2})/i; +var WEEKDAYS = ['ПН', 'ВТ', 'СР', 'ЧТ', 'ПТ', 'СБ', 'ВС']; /** * @param {Object} schedule – Расписание Банды @@ -15,16 +24,24 @@ exports.isStar = true; * @returns {Object} */ exports.getAppropriateMoment = function (schedule, duration, workingHours) { + BANK_TIMEZONE = getBaseTimeZone(workingHours); console.info(schedule, duration, workingHours); + ROBBERY_DURATION = duration; + var robberyEvent = getRobberyEvent(schedule, workingHours); return { + ready: robberyEvent.ready, + day: robberyEvent.day, + hours: robberyEvent.hours, + minutes: robberyEvent.minutes, + /** * Найдено ли время * @returns {Boolean} */ exists: function () { - return false; + return this.ready; }, /** @@ -35,7 +52,14 @@ exports.getAppropriateMoment = function (schedule, duration, workingHours) { * @returns {String} */ format: function (template) { - return template; + if (!this.ready) { + return ''; + } + + return template + .replace('%DD', this.day) + .replace('%HH', this.hours) + .replace('%MM', this.minutes); }, /** @@ -48,3 +72,233 @@ exports.getAppropriateMoment = function (schedule, duration, workingHours) { } }; }; + +function getBaseTimeZone(workingHours) { + return parseInt(DATE_PATTERN.exec(workingHours.from)[5], 10); +} + +function getRobberyEvent(schedule, workingHours) { + // Диапазоны времени когда грабители могут пойти на ограбление + var robberyIntervals = {}; + // Высчитываем диапазоны, когда грабители могут пойти на ограбление + parseGangIntervals(robberyIntervals, schedule); + // Составляем диапазоны времени работы банка + var bankSchedule = getBankSchedule(workingHours); + // Делаем пересечение всех диапазонов свободного времени грабителей + // и времени работы банка + robberyIntervals.Gang = getRobberyIntervals(robberyIntervals, bankSchedule); + // Из готовых диапазонов получаем время, когда можно ограбить банк + // с учётом продолжительности ограбления (если вообще можно) + var robberyTime = getRobberyTime(robberyIntervals); + var robberyEvent = findRobberyEvent(robberyTime); + + return robberyEvent; +} + +function parseGangIntervals(robberyIntervals, schedule) { + for (var robber in schedule) { + if (schedule.hasOwnProperty(robber)) { + robberyIntervals[robber] = getFreeIntervals(schedule[robber]); + } + } +} + +// Высчитываем диапазоны, когда грабитель может совершить ограбление +// без учёта времени работы банка +function getFreeIntervals(schedule) { + var freeIntervals = []; + if (schedule.length === 0) { + freeIntervals.push(getInterval(0, MAX_MINUTES)); + + return freeIntervals; + } + var busyIntervals = getBusyIntervals(schedule); + var from = 0; + for (var i = 0; i < busyIntervals.length; i++) { + // Если грабитель занят с в ПН 00:00, то интервала с ПН 00:00 до ПН 00:00 нет + if (i === 0 && busyIntervals[i].from === 0) { + continue; + } + from = (i === 0) ? 0 : busyIntervals[i - 1].to; + freeIntervals.push(getInterval(from, busyIntervals[i].from)); + } + // Если последний интервал, когда рабитель занят, не приходится на СР 23:59 + // то добавляем интервал с конца рабочего времени грабителя по СР 23:59 + if (busyIntervals[busyIntervals.length - 1].to !== MAX_MINUTES) { + from = busyIntervals[busyIntervals.length - 1].to; + freeIntervals.push(getInterval(from, MAX_MINUTES)); + } + + return freeIntervals; +} + +function getBusyIntervals(schedule) { + var intervals = schedule.reduce(function (accumulator, scheduleItem) { + var interval = getInterval(dateToMinutes(scheduleItem.from), + dateToMinutes(scheduleItem.to)); + for (var i = 0; i < accumulator.length; i++) { + var mergedInterval = getMergedInterval(accumulator[i], interval); + if (accumulator[i] !== mergedInterval) { + accumulator[i] = mergedInterval; + + return accumulator; + } + } + accumulator.push(interval); + + return accumulator; + }, []).sort(intervalsComparator); + + return intervals; +} + +function getMergedInterval(first, second) { + var from = 0; + var to = 0; + // Если первый содержится во втором или совпадает с ним, или + // Если второй содержится в первом или совпадает с ним + if (first.from >= second.from && first.to <= second.to || + first.from <= second.from && first.to >= second.to) { + from = Math.min(first.from, second.from); + to = Math.max(first.to, second.to); + + return getInterval(from, to); + } + // Если второй пересекает первого слева + if (first.from >= second.from && first.from <= second.to) { + from = second.from; + to = first.to; + + return getInterval(from, to); + } + // Если второй пересекает первого справа + if (first.to <= second.to && first.to >= second.from) { + from = first.from; + to = second.to; + + return getInterval(from, to); + } + + return first; +} + +function getBankSchedule(workingHours) { + var schedule = []; + for (var day = 0; day < 3; day++) { + var from = dateToMinutes(WEEKDAYS[day] + ' ' + workingHours.from); + var to = dateToMinutes(WEEKDAYS[day] + ' ' + workingHours.to); + schedule.push(getInterval(from, to)); + } + + return schedule; +} + +// Строит пересечение всех диапазонов времени всех троих грабителей, когда они +// могут совершить ограбление и времени работы банка +function getRobberyIntervals(robberyIntervals, bankSchedule) { + var intervals = bankSchedule; + for (var robber in robberyIntervals) { + if (robberyIntervals.hasOwnProperty(robber)) { + intervals = getCrossedIntervals(intervals, robberyIntervals[robber]); + } + } + + return intervals; +} + +// Находит все общие диапазоны, когда грабители могут совершить ограбление +function getCrossedIntervals(firstIntervals, secondIntervals) { + var crossedIntervals = []; + for (var i = 0; i < firstIntervals.length; i++) { + for (var j = 0; j < secondIntervals.length; j++) { + crossTwoIntervals(firstIntervals[i], secondIntervals[j], crossedIntervals); + } + } + + return crossedIntervals; +} + +// Находим пересечение двух диапазонов времени +function crossTwoIntervals(first, second, crossedIntervals) { + if (first.from > second.to || first.to < second.from) { + return; + } + var maxFrom = Math.max(first.from, second.from); + var minTo = Math.min(first.to, second.to); + crossedIntervals.push(getInterval(maxFrom, minTo)); +} + +function getInterval(argFrom, argTo) { + return { + from: argFrom, + to: argTo + }; +} + +function intervalsComparator(first, second) { + if (first.from > second.from || + first.from === second.from && first.to > second.to) { + return 1; + } + if (first.from < second.from || + first.from === second.from && first.to < second.to) { + return -1; + } + + return 0; +} + +// Считаем количество минут, прошедших с ПН 00:00+5 +// Например, СР 15:00+3 -> 3900 (2880 прошло с ПН по СР 00:00 и 17 часов = 1020) +function dateToMinutes(date) { + var timeZoneOffset = BANK_TIMEZONE - Number(DATE_PATTERN.exec(date)[5]); + var day = DATE_PATTERN.exec(date)[2]; + var hours = Number(DATE_PATTERN.exec(date)[3]) + timeZoneOffset; + var minutes = Number(DATE_PATTERN.exec(date)[4]); + var result = dayToMinutes(day) + hours * MINUTES_IN_HOUR + minutes; + + return Math.min(result, MAX_MINUTES); +} + +function dayToMinutes(day) { + if (WEEKDAYS.indexOf(day) !== -1) { + return WEEKDAYS.indexOf(day) * MINUTES_IN_DAY; + } + + return 0; +} + +// Вернёт количество минут, считая с ПН 00:00 до момента, когда можно +// совершить ограбление, либо undefined +function getRobberyTime(robberyIntervals) { + var filtered = robberyIntervals.Gang.filter(filterIntervals); + + return (filtered.length > 0) ? filtered[0].from : undefined; +} + +function filterIntervals(interval) { + return interval.to - interval.from >= ROBBERY_DURATION; +} + +// Преобразуем количество минут, считая с ПН 00:00 до момента, когда можно +// совершить ограбление +function findRobberyEvent(robberyTime) { + if (robberyTime === undefined) { + return { + ready: false + }; + } + var eventDay = WEEKDAYS[Math.floor(robberyTime / MINUTES_IN_DAY)]; + robberyTime -= WEEKDAYS.indexOf(eventDay) * MINUTES_IN_DAY; + var eventMinutes = robberyTime % MINUTES_IN_HOUR; + eventMinutes = (eventMinutes < 10) ? '0' + eventMinutes : eventMinutes; + var eventHours = (robberyTime - eventMinutes) / MINUTES_IN_HOUR; + eventHours = (eventHours < 10) ? '0' + eventHours : eventHours; + + return { + ready: true, + day: eventDay, + hours: eventHours, + minutes: eventMinutes + }; +}