2024-11-21 16:22:22 +08:00
|
|
|
|
import json
|
|
|
|
|
import re
|
|
|
|
|
|
2024-11-21 17:42:32 +08:00
|
|
|
|
def generate_key_paths(data, parent_key='', good_list=None, seen=None):
|
2024-11-21 16:22:22 +08:00
|
|
|
|
"""
|
2024-11-21 17:42:32 +08:00
|
|
|
|
生成嵌套字典中的键路径,并提取最内层的键名。
|
|
|
|
|
同时,提取特定模式的键(如 '交换机-1', '交换机-2')的父路径。
|
|
|
|
|
如果同一层级下只有'交换机-1'但没有'交换机-2',则视为错误输入,删除后缀'-1',将'交换机'加入key_paths。
|
|
|
|
|
并从data中移除错误的键。
|
2024-11-21 16:22:22 +08:00
|
|
|
|
|
|
|
|
|
参数:
|
2024-11-21 17:42:32 +08:00
|
|
|
|
data (dict): 输入的字典数据
|
|
|
|
|
parent_key (str): 上级键路径,用于递归调用
|
|
|
|
|
good_list (list): 用于存储去重后的最内层键名
|
|
|
|
|
seen (set): 用于跟踪已添加到 good_list 的元素
|
2024-11-21 16:22:22 +08:00
|
|
|
|
|
|
|
|
|
返回:
|
2024-11-21 17:42:32 +08:00
|
|
|
|
tuple: 包含键路径列表、最内层键名列表、分组路径列表以及 no_keys_added 的元组
|
|
|
|
|
(key_paths, good_list, grouped_paths, no_keys_added)
|
2024-11-21 16:22:22 +08:00
|
|
|
|
"""
|
2024-11-21 17:42:32 +08:00
|
|
|
|
if good_list is None:
|
|
|
|
|
good_list = []
|
|
|
|
|
if seen is None:
|
|
|
|
|
seen = set()
|
|
|
|
|
|
|
|
|
|
key_paths = []
|
|
|
|
|
grouped_paths = set() # 使用集合避免重复路径
|
|
|
|
|
no_keys_added = True # 默认假设没有添加任何键
|
|
|
|
|
|
|
|
|
|
# Step 1: Collect keys that match the pattern
|
|
|
|
|
pattern = re.compile(r'(.+)-\d+$') # 匹配形如 '交换机-1', '交换机-2' 的键
|
|
|
|
|
prefix_groups = {}
|
|
|
|
|
other_keys = []
|
|
|
|
|
|
|
|
|
|
for key in list(data.keys()): # 使用 list(data.keys()) 防止修改字典时出错
|
|
|
|
|
clean_key = key.replace(" ", "")
|
|
|
|
|
match = pattern.match(clean_key)
|
|
|
|
|
if match:
|
|
|
|
|
prefix = match.group(1)
|
|
|
|
|
if prefix not in prefix_groups:
|
|
|
|
|
prefix_groups[prefix] = []
|
|
|
|
|
prefix_groups[prefix].append(key)
|
|
|
|
|
else:
|
|
|
|
|
other_keys.append(key)
|
|
|
|
|
|
|
|
|
|
# Step 2: Handle grouped keys
|
|
|
|
|
for prefix, keys in prefix_groups.items():
|
|
|
|
|
current_prefix_path = f"{parent_key}.{prefix}" if parent_key else prefix
|
|
|
|
|
if len(keys) > 1:
|
|
|
|
|
# 多个键匹配同一前缀:添加到 grouped_paths
|
|
|
|
|
grouped_paths.add(current_prefix_path)
|
|
|
|
|
if prefix not in seen:
|
|
|
|
|
good_list.append(prefix)
|
|
|
|
|
seen.add(prefix)
|
|
|
|
|
no_keys_added = False
|
|
|
|
|
else:
|
|
|
|
|
# 只有一个键匹配:删除后缀并添加到 key_paths,同时从 data 中移除该键
|
|
|
|
|
key = keys[0]
|
|
|
|
|
key_path = current_prefix_path # 去掉后缀后,路径为父路径 + 前缀
|
|
|
|
|
key_paths.append(key_path)
|
|
|
|
|
if prefix not in seen:
|
|
|
|
|
good_list.append(prefix)
|
|
|
|
|
seen.add(prefix)
|
|
|
|
|
no_keys_added = False
|
|
|
|
|
# 从 data 中移除错误的键
|
|
|
|
|
data.pop(key)
|
|
|
|
|
|
|
|
|
|
# Step 3: Handle other keys
|
|
|
|
|
for key in other_keys:
|
|
|
|
|
value = data[key]
|
|
|
|
|
current_key = f"{parent_key}.{key}" if parent_key else key
|
|
|
|
|
|
|
|
|
|
if isinstance(value, dict):
|
|
|
|
|
if value:
|
|
|
|
|
# 递归调用,并获取子路径、子 good_list、子分组路径以及子 no_keys_added
|
|
|
|
|
sub_key_paths, _, sub_grouped_paths, sub_no_keys_added = generate_key_paths(
|
|
|
|
|
value, current_key, good_list, seen
|
|
|
|
|
)
|
|
|
|
|
key_paths.extend(sub_key_paths)
|
|
|
|
|
grouped_paths.update(sub_grouped_paths) # 合并子分组路径到当前分组路径
|
|
|
|
|
# 更新 no_keys_added
|
|
|
|
|
no_keys_added = no_keys_added and sub_no_keys_added
|
|
|
|
|
else:
|
|
|
|
|
# 空字典视为叶子节点
|
|
|
|
|
clean_key = key.replace(" ", "")
|
|
|
|
|
key_paths.append(current_key.replace(" ", ""))
|
|
|
|
|
if clean_key not in seen:
|
|
|
|
|
good_list.append(clean_key) # 去掉空格后添加
|
|
|
|
|
seen.add(clean_key)
|
|
|
|
|
|
|
|
|
|
# 更新 no_keys_added
|
|
|
|
|
no_keys_added = False
|
|
|
|
|
elif isinstance(value, list):
|
|
|
|
|
# 列表类型视为叶子节点,无论是否为空
|
|
|
|
|
key_paths.append(current_key.replace(" ", ""))
|
|
|
|
|
clean_key = key.replace(" ", "")
|
|
|
|
|
if clean_key not in seen:
|
|
|
|
|
good_list.append(clean_key) # 去掉空格后添加
|
|
|
|
|
seen.add(clean_key)
|
|
|
|
|
# 更新 no_keys_added
|
|
|
|
|
no_keys_added = False
|
|
|
|
|
elif value in {"未知", "", "/"}:
|
|
|
|
|
# 特定值视为叶子节点
|
|
|
|
|
key_paths.append(current_key.replace(" ", ""))
|
|
|
|
|
clean_key = key.replace(" ", "")
|
|
|
|
|
if clean_key not in seen:
|
|
|
|
|
good_list.append(clean_key) # 去掉空格后添加
|
|
|
|
|
seen.add(clean_key)
|
|
|
|
|
# 更新 no_keys_added
|
|
|
|
|
no_keys_added = False
|
|
|
|
|
else:
|
|
|
|
|
# 其他情况视为叶子节点
|
|
|
|
|
key_paths.append(current_key.replace(" ", ""))
|
|
|
|
|
clean_key = key.replace(" ", "")
|
|
|
|
|
if clean_key not in seen:
|
|
|
|
|
good_list.append(clean_key) # 去掉空格后添加
|
|
|
|
|
seen.add(clean_key)
|
|
|
|
|
# 更新 no_keys_added
|
|
|
|
|
no_keys_added = False
|
|
|
|
|
|
|
|
|
|
return key_paths, good_list, grouped_paths, no_keys_added
|
|
|
|
|
|
|
|
|
|
# 示例使用
|
|
|
|
|
data1 = {
|
|
|
|
|
"采购需求": {
|
|
|
|
|
"高清数字枪机-1": [],
|
|
|
|
|
"枪机支架-1": [],
|
|
|
|
|
"高清数字半球机-1": [],
|
|
|
|
|
"网络硬盘录像机-1": [],
|
|
|
|
|
"监硬控硬盘-1": [],
|
|
|
|
|
"交换机-1": [],
|
|
|
|
|
"交换机-2": [],
|
|
|
|
|
"监视器-1": [],
|
|
|
|
|
"电源线-1": [],
|
|
|
|
|
"网线-1": [],
|
|
|
|
|
"水晶头-1": [],
|
|
|
|
|
"PVC线槽-1": [],
|
|
|
|
|
"辅料-1": [],
|
|
|
|
|
"安装调试-1": [],
|
|
|
|
|
"视频图像采集及存储系统": {
|
|
|
|
|
"系统功能": []
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
key_paths, good_list, grouped_paths, no_keys_added = generate_key_paths(data1)
|
|
|
|
|
print(json.dumps(data1,ensure_ascii=False,indent=4))
|