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()
|