417 lines
12 KiB
Python
417 lines
12 KiB
Python
|
def combine_and_update_results(original_data, updates):
|
|||
|
def normalize_key(key):
|
|||
|
"""
|
|||
|
规范化键名:
|
|||
|
- 替换全角点号为半角点号。
|
|||
|
- 删除所有空格(包括半角和全角)。
|
|||
|
"""
|
|||
|
# 替换全角点号(.、。)为半角点号(.)
|
|||
|
key = key.replace('.', '.').replace('。', '.')
|
|||
|
# 删除所有空格(半角空格和全角空格)
|
|||
|
key = key.replace(' ', '').replace('\u3000', '')
|
|||
|
return key
|
|||
|
|
|||
|
def normalize_original_data(d):
|
|||
|
"""
|
|||
|
递归规范化原始数据字典的键。
|
|||
|
"""
|
|||
|
if not isinstance(d, dict):
|
|||
|
return d
|
|||
|
normalized = {}
|
|||
|
for k, v in d.items():
|
|||
|
nk = normalize_key(k)
|
|||
|
normalized[nk] = normalize_original_data(v)
|
|||
|
return normalized
|
|||
|
|
|||
|
def normalize_update_value(value):
|
|||
|
"""
|
|||
|
递归规范化更新字典中嵌套的字典的键。
|
|||
|
"""
|
|||
|
if isinstance(value, dict):
|
|||
|
return {normalize_key(k): normalize_update_value(v) for k, v in value.items()}
|
|||
|
else:
|
|||
|
return value
|
|||
|
|
|||
|
def recursive_update(data, key, value):
|
|||
|
"""
|
|||
|
递归更新嵌套字典。
|
|||
|
"""
|
|||
|
keys = key.split('.')
|
|||
|
for k in keys[:-1]:
|
|||
|
data = data.setdefault(k, {})
|
|||
|
if isinstance(value, dict) and isinstance(data.get(keys[-1], None), dict):
|
|||
|
data[keys[-1]] = {**data.get(keys[-1], {}), **value}
|
|||
|
else:
|
|||
|
data[keys[-1]] = value
|
|||
|
|
|||
|
# 1. 规范化原始数据字典的键
|
|||
|
original_data = normalize_original_data(original_data)
|
|||
|
|
|||
|
# 2. 规范化更新字典的键
|
|||
|
normalized_updates = {}
|
|||
|
for key, value in updates.items():
|
|||
|
nk = normalize_key(key)
|
|||
|
nv = normalize_update_value(value)
|
|||
|
normalized_updates[nk] = nv
|
|||
|
|
|||
|
# 3. 执行递归更新
|
|||
|
for key, value in normalized_updates.items():
|
|||
|
recursive_update(original_data, key, value)
|
|||
|
|
|||
|
return original_data
|
|||
|
|
|||
|
|
|||
|
# 测试用例1:复杂情况
|
|||
|
def test_complex_case():
|
|||
|
original_data = {
|
|||
|
'user.name': 'Alice',
|
|||
|
'user。details': {
|
|||
|
'age': 30,
|
|||
|
'address line': '123 Main St', # 全角空格
|
|||
|
'preferences': {
|
|||
|
'color': 'blue',
|
|||
|
'food': 'sushi'
|
|||
|
}
|
|||
|
},
|
|||
|
'status': 'active',
|
|||
|
'metrics': {
|
|||
|
'score': 85,
|
|||
|
'rank': 5
|
|||
|
},
|
|||
|
'list_field': [1, 2, 3]
|
|||
|
}
|
|||
|
|
|||
|
updates = {
|
|||
|
'user. name': 'Bob', # 更新user.name
|
|||
|
'user.details.age': 31, # 更新嵌套字段
|
|||
|
'user.details.addressline': '456 Elm St', # 更新地址,键中有空格被移除
|
|||
|
'user.details.preferences.hobby': 'cycling', # 添加新的嵌套字段
|
|||
|
'status': 'inactive', # 更新顶级字段
|
|||
|
'metrics.score': {'current': 90, 'max': 100}, # 更新为字典
|
|||
|
'metrics.new_metric': 50, # 添加新的顶级嵌套字段
|
|||
|
'new_field': 'new_value', # 添加新的顶级字段
|
|||
|
'list_field': [4, 5] # 更新列表字段
|
|||
|
}
|
|||
|
|
|||
|
expected_result = {
|
|||
|
'user': {
|
|||
|
'name': 'Bob',
|
|||
|
'details': {
|
|||
|
'age': 31,
|
|||
|
'addressline': '456 Elm St',
|
|||
|
'preferences': {
|
|||
|
'color': 'blue',
|
|||
|
'food': 'sushi',
|
|||
|
'hobby': 'cycling'
|
|||
|
}
|
|||
|
}
|
|||
|
},
|
|||
|
'status': 'inactive',
|
|||
|
'metrics': {
|
|||
|
'score': {'current': 90, 'max': 100},
|
|||
|
'rank': 5,
|
|||
|
'new_metric': 50
|
|||
|
},
|
|||
|
'list_field': [4, 5],
|
|||
|
'new_field': 'new_value'
|
|||
|
}
|
|||
|
|
|||
|
result = combine_and_update_results(original_data, updates)
|
|||
|
assert result == expected_result, f"测试复杂情况失败。\n预期: {expected_result}\n实际: {result}"
|
|||
|
print("测试复杂情况通过。")
|
|||
|
|
|||
|
|
|||
|
# 测试用例2:更新为空
|
|||
|
def test_empty_updates():
|
|||
|
original_data = {
|
|||
|
'key1': 'value1',
|
|||
|
'key2': {
|
|||
|
'subkey': 'subvalue'
|
|||
|
}
|
|||
|
}
|
|||
|
updates = {}
|
|||
|
expected_result = {
|
|||
|
'key1': 'value1',
|
|||
|
'key2': {
|
|||
|
'subkey': 'subvalue'
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
result = combine_and_update_results(original_data, updates)
|
|||
|
assert result == expected_result, f"测试更新为空失败。\n预期: {expected_result}\n实际: {result}"
|
|||
|
print("测试更新为空通过。")
|
|||
|
|
|||
|
|
|||
|
# 测试用例3:原始数据为空
|
|||
|
def test_empty_original():
|
|||
|
original_data = {}
|
|||
|
updates = {
|
|||
|
'new.key': 'new value',
|
|||
|
'nested。key.sub key': 123
|
|||
|
}
|
|||
|
expected_result = {
|
|||
|
'new.key': 'newvalue',
|
|||
|
'nested.key': {
|
|||
|
'subkey': 123
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
result = combine_and_update_results(original_data, updates)
|
|||
|
assert result == expected_result, f"测试原始数据为空失败。\n预期: {expected_result}\n实际: {result}"
|
|||
|
print("测试原始数据为空通过。")
|
|||
|
|
|||
|
|
|||
|
# 测试用例4:更新中包含非字典类型的值
|
|||
|
def test_non_dict_values():
|
|||
|
original_data = {
|
|||
|
'a.b': {
|
|||
|
'c.d': 1
|
|||
|
},
|
|||
|
'list.field': [1, 2, 3]
|
|||
|
}
|
|||
|
updates = {
|
|||
|
'a.b.c.d': {'new': 2},
|
|||
|
'list.field': 'not a list anymore'
|
|||
|
}
|
|||
|
expected_result = {
|
|||
|
'a': {
|
|||
|
'b': {
|
|||
|
'c': {
|
|||
|
'd': {'new': 2}
|
|||
|
}
|
|||
|
}
|
|||
|
},
|
|||
|
'list.field': 'not a list anymore'
|
|||
|
}
|
|||
|
|
|||
|
result = combine_and_update_results(original_data, updates)
|
|||
|
assert result == expected_result, f"测试更新中包含非字典类型的值失败。\n预期: {expected_result}\n实际: {result}"
|
|||
|
print("测试更新中包含非字典类型的值通过。")
|
|||
|
|
|||
|
|
|||
|
# 测试用例5:键中包含全角空格
|
|||
|
def test_full_width_spaces():
|
|||
|
original_data = {
|
|||
|
'key with fullwidth spaces': 'value1',
|
|||
|
'nested.key with.spaces': {
|
|||
|
'sub key': 'subvalue'
|
|||
|
}
|
|||
|
}
|
|||
|
updates = {
|
|||
|
'key with fullwidth spaces': 'updated_value',
|
|||
|
'nested.key with spaces.new subkey': 'new_subvalue'
|
|||
|
}
|
|||
|
expected_result = {
|
|||
|
'keywithfullwidthspaces': 'updated_value',
|
|||
|
'nested': {
|
|||
|
'keywithspaces': {
|
|||
|
'subkey': 'new_subvalue'
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
result = combine_and_update_results(original_data, updates)
|
|||
|
assert result == expected_result, f"测试键中包含全角空格失败。\n预期: {expected_result}\n实际: {result}"
|
|||
|
print("测试键中包含全角空格通过。")
|
|||
|
|
|||
|
|
|||
|
# 测试用例6:复杂嵌套和不同数据类型
|
|||
|
def test_nested_and_various_types():
|
|||
|
original_data = {
|
|||
|
'config.settings': {
|
|||
|
'resolution': '1080p',
|
|||
|
'volume': 75,
|
|||
|
'controls': {
|
|||
|
'jump': 'space',
|
|||
|
'crouch': 'ctrl'
|
|||
|
}
|
|||
|
},
|
|||
|
'user.preferences': {
|
|||
|
'theme': 'dark',
|
|||
|
'notifications': True
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
updates = {
|
|||
|
'config.settings.resolution': '4K',
|
|||
|
'config.settings.controls.run': 'shift', # 添加新控制
|
|||
|
'config.settings.volume': 80, # 更新音量
|
|||
|
'user.preferences.theme': 'light', # 更新主题
|
|||
|
'user.preferences.language': 'en-US', # 添加新偏好
|
|||
|
'new.section.setting': 'enabled' # 添加新部分
|
|||
|
}
|
|||
|
|
|||
|
expected_result = {
|
|||
|
'config': {
|
|||
|
'settings': {
|
|||
|
'resolution': '4K',
|
|||
|
'volume': 80,
|
|||
|
'controls': {
|
|||
|
'jump': 'space',
|
|||
|
'crouch': 'ctrl',
|
|||
|
'run': 'shift'
|
|||
|
}
|
|||
|
}
|
|||
|
},
|
|||
|
'user': {
|
|||
|
'preferences': {
|
|||
|
'theme': 'light',
|
|||
|
'notifications': True,
|
|||
|
'language': 'en-US'
|
|||
|
}
|
|||
|
},
|
|||
|
'new.section': {
|
|||
|
'setting': 'enabled'
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
result = combine_and_update_results(original_data, updates)
|
|||
|
assert result == expected_result, f"测试复杂嵌套和不同数据类型失败。\n预期: {expected_result}\n实际: {result}"
|
|||
|
print("测试复杂嵌套和不同数据类型通过。")
|
|||
|
|
|||
|
|
|||
|
# 测试用例7:键仅包含空格和点号
|
|||
|
def test_keys_only_spaces_and_dots():
|
|||
|
original_data = {
|
|||
|
'. ..': 'value1',
|
|||
|
' ': 'value2', # 仅全角空格
|
|||
|
'nested.key': {
|
|||
|
'.sub.key.': 'subvalue'
|
|||
|
}
|
|||
|
}
|
|||
|
updates = {
|
|||
|
'...': 'updated_value1',
|
|||
|
'nested.key..sub.key.': 'updated_subvalue'
|
|||
|
}
|
|||
|
expected_result = {
|
|||
|
'..': 'updated_value1',
|
|||
|
'': 'value2', # 全角空格被删除后键为空字符串
|
|||
|
'nested': {
|
|||
|
'key': {
|
|||
|
'subkey': 'updated_subvalue'
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
result = combine_and_update_results(original_data, updates)
|
|||
|
assert result == expected_result, f"测试键仅包含空格和点号失败。\n预期: {expected_result}\n实际: {result}"
|
|||
|
print("测试键仅包含空格和点号通过。")
|
|||
|
|
|||
|
|
|||
|
# 测试用例8:更新覆盖整个嵌套结构
|
|||
|
def test_overwrite_nested_structure():
|
|||
|
original_data = {
|
|||
|
'settings': {
|
|||
|
'display': {
|
|||
|
'brightness': 70,
|
|||
|
'contrast': 50
|
|||
|
},
|
|||
|
'sound': {
|
|||
|
'volume': 80
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
updates = {
|
|||
|
'settings.display': 'default', # 覆盖整个display字典
|
|||
|
'settings.sound.volume': 90 # 更新音量
|
|||
|
}
|
|||
|
expected_result = {
|
|||
|
'settings': {
|
|||
|
'display': 'default',
|
|||
|
'sound': {
|
|||
|
'volume': 90
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
result = combine_and_update_results(original_data, updates)
|
|||
|
assert result == expected_result, f"测试覆盖整个嵌套结构失败。\n预期: {expected_result}\n实际: {result}"
|
|||
|
print("测试覆盖整个嵌套结构通过。")
|
|||
|
|
|||
|
|
|||
|
# 测试用例9:更新包含列表中的字典
|
|||
|
def test_update_with_list_of_dicts():
|
|||
|
original_data = {
|
|||
|
'users': [
|
|||
|
{'name': 'Alice', 'age': 30},
|
|||
|
{'name': 'Bob', 'age': 25}
|
|||
|
],
|
|||
|
'settings': {
|
|||
|
'theme': 'dark'
|
|||
|
}
|
|||
|
}
|
|||
|
updates = {
|
|||
|
'users': [
|
|||
|
{'name': 'Alice', 'age': 31}, # 更新第一个用户的年龄
|
|||
|
{'name': 'Bob', 'age': 26}, # 更新第二个用户的年龄
|
|||
|
{'name': 'Charlie', 'age': 22} # 添加新用户
|
|||
|
],
|
|||
|
'settings.theme': 'light' # 更新主题
|
|||
|
}
|
|||
|
expected_result = {
|
|||
|
'users': [
|
|||
|
{'name': 'Alice', 'age': 31},
|
|||
|
{'name': 'Bob', 'age': 26},
|
|||
|
{'name': 'Charlie', 'age': 22}
|
|||
|
],
|
|||
|
'settings': {
|
|||
|
'theme': 'light'
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
result = combine_and_update_results(original_data, updates)
|
|||
|
assert result == expected_result, f"测试更新包含列表中的字典失败。\n预期: {expected_result}\n实际: {result}"
|
|||
|
print("测试更新包含列表中的字典通过。")
|
|||
|
|
|||
|
|
|||
|
# 测试用例10:键重复但规范化后不同
|
|||
|
def test_duplicate_keys_after_normalization():
|
|||
|
original_data = {
|
|||
|
'key': {
|
|||
|
'one':'value1',
|
|||
|
'two':'value2'
|
|||
|
}
|
|||
|
}
|
|||
|
updates = {
|
|||
|
'key.one': 'updated_value',
|
|||
|
'keytwo': 'updated_value3'
|
|||
|
}
|
|||
|
expected_result = {
|
|||
|
'keyone': 'updated_value', # 'key.one' 和 'key.one ' 规范化后合并为 'keyone'
|
|||
|
'keytwo': 'updated_value3' # 'key two' 规范化后为 'keytwo'
|
|||
|
}
|
|||
|
|
|||
|
result = combine_and_update_results(original_data, updates)
|
|||
|
assert result == expected_result, f"测试键重复但规范化后不同失败。\n预期: {expected_result}\n实际: {result}"
|
|||
|
print("测试键重复但规范化后不同通过。")
|
|||
|
|
|||
|
|
|||
|
# 主函数,运行所有测试用例
|
|||
|
def run_all_tests():
|
|||
|
test_functions = [
|
|||
|
# test_complex_case,
|
|||
|
# test_empty_updates,
|
|||
|
# test_empty_original,
|
|||
|
# test_non_dict_values,
|
|||
|
# test_full_width_spaces,
|
|||
|
# test_nested_and_various_types,
|
|||
|
# test_keys_only_spaces_and_dots,
|
|||
|
# test_overwrite_nested_structure,
|
|||
|
# test_update_with_list_of_dicts,
|
|||
|
test_duplicate_keys_after_normalization
|
|||
|
]
|
|||
|
|
|||
|
for test_func in test_functions:
|
|||
|
try:
|
|||
|
test_func()
|
|||
|
except AssertionError as e:
|
|||
|
print(e)
|
|||
|
except Exception as ex:
|
|||
|
print(f"{test_func.__name__} 运行时发生错误: {ex}")
|
|||
|
|
|||
|
print("所有测试用例已执行完毕。")
|
|||
|
|
|||
|
|
|||
|
if __name__ == '__main__':
|
|||
|
run_all_tests()
|