如何计算使用LINQ递归报告给经理的员工?

时间:2015-04-08 18:48:02

标签: c# linq

我有一本字典,其中包含员工和他/她的经理的映射

Dictionary<string, string> employees = new Dictionary<string, string>()
{
    { "A","C" },
    { "B","C" },
    { "C","F" },
    { "D","E" },
    { "E","F" },
    { "F","F" }
};

我希望层次结构中每个经理下的员工不仅仅是他们的直接下属,而是层次结构链。 在上面的字典中,根节点/ ceo被列为向自己报告。这是唯一保证具有这种自我关系的节点。

如何找到向每位经理报告的员工总数。输出应该是

A - 0
B - 0
C - 2
D - 0
E - 1
F - 5

这是我到目前为止所尝试的内容,但它只提供了直接报告的计数而不是链中的所有报告

var reports = employees
    .GroupBy(e => e.Value, (key, g) => new { employee = key, reports = g.Count() });

2 个答案:

答案 0 :(得分:1)

您描述的问题几乎与this blog post中描述的问题相同。

你的规范可以写成(这是引用文本中的一个微不足道的改编):

  

员工所依赖的完整报告是直接报告与关系的传递关闭。

然后帖子继续提供以下代码来计算特定关系的传递闭包:

static HashSet<T> TransitiveClosure<T>(
    this Func<T, IEnumerable<T>> relation,
    T item)
{
    var closure = new HashSet<T>();
    var stack = new Stack<T>();
    stack.Push(item);
    while (stack.Count > 0)
    {
        T current = stack.Pop();
        foreach (T newItem in relation(current))
        {
            if (!closure.Contains(newItem))
            {
                closure.Add(newItem);
                stack.Push(newItem);
            }
        }
    }
    return closure;
}

所以剩下的就是提供直接报告关系的代码。

这可以通过从您的字典中创建查找,将每个员工映射到他们的报告来轻松计算:

var directReportLookup = employees.ToLookup(pair => pair.Value, pair => pair.Key);

Func<string, IEnumerable<string>> directReports =
    employee => directReportLookup[employee];

employees.Select(pair => new
{
    Employee = pair.Key,
    Count = directReports.TransitiveClosure(pair.Key).Count,
});

答案 1 :(得分:0)

您可能对匿名方法中的递归感兴趣。函数式语言有一种有趣的方法:定点组合器。

看起来像这样:

public static class Combinator
{
    public static Func<TInput, TResult> Y<TInput, TResult>(Func<Func<TInput,TResult>, TInput, TResult> function)
    {
        return input => function(Y(function), input);
    }
}

可以像这样使用:

    var result = employees
        .Select(employee => Combinator.Y<string, int>
            (
                (f, e) => employees.Where(x => x.Value == e && x.Value != x.Key)
                    .Aggregate(employees.Count(x => x.Value == e && x.Value != x.Key), (current, next) => current + f(next.Key))
            )
            .Invoke(employee.Key))
        .ToList();

当然,它对simler任务更有用,例如:

    var fact = Combinator.Y<int, int>((f, n) => n > 1 ? n * f(n - 1) : 1);
    var fib = Combinator.Y<uint, int>((f, n) => n > 2 ? f(n - 1) + f(n - 2) : (n == 0 ? 0 : 1));