简体   繁体   中英

Get All Parents Child-Parent Relationship

I am receiving the following list (which is more like a join table) from external source:

Note that there are cases where a person reports to more than one. In this sample C001.

List<DirectReport> list = new List<DirectReport>()
{
    new DirectReport(){ EmployeeId = "B001", ReportsTo = "A001" },
    new DirectReport(){ EmployeeId = "B002", ReportsTo = "A001" },
    new DirectReport(){ EmployeeId = "B003", ReportsTo = "A002" },
    new DirectReport(){ EmployeeId = "B004", ReportsTo = "A003" },
    new DirectReport(){ EmployeeId = "C001", ReportsTo = "B001" },
    new DirectReport(){ EmployeeId = "C001", ReportsTo = "B003" },
    new DirectReport(){ EmployeeId = "C002", ReportsTo = "B002" },
    ...
};

To get all immediate superiors for C001, I came up with the following:

IEnumerable<string> listC001sSuperiors = list.Where(x => x.EmployeeId == "C001").Select(y => y.ReportsTo);

This yields:

"B001"
"B003"

How do I include all superiors including his immediate superior's superiors and so on?

Desired result for C001:

"B001"
"B003"
"A001"
"A002"

The accepted answer works, but this solution is inefficient if the list of reports is large or if the number of queries run against it is large. It also allocates a large number of sub-lists in the worst cases, which produces collection pressure. You can do much better.

To make an efficient solution, the first thing to do is to make a better data structure:

static IDictionary<string, IEnumerable<string>> ToDictionary(
  this List<DirectReport> reports)
{
  // Fill this in
}

What we want here is a multidictionary . That is, given the id of a report, the dictionary returns a sequence of direct managers. It should be straightforward to implement this data structure, or, there are third party implementations available in various packages. Note that we're assuming that the multidictionary returns an empty sequence if an id has no managers, so make sure that invariant is maintained.

Once we have that, then we can make a traversal algorithm :

static IEnumerable<T> BreadthFirst(
  T item,
  Func<T, IEnumerable<T>> children
)
{
  var q = new Queue<T>();
  q.Enqueue(item);
  while(q.Count != 0)
  {
    T t = q.Dequeue();
    yield return t;
    foreach(T child in children(t))
      q.Enqueue(child);
  }
}

Note that this solution is not recursive . The stack consumption of this answer is constant, not dependent on the topology of the graph.

Now that we have these two tools, your problem can be solved in a straightforward manner:

var d = reports.ToDictionary();
var r = BreadthFirst("C001", x => d[x]).Skip(1);

The "skip one" removes the item from the sequence, since you want the transitive closure of the manager relation, not the transitive reflexive closure.

Exercise: Suppose the graph can contain a cycle. Can you modify BreadthFirst to detect and skip enumerating a cycle the second time it is encountered? Can you do it in four (or fewer) lines of new code?

Exercise: Implement DepthFirst similarly.

Tested in DotNetFiddle with list as static.
Tested in DotNetFiddle with list as variable.

You can use a recursive function to look up managers until you can no longer get one. Following is one way of looking up the entire tree. If you can make list of direct reports static, you wont have to pass it around.

    public static List<DirectReport> list = new List<DirectReport>()
     {
      new DirectReport() { EmployeeId = "B001", ReportsTo = "A001"},
      new DirectReport(){EmployeeId = "B002", ReportsTo = "A001"},
      new DirectReport() {EmployeeId = "B003", ReportsTo = "A002"},
      new DirectReport() {EmployeeId = "B004", ReportsTo = "A003"},
      new DirectReport() {EmployeeId = "C001", ReportsTo = "B001"},
      new DirectReport() {EmployeeId = "C001", ReportsTo = "B003"},
      new DirectReport() {EmployeeId = "C002", ReportsTo = "B002"},
      new DirectReport() {EmployeeId = "A002", ReportsTo = "C001"},
     };

    public class DirectReport
    {
        public string EmployeeId { get; set; }
        public string ReportsTo { get; set; }
    }

    public static void ReportsTo(string employeeId, List<string> results)
    {
        var managers = list.Where(x => x.EmployeeId.Equals(employeeId)).Select(x => x.ReportsTo).ToList();
        if (managers != null && managers.Count > 0)
            foreach (string manager in managers)
            {
                if (results.Contains(manager))
                    continue;

                results.Add(manager);
                ReportsTo(manager, results);
            }

    }

and you would use the above in main like,

    List<string> results = new List<string>();
    ReportsTo("C001", results);
    Console.WriteLine(string.Join(Environment.NewLine, results));

Output

B001
A001
B003
A002

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM