Jonathan's profileJonathan Chayce Dickinso...Blog Tools Help

Blog


    10 March

    Trees and Anonymous Delegates

    Trees and Anonymous Delegates

    Something that I found to be really useful with the new .Net 2.0 anonymous delegates is "actioning".

    Scenario

    I had a situation where I was recursing through a tree to perform some action against all the items. Lets set up a simple tree node:

        public class TreeItem : List<TreeItem>
        {
            public string Text;
    
            public TreeItem(string text)
            {
                Text = text;
            }
        }

    The old way

    Using the old naive way we could do the following:

        static class Program
        {
            static TreeItem RootNode = new TreeItem("Root");
    
            static void Main()
            {
                // Fill the tree with useless data.
                RootNode.Add(new TreeItem("Node 1"));
                RootNode[0].Add(new TreeItem("Node 1.1"));
                RootNode[0].Add(new TreeItem("Node 1.2"));
    
                RootNode.Add(new TreeItem("Node 2"));
                RootNode[1].Add(new TreeItem("Node 2.1"));
                RootNode[1].Add(new TreeItem("Node 2.2"));
    
                // Do the actions, the old way.
                TreeItem[] items = FlattenTree();
                foreach (TreeItem item in items)
                    Console.WriteLine(item.Text);
    
                if (System.Diagnostics.Debugger.IsAttached)
                    Console.ReadLine();
            }
    
            static TreeItem[] FlattenTree()
            {
                TreeItem newItems = new TreeItem("Nothing");
    
                FlattenTree(newItems, RootNode);
    
                return newItems.ToArray();
            }
    
            private static void FlattenTree(TreeItem newItems, 
                                            TreeItem node)
            {
                newItems.Add(node);
                foreach (TreeItem item in node)
                    FlattenTree(newItems, item);
            }
        }

    As you can see, there are two loops occuring here (once to recurse the tree, and once to action all the items). That is far from optimal.

    The new way

    Let's see how we can optimize this further. Using anonymous delegates we can action all the items in the tree in a very clean fashion with little code changes required.

        static class Program
        {
            static TreeItem RootNode = new TreeItem("Root");
    
            static void Main()
            {
                // Fill the tree with useless data.
                RootNode.Add(new TreeItem("Node 1"));
                RootNode[0].Add(new TreeItem("Node 1.1"));
                RootNode[0].Add(new TreeItem("Node 1.2"));
    
                RootNode.Add(new TreeItem("Node 2"));
                RootNode[1].Add(new TreeItem("Node 2.1"));
                RootNode[1].Add(new TreeItem("Node 2.2"));
    
                // Do the actions, the new way.
                ActionTree(delegate(TreeItem item)
                {
                    Console.WriteLine(item.Text);
                });
    
                if (System.Diagnostics.Debugger.IsAttached)
                    Console.ReadLine();
            }
    
            static void ActionTree(Action<TreeItem> action)
            {
                ActionTree(action, RootNode);
            }
    
            private static void ActionTree(Action<TreeItem> action, 
                                           TreeItem node)
            {
                action(node);
                foreach (TreeItem item in node)
                    ActionTree(action, item);
            }
        }

    Now the tree is only being recursed, no iterating and assigning a useless array.

    Breaking Out the Generic Solution

    Now we need the ability to break out of the recursion. First we need to define a delegate.

        public delegate bool ContinueAction<T>(T t);

    Next we need to make the iteration process. Enter extension methods, after further tweaking I acheived the following:

        public class TreeItem : List<TreeItem>
        {
            public string Text;
    
            public TreeItem(string text)
            {
                Text = text;
            }
        }
    
        public delegate bool ContinueAction<T>(T t);
    
        static class Program
        {
            static TreeItem RootNode = new TreeItem("Root");
    
            static void Main()
            {
                // Fill the tree with useless data.
                RootNode.Add(new TreeItem("Node 1"));
                RootNode[0].Add(new TreeItem("Node 1.1"));
                RootNode[0].Add(new TreeItem("Node 1.2"));
    
                RootNode.Add(new TreeItem("Node 2"));
                RootNode[1].Add(new TreeItem("Node 2.1"));
                RootNode[1].Add(new TreeItem("Node 2.2"));
    
                // Do the actions, the generic way.
                RootNode.ActionTree<TreeItem>(delegate(TreeItem item)
                {
                    if (item.Text == "Node 2")
                        return false;
                    Console.WriteLine(item.Text);
                    return true;
                });
    
                if (System.Diagnostics.Debugger.IsAttached)
                    Console.ReadLine();
            }
    
            private static bool ActionTree<T>(this T node, ContinueAction<T> action) where T : IEnumerable<T>
            {
                if (!action(node))
                    return false;
                foreach (T item in node)
                {
                    if (!ActionTree<T>(item, action))
                        return false;
                }
                return true;
            }
        }

    And that's it! Those bound to .Net 2.0 can just remove the this from the extension method and call the method as follows:

        ActionTree<TreeItem>(RootNode, delegate(TreeItem item) {...});

    Hope this helps!!!