【问题标题】:How to calculate employees reporting to a manager recursively using LINQ?如何使用 LINQ 计算递归地向经理报告的员工?
【发布时间】:2015-04-08 18:48:01
【问题描述】:

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

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

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

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

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() });

【问题讨论】:

  • 似乎您应该将数据视为邻接列表并构造一棵树,然后您可以遍历树并获取每个节点的子节点数(除其他外)。
  • 您的报告图表有一个循环:“F”报告给“F”。

标签: c# linq


【解决方案1】:

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

您的规范可以写成(这是对引用文本的简单改编):

Employee 所依赖的完整报告集是 direct-reports-to 关系的传递闭包。

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

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,
});

【讨论】:

  • 按预期工作。谢谢
【解决方案2】:

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

看起来像这样:

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();

当然,它对于更简单的任务会更有用,像这样:

    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));

【讨论】:

  • 唯一的事情是 ToList() 返回一个整数列表,而不是员工的字典及其预期的计数
猜你喜欢
  • 2019-04-23
  • 1970-01-01
  • 2021-07-09
  • 1970-01-01
  • 1970-01-01
  • 2015-10-23
  • 1970-01-01
  • 2022-01-23
  • 1970-01-01
相关资源
最近更新 更多