利用Duckdb求解Advent of Code 2025第2题 礼品店

原题地址

— 第 2 天:礼品店 —
你进入內部,乘电梯到达它的另一个也是唯一一个停靠点:礼品店。"感谢您访问北极!"附近的一个标志欢快地呼喊着。你不确定谁甚至被允许访问北极,但你知道你可以从这里进入大厅,并从那里进入北极基地的其余部分。

当你浏览这令人惊讶地种类繁多的商品时,一位店员认出了你并请求你的帮助。

原来,一位年轻的精灵在礼品店的电脑上玩耍,结果给他们的礼品店数据库添加了一大堆无效的产品ID!对你来说,为他们识别出这些无效的产品ID肯定没问题,对吧?

他们甚至已经检查了大部分产品ID范围;只剩下几个产品ID范围(你的谜题输入)需要你检查。例如:

11-22,95-115,998-1012,1188511880-1188511890,222220-222224,
1698522-1698528,446443-446449,38593856-38593862,565653-565659,
824824821-824824827,2121212118-2121212124

(ID 范围在这里换行是为了清晰;在你的输入中,它们出现在一行很长的文本上。)

范围之间用逗号(,)分隔;每个范围给出其第一个ID和最后一个ID,中间用短横线(-)分隔。

由于那个小精灵只是在玩愚蠢的模式,你可以通过寻找那些仅由某个数字序列重复两次构成的ID来找到无效ID。因此,55(5 重复两次)、6464(64 重复两次)和 123123(123 重复两次)都将是无效ID。

这些数字都没有前导零;0101 根本不是一个ID。(101 是一个你需要忽略的有效ID。)

你的工作是找出给定范围内出现的所有无效ID。在上面的例子中:

  • 11-22 有两个无效ID:1122
  • 95-115 有一个无效ID:99
  • 998-1012 有一个无效ID:1010
  • 1188511880-1188511890 有一个无效ID:1188511885
  • 222220-222224 有一个无效ID:222222
  • 1698522-1698528 不包含无效ID。
  • 446443-446449 有一个无效ID:446446
  • 38593856-38593862 有一个无效ID:38593859
  • 其余范围不包含无效ID。

将此示例中的所有无效ID相加,得到 1227775554

如果将所有的无效ID加起来,你会得到什么?

第一部分比较简单,出现两次的ID是11、101、1001等的倍数,由于要求出无效ID,为了避免复杂的数学运算,根据输入的特点,用穷举硬除的方法。注意整除后的商的位数要比除数少一位,比如99/11=9,9比11少1位,符合要求;而110/11=10,10和11位数相等,不符合要求。

with recursive t as(select '11-22,95-115,998-1012,1188511880-1188511890,222220-222224,
1698522-1698528,446443-446449,38593856-38593862,565653-565659,
824824821-824824827,2121212118-2121212124' t),

b as(select row_number()over()rn,substr(b,1,instr(b,'-')-1)::bigint l,substr(b,instr(b,'-')+1)::bigint r 
from(select unnest(string_split(replace(t,chr(10),''),','))b from t)) , 
c as(select b.*, (power(10,length(l::text)//2)+1)::bigint d,(power(10,length(r::text)//2)+1)::bigint d2,

(select count(*) from range(l,r+1)t(i) where i%d =0 and length((i//d)::text)=length(d::text)-1
or  i%d2 =0 and length((i//d2)::text)=length(d2::text)-1)cnt,
(select sum(i) from range(l,r+1)t(i) where i%d =0 and length((i//d)::text)=length(d::text)-1
or  i%d2 =0 and length((i//d2)::text)=length(d2::text)-1)sumi
from b
)
select sum(sumi)from c;

第二部分,除了上述倍数,还包括111、1111、10101、11111、1010101、101010101等的倍数,首先要把这些除数找出来。从输入数据可见,除数最大是10位,用repeat函数比较方便。整除后的商要和重复部分位数相同。

with recursive t as(select '11-22,95-115,998-1012,1188511880-1188511890,222220-222224,
1698522-1698528,446443-446449,38593856-38593862,565653-565659,
824824821-824824827,2121212118-2121212124' t),

b as(select row_number()over()rn,substr(b,1,instr(b,'-')-1)::bigint l,substr(b,instr(b,'-')+1)::bigint r 
  from(select unnest(string_split(replace(t,chr(10),''),','))b from t)) , 
c as(select k,j,('1'||repeat(substr('00001',j),k))::bigint a from range(1,11)t(k),range(1,6)t(j) where k*(6-j)<10),
c2 as(select b.*,(select count(*) from range(l,r+1)t(i),c where i%a =0 and length((i//a)::text)=6-j)cnt,
(select sum(distinct i) from range(l,r+1)t(i),c where i%a =0 and length((i//a)::text)=6-j)sumi
from b
)
--from c2 order by rn;
select sum(sumi)from c2;

先用子查询c构造10位以内的所有除数,共19种

select k,j,('1'||repeat(substr('00001',j),k))::bigint a from range(1,11)t(k),range(1,6)t(j) where k*(6-j)<10;
┌───────┬───────┬────────────┐
│   k   │   j   │     a      │
│ int64 │ int64 │   int64    │
├───────┼───────┼────────────┤
│     11100001 │
│     1210001 │
│     22100010001 │
│     131001 │
│     231001001 │
│     331001001001 │
│     14101 │
│     2410101 │
│     341010101 │
│     44101010101 │
│     1511 │
│     25111 │
│     351111 │
│     4511111 │
│     55111111 │
│     651111111 │
│     7511111111 │
│     85111111111 │
│     951111111111 │
├───────┴───────┴────────────┤
│ 19 rows          3 columns │
└────────────────────────────┘

然后逐个试除,注意笛卡尔积会产生重复的被除数,比如222222,既能被看做2210101,也能被看做2221001,还能看做2*111111,需要用distinct去重。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值