1

This is a little hard to explain. I have a datatable with schedule information. Each row represents a schedule with a start date/time and an end date/time. I need to group these such that the overall start to end time matches a given duration.

For example, I might have the following in my datatable:

Schedule1: Start - 9:00AM, End - 9:30AM
Schedule2: Start - 9:30AM, End - 10:00AM
Schedule3: Start - 10:00AM, End - 10:30AM
Schedule4: Start - 10:30AM, End - 11:00AM

Now if I'm given a duration value of 60 min, then I need to be able to produce the following as output:

Block1: Schedules(1,2): 9:00AM - 10:00AM
Block2: Schedules(2,3): 9:30AM - 10:30AM
Block3: Schedules(3,4): 10:00AM - 11:00AM

If however the duration was instead 120 min, then I would need to produce the following:

Block1: Schedules(1,2,3,4): 9:00AM - 11:00AM

Let me know if this needs clarification. I need to write a method in C# to do this conversion. Please help me with this as I've been stuck on it for a long time.

3
  • 1
    Why do you need to do it in C# as opposed to writing a SQL query to get the results for you? Commented Apr 8, 2014 at 16:10
  • To expand on SpaceghostAli's point, are you using Entity Framework to do this query? If you're using ADO, it would be straight SQL. Commented Apr 8, 2014 at 16:20
  • I guess I could do it in sql....I just thought it is easier in c#. If you have a solution in sql I could use that also. Commented Apr 8, 2014 at 18:50

1 Answer 1

1

Whether you choose to do this in C# or SQL depends partly on the scale of the data. Assuming that we're working with a relatively small number of time ranges (say < 10), it would be reasonable to pull all the times into memory and find the blocks in C#.

Given the following classes:

public class Schedule {
    public int ID { get; set; }
    public DateTime Start { get; set; }
    public DateTime End { get; set; }
    public int Minutes { get; set; }
}

public class ScheduleBlock : Schedule {
    public List<Schedule> Schedules { get; set; }
}

Here is a simple algorithm that iteratively combines ranges together until every possible combination is represented (note that the number of combinations grows as O(n^2)):

public List<ScheduleBlock> CombineAllSchedules(List<Schedule> origschedules, out int added)
{
    added = 0;
    var schedules = new List<ScheduleBlock>();
    foreach (var s in origschedules) {
        var snew = new ScheduleBlock { Schedules = new List<Schedule> { s }, Start = s.Start, End = s.End, Minutes = s.Minutes };
        schedules.Add(snew);
    }

    for (var i = 0; i < schedules.Count; i++) {
        var s = schedules[i];
        var matchstart = schedules.Where (s2 => s2.End == s.Start).ToList();
        var matchend = schedules.Where (s2 => s2.Start == s.End).ToList();
        foreach (var s2 in matchstart) {
            var newschedule = CombineSchedules(s2, s);
            if (!schedules.Any (sc => sc.Start == newschedule.Start && sc.End == newschedule.End)) {
                schedules.Add(newschedule);
                added++;
            }
        }

        foreach (var s2 in matchend) {
            var newschedule = CombineSchedules(s, s2);
            if (!schedules.Any (sc => sc.Start == newschedule.Start && sc.End == newschedule.End)) {
                schedules.Add(newschedule);
                added++;
            }
        }
    }
    return schedules;
}

public ScheduleBlock CombineSchedules(Schedule s1, Schedule s2)
{
    var schedules = new List<Schedule>();
    if (s1 is ScheduleBlock) schedules.AddRange(((ScheduleBlock)s1).Schedules);
    else schedules.Add(s1);
    if (s2 is ScheduleBlock) schedules.AddRange(((ScheduleBlock)s2).Schedules);
    else schedules.Add(s2);
    var s = new ScheduleBlock {
        Schedules = schedules,
        Start = s1.Start, End = s2.End, Minutes = s1.Minutes + s2.Minutes
    };
    return s;
}

Once the combinations are put together, then it is a simple matter to query them and get specific lengths (like 60 minutes or 120 minutes):

public List<ScheduleBlock> FindBlocks(List<Schedule> schedules, int blockLength)
{
    int added;
    var combinedSchedules = CombineAllSchedules(schedules, out added);
    var result = combinedSchedules.Where (s => s.Minutes == blockLength).ToList();
    return result;
}

With this algorithm in place, you can do something like this for example to get the output you're looking for:

var schedules = new List<Schedule> {
    new Schedule { ID = 1, Start = DateTime.Parse("09:00 AM"), End = DateTime.Parse("09:30 AM") },
    new Schedule { ID = 2, Start = DateTime.Parse("09:30 AM"), End = DateTime.Parse("10:00 AM") },
    new Schedule { ID = 3, Start = DateTime.Parse("10:00 AM"), End = DateTime.Parse("10:30 AM") },
    new Schedule { ID = 4, Start = DateTime.Parse("10:30 AM"), End = DateTime.Parse("11:00 AM") },
};

foreach (var s in schedules) {
    s.Minutes = (int)(s.End - s.Start).TotalMinutes;
}

Console.WriteLine("60 Minute Blocks");
Console.WriteLine("----------------");
var blocks = FindBlocks(schedules, 60);
var blockId = 1;
foreach (var block in blocks) {
    var output = "Block" + blockId + 
        ": Schedules(" + string.Join(",", block.Schedules.Select (s => s.ID)) + "): " +
        block.Start.ToString("h:mmtt") + " - " + block.End.ToString("h:mmtt");
    Console.WriteLine(output);
    blockId++;
}

Console.WriteLine();
Console.WriteLine("120 Minute Blocks");
Console.WriteLine("----------------");
blocks = FindBlocks(schedules, 120);
blockId = 1;
foreach (var block in blocks) {
    var output = "Block" + blockId + 
        ": Schedules(" + string.Join(",", block.Schedules.Select (s => s.ID)) + "): " +
        block.Start.ToString("h:mmtt") + " - " + block.End.ToString("h:mmtt");
    Console.WriteLine(output);
    blockId++;
}

Sample Result:

60 Minute Blocks
----------------
Block1: Schedules(1,2): 9:00AM - 10:00AM
Block2: Schedules(2,3): 9:30AM - 10:30AM
Block3: Schedules(3,4): 10:00AM - 11:00AM

120 Minute Blocks
----------------
Block1: Schedules(1,2,3,4): 9:00AM - 11:00AM
Sign up to request clarification or add additional context in comments.

4 Comments

If you are going to put that much effort in to an answer, at least make it elegant. Try using recursion and PLinq or something.
@Tsabo: Feel free to provide an alternative solution. This was my lunchtime exercise :) I did consider recursion, but I couldn't think of a good algorithm. Using PLinq may make the code slightly more efficient but it wouldn't change the algorithm.
For example, give SheduleBlock a reference to all available schedules and a recursive method to find all possible continuations. Deep clone ScheduleBlock at each step as a potential result. Launch the method on all schedules with PLinq.
@mellamokb - Thank you so much for taking the time to answer my question. Your solution will really help me. And it doesn't matter if it is recursion or not, anything that works is fine. I'll respond back to you if I have any questions or if there is something I don't understand.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.