Я думаю, что это работа для битовых полей. К сожалению, это решение будет опираться на магические числа, помощники по преобразованию и немного бинарной логики, поэтому оно не будет красивым. Но это будет работать и будет очень эффективно.
Вот как я подхожу к проблеме:
Распыляйте свои дни на разумные промежутки времени. Я последую вашему примеру и буду рассматривать каждый 15-минутный отрезок времени как один раз (в основном потому, что пример прост). Затем представьте свою доступность за час в виде шестнадцатеричной цифры.
Пример:
- 0xF = 0x1111 => доступно на весь час.
- 0xC = 0x1100 => доступно в течение первой половины часа.
Строка 24 из них вместе, чтобы представить день. Или меньше, если вы можете быть уверены, что за пределами диапазона не произойдет никаких событий. В примере продолжается 24 часа.
С этого момента я разбил длинные шестнадцатеричные числа на слова для удобства чтения.
Предполагая, что день идет с 00:00 до 23:59 business_hours['monday'] = 0x0000 0000 FFFF 0FFF F000 0000
Для того, чтобы занять часы, вы сохраняете события в аналогичном формате, и просто & все вместе.
Пример:
event_a = 0x0000 0000 00F0 0000 0000 0000 # 10:00 - 11:00
event_b = 0x0000 0000 0000 07F8 0000 0000 # 13:15 - 15:15
busy_hours = event_a & event_b
Из часов занятости и рабочих часов вы можете получить доступные часы:
available_hours = business_hours & (busy_hours ^ 0xFFFF FFFF FFFF FFFF FFFF FFFF)
xor (^) существенно переводит busy_hours в not_busy_hours. Anding (&) not_busy_hours с business_hours дает нам доступное время для дня.
Эта схема также упрощает сравнение доступных часов для многих людей.
all_available_hours = person_a_available_hours & person_b_available_hours & person_c_available_hours
Затем найдите временной интервал, который соответствует доступным часам. Вам нужно сделать что-то вроде этого:
Преобразуйте ваш отрезок времени в аналогичную шестнадцатеричную цифру в час, где единица представляет все отрезки времени того часа, который охватит временной интервал. Затем сдвиньте цифру вправо, чтобы не было завершающих нулей.
Примеры лучше объяснений:
0x1 => 15 минут, 0x3 => полчаса, 0x7 => 45 минут, 0xF => полный час, ... 0xFF => 2 часа и т. Д.
Как только вы это сделаете, вы сделаете это:
acceptable_times =[]
(0 .. 24 * 4 - (#of time chunks time slot)).each do |i|
acceptable_times.unshift(time_slot_in_hex) if available_hours & (time_slot_in_hex << i) == time_slot_in_hex << i
end
Верхний предел диапазона немного беспорядок. Итак, давайте посмотрим немного больше на это. Мы не хотим сдвигаться слишком много раз, иначе мы можем начать получать ложные срабатывания в начале спектра.
24 * 4
24 часа в сутки, каждый из которых представлен 4 битами.
- (#of time chunks in time slot)
Вычтите 1 чек за каждые 15 минут в искомом интервале времени. Это значение можно найти по (Math.log(time_slot_in_hex)/Math.log(2)).floor + 1
Который начинается в конце дня, проверяя каждый временной интервал, перемещаясь раньше на временной отрезок (в этом примере 15 минут) на каждой итерации. Если временной интервал доступен, он добавляется к началу приемлемого времени. Поэтому, когда процесс завершается, значение visible_times сортируется в порядке появления.
Круто то, что эта реализация учитывает временные интервалы, которые включают в себя, так что у вашего участника может быть занятой период в их день, который делит временной интервал, который вы ищете, с перерывом, где они могут быть заняты в противном случае.
Вы должны написать вспомогательные функции, которые преобразуются между массивом диапазонов (то есть: [800..1200, 1300..1700]) и шестнадцатеричным представлением. Лучший способ сделать это - инкапсулировать поведение в объекте и использовать собственные методы доступа. А затем используйте те же объекты для представления дней, событий, часов работы и т. Д. Единственное, что не встроено в эту схему, - это как запланировать события, чтобы они могли охватывать границы дней.