移除列表或Pandas DataFrame中为同集合其他元素子集的子列表
问题说明
现有嵌套列表IDlist,需求为移除列表中所有属于同列表内其他列表元素子集的子列表:
- 例如下方样例中行号61、62、64对应的列表均为行63对应列表的子集,最终仅需保留行63的元素
- 完全重复的子列表互为子集,最终仅需保留1份
样例原始数据片段:
56 ['2588446634610274688', '2588446634612110336'] 57 ['348020242217448576', '348020448377061376', '348020482735930112'] 58 ['565983471644073472', '565989347158652288'] 59 ['4912580642524184960', '4912898156569562624'] 60 ['318121222523445376', '318121256883850112'] 61 ['356731363606425856', '357478894075788928', '357479272034582528'] 62 ['356731363606425856', '357478894075788928', '357479272034582528'] 63 ['356731363606425856', '356731363608936576', '357478894075788928', '357479272034582528'] 64 ['356731363606425856', '356731363608936576', '357478894075788928'] 65 ['2512629230496996992', '2512629230497166848']
测试用的templist打印结果:
>>> print(templist) [['318121222523445376', '318121256883850112'], ['356731363606425856', '357478894075788928', '357479272034582528'], ['356731363606425856', '357478894075788928', '357479272034582528'], ['356731363606425856', '356731363608936576', '357478894075788928', '357479272034582528'], ['356731363606425856', '356731363608936576', '357478894075788928'], ['2512629230496996992', '2512629230497166848']]
实现方案
方案1:Python原生列表实现(无第三方依赖,通用性强)
核心逻辑:
- 先将所有子列表转为集合,子集判断直接用
set.issubset()方法,比遍历列表判断效率高很多,也不会因为子列表内部元素顺序不同出现判断错误 - 排除「元素自己和自己比」的情况,同时处理完全重复的子列表:如果两个子列表完全相等,只保留一份
- 对每个待检查元素,只要列表中存在任意一个其他元素能完全包含它,就把它过滤掉
代码如下:
def filter_subset_lists(input_list): # 转frozenset既支持子集判断,也可哈希用于去重 all_sets = [frozenset(item) for item in input_list] res = [] added_sets = set() # 记录已保留的集合,避免重复元素重复加入 for idx, current_set in enumerate(all_sets): # 重复元素直接跳过 if current_set in added_sets: continue # 检查是否存在其他集合包含当前集合 is_subset = False for other_idx, other_set in enumerate(all_sets): if idx == other_idx: continue # 跳过自身对比 if current_set.issubset(other_set): is_subset = True break # 不是任何其他集合的子集则保留 if not is_subset: res.append(input_list[idx]) added_sets.add(current_set) return res
用给出的templist测试:
result = filter_subset_lists(templist) print(result)
输出结果符合预期,61、62、64对应的元素都会被过滤,仅保留63的长列表,其余无包含关系的短列表正常保留:
[ ['318121222523445376', '318121256883850112'], ['356731363606425856', '356731363608936576', '357478894075788928', '357479272034582528'], ['2512629230496996992', '2512629230497166848'] ]
如果列表数据量很大,可以先把所有集合按长度降序排序,判断时仅对比长度大于等于当前集合的元素,能进一步减少判断次数,提升运行速度。
方案2:Pandas实现(适合已在Pandas流程中处理的数据)
如果数据已经存在DataFrame中,可以用以下写法:
import pandas as pd # 假设df为目标数据表,子列表存储在col列 df['item_set'] = df['col'].apply(frozenset) all_sets = df['item_set'].tolist() keep_mask = [] for s in all_sets: keep = True for other_s in all_sets: if s == other_s: continue if s.issubset(other_s): keep = False break keep_mask.append(keep) # 过滤后去重得到最终结果 res_df = df[keep_mask].drop_duplicates(subset='item_set').drop(columns='item_set')
常见踩坑点
之前的方法不通用,大概率是犯了以下错误:
- 子集判断时没有跳过「元素和自身对比」的情况,导致所有元素都被判定为自己的子集被全部过滤
- 没有处理完全重复的子列表,导致重复元素互为子集被全部删掉
- 直接用列表遍历判断元素是否存在,没有用set做子集判断,不仅效率低,还会因为列表内元素顺序不同判断错误(比如
['a','b']和['b','a']是同一个集合,但直接列表遍历会判定为不相等)
内容的提问来源于stack exchange,提问作者AiyaEarendil
相关产品推荐
相关产品推荐

