python-3.x 如何在没有递归的情况下扁平化嵌套字典?

hiz5n14c  于 2023-05-30  发布在  Python
关注(0)|答案(5)|浏览(111)

我遇到了这个函数,它可以使字典变平:

def flatten(dictionnary, container=None):
    if container is None:
        container = []
    for k, v in dictionnary.items():
        container.append(k)
        if v:
            flatten(v, container)
    return container

为了测试它,我创建了一个嵌套n次的字典,如下所示:

nesteddict = {}
for i in range(n, 0, -1):
    emptydict = {}
    emptydict[i] = nesteddict
    nesteddict = emptydict

n小于999时,函数工作,否则它会达到递归极限:

RecursionError: maximum recursion depth exceeded while calling a Python object

因此,经过一点搜索,似乎any recursive function can rewritten to iteration,但我不能看到它如何可以做的功能,我必须产生相同的结果。
我在玩这个的时候遇到的另一个奇怪的问题是,如果我尝试下面的n >= 998代码:

nesteddict = {}
for i in range(n, 0, -1):
    emptydict = {}
    emptydict[i] = nesteddict
    nesteddict = emptydict
print(nesteddict)

我得到递归错误:

RecursionError: maximum recursion depth exceeded while getting the repr of an object

这很奇怪,因为我在这里没有看到任何递归。

guykilcj

guykilcj1#

你应该把项的迭代器保存在堆栈中,而不是把字典保存在堆栈中。
这样,您就可以在命令下恢复迭代器。
另外,因为你是按顺序暂停和恢复迭代器的执行,所以结果总是按照指令的顺序。
顺便说一下,@iBug,dicts是按照Python 3.7的规范排序的

def flatten(dictionary, container=None):
    if container is None:
        container = []
    iterators = []
    iterator = iter(dictionary.items())
    while True:
        for k, v in iterator:
            container.append(k)
            if v:
                # Save the current iterator for later
                iterators.append(iterator)
                # Run on the new dict
                iterator = iter(v.items())
                break

        # Current iterator is done, fetch the next one
        else:
            try:
                iterator = iterators.pop()
            except IndexError:
                return container

print(flatten({1: None, 2: {3: None, 4: None}, 5: None}))
[1, 2, 3, 4, 5]
lrpiutwd

lrpiutwd2#

从逻辑上讲,嵌套字典(和列表)是一种递归,所以如果你想避免逻辑递归,那是不可能的。
但是,由于递归只是递归,你可以保留一个自己的堆栈并在循环中模拟它:

def flatten(dct, c=None):
    if c is None:
        c = []
    stack = [dct]
    while stack:  # non-empty
        d = stack.pop()
        for k, v in d.items():
            c.append(k)
            if v:
                stack.append(v)
    return c

这个函数很好地模拟了函数递归的行为,使用了一个自定义堆栈。
有一个潜在的缺点:从理论上讲,像这样的字典

{1: None, 2: {3: None, 4: None}, 5: None}

应该被展平为[1, 2, 3, 4, 5],而这种方法将给予[1, 2, 5, 3, 4]。这很像图上的DFS搜索与BFS搜索。

但是,因为dictionary是无序的,这应该不是什么大问题(除非你使用collections.OrderedDict),这就是为什么我说这是一个潜在的缺点。

xu3bshqb

xu3bshqb3#

变平可以有不同的解释;如果您想将一个字典扁平化为一个带有点分隔键字典,那么这种方法可能会有效:

def flatten(it=None, sep="."):

    ot = {}

    if isinstance(it, dict):
        stack = list(it.items())[::-1]
    elif isinstance(it, list):
        stack = list(enumerate(it))[::-1]

    while stack:

        head = stack.pop()

        if isinstance(head[1], dict):
            stack = stack + [(f'{head[0]}{sep}{item[0]}', item[1]) for item in head[1].items()][::-1]
        elif isinstance(head[1], list):
            stack = stack + [(f'{head[0]}{sep}{item[0]}', item[1]) for item in enumerate(head[1])][::-1]
        else:
            ot[head[0]] = head[1]

    return ot

给定输入:{'b': 2, 'a': 1, 'c': {'a': 1, 'b': [1, 2, 3]}, 'd': [1, 2, {'b': 1, 'a': 2}]}
输出为:

{'b': 2,
 'a': 1,
 'c.a': 1,
 'c.b.0': 1,
 'c.b.1': 2,
 'c.b.2': 3,
 'd.0': 1,
 'd.1': 2,
 'd.2.b': 1,
 'd.2.a': 2}
dgjrabp2

dgjrabp25#

如果你想不使用递归来实现它,那是不可能的。
这里是RecursionError的解决方案。
基于doc of python.你可以使用sys.getrecursionlimit()来查看递归的限制。您也可以使用sys.setrecursionlimit()来设置上限。

相关问题