【问题标题】:Traversing of arbitrary order tree任意顺序树的遍历
【发布时间】:2022-07-20 19:37:54
【问题描述】:

我知道以前有人问过这个问题,我已经看到了答案,但仍然无法弄清楚发生了什么。

我试图根据文件的某些元数据(日期和位置)和一组条件有条件地构建文件夹结构。例如,为了测试我正在使用这些:

COND = [\"Y\", \"m\", \"C\"]

这意味着在文件夹结构中文件需要首先按年份拆分文件,然后是日历月份,然后是原产国。

这是我为测试创建的示例数据:

data = [
    [\"111\", dt.datetime(2019, 1, 1), \"Aus\", \"Bri\"],
    [\"112\", dt.datetime(2019, 1, 5), \"Aus\", \"Bri\"],
    [\"113\", dt.datetime(2019, 2, 10), \"Aus\", \"Mel\"],
    [\"114\", dt.datetime(2020, 1, 1), \"Aus\", \"Per\"],
    [\"115\", dt.datetime(2020, 1, 10), \"Aus\", \"Per\"],
    [\"116\", dt.datetime(2020, 1, 25), \"Aus\", \"Per\"],
    [\"117\", dt.datetime(2020, 10, 5), \"My\", \"KL\"],
    [\"118\", dt.datetime(2020, 11, 6), \"Ru\", \"Led\"],
    [\"119\", dt.datetime(2020, 12, 1), \"Ru\", \"Mos\"],
    [\"120\", dt.datetime(2021, 3, 5), \"Aus\", \"Syd\"],
    [\"121\", dt.datetime(2021, 5, 1), \"Aus\", \"Mel\"],
    [\"122\", dt.datetime(2021, 6, 1), \"Aus\", \"Per\"],
    [\"123\", dt.datetime(2021, 11, 1), \"Chi\", \"Bei\"],
    [\"124\", dt.datetime(2021, 11, 15), \"Jp\", \"Tok\"],
    [\"125\", dt.datetime(2022, 1, 1), \"Aus\", \"Per\"],
    [\"126\", dt.datetime(2022, 3, 1), \"Aus\", \"Bri\"],
    [\"127\", dt.datetime(2022, 3, 5), \"Aus\", \"Per\"],
    [\"128\", dt.datetime(2022, 3, 11), \"My\", \"KL\"],
    [\"129\", dt.datetime(2022, 5, 1), \"Aus\", \"Syd\"],
    [\"130\", dt.datetime(2022, 8, 8), \"Aus\", \"Bri\"],
]

这些简单的函数执行过滤:

def filter_year(data: list[list[str | dt.datetime]]) -> list[int]:
    return {i[1].year for i in data}


def filter_month(data: list[list[str | dt.datetime]]) -> list[int]:
    return {i[1].month for i in data}


def filter_day(data: list[list[str | dt.datetime]]) -> list[int]:
    return {i[1].day for i in data}


def filter_country(data: list[list[str | dt.datetime]]) -> list[str]:
    return {i[2] for i in data}


def filter_city(data: list[list[str | dt.datetime]]) -> list[str]:
    return {i[3] for i in data}

condition_dict = {
    \"Y\": {\'fun\': filter_year, \'id\': 1 },
    \"m\": {\'fun\': filter_month,\'id\': 1 },
    \"d\": {\'fun\': filter_day,\'id\': 1},
    \"C\": {\'fun\': filter_country, \'id\': 2},
    \"c\": {\'fun\': filter_city, \'id\': 3 }

我正在尝试使用任意顺序树自动构建结构。节点上的数据拆分工作正常:

from typing import Any
from pathlib import Path
from dataclasses import dataclass, field

@dataclass
class Node:
    folder: Path
    metadata: list[list[Any]] = field(default_factory=list)
    conditions: list[str] = field(default_factory=list)
    
    @property
    def children(self) -> list[\'Node\']:
        if len(self.conditions) == 0:
            return []
        current_condition = self.conditions[0]
        fun = condition_dict[current_condition][\'fun\']
        
        fnames: list[int | str] = fun(self.metadata)
        children_data = {str(n): {} for n in fnames}
        for f in fnames:
            children_data[str(f)][\'folder\'] = self.folder / str(f)    
            children_data[str(f)][\'conditions\'] = self.conditions[1:]   
            if current_condition == \'Y\':
                children_data[str(f)][\'metadata\'] = [i for i in self.metadata if i[1].year == f]
            elif current_condition == \'m\':
                children_data[str(f)][\'metadata\'] = [i for i in self.metadata if i[1].month == f]
            elif current_condition == \'d\':
                children_data[str(f)][\'metadata\'] = [i for i in self.metadata if i[1].day == f]    
            elif current_condition == \'C\':
                children_data[str(f)][\'metadata\'] = [i for i in self.metadata if i[2] == f]
            elif current_condition == \'c\':
                children_data[str(f)][\'metadata\'] = [i for i in self.metadata if i[3] == f]
        
        return [Node(**i) for i in children_data.values()]

现在,我正在尝试遍历我使用此处答案的修改版本的树(Traverse Non-Binary Tree

@dataclass
class Tree:
    def traverse(self, root: Node):
        r = root.children
        if not r or len(root.conditions) == 0:
            print(\'The end of subtree:\', root.folder)
        else:
            for child in r:            
                print(\'\\n\'.join(str(i.folder) for i in r))
                if isinstance(child, Node):
                    for x in self.traverse(child):
                        print(str(x.folder))
                else:
                    print(child) 

但是当我在几次正确输出后尝试使用我的数据时,我总是遇到错误NoneType is not iterable

n = Node(folder=Path(\'/home\'), metadata=data, conditions=COND)

tree = Tree()
tree.traverse(n)

输出:

/home/2019
/home/2020
/home/2021
/home/2022
/home/2019/1
/home/2019/2
/home/2019/1/Aus
The end of subtree: /home/2019/1/Aus
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/home/pavel/python/photo_manager/temp/tree_test.ipynb Cell 4 in <cell line: 4>()
      1 n = Node(folder=Path(\'/home\'), metadata=data, conditions=COND)
      3 tree = Tree()
----> 4 tree.traverse(n)

/home/pavel/python/photo_manager/temp/tree_test.ipynb Cell 4 in Tree.traverse(self, root)
     45 print(\'\\n\'.join(str(i.folder) for i in r))
     46 if isinstance(child, Node):
---> 47     for x in self.traverse(child):
     48         print(str(x.folder))
     49 else:

/home/pavel/python/photo_manager/temp/tree_test.ipynb Cell 4 in Tree.traverse(self, root)
     45 print(\'\\n\'.join(str(i.folder) for i in r))
     46 if isinstance(child, Node):
---> 47     for x in self.traverse(child):
     48         print(str(x.folder))
     49 else:

/home/pavel/python/photo_manager/temp/tree_test.ipynb Cell 4 in Tree.traverse(self, root)
     45 print(\'\\n\'.join(str(i.folder) for i in r))
     46 if isinstance(child, Node):
---> 47     for x in self.traverse(child):
     48         print(str(x.folder))
     49 else:

TypeError: \'NoneType\' object is not iterable

我不明白为什么会发生这种情况,因为我相信我提防了 NoneType。出于某种原因,我只到达一个子树的末尾,但没有遍历其他子树。我在这里做错了什么?

    标签: python tree tree-traversal


    【解决方案1】:

    我并没有真正了解整个故事,但是您在这一行中遇到的错误是预料之中的:

     for x in self.traverse(child):
    

    问题是self.traverse 没有return 语句,所以这个递归调用返回None,而for x in None 没有意义。

    我认为您实际上不想从该递归调用中获得一些 x 值,因为该递归调用负责自己的业务。无需再次打印该递归调用已打印的内容。

    这里还有第二个问题:

            for child in r:            
                print('\n'.join(str(i.folder) for i in r))
    

    在这里,对于r 中的每个孩子,您迭代r再次print 电话中。那只会打印重复项。您只需打印来自r 的当前孩子。这将使其下方的else 块过时:当您刚刚打印child.folder 时,似乎没有必要再次打印child

    因此,纠正这两个问题,以下至少运行没有错误:

    @dataclass
    class Tree:
        def traverse(self, root: Node):
            r = root.children
            if not r or len(root.conditions) == 0:
                print('The end of subtree:', root.folder)
            else:
                for child in r:
                    print(str(child.folder))
                    if isinstance(child, Node):
                        self.traverse(child)
    

    【讨论】:

    • 我实际上添加了打印语句以查看发生了什么,但我有一段时间没有处理递归并且忘记了递归调用总是必须返回/产生一些东西。实际上,我确实需要传单节点返回完整路径(文件夹)。但这可以通过在if 语句中将print 替换为return 来解决。
    • 如果您需要递归调用来返回某些内容,请确保它总是返回一些东西,所以也在else 部分。让你的函数成为生成器通常更容易,即使用yield 而不是return
    猜你喜欢
    • 2020-07-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-28
    • 1970-01-01
    相关资源
    最近更新 更多