#c# #algorithm
#c# #алгоритм
Вопрос:
У меня есть набор встреч на определенный рабочий день, который длится с 8 утра до 18 вечера:
Прием с 1: 9 до 11:00 Прием с 2: 14 до 17:00
Я ищу эффективный способ найти свободное время. Таким образом, в этом случае доступное время будет:
с 8 утра до 9 утра, с 11 утра до 14:00, с 5 до 18:00
Итак, у меня есть класс TimeBlock
class TimeBlock {
public DateTime start
public DateTime end
}
var appointments = new List<TimeBlock>();
var freeTimeBlocks = new List<TimeBlock>();
' add appointments
appointments.Add(new TimeBlock{start...
appointments.Add(new TimeBlock{start...
Я ищу эффективный способ найти свободное время, потому что алгоритм будет выполняться на довольно большом наборе данных.
Комментарии:
1. Как это будет работать, если есть более одного дня? Будет ли он учитывать разные дни отдельно? Я так полагаю, потому что, по-видимому, вы не хотели бы, чтобы середина ночи считалась доступным временем!
2. Какой результат вам нужен? Список доступных диапазонов?
3. @Робин Грин — в настоящее время меня беспокоит один день. Рабочий день длится только с 8 утра до 18 вечера, поэтому мне не нужно беспокоиться о сценариях, связанных с переходом к полуночи.
4. @SLaks — Да, выводом является список доступных диапазонов.
Ответ №1:
Попробуйте это, предполагается, что нет совпадающих встреч:
var orderedAppointments = appointments.OrderBy(a => a.start).ToArray();
freeTimeBlocks.Clear();
for(int i = 0; i < orderedAppointments.Length - 1; i )
{
freeTimeBlocks.Add(new TimeBlock(){ start = orderedAppointments[i].end; end = orderedAppointments[i 1].start });
}
var firstAppointment = orderedAppointments.First();
var lastAppointment = orderedAppointments.Last();
if(firstAppointment.start.Hour > 8)
freeTimeBlocks.Add(new TimeBlock() { start = firstAppointment.Today.AddHours(8); end = firstAppointment.start });
if(lastAppointment.end.Hour < 18)
freeTimeBlocks.Add(new TimeBlock() { start = lastAppointment.end; end = lastAppointment.Today.AddHours(18) });
Ответ №2:
Убедитесь, что временные блоки отсортированы ( O(nlogn)
или лучше), затем выполните цикл по ним и создайте диапазоны доступности от конца каждого блока до начала следующего ( O(n)
).
Ответ №3:
Я думаю, что этот подход был бы асимптотически очень эффективным для огромных наборов данных (d * u >> n), если они могут поместиться в памяти:
Если количество дней равно d, количество пользователей равно u, а среднее количество встреч на пользователя в день равно n, это равно O (d * u * n), тогда как основанный на сортировке подход к каждому дню был бы больше похож на O (d * u * n * log n).
(Если u= 1 или d= 1, это не имеет значения — это не имеет значения.)
-
Создайте массив списков, проиндексированных по всем возможным временным интервалам. Например, если вы можете использовать временные интервалы каждые 5 минут, у вас будет массив размером 120. Этот шаг равен O (1). Мы предполагаем, что количество возможных временных интервалов фиксировано, и поэтому оно не должно иметь никакого отношения к анализу сложности.
-
Просмотрите все встречи для всех дней и всех пользователей и добавьте встречи (вместе с записью о том, для какого пользователя и в какой день это назначено) в соответствующий список для как времени начала, так и времени окончания встречи. Этот шаг выполняется автоматически, если вы используете связанный список и каждый раз добавляете их в начало списка.
-
Создайте массив для записи «текущего» свободного временного интервала для каждого дня и пользователя — вы увидите, как это работает на следующем шаге.
-
Выполните цикл по первому массиву, и для каждого списка в массиве выполните цикл по этому списку (таким образом, циклы являются вложенными). Для каждой встречи, которую вы видите с надписью «завершить встречу сейчас», начните записывать новый свободный временной интервал в это время, для этого дня и пользователя. Для каждой встречи, которую вы видите с надписью «начать встречу сейчас», завершите запись свободного временного интервала для этого дня и пользователя, если таковой имеется, и отбросьте его, если он равен нулевой продолжительности — если его нет и сейчас не 8 утра, создайте его. Этот шаг также является O (d * u * n).
-
Завершите все открытые записи о свободном времени в 18:00. Этот шаг является наихудшим вариантом O (d * u).
Не требуется сопоставлений или поиска между назначениями!
Это основано на сортировке по радиусу.
Однако я не уверен, будет ли это лучше на практике. Это, безусловно, требует больше места!
Ответ №4:
Могло бы быть так же просто, как это, при условии, что у вас нет никаких перекрывающихся встреч (из вашего заявления о проблеме, которое не должно быть возможным):
appointments.Sort((a,b) => a.Start.CompareTo(b.Start));
for(int i = 0; i< appointments.Count-1; i )
{
if(appointments[i].End < appointments[i 1].Start)
freeTimeBlocks.Add(new TimeBlock() { Start = appointments[i].End, End = appointments[i 1].Start });
}
Это не учитывает крайние случаи нехватки времени до первой встречи и после последней, которые вам пришлось бы вручную проверять и добавлять.
Ответ №5:
Этот ответ также работает с предположением, что не будет никаких перекрывающихся встреч. Вместо того, чтобы работать с временными рамками с 8 утра до 6 вечера, указанными в вопросе, это решение позволяет установить один или несколько доступных периодов времени.
Я думаю, это можно сделать более элегантно, но я не могу понять, как это сделать.
private static List<TimeBlock> GetDayOpenSlots(IEnumerable<TimeBlock> daySlots,
IReadOnlyCollection<TimeBlock> usedDaySlots)
{
var freeTimeBlocks = new List<TimeBlock>();
foreach (var daySlot in daySlots)
{
var selectedSlotsWithinRange = usedDaySlots
.Where(x => x.StartTime >= daySlot.StartTime amp;amp; x.EndTime <= daySlot.EndTime)
.OrderBy(x => x.StartTime)
.ToList();
if (!selectedSlotsWithinRange.Any())
{
freeTimeBlocks.Add(daySlot);
continue;
}
for (var i = 0; i < selectedSlotsWithinRange.Count - 1; i )
{
var currentSlot = selectedSlotsWithinRange[i];
var nextSlot = selectedSlotsWithinRange[i 1];
if (currentSlot.EndTime == nextSlot.StartTime)
{
continue;
}
freeTimeBlocks.Add(new TimeBlock(currentSlot.EndTime,
nextSlot.StartTime));
}
var firstAppointment = selectedSlotsWithinRange.First();
var lastAppointment = selectedSlotsWithinRange.Last();
if (firstAppointment.StartTime > daySlot.StartTime)
{
freeTimeBlocks.Add(new TimeBlock(daySlot.StartTime,
firstAppointment.StartTime));
}
if (lastAppointment.EndTime < daySlot.EndTime)
{
freeTimeBlocks.Add(new TimeBlock(lastAppointment.EndTime, daySlot.EndTime));
}
}
return freeTimeBlocks
.OrderBy(x => x.StartTime)
.ToList();
}