You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python脚本修改需求:Excel物料数据生成正确层级树结构

问题描述

我开发了一段Python脚本,用于从Excel文件中提取Finished Good、Parent Part Code、Material Code三列数据,生成物料层级树结构。现有脚本运行后出现异常:代码V371同时作为Finished Good 10020115HU的直接子节点,以及C00211L0的子节点存在,不符合预期的树结构。

需求:修改脚本,使V371仅作为C00211L0的子节点,并包含其完整的子树,生成符合预期的层级树结构。


数据示例

Material Code    Parent Part Code   Finished Good
M1               P1                 F1
M2               P2                 F2
M3               M2                 F2
M4               P3                 F2
.....

注:Material Code也可能出现在Parent Part Code列中。

预期树结构示例

F1
  P1
    M1

F2
  P2
    M2
      M3
  P3
    M4

当前Python脚本

import pandas as pd
from anytree import Node, RenderTree
import json

# Read excel
df = pd.read_excel('excelFile.xlsx')

root_dict = {}

for index, row in df.iterrows():
    finished_good = row['Finished Good']
    parent_part_code = row['Parent Part Code']
    material_code = row['Material Code']
    
    if finished_good not in root_dict:
        root = Node(finished_good)
        root_dict[finished_good] = root
    else:
        root = root_dict[finished_good]

    if parent_part_code in [node.name for node in root.descendants]:
        parent_node = [node for node in root.descendants if node.name == parent_part_code][0]
        
        if material_code in [node.name for node in root.descendants]:
            material_node = [node for node in root.descendants if node.name == material_code][0]
            material_node.parent = parent_node
        else:
            material = Node(material_code, parent=parent_node)
            while material_code in df['Parent Part Code'].values:
                filtered = df[df['Parent Part Code'] == material_code]
                material_code = filtered.iloc[0]['Material Code']
                parent_node = material
                material = Node(material_code, parent=parent_node)
    else:
        parent_node = Node(parent_part_code, parent=root)
        material = Node(material_code, parent=parent_node)
        while material_code in df['Parent Part Code'].values:
            filtered = df[df['Parent Part Code'] == material_code]
            material_code = filtered.iloc[0]['Material Code']
            parent_node = material
            material = Node(material_code, parent=parent_node)

# Print the trees
for root in root_dict.values():
    print(RenderTree(root))

# Save the trees
def node_to_dict(node):
    return {
        'name': node.name,
        'children': [node_to_dict(child) for child in node.children]
    }

with open('normTrees.json', 'w') as file:
    json.dump({key: node_to_dict(root) for key, root in root_dict.items()}, file)
    print("The trees were successfully saved")

当前输出结果

Node('/10020115HU')
├── Node('/10020115HU/V371')
│   ├── Node('/10020115HU/V371/YG10-30300')
│   ├── Node('/10020115HU/V371/VECTC002')
│   │   ├── Node('/10020115HU/V371/VECTC002/YG10-30200')
│   │   ├── Node('/10020115HU/V371/VECTC002/VNCTC002')
│   │   │   ├── Node('/10020115HU/V371/VECTC002/VNCTC002/YG10-30300')
│   │   │   ├── Node('/10020115HU/V371/VECTC002/VNCTC002/SZVIZ')
│   │   │   └── Node('/10020115HU/V371/VECTC002/VNCTC002/RVSZALLPOR')
│   │   └── Node('/10020115HU/V371/VECTC002/RVECTC002')
│   └── Node('/10020115HU/V371/U100KOCS')
│       └── Node('/10020115HU/V371/U100KOCS/YG10-30300')
├── Node('/10020115HU/C00211L0')
│   ├── Node('/10020115HU/C00211L0/V371')
│   │   └── Node('/10020115HU/C00211L0/V371/YG10-30300')
│   ├── Node('/10020115HU/C00211L0/RWINNOVERS')
│   └── Node('/10020115HU/C00211L0/RSZENNYEZETT')
├── Node('/10020115HU/10020115HU')
│   └── Node('/10020115HU/10020115HU/TTK00001HU')
├── Node('/10020115HU/D67AMBR910')
│   └── Node('/10020115HU/D67AMBR910/RWINNOVERS')
└── Node('/10020115HU/D67LTRR910')
    ├── Node('/10020115HU/D67LTRR910/RWINNOVERS')
    └── Node('/10020115HU/D67LTRR910/RSECONDUST')
The trees were successfully saved

预期输出结构

