Мне нужно создать «структуру расписания», основанную на времени, с помощью следующих методов:

Void  addTask(DateTime startTime, int durationInMinutes, TaskObject myObj)
{
   // add TaskObject to calendar structure
}
List<TaskObject> getRunningTasks (DateTime startTime, DateTime endTime)
{
  //this procedure have to efficiently return list of running tasks in specified time frame
}
List<TaskObject> getRunningTasks (DateTime exactTime)
{
    return getRunningTaks(exactTime,exactTime);
}

У меня есть около 60 тыс. TaskObject для подсчета, и мне нужно пересчитать в часах и минутах (getRunningTasks будет вызываться примерно ~ 400 тыс. Раз)

Пока использую:

public Dictionary<long, Dictionary<int, Dictionary<int, List< TaskObject>>>> scheduleTable;

ScheduleTable [dayInTicks] [час] [минута]

Где я добавляю все подходящие задачи к каждому часу и минуте, где они запланированы.

Идея DrKoch

    public class TaskList
    {
        private SortedDictionary<DateTime, TaskObject> startTimes;
        private SortedDictionary<DateTime, TaskObject> endTimes;
        private SortedSet<DateTime> startTimeIndexes;
        private SortedSet<DateTime> endTimeIndexes;
        public TaskList()
        {
            reset();
        }
        public void addTask(TaskObject taskToAdd, DateTime startTime, int durationInMinutes)
        {
            // start time
            while (startTimes.ContainsKey(startTime))
            {
                startTime = startTime.AddTicks(1);
            }
            startTimes.Add(startTime, taskToAdd);
            startTimeIndexes.Add(startTime);
            //end time
            DateTime endTime = startTime.AddMinutes(durationInMinutes);
            while (endTimes.ContainsKey(endTime))
            {
                endTime = endTime.AddTicks(1);
            }
            endTimes.Add(endTime, taskToAdd);
            endTimeIndexes.Add(endTime);
        }
        public List<TaskObject> getRunningTasks(DateTime startTime, DateTime endTime)
        {
            DateTime fromBeginingOfDay = new DateTime(endTime.Year, endTime.Month, endTime.Day);
            SortedSet<DateTime> myEndTimeIndexes =  endTimeIndexes.GetViewBetween(fromBeginingOfDay, startTime); // tasks, that already finished during specified day
            SortedSet<DateTime> myStartTimeIndexes = endTimeIndexes.GetViewBetween(fromBeginingOfDay, endTime);  // tasks, that started from the beginig of the day
            List<TaskObject> result = new List<TaskObject>();
            // Fill result with all matching tasks
            foreach (DateTime myStartTimeIndex in myStartTimeIndexes)
            {
                result.Add(startTimes[myStartTimeIndex]);
            }
            // Remove finished tasks from result
            foreach (DateTime myEndTimeIndex in myEndTimeIndexes)
            {
                if (result.Contains(endTimes[myEndTimeIndex]))
                {
                    result.Remove(startTimes[myEndTimeIndex]);
                }
            }
            return result;
        }
        public List<TaskObject> getRunningTasks(DateTime exactTime)
        {
            return getRunningTasks(exactTime, exactTime.addSeconds(1));
        }
        public void reset()
        {
            startTimes = new SortedDictionary<DateTime, TaskObject>();
            endTimes = new SortedDictionary<DateTime, TaskObject>();
            startTimeIndexes = new SortedSet<DateTime>();
            endTimeIndexes = new SortedSet<DateTime>();
        }
    }
    public class TaskObject
    {
        public string Name;
        public TaskObject(string name)
        {
            Name = name;
        }
    }
-4
Laky 28 Янв 2015 в 12:28

3 ответа

Лучший ответ

Допустим, вы храните свою задачу в таком классе:

public class MyTask
{
    public string name;
    public DateTime startDt;
    public DateTime endDt;
    // ...
}

Основная идея состоит в том, чтобы поддерживать две коллекции с задачами, одна упорядочена startDt, а scond - endDt.

Мы собираемся использовать SortedSet по двум причинам:

  1. он имеет вычислительную сложность O (log n) для вставки и поиск. Если вы столкнулись с проблемой со многими предметами, это очень желательно. иметь сложность лучше, чем O (n).

  2. он позволяет вернуть все товары в определенном «диапазоне». Не нужно знать точные «ключи» для поиска, как в Словаре

Поскольку все элементы в SortedSet уникальны и поскольку несколько задач могут иметь одинаковые startDt или endDt, мы не можем хранить задачи непосредственно в SortedSet, вместо этого мы поддерживаем " ведро »всех задач с одинаковым временем:

public class SameTimeTaskList
{
    public DateTime time; // common start or end time of all tasks in list
    public List<MyTask> taskList = new List<MyTask>();
}

Критерий сортировки для этого, конечно, time:

