2

I have a Task class which can have sub tasks of the same type

public class Task
{
  public DateTime Start { get; set;}
  public DateTime Finish { get; set;}
  public List<Task> Tasks {get; set;}
  public DateTime FindTaskStartDate(Task task)
  {}
}

How should i perform a recursive search (linq perhaps) to find the task with the earliest start date?

My initial approach involved too many for loops and it ended becoming a bit of a mess and quickly spiraling out of control. Here's my second attempt:

public DateTime FindTaskStartDate(Task task)
{
    DateTime startDate = task.Start;

    if(task.HasSubTasks())
    {
        foreach (var t in task.Tasks)
        {
            if (t.Start < startDate)
            {
                startDate = t.Start;

                if (t.HasSubTasks())
                {
                    //What next?
                    //FindTaskStartDate(t);
                }
            }
        }
    }

    return startDate;
}

Any nicer solutions out there to solve this problem?

Thanks

Fixer
  • 5,985
  • 8
  • 40
  • 58

3 Answers3

15

Svick's solution is fine, but I thought I'd add a bit more general advice. It seems like you are new to writing recursive methods and were struggling a bit there. The easiest way to write a recursive method is to strictly follow a pattern:

Result M(Problem prob)
{
    if (<problem can be solved easily>)
        return <easy solution>;
    // The problem cannot be solved easily.
    Problem smaller1 = <reduce problem to smaller problem>
    Result result1 = M(smaller1);
    Problem smaller2 = <reduce problem to smaller problem>
    Result result2 = M(smaller2);
    ...
    Result finalResult = <combine all results of smaller problem to solve large problem>
    return finalResult;
}

So suppose you want to solve the problem "what is the maximum depth of my binary tree?"

int Depth(Tree tree)
{
    // Start with the trivial case. Is the tree empty?
    if (tree.IsEmpty) return 0;
    // The tree is not empty. 
    // Reduce the problem to two smaller problems and solve them:
    int depthLeft = Depth(tree.Left);
    int depthRight = Depth(tree.Right);
    // Now combine the two solutions to solve the larger problem.
    return Math.Max(depthLeft, depthRight) + 1;
}

You need three things to make recursion work:

  • The problem has to get smaller every time you recurse.
  • The problem has to eventually get so small that it can be solved without recursion
  • The problem has to be solvable by breaking it down into a series of smaller problems, solving each one, and combining the results.

If you cannot guarantee those three things then do not use a recursive solution.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Of course, for the third prerequisite, for some problems, the series may never have more than one element. An example would be the length of an immutable list: `int Length { get { return this.IsEmpty ? 0 : this.Tail.Length + 1; } }` – phoog Feb 16 '12 at 18:30
6

You're right, recursion is the right approach here. Something like this should work:

public DateTime FindTaskStartDate(Task task)
{
    DateTime startDate = task.Start;

    foreach (var t in task.Tasks)
    {
        var subTaskDate = FindTaskStartDate(t);
        if (subTaskDate < startDate)
            startDate = subTaskDate;
    }

    return startDate;
}

I removed the check for task.HasSubTasks(), because it only makes the code more complicated without any added benefit.

If you find your often write code that needs to walk all of the tasks in the tree, you might want to make this more general. For example, you could have a method that returns IEnumerable<Task> that returns all the tasks in the tree. Finding the smallest start date would then be as easy as:

IterateSubTasks(task).Min(t => t.Start)
svick
  • 236,525
  • 50
  • 385
  • 514
  • Exactly how I wrote it, though subTaskDate should probably be of type DateTime :) – Ryan Gibbons Feb 16 '12 at 02:17
  • 1
    @Ryan Gibbons: You can use [var](http://msdn.microsoft.com/en-us/library/bb383973.aspx) without specifying the actual type and it will work just fine. It's type is determined implicitly. – Bernard Feb 16 '12 at 02:21
  • @Bernard: Yeah, but I like to explicitly define it, especially when doing comparisons. IMO makes the code more readable, and you aren't relying on the compiler to determine the type and possibly a make a strange comparison. Granted, which shouldn't happen. – Ryan Gibbons Feb 16 '12 at 02:23
  • @Ryan Gibbons: I prefer being explicit as well. :) – Bernard Feb 16 '12 at 02:27
  • Good answer on the subject of implicit vs explicit. http://stackoverflow.com/a/545647/53715 I still prefer explicit at all the time. – Ryan Gibbons Feb 16 '12 at 02:27
0

Separating iteration over tree from search may be beneficial if there are other tasks you want to do on all items. I.e. if you implement IEnumerable over the tree items you can use LINQ queries to search for anything you want or perform other operations on all tasks in you tree. Check out Implementing IEnumerable on a tree structure for a way to do so.

Community
  • 1
  • 1
Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179