Python iterating through nested dictionaries

2019-08-12 03:54发布

First of all, here is the question and the code written:

def family_lineage(familytree, lineage):
    '''(dict, list of strs) -> boolean
    Return True if lineage specifies a list of names who are directly related
    in a chain of parent-child relationships, and NOT child-parent, parent-grandchild..etc,
    beginning from the first generation listed.

    >>> trace_lineage({'Gina': {'Sam': {'Tina': {}},
                           'Li': {}},
                   'Guy': {}},
                      ['Gina'])
    True

    >>> trace_lineage({'Gina': {'Sam': {'Tina': {}},
                               'Li': {}},
                       'Guy': {}},
                      ['Gina', 'Sam', 'Tina'])
    True
    >>> trace_lineage({'Gina': {'Sam': {'Tina': {}},
                               'Li': {}},
                       'Guy': {}},
                      ['Gina', 'Tina'])
    False
    '''

So, in the above example, it shows the 'Guy' has no children, and 'Gina' has two children, 'Sam', and 'Li'. 'Sam' has one child, 'Tina'.

for k, v in familytree.items():
    for n, m in v.items():
        if lineage[0] == any(k) and len(lineage) == 1:
            return True
        elif lineage[0] == k and lineage[1] == n and len(lineage) ==2:
            return True
        elif lineage[0] == k and lineage[1] == n and lineage[2] == m and \
        len(lineage) == 3:
            return True
        else:
            return False

So, my question is, how would I write this if the familytree extended beyond three generations? Is there a more concise way of writing this code?

2条回答
劫难
2楼-- · 2019-08-12 04:44

Here is an iterative approach that will work even if lineage does not start at the top of the family tree:

def family_lineage(familytree, lineage):
    trees = [familytree]
    while trees:
        tree = trees.pop()
        trees.extend(t for t in tree.values() if t)
        for name in lineage:
            if name not in tree:
                break
            tree = tree[name]
        else:
            return True
    return False
查看更多
来,给爷笑一个
3楼-- · 2019-08-12 04:52

Basically, you want to see if you can traverse the tree; use reduce() to loop over the elements and if a KeyError is raised, the path doesn't exist:

def family_lineage(familytree, lineage):
    if not familytree:
        return False
    try:
        reduce(lambda d, k: d[k], lineage, familytree)
        return True
    except KeyError:
        # No match at this level, recurse down the family tree
        return any(family_lineage(val, lineage) for val in familytree.itervalues())

reduce() applies the lambda function recursively to lineage, starting with familytree.

To support finding lineages deeper down the tree, you need to recurse down the tree on KeyErrors.

Demo:

>>> tree = {'Gina': {'Sam': {'Tina': {}}, 'Li': {}}, 'Guy': {}}
>>> family_lineage(tree, ['Gina'])
True
>>> family_lineage(tree, ['Gina', 'Sam', 'Tina'])
True
>>> family_lineage(tree, ['Gina', 'Tina'])
False
>>> family_lineage(tree, ['Sam', 'Tina'])
True
查看更多
登录 后发表回答