// Defines a comparer to create a sorted set 
// that is sorted by time. 
public class ByTime : IComparer<SameTimeTaskList>
{
    public int Compare(SameTimeTaskList x, SameTimeTaskList y)
    {
        return x.time.CompareTo(y.time);
    }
}

С его помощью мы можем построить наши два отсортированных набора:

SortedSet<SameTimeTaskList> startTimeSet = 
  new SortedSet<SameTimeTaskList>(new ByTime());
SortedSet<SameTimeTaskList> endTimeSet = 
  new SortedSet<SameTimeTaskList>(new ByTime());

В оба набора вставляется новая задача. Если сегмент для этого time не существует, создается новый сегмент. В противном случае задача просто добавляется в правильную корзину:

    public void Add(MyTask task)
    {
        // startTimeSet
        refTime.time = task.startDt;
        var lst = startTimeSet.GetViewBetween(refTime,
            refTime).FirstOrDefault();
        if (lst == null) // no bucket found for time
        {
            lst = new SameTimeTaskList { time = task.startDt };
            startTimeSet.Add(lst);
        }
        lst.taskList.Add(task); // add task to bucket
        // endTimeSet
        refTime.time = task.endDt;
        lst = endTimeSet.GetViewBetween(refTime,
            refTime).FirstOrDefault();
        if (lst == null) // no bucket found for time
        {
            lst = new SameTimeTaskList { time = task.endDt };
            endTimeSet.Add(lst);
        }
        lst.taskList.Add(task); // add task to bucket
    }

Теперь легко получить все интервалы, которые активны в определенный exactTime. Каждая задача должна соответствовать двум условиям:

task.startDt <= exactTime
&&
task.endDt >= exactTime

Мы проверяем оба SortedSets, чтобы увидеть, какой из них возвращает меньший набор для одного условия. Затем мы проверяем все задачи в меньшем наборе, если оно соответствует второму условию:

    public IEnumerable<MyTask> Get(DateTime exactTime)
    {
        refTime.time = exactTime;
        // set of all tasks started before exactTime
        SortedSet<SameTimeTaskList> sSet =
           startTimeSet.GetViewBetween(minTime, refTime);
        // set of all tasks ended after exactTime
        SortedSet<SameTimeTaskList> eSet =
           endTimeSet.GetViewBetween(refTime, maxTime);

        List<MyTask> result = new List<MyTask>();
        if (sSet.Count < eSet.Count) // check smaller set for 2nd condition
        {
            foreach (var tl in sSet)
                foreach (MyTask tsk in tl.taskList)
                    if(tsk.endDt >= exactTime) result.Add(tsk);
        }
        else // eSet is smaller
        {
            foreach (var tl in eSet)
                foreach (MyTask tsk in tl.taskList)
                    if (tsk.startDt <= exactTime) result.Add(tsk);

        }
        return result;
    }

