如何高效迭代生成无重复ID且ratio总和为1的字典数组组合
组合筛选性能优化方案
需求背景
需要从字典数组中筛选符合以下规则的所有组合:
- 组合内不存在id重复的字典
- 组合中所有字典的ratio值相加总和为1.0
- 每个id对应的ratio取值范围为0.01到1.0,步长0.01
输入输出示例
输入数据源示例
_array_list = [ { 'id': 1, 'ratio': .01 }, { 'id': 1, 'ratio': .02 }, # 省略同id其他ratio项 { 'id': 2, 'ratio': .01 }, { 'id': 3, 'ratio': .02 }, # 省略其他id的ratio项 ]
预期输出结构
results = [ [ { 'id': 1, 'ratio': .01 }, { 'id': 2, 'ratio': .99 }, ], [ { 'id': 1, 'ratio': .50 }, { 'id': 2, 'ratio': .50 }, ], # 省略其他符合要求的组合 ]
现有问题
原有实现采用全量组合遍历的逻辑,先枚举所有长度的组合,再判断id是否重复、总和是否为1.0,当存在5个不同ID、每个ID对应100个字典时,需要遍历约6亿次组合,耗时约20分钟,性能极差。
优化方案
核心思路
- 先按id对原始数组分组,每个id对应的所有ratio作为独立分组,组合时每个分组最多选1个元素,天然避免id重复的判断逻辑
- 将所有ratio乘以100转为整数计算,避免浮点数精度问题,求和目标转为100
- 采用回溯+剪枝的方式遍历分组,当前累加和超过100时直接停止该分支的遍历,砍掉绝大多数无效计算
优化后代码
from collections import defaultdict def get_valid_combinations(_array_list): # 第一步:按id分组 id_groups = defaultdict(list) for item in _array_list: # 转整数避免浮点误差,同时保留原字典引用 ratio_int = int(round(item['ratio'] * 100)) id_groups[item['id']].append((ratio_int, item)) groups = list(id_groups.values()) res = [] # 回溯函数:当前处理的分组索引、当前累加和、当前已选的元素列表 def backtrack(index, current_sum, path): if current_sum == 100: res.append(path.copy()) return if current_sum > 100 or index >= len(groups): return # 分支1:不选当前分组的元素,直接处理下一个分组 backtrack(index + 1, current_sum, path) # 分支2:选当前分组的一个元素 for ratio_int, item in groups[index]: new_sum = current_sum + ratio_int if new_sum > 100: continue # 剪枝,超过目标和直接跳过 path.append(item) backtrack(index + 1, new_sum, path) path.pop() backtrack(0, 0, []) return res # 调用方式 all_combos = get_valid_combinations(_array_list)
性能提升说明
针对5个id、每个id100个ratio的场景,优化后的实现状态数最多为id数量 * 目标和(100),仅为500个核心状态,加上剪枝逻辑,整体遍历次数不到10万次,运行耗时可从20分钟压缩到1秒以内。同时避免了浮点数求和的精度误差问题,结果准确性更高。
内容的提问来源于stack exchange,提问作者Bob Linux
相关产品推荐
相关产品推荐

