基于日期合并元组列表:同名元组保留最长时间跨度项
问题描述
我有一个元组列表,每个元组前两项是pd.Timestamp类型的日期,第三项是名称。需求如下:
- 识别存在相同名称的多个元组
- 判断元组的日期区间是否被其他同名元组的日期区间包含
- 同时满足上述条件时,仅保留该名称下时间跨度最长的元组
示例数据
import pandas as pd data = [(pd.Timestamp('2017-01-02 00:00:00'), pd.Timestamp('2017-01-21 00:00:00'), 'John'), (pd.Timestamp('2017-01-02 00:00:00'), pd.Timestamp('2017-01-21 00:00:00'), 'John'), (pd.Timestamp('2017-01-02 00:00:00'), pd.Timestamp('2017-02-04 00:00:00'), 'Jane'), (pd.Timestamp('2017-01-02 00:00:00'), pd.Timestamp('2017-02-04 00:00:00'), 'John'), (pd.Timestamp('2017-01-21 00:00:00'), pd.Timestamp('2017-02-04 00:00:00'), 'John'), (pd.Timestamp('2017-01-01 00:00:00'), pd.Timestamp('2017-02-10 00:00:00'), 'Jane'),]
期望输出
[(pd.Timestamp('2017-01-02 00:00:00'), pd.Timestamp('2017-02-04 00:00:00'), 'John'), (pd.Timestamp('2017-01-01 00:00:00'), pd.Timestamp('2017-02-10 00:00:00'), 'Jane')]
当前错误代码及输出
错误代码
names = set([x[2] for x in data]) to_remove = [] for i in range(len(data)): for j in range(i+1, len(data)): if data[i][2] == data[j][2]: if data[i][0] >= data[j][0] and data[i][1] <= data[j][1]: to_remove.append(j) elif data[j][0] >= data[i][0] and data[j][1] <= data[i][1]: to_remove.append(i) data = [x for i,x in enumerate(data) if i not in set(to_remove)]
错误输出
[(pd.Timestamp('2017-01-02 00:00:00'), pd.Timestamp('2017-01-21 00:00:00'), 'John'), (pd.Timestamp('2017-01-02 00:00:00'), pd.Timestamp('2017-02-04 00:00:00'), 'Jane'), (pd.Timestamp('2017-01-21 00:00:00'), pd.Timestamp('2017-02-04 00:00:00'), 'John')]
修复方案
原代码问题分析
原代码采用两两比较的方式标记需要删除的项,但逻辑存在漏洞:
- 当存在多个同名称区间时,无法确保所有被包含的项都被标记删除
- 没有优先筛选出能覆盖所有同名称区间的最长跨度项,导致部分未被完全覆盖的区间残留
方案一:纯Python实现
按名称分组处理,每组内筛选出能覆盖所有区间的最长跨度项:
import pandas as pd def filter_longest_intervals(data): # 按名称分组存储区间 name_groups = {} for interval in data: name = interval[2] name_groups.setdefault(name, []).append(interval) result = [] for name, intervals in name_groups.items(): # 为每个区间计算跨度,按跨度降序排序 intervals_with_dur = [(end - start, start, end, name) for start, end, name in intervals] intervals_with_dur.sort(reverse=True, key=lambda x: x[0]) # 找到第一个能覆盖当前组所有区间的项 for dur, start, end, _ in intervals_with_dur: all_covered = True for s, e, _ in intervals: if not (s >= start and e <= end): all_covered = False break if all_covered: result.append((start, end, name)) break return result # 测试执行 data = [(pd.Timestamp('2017-01-02 00:00:00'), pd.Timestamp('2017-01-21 00:00:00'), 'John'), (pd.Timestamp('2017-01-02 00:00:00'), pd.Timestamp('2017-01-21 00:00:00'), 'John'), (pd.Timestamp('2017-01-02 00:00:00'), pd.Timestamp('2017-02-04 00:00:00'), 'Jane'), (pd.Timestamp('2017-01-02 00:00:00'), pd.Timestamp('2017-02-04 00:00:00'), 'John'), (pd.Timestamp('2017-01-21 00:00:00'), pd.Timestamp('2017-02-04 00:00:00'), 'John'), (pd.Timestamp('2017-01-01 00:00:00'), pd.Timestamp('2017-02-10 00:00:00'), 'Jane'),] print(filter_longest_intervals(data))
方案二:Pandas DataFrame实现
利用Pandas的分组和筛选功能,更简洁高效:
import pandas as pd df = pd.DataFrame(data, columns=['start', 'end', 'name']) # 计算每个区间的时间跨度 df['duration'] = df['end'] - df['start'] result = [] for name, group in df.groupby('name'): # 获取当前组所有区间的最小起始和最大结束(即需要覆盖的范围) min_start = group['start'].min() max_end = group['end'].max() # 按跨度降序排序,找到第一个覆盖目标范围的区间 target = group.sort_values('duration', ascending=False)[ (group['start'] <= min_start) & (group['end'] >= max_end) ].iloc[0] result.append((target['start'], target['end'], name)) print(result)
两种方案都能得到符合预期的输出,核心逻辑都是先按名称分组,再筛选出每组中能覆盖所有同名称区间的最长跨度项。
内容的提问来源于stack exchange,提问作者Blue482
相关产品推荐
相关产品推荐

