Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/309.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python中任意深度dict树的高效迭代_Python_Algorithm_Tree_Python 3.4 - Fatal编程技术网

Python中任意深度dict树的高效迭代

Python中任意深度dict树的高效迭代,python,algorithm,tree,python-3.4,Python,Algorithm,Tree,Python 3.4,我在字典中存储了以下树数据结构: 1 2 3 4 -> ["a", "b", "c"] 5 -> ["x", "y", "z"] 3 5 7 -> ["e", "f", "j"] 下面是我如何在Python中构建它的示例: tree = dict() for i in range(100): tree[i] = dict() for j in range(10):

我在字典中存储了以下树数据结构:

1
   2
      3
         4 -> ["a", "b", "c"]
         5 -> ["x", "y", "z"]
   3
      5
         7 -> ["e", "f", "j"]
下面是我如何在Python中构建它的示例:

tree = dict()
for i in range(100):
    tree[i] = dict()
    for j in range(10):
        tree[i][j] = dict()
        for k in range(10):
            tree[i][j][k] = dict()
            for l in range(10):
                tree[i][j][k][l] = dict()
                for m in range(10):
                    tree[i][j][k][l][m] = dict()
                    for n in range(10):
                        tree[i][j][k][l][m][n] = ["a", "b", "c", "d", "e", "f", "g"]
我想遍历它,在到达每片叶子时做一些计算。在进行计算时,我需要知道叶的路径

即给定回调

def callback(p1, p2, p3, p4, leaf):
    ...
我希望使用我的树示例对其进行如下调用:

callback(1, 2, 3, 4, ["a", "b", "c"])
callback(1, 2, 3, 5, ["x", "y", "z"])
callback(1, 3, 5, 7, ["e", "f", "j"])
问题:如何最有效地实现遍历?请注意,树深度不是静态的

def callback(*args):
    assert isinstance(args[-1], list)

start = time.time()
for k1, leafs1 in tree.items():
    for k2, leafs2 in leafs1.items():
        for k3, leafs3 in leafs2.items():
            for k4, leafs4 in leafs3.items():
                for k5, leafs5 in leafs4.items():
                    for k6, val in leafs5.items():
                        callback(k1, k2, k3, k4, k5, k6, val)
print("inline: %f" % (time.time() - start))
以下是我尝试过的:

1。内联代码。这是最快的,但在实践中不可用,因为树深度不是静态的

def callback(*args):
    assert isinstance(args[-1], list)

start = time.time()
for k1, leafs1 in tree.items():
    for k2, leafs2 in leafs1.items():
        for k3, leafs3 in leafs2.items():
            for k4, leafs4 in leafs3.items():
                for k5, leafs5 in leafs4.items():
                    for k6, val in leafs5.items():
                        callback(k1, k2, k3, k4, k5, k6, val)
print("inline: %f" % (time.time() - start))
在我的笔记本电脑上使用Python3.4.2平均运行3.5秒

2。递归方法

from functools import partial
def iterate_tree(tree, depth, callback):
    if depth:
        for k, subtree in tree.items():
            cb = partial(callback, k)
            yield from iterate_tree(subtree, depth-1, cb)
    else:
        for k, v in tree.items():
            rv = callback(k, v)
            yield rv

start = time.time()
for i in iterate_tree(tree, 5, callback):
    pass
print("iterate_tree: %f" % (time.time() - start))
这是通用的和所有的好,但2倍慢

3。非递归方法我认为可能是递归,
产生的收益和
部分
都在拖我的后腿。所以我试着让它变平:

def iterate_tree2(tree, depth, callback):
    iterators = [iter(tree.items())]
    args = []
    while iterators:
        try:
            k, v = next(iterators[-1])
        except StopIteration:
            depth += 1
            iterators.pop()
            if args:
                args.pop()
            continue

        if depth:
            args.append(k)
            iterators.append(iter(v.items()))
            depth -= 1
        else:
            yield callback(*(args + [k, v]))

start = time.time()
for i in iterate_tree2(tree, 5, callback):
    pass
print("iterate_tree2: %f" % (time.time() - start))
这是泛型的,可以工作,但与递归相比性能有所提高,即仍然比内联版本慢两倍

那么如何以通用方式实现遍历呢?是什么让内联版本更快?

另外,上面的代码是针对Python3.3+的。我已经将其应用于Python2,结果类似

解决方案和分析

我已经对所有的解决方案和优化进行了比较分析。代码和结果可从中获得

TL;博士最快的解决方案是使用基于循环的优化版本:

  • 它是最快的版本,支持从回调中方便地报告结果
  • 它的速度仅比内联版本慢30%(在Python3.4上)
  • 在PyPy上,它获得了惊人的速度提升,甚至超过了内联版本
