利用Duckdb求解Advent of Code 2025第5题 自助餐厅

编程达人挑战赛·第5期 10w+人浏览 221人参与

原题地址
— 第 5 天:自助餐厅 —
当叉车打通墙壁时,精灵们高兴地发现,另一边确实有一个自助餐厅。

你能听到厨房传来的喧闹声。"照这样下去,我们就没有时间在餐厅挂花环了!"你决心继续你的任务,于是前去查看。

"要是我们没有在圣诞节前换用新的库存管理系统就好了!"另一位精灵大声说道。你询问发生了什么事。

厨房里的精灵解释了情况:由于他们复杂的新库存管理系统,他们无法分辨哪些食材是新鲜的,哪些是变质的。当你询问系统如何工作时,他们给了你一份他们的数据库副本(你的谜题输入)。

该数据库基于食材ID运作。它由一个新鲜食材ID范围列表、一个空行和一个可用食材ID列表组成。例如:

3-5
10-14
16-20
12-18

1
5
8
11
17
32

新鲜ID范围是包含性的:范围 3-5 意味着食材ID 3、4 和 5 都是新鲜的。范围也可以重叠;如果一个食材ID位于任何范围内,它就是新鲜的。

精灵们试图确定哪些可用食材ID是新鲜的。在这个例子中,判断如下:

  • 食材ID 1 是变质的,因为它不在任何范围内。
  • 食材ID 5 是新鲜的,因为它在范围 3-5 内。
  • 食材ID 8 是变质的。
  • 食材ID 11 是新鲜的,因为它在范围 10-14 内。
  • 食材ID 17 是新鲜的,因为它在范围 16-20 内,同时也在范围 12-18 内。
  • 食材ID 32 是变质的。

所以,在这个例子中,3 个可用食材ID是新鲜的。

处理来自新库存管理系统的数据库文件。有多少个可用食材ID是新鲜的?

— 第二部分 —
精灵们开始把变质的库存搬到厨房后面的垃圾滑道。

为了让它们在收到新库存时不再烦你,精灵们想知道所有被新鲜食材ID范围认为是新鲜的ID。如果一个食材ID位于任何范围内,它仍然被认为是新鲜的。

现在,数据库的第二部分(可用食材ID)已经无关紧要。以下是来自上面示例的新鲜食材ID范围:

3-5
10-14
16-20
12-18

这些范围认为新鲜的食材ID是 3, 4, 5, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20。因此,在这个例子中,新鲜食材ID范围认为总共有 14 个食材ID是新鲜的。

再次处理数据库文件。根据新鲜食材ID范围,有多少个食材ID被认为是新鲜的?

解题方法:
第一部分很简单,就是求哪些食材在新鲜食材范围中,用数据库exists就行了。
代码如下

with t as (select '3-5
10-14
16-20
12-18

1
5
8
11
17
32' t),
a as(select b::bigint a from(select unnest(string_split(t,chr(10)))b from t)where instr(b,'-')=0 and b<>''),
r as(select substr(b,1,instr(b,'-')-1)::bigint l,substr(b,instr(b,'-')+1)::bigint r from(select unnest(string_split(t,chr(10)))b from t)where instr(b,'-')>0)
select count(*) from a where exists(select 1 from r where a.a between l and r);

第二部分涉及新鲜范围中的食材总数,因为正式数据很大,所以不可能把它们都展开再去重计数,而要采取范围上下限差值加总的办法,但要求这些范围都是彼此不重叠的。
SQL没有简单的操作可以实现范围合并,于是请教DeepSeek,他提供了如下代码

def merge_intervals(intervals):
    """
    合并重叠区间,直到所有区间互不重叠。
    
    参数:
    intervals: 区间列表,例如 [[1,4], [2,7], [9,10]]
    
    返回:
    合并后的区间列表,例如 [[1,7], [9,10]]
    """
    if not intervals:
        return []
    
    # 按区间起点排序
    intervals.sort(key=lambda x: x[0])
    
    merged = []
    
    for interval in intervals:
        # 如果merged为空,或者当前区间与上一个区间不重叠
        if not merged or merged[-1][1] < interval[0]:
            merged.append(interval)
        else:
            # 有重叠,合并区间(取右端点的最大值)
            merged[-1][1] = max(merged[-1][1], interval[1])
    
    return merged

# 测试
intervals = [[1, 4], [2, 7], [9, 10]]
result = merge_intervals(intervals)
print(result)  # 输出: [[1, 7], [9, 10]]

这个算法简单,可以用递归CTE结合数组实现,以下是我翻译的代码,注意数组合并||要求左右值均是数组。

with recursive t as (select '3-5
10-14
16-20
12-18' t),
r as(select substr(b,1,instr(b,'-')-1)::bigint l,substr(b,instr(b,'-')+1)::bigint r from(select unnest(string_split(t,chr(10)))b from t)where instr(b,'-')>0)
,ar as(select list([l,r] order by l)ar from r)
,a as(select 2 lv ,[ar[1]] mg from ar 
union all
select lv+1, case when len(mg)=0 or mg[-1][2] < ar[lv][1] then mg||[ar[lv]] else mg[:len(mg)-1]::BIGINT[][]||[[mg[-1][1] , greatest(mg[-1][2], ar[lv][2]) ]]end  from a,ar where lv<=len(ar))
select sum(b[2]-b[1]+1) from(select unnest(mg) b from a where lv=(select max(lv) from a))
;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值