Node('/10020115HU')
├── Node('/10020115HU/C00211L0')
│   ├── Node('/10020115HU/C00211L0/V371')
│   │   └── Node('/10020115HU/C00211L0/V371/YG10-30300')
│   │       └── Node('/10020115HU/C00211L0/V371/VECTC002')
│   │           ├── Node('/10020115HU/C00211L0/V371/VECTC002/YG10-30200')
│   │           ├── Node('/10020115HU/C00211L0/V371/VECTC002/VNCTC002')
│   │           │   ├── Node('/10020115HU/C00211L0/V371/VECTC002/VNCTC002/YG10-30300')
│   │           │   ├── Node('/10020115HU/C00211L0/V371/VECTC002/VNCTC002/SZVIZ')
│   │           │   └── Node('/10020115HU/C00211L0/V371/VECTC002/VNCTC002/RVSZALLPOR')
│   │           └── Node('/10020115HU/C00211L0/V371/VECTC002/RVECTC002')
│   ├── Node('/10020115HU/C00211L0/V371/U100KOCS')
│   │   └── Node('/10020115HU/C00211L0/V371/U100KOCS/YG10-30300')
│   ├── Node('/10020115HU/C00211L0/RWINNOVERS')
│   └── Node('/10020115HU/C00211L0/RSZENNYEZETT')
├── Node('/10020115HU/10020115HU')
│   └── Node('/10020115HU/10020115HU/TTK00001HU')
├── Node('/10020115HU/D67AMBR910')
│   └── Node('/10020115HU/D67AMBR910/RWINNOVERS')
└── Node('/10020115HU/D67LTRR910')
    ├── Node('/10020115HU/D67LTRR910/RWINNOVERS')
    └── Node('/10020115HU/D67LTRR910/RSECONDUST')

修改后的脚本及说明

问题根源

原脚本核心问题是没有全局跟踪所有已创建的节点,仅在每个根节点的后代中查找。处理不同行时可能重复创建同一物料的节点,导致同一物料出现在树的多个位置;同时递归创建子节点的逻辑仅处理单条关联行,未覆盖所有子节点数据。

修改后的代码

import pandas as pd
from anytree import Node, RenderTree
import json

# 读取Excel文件
df = pd.read_excel('excelFile.xlsx')

# 全局字典:跟踪所有已创建的节点,key为物料/成品代码,value为对应的Node对象
all_nodes = {}
# 根节点字典:存储每个Finished Good对应的根节点
root_dict = {}

# 第一步:先创建所有节点(确保每个代码只创建一次)
for _, row in df.iterrows():
    finished_good = row['Finished Good']
    parent_part = row['Parent Part Code']
    material = row['Material Code']
    
    # 创建根节点(如果不存在)
    if finished_good not in all_nodes:
        root_node = Node(finished_good)
        all_nodes[finished_good] = root_node
        root_dict[finished_good] = root_node
    
    # 创建父节点(如果不存在)
    if parent_part not in all_nodes:
        all_nodes[parent_part] = Node(parent_part)
    
    # 创建物料节点(如果不存在)
    if material not in all_nodes:
        all_nodes[material] = Node(material)

# 第二步:建立父子关系
for _, row in df.iterrows():
    finished_good = row['Finished Good']
    parent_part = row['Parent Part Code']
    material = row['Material Code']
    
    parent_node = all_nodes[parent_part]
    material_node = all_nodes[material]
    
    # 如果父节点还没有父节点,且父节点不是根节点,则将父节点挂到对应的成品根节点下
    if parent_node.parent is None and parent_node.name != finished_good:
        parent_node.parent = all_nodes[finished_good]
    
    # 设置物料节点的父节点
    material_node.parent = parent_node

# 打印树结构
for root in root_dict.values():
    print(RenderTree(root))

# 将树结构保存为JSON
def node_to_dict(node):
    return {
        'name': node.name,
        'children': [node_to_dict(child) for child in node.children]
    }

with open('normTrees.json', 'w') as file:
    json.dump({key: node_to_dict(root) for key, root in root_dict.items()}, file, indent=2)
print("树结构已成功保存到normTrees.json")

关键修改点

  1. 全局节点跟踪:新增all_nodes字典,确保每个物料/成品代码只创建一个Node对象,从根本上避免重复节点。
  2. 分两步处理:先批量创建所有节点,再统一建立父子关系,确保所有关联都能正确映射,不会因为行处理顺序导致遗漏或错误。
  3. 修复父节点挂载逻辑:确保非根节点的父节点正确挂到对应的成品根节点下,避免出现游离节点。

修改后,V371只会存在一个节点,且会被正确挂载到C00211L0下,同时保留其完整的子树结构,符合预期输出。


内容的提问来源于stack exchange,提问作者David

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 16:37:38