使用 Python 合并范围数组

发布: (2025年12月31日 GMT+8 06:16)
6 min read
原文: Dev.to

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‑1412‑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 上关注我。

Back to Blog

相关文章

阅读更多 »

每周挑战:新年,新挑战

新年快乐,大家。每周,Mohammad S. Anwar 会发布 The Weekly Challenge https://theweeklychallenge.org/,这是一个让我们所有人提出解决方案的机会……