使用 Python 合并范围数组
Source: Dev.to
请提供您想要翻译的正文内容,我将按照要求保留源链接并翻译其余部分。
计数跨越重叠区间的唯一数字
灵感来源于 Advent of Code 2025 挑战以及之前的预订系统实现。
朴素方法
unique_values = set()
for start, end in ranges:
unique_values.update(range(start, end + 1))
对于像 1‑100_000_000_000 这样的区间,这将会在内存中创建 10 亿 整数——代价太高。我们需要一种方法在不实际生成每个值的情况下计数。
解决方案 – 先合并后计数
与其存储每个数字,我们 合并重叠(或相邻)的区间 成为一组更小的不相交区间,然后用数学方式计算总数。
示例区间
3‑5,
10‑14,
16‑20,
12‑18
10‑14 和 12‑18 重叠(它们共享 12、13、14)。合并后得到 10‑18。如果不进行合并,我们会对重叠部分进行双重计数或浪费内存。
如何合并重叠区间
1️⃣ 对区间进行排序
按起始位置排序可以让我们从数轴的左侧到右侧依次处理区间。
sorted_ranges = sorted(fresh_ranges)
# Before: [(3,5), (10,14), (16,20), (12,18)]
# After : [(3,5), (10,14), (12,18), (16,20)]
列表排序后,任何新出现的区间只能与最后一个已合并的区间重叠,因此我们不需要再向前查找。
已排序的数轴视图:
3---5 10----14
12------18
16----20
# 重叠从左到右流动!
2️⃣ 合并重叠/相邻区间
merged = []
for start, end in sorted_ranges:
# 如果当前区间与最后一个已合并区间相接或重叠 …
if merged and start <= merged[-1][1] + 1:
# … 扩展最后一个已合并区间
merged[-1] = (merged[-1][0], max(merged[-1][1], end))
else:
# 没有重叠 – 开始一个新区间
merged.append((start, end))
为什么使用 start <= merged[-1][1] + 1?
| 情形 | 最后合并的区间 | 当前区间 | 检查 (start <= last_end + 1) | 结果 |
|---|---|---|---|---|
| 相邻(相接) | (10, 14) | (15, 20) | 15 ≤ 14 + 1 → True | 合并 |
| 有间隔 | (3, 5) | (10, 14) | 10 ≤ 5 + 1 → False | 分开 |
+ 1 允许相邻的区间(例如 (5, 9) 与 (10, 15))合并,因为 9 与 10 之间没有整数。
可视化示例
区间 A: (5, 9) → 5,6,7,8,9
区间 B: (10,15) → 10,11,12,13,14,15
# 它们在 9→10 处相接
检查: 10 <= 9 + 1 → True → 合并 → (5,15)
区间 A: (5, 9) → 5,6,7,8,9
区间 B: (11,15) → 11,12,13,14,15
# 在 10 处有间隔
检查: 11 <= 9 + 1 → False → 保持分开
3️⃣ 统计数字个数
合并完毕后,不同整数的总数就是各不相交区间长度之和。
total_count = sum(end - start + 1 for start, end in merged)
return total_count
最终实现
def count_unique_numbers(fresh_ranges):
# 1️⃣ Sort by start position
sorted_ranges = sorted(fresh_ranges)
# 2️⃣ Merge overlapping / adjacent ranges
merged = []
for start, end in sorted_ranges:
if merged and start <= merged[-1][1] + 1:
# Overlap or adjacency – extend the last interval
merged[-1] = (merged[-1][0], max(merged[-1][1], end))
else:
# No overlap – start a new interval
merged.append((start, end))
# 3️⃣ Compute total count
total_count = sum(end - start + 1 for start, end in merged)
return total_count
Source: …
实际使用案例
预约预订系统
Meeting A: 09:00‑10:30
Meeting B: 10:15‑11:00
Meeting C: 14:00‑15:30
Meeting D: 15:30‑17:00
已排序:[(09:00,10:30), (10:15,11:00), (14:00,15:30), (15:30,17:00)]
已合并:[(09:00,11:00), (14:00,17:00)]
合并后的区间给出了总占用时间,避免了对重叠会议的重复计数。
其他场景
- IP 地址分配 – 合并 CIDR 块。
- 基因组区间分析 – 合并重叠的基因区域。
- 日志文件处理 – 整合活动的时间窗口。
- 资源预订 – 汇总设备或房间的已预订时段。
重叠 — 会议 C + D 触摸(相邻)
空闲时间:
- 9:00 前
- 11:00 – 14:00
- 17:00 后
实际上房间何时空闲?
IP 地址阻断
阻断: 192.168.1.0 - 192.168.1.50
阻断: 192.168.1.45 - 192.168.1.100
阻断: 192.168.1.200 - 192.168.1.255
不合并: 每次连接检查 3 条规则。
合并后:
[(192.168.1.0, 192.168.1.100), (192.168.1.200, 192.168.1.255)]
只需检查 2 条规则 → 更快的防火墙!
维护停机时间
计划进行维护的服务器或机器:
Maintenance A: Hour 0‑5
Maintenance B: Hour 4‑8
Maintenance C: Hour 20‑24
合并后的停机时间:
[(0, 8), (20, 24)]
总停机时间: 8 + 4 = 12 小时(如果单独计算则为 17 小时,但实际不是)。
为什么在所有情况下这都很重要
- 避免对重叠期间进行双重计数。
- 优化检查——减少要搜索的范围。
- 找出空档——可用时间段。
- 精准报告——真实的忙碌与空闲时间。
- 性能提升——检查更少的合并范围,而不是大量重叠范围。
结论
上述模式/方法已被全球多家公司用于处理日历邀请、数据库索引、网络流量分析等。
请大胆使用这些代码来提升您的应用性能。
如果您想交流或联系,欢迎在下方留言,或在 X/Twitter 上关注我。