Home > Software engineering >  Iterate through a Nested Python Dictionary?
Iterate through a Nested Python Dictionary?

Time:10-07

I am trying to iterate through a multi-level, and varied-level dictionary. I have an org char (dictionary) with the keys as people names, and values as either None or a dictionary with another org chart.

If the name has any values other than None they're a "leader", otherwise, they're "non-leader". Every first-level dictionary names are the leader's direct reports, and further through the nested dictionaries are the leader's indirect reports.

Given a name (string) and an org_chart (nested dictionary), I'd like to implement a function lookup_info, that outputs whether the name is a "leader" or "non-leader", how many direct reports they have, and how many indirect reports they have.

I'm not new to Python, but I'm stuck at the traversing (is this BFS or DFS) and how to use recursion here.

Here's an example:

ORG_CHART = {

"chloe" : {
        "mark" : {
            "tim" : {
                "varun" : None,
                "misha" : None
                }
            },
        "julie" : None
        },
"annie" : {
        "jimmy" : {
            "haily" : None,
            "alex" : None
        },
        "helen" : {
            "miley" : {
                "stella" : {
                    "allie" : None
                },
                "tom" : None
            }
        }
},
"jenny" : None
}


def lookup(name, org_chart):
#TODO
return leadership_type, num_direct_reports, num_total_reports


name = "annie" should output: "leader", 2, 8 because annie has people reporting to her so she's a leader. She has 2 people directly in her values dict, so 2 direct reports, and 8 total people working under her.

name = "tom" should output: "non-leader", 0, 0

name = "miley" should output "leader", 2, 3

I've tried the following to recurse the nested dictionaries (not actually getting any info yet, but just trying to find the values in the dictionaries first, because the counting is the next step), but I'm stuck at this very first step. It's returning None of course if it hits any level above the first level that doesn't have a dictionary as it's values.

def lookup_info(name, org_chart):

    def get_name_value(name, org_chart):
        if org_chart != None:
            for k, v in org_chart.items():
                if k == name:
                    return k
                else:
                    if v != None:
                        for i, j in v.items():
                            get_name_value(name, j)

    name = get_name_value(name, org_chart)

    return name

Any ideas please let me know, thank you!

CodePudding user response:

A quick and dirty solution would be to define some other helper functions, and recurse through the org_chart object:

def lookup(name: str, org_chart: dict):
    it = _in(name, org_chart)
    if not it:
        return 'non-leader', 0, 0
    return 'leader', len(it), _count(it)


def _count(o: dict):
    if not o:
        return 0
    return len(o)   sum(_count(v) for v in o.values())


def _in(name: str, o: dict):
    if not o:
        return None
    if name in o:
        return o[name]
    for v in o.values():
        res = _in(name, v)
        if res:
            return res
    return None

Usage:

ORG_CHART = {
    "chloe": {
        "mark": {
            "tim": {
                "varun": None,
                "misha": None
            }
        },
        "julie": None
    },
    "annie": {
        "jimmy": {
            "haily": None,
            "alex": None
        },
        "helen": {
            "miley": {
                "stella": {
                    "allie": None
                },
                "tom": None
            }
        }
    },
    "jenny": None
}

print(lookup('annie', ORG_CHART))
print(lookup('miley', ORG_CHART))
print(lookup('tom', ORG_CHART))

Out:

('leader', 2, 8)
('leader', 2, 3)
('non-leader', 0, 0)

CodePudding user response:

Recursion is a bit tricky to wrap the brain around, so here is a more verbose option which takes a naïve step-by-step through the process in a human readable manner.

Note that this does not account for duplicates or other edge cases and is certainly neither the shortest or most efficient method, but I think it should serve you well as a good jumping off point.

def lookup_info(name, org_chart):
    # some local variables to hold our results
    leadership_type = None
    num_direct_reports = 0
    num_total_reports = 0

    def helper(obj, depth):
        # declare our intent to use the variables from above as nonlocals
        nonlocal leadership_type, num_direct_reports, num_total_reports

        # if the value is None, we hit a leaf and should return
        if obj is None:
            return

        # otherwise, iterate through the key value pairs
        for key, value in obj.items():
            # if we see the name in the keys, we proceed accordingly
            if key == name:
                if value is not None:
                    # mark as a leader, and recurse with increased depth noted in param
                    leadership_type = "leader"
                    helper(obj=value, depth=depth   1)
                else:
                    # otherwise this is a non-leader, we can return
                    leadership_type = "non-leader"
                    return
            elif depth == 1:
                # at depth 1 we handle direct subordinates, and recurse
                num_direct_reports  = 1
                num_total_reports  = 1
                helper(obj=value, depth=depth   1)
            elif depth > 1:
                # at depth > 1 we handle indirect subordinates, and recurse
                num_total_reports  = 1
                helper(obj=value, depth=depth   1)
            else:
                # here we continue checking deeper into the structure to cover all other cases from depth 0
                helper(obj=value, depth=0)

    # run our helper with the appropriate defaults to start
    helper(obj=org_chart, depth=0)

    # finally return the thruple
    return leadership_type, num_direct_reports, num_total_reports

CodePudding user response:

If computational cost is a matter and you expect to call your function with a significant number of individuals, I would rather advise to scan one time the whole org_chart and save the resulting values in a new dict like so:

def reports(d):
    ret = {}
    total_reports = 0
    for k in d: # scan children one at the time
        if d[k] is None: # if None, direct and indirect reports are 0
            ret[k] = [0, 0]
            total_reports  = 1
        else:
            d_k, reports_son = reports(d[k]) # recursion on the son
            ret = {**ret, **d_k} # merge final dict and son's one
            ret[k] = [len(d[k]), reports_son - len(d[k])]
            total_reports  = reports_son   1 
    return ret, total_reports

The results are computed, which outputs a directory of the form name -> [direct reports, indirect reports]. Note that I did not add the "leader" field because it is redundant with a direct reports number of 0.

mapping = reports(ORG_CHART)[0]
print(mapping)

outputs:

{'varun': [0, 0],
 'misha': [0, 0],
 'tim': [2, 0],
 'mark': [1, 2],
 'julie': [0, 0],
 'chloe': [2, 3],
 'haily': [0, 0],
 'alex': [0, 0],
 'jimmy': [2, 0],
 'allie': [0, 0],
 'stella': [1, 0],
 'tom': [0, 0],
 'miley': [2, 1],
 'helen': [1, 3],
 'annie': [2, 6],
 'jenny': [0, 0]}

And you can access the results in O(1).

  • Related