在PyPy上运行时,基于循环的迭代拥有一切

在非pypy上,主要的减速是回调报告的结果:

  • yield
    ing结果与inline相比是最慢的-约30%的惩罚。循环版本见
    iterate_tree6
    ,递归版本见
    iterate_tree3
  • 通过从回调调用回调进行报告稍微好一点——比内联(在Python3.4上)慢17%。请参见
    iterate\u tree3\u noyield
  • 任何报告都不能比内联报告运行得更好。请参见
    iterate\u tree6\u nofeedback
对于基于递归的版本,使用元组进行参数累积,而不是列表。性能差异相当显著


感谢所有对此主题做出贡献的人。

我成功地将性能提高到内联版本和第一个递归版本之间的一半,我认为这是等效的

def iterate_tree_2(tree, depth, accumulator, callback):
    if depth:
        for k, subtree in tree.items():
            yield from iterate_tree_2(subtree, depth-1, accumulator + (k,), callback)
    else:
        for k, v in tree.items():
            rv = callback(accumulator + (k,), v)
            yield rv

>>> for i in iterate_tree_2(tree, depth, (), callback): pass
它有点不同,因为它使用

callback((1, 2, 3, 4), ["a", "b", "c"])
而不是

callback(1, 2, 3, 4, ["a", "b", "c"])

实现的不同之处在于它构建参数元组,而不是使用
partial
。我想这是有道理的,因为每次调用
partial
时,都会向回调中添加一个额外的函数调用层。

这里有一种递归方法,它的性能似乎比内联方法高出5-10%:

def iter_tree(node, depth, path):
    path.append(node)
    for v in node.values():
        if depth:
            iter_tree(v, depth-1, path)
        else:
            callback(path)
Iteration 1  21.3142
Iteration 2  11.2947
Iteration 3   1.3979
您可以通过以下方式拨打:

iter_tree(tree, 5, [])
根据您的评论,编辑类似的方法,但保留关键点:

def iter_tree4(node, depth, path):
    for (k,v) in node.items():
        kpath = path + [k]
        if depth:
            iter_tree4(v, depth-1, kpath)
        else:
            callback(kpath, v)
我也这么叫

请注意,我们失去了仅仅跟踪值所带来的性能增益,但它仍然与您的内联方法具有竞争力:

def iter_tree(node, depth, path):
    path.append(node)
    for v in node.values():
        if depth:
            iter_tree(v, depth-1, path)
        else:
            callback(path)
Iteration 1  21.3142
Iteration 2  11.2947
Iteration 3   1.3979

列出的数字是性能损失的百分比:[(递归内联)/内联]

这里是迭代的优化版本
iterate\u tree2
。这在我的系统上快了40%,主要是由于改进了循环结构和消除了
try-except
。Andrew Magee的递归代码执行大致相同的操作

def iterate_tree4(tree, depth, callback):
    iterators = [iter(tree.items())]
    args = [] 
    while iterators:
        while depth:
            for k, v in iterators[-1]:
                args.append(k)
                iterators.append(iter(v.items()))
                depth -= 1
                break
            else:
                break
        else:
            for k, v in iterators[-1]:
                yield callback(*(args + [k, v]))
        depth += 1
        del iterators[-1]
        del args[-1:]

Python2的结果不应该类似,至少大型字典的结果不应该类似——在Python2中
items()
返回了对的副本,在Python3中它返回了一个“视图”,没有复制任何内容。你是对的。但我的树是“方形”的,所以它不会受到太大的影响。它为回调积累值,而我需要为回调积累键。所以你的函数做了错误的事情。而且它不提供一种使用回调结果的方法(就像我对yield所做的那样)。@ZaarHai除非您需要在回调中使用路径键,否则您的回调将更有效地引用字典,而不必根据累积的键重新遍历。是这样吗,你需要钥匙吗?是的,我肯定需要钥匙。它们是“路径”。回调需要接收
(key1,key2,…,keyN,val\u of_last\u dict)
,并为每个叶调用一次。@ZaarHai使用跟踪键的类似方法进行了更新。看看结果在你的基准上落在哪里会很有趣。以下是结果:。看起来,从回调中报告结果会减慢速度。最慢的方法是
yield
,从回调调用回调更好。完全没有报告提供类似于使用@Andew版本内联的结果(您的版本稍微慢一点)。无论如何谢谢你!与我的
迭代树
相比,您的版本确实快了约25%,这是一个非常好的提升。谢谢要点:哈哈,你的第一句话让我一时糊涂!明亮的事实上,它的工作原理和安德鲁的一样。基于PyPy循环的解决方案的另一个好处是获得了难以置信的提升。所以我认为你的解决方案是最普遍的。测试要点:(你的是迭代)