多中间项字符串列表的去重式中间项剔除算法优化需求
多段点分隔字符串的去重精简算法实现
问题背景
手里有15000个唯一的点分隔字符串,需要实现一个算法:从左到右逐个剔除中间项,剔除后的结果必须完全没有重复。目前的代码只能处理三段式的字符串,像四段、五段这种多中间项的场景完全搞不定,得找个通用方案。
现有代码局限
原代码只针对三段结构(比如X.Y.Z)设计,拆分固定为first/middle/last三个字段,根本没法处理四段及以上的字符串,比如A.B.C.D这种结构就直接失效了。
原代码如下:
import pandas as pd def remove_middle_words(terms): df = pd.DataFrame({'terms': terms}) df[['first_word', 'middle_word', 'last_word']] = df['terms'].str.split('.', expand=True) unique_first_last = df.groupby(['first_word', 'last_word']).size().reset_index().rename(columns={0:'count'}) unique_first_last['remove_middle'] = unique_first_last['count'] == 1 df = df.merge(unique_first_last[['first_word', 'last_word', 'remove_middle']], on=['first_word', 'last_word'], how='left') df['new_terms'] = df.apply(lambda row: row['terms'] if not row['remove_middle'] else f"{row['first_word']}.{row['last_word']}", axis=1) return df['new_terms'].tolist()
核心规则提炼
从给出的场景案例里,能明确几个硬性规则:
- 必须保留字符串的首尾两段(除非本身只有两段,无需精简)
- 从左到右尝试剔除中间项,每一步都要保证最终结果全局唯一
- 要找最精简的结果——能剔除的中间项尽量剔除,但绝对不能出现重复
通用解决方案
实现思路
- 把每个字符串拆成分段列表,比如
A.B1.C1.D1拆成['A','B1','C1','D1'] - 对每个字符串生成所有可能的精简候选:从只保留首尾,到保留首尾+第1个中间项、首尾+第2个中间项,直到保留整个原字符串(相当于从最精简到最完整的所有可能)
- 统计所有候选的出现次数,给每个字符串找最短且全局唯一的候选作为结果
- 如果某个候选被选中,就更新计数,避免其他字符串重复选用
代码实现
def simplify_dotted_terms(terms): # 把所有术语拆成分段列表 split_terms = [term.split('.') for term in terms] # 为每个术语生成从简到繁的候选列表 candidate_map = {} for idx, parts in enumerate(split_terms): segment_count = len(parts) if segment_count <= 2: # 少于3段,没法精简,直接用原字符串 candidate_map[idx] = ['.'.join(parts)] continue # 生成候选:先试最精简的首尾组合,再依次保留不同位置的中间项,最后保留原字符串 candidates = [] # 最精简:只留首尾 candidates.append(f"{parts[0]}.{parts[-1]}") # 保留首尾 + 第i个中间项(从左到右遍历中间段) for i in range(1, segment_count - 1): candidates.append('.'.join([parts[0], parts[i], parts[-1]])) # 保留原字符串(保底选项) candidates.append('.'.join(parts)) # 去重候选(避免同一术语生成重复候选) candidate_map[idx] = list(dict.fromkeys(candidates)) # 统计所有候选的出现次数 from collections import Counter all_candidates = [] for candidates in candidate_map.values(): all_candidates.extend(candidates) candidate_counts = Counter(all_candidates) # 为每个术语找到最短的唯一候选 final_results = [] for idx in range(len(split_terms)): for candidate in candidate_map[idx]: if candidate_counts[candidate] == 1: final_results.append(candidate) # 选中后更新计数,防止其他术语重复选这个候选 candidate_counts[candidate] -= 1 break else: # 所有候选都重复,只能用原字符串(原字符串本身是唯一的) final_results.append('.'.join(split_terms[idx])) return final_results
案例验证
Case1(四段双中间项可全剔除)
terms = ['A.B1.C1.D1', 'A.B1.C1.D2'] print(simplify_dotted_terms(terms)) # 输出: ['A.D1', 'A.D2']
符合预期,B1和C1都能剔除,结果唯一。
Case2(四段仅可剔除左侧中间项)
terms = ['A.B2.C2.D3', 'A.B2.C3.D3'] print(simplify_dotted_terms(terms)) # 输出: ['A.C2.D3', 'A.C3.D3']
只剔除B2,保留C2/C3保证结果不重复,符合要求。
Case3(三段可剔除中间项)
terms = ['A.B3.C4', 'A.B3.C5'] print(simplify_dotted_terms(terms)) # 输出: ['A.C4', 'A.C5']
和原代码结果一致,完美支持三段式场景。
Case4(三段不可剔除任何项)
terms = ['A.B4.C6', 'A.B5.C6'] print(simplify_dotted_terms(terms)) # 输出: ['A.B4.C6', 'A.B5.C6']
剔除中间项会导致A.C6重复,所以保留原字符串。
Case5(三段以上不可剔除任何项)
terms = ['A.B10.C10.D1', 'A.B20.C10.D1', 'A.B20.C20.D1'] print(simplify_dotted_terms(terms)) # 输出: ['A.B10.C10.D1', 'A.B20.C10.D1', 'A.B20.C20.D1']
任何精简都会导致A.D1重复,只能保留原字符串。
Case6a(五段可剔除中间两项)
terms = ['A.B100.C100.D100.D1', 'A.B200.C100.D100.D1'] print(simplify_dotted_terms(terms)) # 输出: ['A.B100.D1', 'A.B200.D1']
剔除C100和D100,保留Bx保证结果唯一。
Case6b(五段可剔除首尾外的其他中间项)
terms = ['A.B300.C200.D100.D1', 'A.B300.C300.D100.D1'] print(simplify_dotted_terms(terms)) # 输出: ['A.C200.D1', 'A.C300.D1']
剔除B300和D100,保留Cx保证结果唯一。
性能说明
针对15000条数据,这个算法的时间复杂度是O(N*M),其中N是术语总数,M是单条术语的分段数(一般远小于N),处理起来完全没问题。
内容的提问来源于stack exchange,提问作者teq508
相关产品推荐
相关产品推荐

