将字典列表整理成嵌套列表

Collate dictionary list into a nested list

很难搜索我无法描述的内容,所以如果在其他地方已经回答了这个问题,我深表歉意。

我有一个来自 cli 工具(timewarrior,如果你熟悉的话!)的字典列表,我想将其组织成层次结构,以便打印 table 或导出 CSV .

如何生成层次结构取决于每个列表元素(称为“标签”)中包含的列表的顺序。每个标签列表都包含一些跟踪时间。我想总结一下从底部到顶部在层次结构上花费的时间。

这是我正在处理的数据的一个过度简化的示例:

data = [{"tags": ["Project A", "Task 1"], "time": 50},
        {"tags": ["Project A", "Task 2"], "time": 20},
        {"tags": ["Do a thing"], "time": 10},
        {"tags": ["Project B", "Do a thing"], "time": 50}]

有了这些数据,我希望为递归函数调用创建以下两种结构之一:

嵌套列表:

outcome_a = [["Project A", 70, [["Task 1", 50], ["Task 2", 20]]],
             ["Do a thing", 10],
             ["Project B", 50, ["Do a thing", 50]]]

或嵌套字典:

outcome_b = {
    "Project A": {
        "time": 70,
        "sub": {
            "Task 1": {
                "time": 50
            },
            "Task 2": {
                "time": 20
            }
        }
    },
    "Do a thing": {
        "time": 10
    },
    "Project B": {
        "time": 50,
        "sub": {
            "Do a thing": {
                "time": 50
            }
        }
    }
}

迭代字典然后迭代里面的标签感觉很简单。令我困惑的是,一旦迭代移动到嵌套数据的第一层,如何优雅地跟踪每个元素的时间上下文。

我显然不想厌烦地重复列表并尝试重新发现上下文。我能想到的最佳解决方案是迭代数据并以某种方式将上下文传递给下一个 tags 元素。我认为某种递归函数或 reducer 可以解决这个问题。

在这一点上,我已经抽出几个空闲时间来考虑如何处理这件事。我确定我想多了。我愿意接受建议:)

如果您持续访问它,我会避免嵌套 list

如果您使用 dict.get,您的 dict 结果就足够简单了。

data = [{"tags": ["Project A", "Task 1"], "time": 50},
        {"tags": ["Project A", "Task 2"], "time": 20},
        {"tags": ["Do a thing"], "time": 10},
        {"tags": ["Project B", "Do a thing"], "time": 50}]

result = {}
for tags in data:
    project, *task = tags['tags']
    task_time = tags['time']
    if task:
        result[project] = result.get(project, {'time': 0, 'sub': {}})
        result[project]['sub'][task[0]] = {'time': task_time}
        result[project]['time'] += task_time
    else:
        result[project] = {'time': task_time}
        
print(result)
# {
#     'Project A': {
#         'time': 70, 
#         'sub': {
#             'Task 1': {'time': 50}, 
#             'Task 2': {'time': 20}
#         }
#      }, 
#     'Do a thing': {'time': 10}, 
#     'Project B': {
#         'time': 50, 
#         'sub': {
#             'Do a thing': {'time': 50}
#         }
#     }
# }

一些亮点:

这会将 'tags' 密钥解压缩为 2 个变量。如果该键下的 list 中只有一项,则将空 [] 分配给 task.

project, *task = tags['tags']

我们知道在这个分支中需要 'sub' 键。 dict.get() 用于检索 project 键。如果它不存在,默认的 dict 结构被分配给它。

result[project] = result.get(project, {'time': 0, 'sub': {}})

由于result[project]['time']的初始值设置为0。我们可以不断地添加到那个键上。

result[project]['time'] += task_time

最后,对于单个任务,当 task 为“Falsey”时。

result[project] = {'time': task_time}

编辑:

对于大小不可知的标签,您可以使用相同的 dict.get 技巧并使用它来嵌套任意深度。它还具有删除 if 语句的额外好处。

data = [{"tags": ["Project A", "Task 1"], "time": 50},
        {"tags": ["Project A", "Task 2", "Part 1"], "time": 10},
        {"tags": ["Project A", "Task 2", "Part 2"], "time": 10},
        {"tags": ["Do a thing"], "time": 10},
        {"tags": ["Project B", "Do a thing"], "time": 50}]

result = {}
for tags in data:
    project, *tasks = tags['tags']
    task_time = tags['time']
    result[project] = result.get(project, {'time': 0})
    result[project]['time'] += task_time
    current_dict = result[project]
    for task in tasks:
        current_dict['sub'] = current_dict.get('sub', {})
        current_dict = current_dict['sub']
        current_dict[task] = current_dict.get(task, {'time': 0})
        current_dict[task]['time'] += task_time
        current_dict = current_dict[task]
        
print(result)
# {
#     'Project A': {
#         'time': 70, 
#         'sub': {
#             'Task 1': {'time': 50}, 
#             'Task 2': {
#                 'time': 20,
#                 'sub': {
#                     'Part 1': {'time': 10},
#                     'Part 2': {'time': 10}
#                 }
#             }
#         }
#      }, 
#     'Do a thing': {'time': 10}, 
#     'Project B': {
#         'time': 50, 
#         'sub': {
#             'Do a thing': {'time': 50}
#         }
#     }
# }

我只是想 post 我自己的答案。再次感谢@Axe319 帮我解决了这个问题。

我想看到嵌套列表发生,所以这里是:

DATA = [{"tags": ["Project A", "Task 1"], "time": 50},
        {"tags": ["Project A", "Task 2", "Part 1"], "time": 10},
        {"tags": ["Project A", "Task 2", "Part 2"], "time": 10},
        {"tags": ["Do a thing"], "time": 10},
        {"tags": ["Project B", "Do a thing"], "time": 50}]

ROWS = []


def update_row_data(rows, task):
    levels = task["tags"]
    time = task["time"]
    level, *nested = levels
    row = -1

    for i, t in enumerate(rows):
        if t[0] == level:
            row = i
            break

    if row > -1:
        rows[row][1] += time
    else:
        rows.append([level, time, []])
        row = len(rows) - 1

    if nested:
        next = {"tags": nested, "time": time}
        rows[row][2] = update_row_data(rows[row][2], next)

    return rows


for entry in DATA:
    ROWS = update_row_data(ROWS, entry)

print(ROWS)