Katkat

Print an Org Chart as a Depth-Ordered List from Supervisor Pairs

SnowflakeSoftware Engineer · Onsite

Last updated

Given a flat list of (person, supervisor) ID pairs, reconstruct the organization as a forest rooted at whoever has no supervisor, then emit a deterministic preorder listing of (id, depth). Tests root detection, building ordered adjacency lists, and careful DFS ordering.

You're given a flat list of supervisor links for a company's staff directory. Each entry is a pair (person_id, supervisor_id), both positive integers, meaning person_id reports directly to supervisor_id. Someone who never shows up as a person_id in any pair has no supervisor and sits at the top of their own chain. Several people can each head an independent chain, so the whole structure is a forest, not a single tree.

Produce a flattened, depth-ordered view of the whole staff directory suitable for rendering as an indented chart.

def org_forest_preorder(links: list[tuple[int, int]]) -> list[tuple[int, int]]:
    """Return one (id, depth) pair per distinct person, in display order."""

Rules

  • A person at the top of a chain (never appears as person_id) has depth 0. Everyone else has one more than their direct supervisor's depth.
  • Every distinct ID that appears anywhere in links must appear exactly once in the output.
  • Output order is a preorder walk: sort the chain tops by ID and visit them lowest first; within a chain, after listing a person, recurse fully into one direct report's subtree before moving on to the next, picking which direct report goes first by comparing their IDs lowest to highest.
  • Each result entry is a (id, depth) tuple.

Constraints

  • 1 <= len(links) <= 10**5, IDs up to 10**9.
  • Each person appears as person_id in at most one pair (a single direct supervisor).
  • No cycles: tracing supervisors upward from any starting person is guaranteed to terminate at a chain top.
  • Nothing caps how many generations one chain may have short of the total number of links supplied.

Example

Input:  links = [(30, 10), (40, 10), (50, 30), (60, 30), (70, 50), (90, 80)]
Output: [(10, 0), (30, 1), (50, 2), (70, 3), (60, 2), (40, 1), (80, 0), (90, 1)]

10 and 80 never appear as a person_id, so they're the two chain tops, visited in ascending order: 10 before 80. Under 10, direct reports 30 and 40 are visited in that order; 30's whole subtree (30, then 50, then 50's child 70, then 50's sibling 60) comes entirely before 40. The chain under 80 is just 80 then 90.

A second check: links = [(200, 100), (300, 200), (400, 200), (500, 300)] should produce [(100, 0), (200, 1), (300, 2), (500, 3), (400, 2)] — a single four-generation chain with one extra branch at generation 2.

Hints

Hint 1

First pass: collect every ID that appears, and every ID that appears as a person_id (has a supervisor). The difference between those two sets is your root list.

Hint 2

Build a supervisor-to-direct-reports map, sorting each supervisor's report list once up front so you never have to sort during the walk.

Hint 3

The output order is exactly what a recursive DFS naturally produces if you visit roots in ascending order and each node's children in ascending order.

Hint 4

Depth is just 'how many visit() calls deep' — pass it down as a parameter rather than computing it separately.

Follow-up questions

  • How would you answer 'what is person X's depth' or 'list person X's full management chain to the top' without rebuilding the whole forest each time?
  • How would the algorithm need to change if a person could report to two different supervisors (a DAG instead of a forest)?
  • Given 10^5 links with a chain depth near 10^5, how would you avoid a Python recursion-limit crash?
  • How would you incrementally update the output if one supervisor link changes, without recomputing the whole forest?