views:

942

answers:

4

I am looking for a good design/alogrithm/pattern for the following:

I have a large list of TODO tasks. Each one of them has an estimated duration. I want to break the larger list into smaller sublists, each sublist containing a max of 4 hours of work.

My current algorithm is something like this:

while( index < list.Count )
{
    List<string> subList = CreateSublist( ref index );
    SaveSubList(subList);
}

Passing the index in as a ref feels awkward and not OOD. I am really consuming the TODO list somewhat like a stream, so I'm wondering if there's something similar I could do, but I'm somewhat of a C# newbie. I am also currently limited to C# 2.0. Any quick pointers on a good design here?

+2  A: 

You can stuff everything in one method:

List<List<TodoTask>> GetTodoTasks(IEnumerable<TodoTask> tasks, int timeWindow)
{
    List<List<TodoTask>> allTasks = new List<List<TodoTask>>();

    List<TodoTask> tasks = new List<TodoTask>();
    int duration = 0;

    foreach(TodoTask task in tasks)
    {
        if(duration > timeWindow)
        {
            allTasks.Add(tasks);

            duration = 0;
            tasks = new List<TodoTask>();
        }

        tasks.Add(task);
        duration += task.Duration;
    }

    allTasks.Add(tasks);        
    return allTasks;
}

Or, using iterators:

IEnumerable<List<TodoTask>> GetTodoTasks(IEnumerable<TodoTask> tasks, int timeWindow)
{        
    List<TodoTask> tasks = new List<TodoTask>();
    int duration = 0;

    foreach(TodoTask task in tasks)
    {
        if(duration > timeWindow)
        {
            yield return tasks;

            duration = 0;
            tasks = new List<TodoTask>();
        }

        tasks.Add(task);
        duration += task.Duration;
    }

    yield return tasks;
}
Anton Gogolev
Ahh, you beat me to it. Almost identical to my solution!
Noldorin
Just spotted something: you increment duration *after* you add the item to the list, which means that a new sub list will only be created once the duration of the current one has *exceeded* 4 hours, which is not quite what the asker wants.
Noldorin
A: 

This should do the job:

public static List<List<Task>> SplitTaskList(List<Task> tasks)
{
    List<List<Task>> subLists = new List<List<Task>>();
    List<Task> curList = new List<Task>();
    int curDuration; // Measured in hours.

    foreach (var item in tasks)
    {
        curDuration += item.Duration;
        if (curDuration > 4)
        {
            subLists.Add(curList);
            curList = new List<Task>();
            curDuration = 0;
        }

        curList.Add(item);
    }

    subLists.Add(curList);

    return subLists;
}

LINQ would probably simplify things, but since you are using C# 2.0 (and likely also .NET 2.0 I would presume), this would seem like the most straightforward solution.

Noldorin
A: 

I would suggest to encapsulate this into a class.

SubListBuilder<WorkItem> slb = new SubListBuilder<WorkItem>(
    workItems, sublist => sublist.Sum(item => item.Duration) <= 4);

This nicely allows to supply a predicate to control how the sublists are build. Then you can just get your results.

while (slb.HasMoreSubLists)
{
    SaveList(slb.GetNextSubList());
}

Or maybe this way.

foreach (var subList in slb.GetSubLists())
{
    SaveList(subList);
}
Daniel Brückner
A: 

Here's my solution:

    class Task
    {
        public string Name { get; set; }
        public int Duration { get; set; }
    }

    class TaskList : List<Task>
    {
        public int Duration { get; set; }

        public void Add(Task task, int duration)
        {
            this.Add(task);
            Duration += duration;
        }
    }

    private static IList<TaskList> SplitTaskList(IList<Task> tasks, int topDuration)
    {
        IList<TaskList> subLists = new List<TaskList>();
        foreach (var task in tasks)
        {
            subLists = DistributeTask(subLists, task, topDuration);
        }
        return subLists;
    }

    private static IList<TaskList> DistributeTask(IList<TaskList> subLists, Task task, int topDuration)
    {
        if (task.Duration > topDuration)
            throw new ArgumentOutOfRangeException("task too long");

        if (subLists.Count == 0)
            subLists.Add(new TaskList());

        foreach (var subList in subLists)
        {
            if (task.Duration + subList.Duration <= topDuration)
            {
                subList.Add(task, task.Duration);
                return subLists;
            }
        }

        TaskList newList = new TaskList();
        newList.Add(task, task.Duration);
        subLists.Add(newList);
        return subLists;
    }

Notice that this is not the optimal solution ... that would go to a whole new level :)

Also, this solution will distribute the items a little better than Noldorin and Anton solutions. You might end up will fewer lists.

bruno conde