如何动态地将条件和方法传递给递归方法

时间:2011-10-24 15:39:46

标签: c# c#-4.0 methods action predicate

我想制作一个像这样的方法,

public dynamic Traverse(dynamic entity, conditions, method)
{
    foreach (var propInfo in GetTraversableProperties(entity))
    {
        if (condition) method(propInfo.GetValue(etc));
        Traverse(propInfo, condition, method);
    }
    return entity;
}

我该怎么做?将条件和方法作为参数传递的语法是什么?另外,将条件设为方法并检查它的返回值是否更有意义?

2 个答案:

答案 0 :(得分:12)

你的方法,以及加里的回答都是完美合理的方法来重新理解对象图的递归遍历的抽象概念。但是,我看到了四个潜在的问题。不知道你的确切场景,也许这些对你来说不是问题,或者你应该考虑它们:

首先,假设您正在遍历的图表具有极长的路径。您隐式执行深度优先遍历,即使在支持尾递归的体系结构上也无法轻松地使尾递归,因此您可能会面临调用堆栈用完的风险。

其次,你假设图是非循环的;如果图形是循环的,那么你肯定会耗尽堆栈空间。

第三,我不明白为什么遍历算法返回一个实体。为什么这个方法无效?或者,如果您使用return作为累加器来累积遍历计算的值,那么为什么递归步骤不会对返回的实体执行某些操作?

第四,你似乎在这里关注的问题很糟糕。调用者负责确定(1)图的根是什么,(2)如何处理每个节点。但被调用者负责(3)确定要递归的对象。这对我来说似乎很奇怪。来电者提供了起点;不应该打电话的人如何继续说话?

我通常按如下方式解决这个问题:

  • 使用在堆上分配的显式堆栈而不是使用控制流的调用堆栈
  • 跟踪之前访问过的节点,不再重新访问它们
  • 让调用者确定对象何时具有可遍历的子节点。如果调用者希望遍历在基本情况下“触底”,则调用者可以返回一组空子。

如果我想要一个累加器,我可能会实现这样的草图:

static R DepthFirstGraphAccumulate<T, R>(
    T root, 
    Func<T, IEnumerable<T>> children, 
    Func<T, R, R> accumulate)
{
    var accumulator = default(R);
    var visited = new HashSet<T>();
    var stack = new Stack<T>;
    stack.Push(root);
    while(stack.Count != 0)
    {
        var current = stack.Pop();
        if (!visited.Contains(current))
        {
            visited.Add(current);
            foreach(var child in children(current))
                stack.Push(child);
            accumulator = accumulate(current, accumulator);
        }
    }
    return accumulator;
}

所以,例如,如果我有一个整数图,我希望总结从特定起始节点可到达的节点,我会说:

int total = DepthFirstGraphAccumulate<Node, int>(
    startNode, 
    node=>node.NeighbouringNodes, 
    (node, sum)=>node.Value + sum);

然而,我很想在“让我们分开我们关注的问题”的道路上更进一步说,嘿,让我们写一个抽象的遍历:

static IEnumerable<T> DepthFirstGraphTraversal<T>(
    T root, 
    Func<T, IEnumerable<T>> children)
{
    var visited = new HashSet<T>();
    var stack = new Stack<T>;
    stack.Push(root);
    while(stack.Count != 0)
    {
        var current = stack.Pop();
        if (!visited.Contains(current))
        {
            visited.Add(current);
            foreach(var child in children(current))
                stack.Push(child);
            yield return current;
        }
    }
}

现在如果我想对图中的每个节点做一些动作,我只想说:

foreach(var node in DepthFirstGraphTraversal<Node>(startNode, n=>n.NeighbouringNodes))
     DoSomething(node);

如果我想表达“对符合条件的每个节点做点什么”的概念,那么我会写:

var nodes = from node in DepthFirstGraphTraversal<Node>(startNode, n=>n.NeighbouringNodes)
            where condition(node)
            select node;
foreach(var matchingNode in nodes) DoSomething(matchingNode);

答案 1 :(得分:4)

我认为使用Func委托条件是有道理的。至于如何传递方法,这将再次使用委托。我会做类似的事情:

public dynamic Traverse(dynamic entity, Func<dynamic, bool> conditions, Action<dynamic> method)
{
    foreach (var propInfo in GetTraversableProperties(entity))
    {
        if (conditions(entity)) method(propInfo.GetValue(etc));
        Traverse(propInfo, conditions, method);
    }
    return entity;
}

Func:http://msdn.microsoft.com/en-us/library/bb549151.aspx

行动:http://msdn.microsoft.com/en-us/library/018hxwa8.aspx