Вот полный код в виде рабочей программы:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace IntervallsTest
{
    class Program
    {
        static void Main(string[] args)
        {
            DateTime exactDate = DateTime.Parse("2015-6-1");

            var tc = new TaskCollection();
            tc.Add(new MyTask { name = "T1", startDt = DateTime.Parse("2015-1-1"), endDt = DateTime.Parse("2015-02-01") });
            tc.Add(new MyTask { name = "T2", startDt = DateTime.Parse("2015-1-1"), endDt = DateTime.Parse("2015-07-01") });
            tc.Add(new MyTask { name = "T2a", startDt = DateTime.Parse("2015-1-1"), endDt = DateTime.Parse("2015-07-02") });
            tc.Add(new MyTask { name = "T3", startDt = DateTime.Parse("2015-05-1"), endDt = DateTime.Parse("2015-12-31") });
            tc.Add(new MyTask { name = "T3a", startDt = DateTime.Parse("2015-04-1"), endDt = DateTime.Parse("2015-12-31") });
            tc.Add(new MyTask { name = "T4", startDt = DateTime.Parse("2015-12-1"), endDt = DateTime.Parse("2015-12-31") });

            var result = tc.Get(exactDate);

            Console.WriteLine("These tasks are active at " + exactDate);
            foreach (var tsk in result)
            {
                Console.WriteLine(tsk.name);
            }
            Console.WriteLine("press any key");
            Console.ReadKey();
        }
    }

    public class TaskCollection
    {
        SortedSet<SameTimeTaskList> startTimeSet = new SortedSet<SameTimeTaskList>(new ByTime());
        SortedSet<SameTimeTaskList> endTimeSet = new SortedSet<SameTimeTaskList>(new ByTime());

        static SameTimeTaskList refTime = new SameTimeTaskList();
        static SameTimeTaskList minTime = new SameTimeTaskList { time = DateTime.MinValue };
        static SameTimeTaskList maxTime = new SameTimeTaskList { time = DateTime.MaxValue };

        public void Add(MyTask task)
        {
            // startTimeSet
            refTime.time = task.startDt;
            var lst = startTimeSet.GetViewBetween(refTime, refTime).FirstOrDefault();
            if (lst == null) // no bucket found for time
            {
                lst = new SameTimeTaskList { time = task.startDt };
                startTimeSet.Add(lst);
            }
            lst.taskList.Add(task); // add task to bucket
            // endTimeSet
            refTime.time = task.endDt;
            lst = endTimeSet.GetViewBetween(refTime, refTime).FirstOrDefault();
            if (lst == null) // no bucket found for time
            {
                lst = new SameTimeTaskList { time = task.endDt };
                endTimeSet.Add(lst);
            }
            lst.taskList.Add(task); // add task to bucket
        }

        public IEnumerable<MyTask> Get(DateTime exactTime)
        {
            refTime.time = exactTime;
            // set of all tasks started before exactTime
            SortedSet<SameTimeTaskList> sSet = startTimeSet.GetViewBetween(minTime, refTime);
            // set of all tasks ended after exactTime
            SortedSet<SameTimeTaskList> eSet = endTimeSet.GetViewBetween(refTime, maxTime);

            List<MyTask> result = new List<MyTask>();
            if (sSet.Count < eSet.Count) // check smaller set for 2nd condition
            {
                foreach (var tl in sSet)
                    foreach (MyTask tsk in tl.taskList)
                        if(tsk.endDt >= exactTime) result.Add(tsk);
            }
            else // eSet is smaller
            {
                foreach (var tl in eSet)
                    foreach (MyTask tsk in tl.taskList)
                        if (tsk.startDt <= exactTime) result.Add(tsk);

            }
            return result;
        }
    }

    public class MyTask
    {
        public string name;
        public DateTime startDt;
        public DateTime endDt;
        // ...
    }

    public class SameTimeTaskList
    {
        public DateTime time; // common start or end time of all tasks in list
        public List<MyTask> taskList = new List<MyTask>();
    }

    // Defines a comparer to create a sorted set 
    // that is sorted by time. 
    public class ByTime : IComparer<SameTimeTaskList>
    {
        public int Compare(SameTimeTaskList x, SameTimeTaskList y)
        {
            return x.time.CompareTo(y.time);
        }
    }
}

Мне было бы очень интересно увидеть результаты тестов, когда вы сравните их со всеми другими версиями, которые вы пробовали.

16
dav_i 2 Фев 2015 в 11:52

Примечание: См. Мой второй ответ для более полного решения.


Вы можете создать два дополнительных отсортированных словаря:

SortedDictionary<DateTime, Task> startTimes; // startTime -> Task
SortedDictionary<DateTime, Task> endTimes;   // endTime -> Task

Эти словари обеспечивают быстрый (O (log N)) доступ ко всем задачам, которые начинаются до exactTime и заканчиваются после exactTime

Пересечение этих множеств - это то, что вы ищете.


Лучшая коллекция SortedSet, в ней есть

SortedSet<T>.GetViewBetween()

, который делает все, что вам нужно: он может возвращать все Задачи в startTimesSet с startTime до exactTime.

0
Community 23 Май 2017 в 12:20

Эта проблема аналогична выделению двухмерных точек, попадающих в указанный прямоугольник. К сожалению, решить эту проблему напрямую с помощью бинарного поиска невозможно.

Основной способ решить эту проблему: разложить «равнину» на квадраты. Небольшой пример:

// minDate means minimal possible date
// maxDate means maximal possible date
// interval means a unit of division in days, f.e. 10 or 30
var size = (maxDate.Subtract(minDate).Days + interval)/interval;
var tasks = new List<Task>[size, size]();

// for each new task:
var startDate = ...
var endDate = ...
var x = (startDate.Subtract(minDate).Days + interval)/interval;
var y = (endDate.Subtract(minDate).Days + interval)/interval;

if (tasks[x, y] == null)
    tasks[x, y] = new List<Task>();

tasks[x, y].Add(newTask);

// search
var startPeriod = ...
var endPeriod = ...
var minIndex = (startPeriod.Subtract(minDate).Days + interval)/interval;
var maxIndex = (endPeriod.Subtract(minDate).Days + interfal)/interval;

for (int x = minIndex + 1; x < maxIndex - 1; x++)
    for (int y = minIndex + 1; y < maxIndex - 1; y++)
        tasks[x, y] ... // All these tasks are yours

for (int x = minIndex; x < maxIndex; x++)
    foreach(var task in tasks[x, minIndex])
        if (task.startDate >= startPeriod && task.endDate <= endPeriod)
            ... // All these tasks also are yours

// Repeat last for/foreach for every boundary interval, since not all tasks
// may be yours there
...

Внутри граничных «квадратов» вы ищите нужные задания грубой силой. Если он слишком медленный, вы можете использовать SortedList вместо List. Это сократит время брутфорса, но не избавится от него полностью.

0
Mark Shevchenko 28 Янв 2015 в 11:11