利用Duckdb求解Advent of Code 2025第4题 叉车纸卷

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

原题地址

— 第 4 天:印刷部门 —
你乘自动扶梯下到印刷部门。他们显然在为圣诞节做准备;到处都是大卷的纸,角落里甚至还有一台巨大的打印机(用来处理真正的大型打印任务)。

在这里装饰会很容易:他们可以制作自己的装饰品。你真正需要的是在电梯离线时能够进一步进入北极基地的方法。

"实际上,也许我们可以帮忙解决这个问题,"当你请求帮助时,一位精灵回答说。“我们很确定后墙的另一边有一个自助餐厅。如果我们能打通那堵墙,你就能继续前进。可惜我们所有的叉车都忙着搬运那些大卷的纸。”

如果你能优化叉车的工作,也许它们就能抽出时间来打通墙壁。

纸卷(@)被放置在一个大网格上;精灵们甚至有一张有用的图表(你的谜题输入)来指示所有东西的位置。

例如:

..@@.@@@@.
@@@.@.@.@@
@@@@@.@.@@
@.@@@@..@.
@@.@@@@.@@
.@@@@@@@.@
.@.@.@.@@@
@.@@@.@@@@
.@@@@@@@@.
@.@.@@@.@.

叉车只能访问那些八个相邻位置中纸卷数量少于四个的纸卷。如果你能找出叉车可以访问哪些纸卷,它们就能花更少的时间寻找,花更多的时间拆通往自助餐厅的墙。

在这个例子中,有 13 个纸卷可以被叉车访问(用 x 标记):

..xx.xx@x.
x@@.@.@.@@
@@@@@.x.@@
@.@@@@..@.
x@.@@@@.@x
.@@@@@@@.@
.@.@.@.@@@
x.@@@.@@@@
.@@@@@@@@.
x.x.@@@.x.

考虑你的完整纸卷位置图。有多少纸卷可以被叉车访问?

此谜题的第一部分已完成!它提供了一颗金星:*

— 第二部分 —
现在,精灵们需要帮助来尽可能多地访问纸卷。

一旦一个纸卷可以被叉车访问,它就可以被移除。一旦一个纸卷被移除,叉车可能就能够访问更多的纸卷,这些纸卷也可能被移除。如果精灵们不断重复这个过程,总共可以移除多少个纸卷?

从上面相同的例子开始,以下是一种尽可能多地移除纸卷的方法,使用高亮的 @ 表示一个纸卷即将被移除,使用 x 表示一个纸卷刚刚被移除:

初始状态:

..@@.@@@@.
@@@.@.@.@@
@@@@@.@.@@
@.@@@@..@.
@@.@@@@.@@
.@@@@@@@.@
.@.@.@.@@@
@.@@@.@@@@
.@@@@@@@@.
@.@.@@@.@.

移除 13 个纸卷:

..xx.xx@x.
x@@.@.@.@@
@@@@@.x.@@
@.@@@@..@.
x@.@@@@.@x
.@@@@@@@.@
.@.@.@.@@@
x.@@@.@@@@
.@@@@@@@@.
x.x.@@@.x.

移除 12 个纸卷:

.......x..
.@@.x.x.@x
x@@@@...@@
x.@@@@..x.
.@.@@@@.x.
.x@@@@@@.x
.x.@.@.@@@
..@@@.@@@@
.x@@@@@@@.
....@@@...

移除 7 个纸卷:

..........
.x@.....x.
.@@@@...xx
..@@@@....
.x.@@@@...
..@@@@@@..
...@.@.@@x
..@@@.@@@@
..x@@@@@@.
....@@@...

移除 5 个纸卷:

..........
..x.......
.x@@@.....
..@@@@....
...@@@@...
..x@@@@@..
...@.@.@@.
..x@@.@@@x
...@@@@@@.
....@@@...

移除 2 个纸卷:

..........
..........
..x@@.....
..@@@@....
...@@@@...
...@@@@@..
...@.@.@@.
...@@.@@@.
...@@@@@x.
....@@@...

移除 1 个纸卷:

..........
..........
...@@.....
..x@@@....
...@@@@...
...@@@@@..
...@.@.@@.
...@@.@@@.
...@@@@@..
....@@@...

移除 1 个纸卷:

..........
..........
...x@.....
...@@@....
...@@@@...
...@@@@@..
...@.@.@@.
...@@.@@@.
...@@@@@..
....@@@...

移除 1 个纸卷:

..........
..........
....x.....
...@@@....
...@@@@...
...@@@@@..
...@.@.@@.
...@@.@@@.
...@@@@@..
....@@@...

移除 1 个纸卷:

..........
..........
..........
...x@@....
...@@@@...
...@@@@@..
...@.@.@@.
...@@.@@@.
...@@@@@..
....@@@...

一旦没有更多的纸卷可以被叉车访问就停止。在这个例子中,总共可以移除 43 个纸卷。

从你的原始图表开始。精灵们和他们的叉车总共可以移除多少个纸卷?

解题方法:
找周围的八个方向有个计算公式,就是行列号差的绝对值小于或等于1,然后扣除它本身,统计每个卷纸附近的卷纸小于4个,就变成count小于5。
第一问的SQL如下:

with t as(select '
..@@.@@@@.
@@@.@.@.@@
@@@@@.@.@@
@.@@@@..@.
@@.@@@@.@@
.@@@@@@@.@
.@.@.@.@@@
@.@@@.@@@@
.@@@@@@@@.
@.@.@@@.@.' t), 
b as(select row_number()over()rn,(rn-1)//10+1 r, (rn-1)%10+1 c,b from(select unnest(string_split(replace(t,chr(10), ''), ''))b from t))
,c as(select b.rn from b,b b2 where b.b='@' and abs(b.r-b2.r)<=1 and abs(b.c-b2.c)<=1 group by b.rn having count(case when b2.b='@' then 1 end) <5)
select count(*) from c;

第二问思路与第一问完全一样,只是迭代一次就去掉一些卷纸,但是用传统的递归CTE遇到一些麻烦,下面用DuckDB专用的using key和recurring语法将被移除的纸卷做上标记。

with recursive t as(select '
..@@.@@@@.
@@@.@.@.@@
@@@@@.@.@@
@.@@@@..@.
@@.@@@@.@@
.@@@@@@@.@
.@.@.@.@@@
@.@@@.@@@@
.@@@@@@@@.
@.@.@@@.@.' t), 
b as(select row_number()over()rn,(rn-1)//10+1 r, (rn-1)%10+1 c,b from(select unnest(string_split(replace(t,chr(10), ''), ''))b from t))
,c using key(rn)as
(
select b.rn,b.r,b.c,b.b from b
union
select b.rn,b.r,b.c,'x' from recurring.c b,recurring.c b2 where b.b='@' and abs(b.r-b2.r)<=1 and abs(b.c-b2.c)<=1 group by b.rn,b.r,b.c,b.b having count(case when b2.b='@' then 1 end) <5
)
select count(*)from c where b='x';

如果用传统递归CTE方法,要注意每轮要检查有没有符合移除条件的行,保留那些不可被移除的。不存在可移除的纸卷时停止迭代,否则会无限循环,因为总存在不可移除的纸卷。写起来冗余一些。

with recursive t as(select '
..@@.@@@@.
@@@.@.@.@@
@@@@@.@.@@
@.@@@@..@.
@@.@@@@.@@
.@@@@@@@.@
.@.@.@.@@@
@.@@@.@@@@
.@@@@@@@@.
@.@.@@@.@.' t), 
b as(select row_number()over()rn,(rn-1)//10+1 r, (rn-1)%10+1 c,b from(select unnest(string_split(replace(t,chr(10), ''), ''))b from t))
,c as 
(
select 1 lv,b.rn,b.r,b.c,b.b from b
union all
select * from(
select 1+any_value(b.lv),b.rn,b.r,b.c,b.b from c b,c b2 where b.b='@' and abs(b.r-b2.r)<=1 and abs(b.c-b2.c)<=1 group by b.rn,b.r,b.c,b.b having count(case when b2.b='@' then 1 end) >=5
)where exists (select b.rn,b.r,b.c,'x' from c b,c b2 where b.b='@' and abs(b.r-b2.r)<=1 and abs(b.c-b2.c)<=1 group by b.rn,b.r,b.c,b.b having count(case when b2.b='@' then 1 end) <5)
)
select count(case when b='@' and lv=1 then 1 end)-count(case when b='@' and lv=(select max(lv) from c)then 1 end) mov from c;

如果不检查,那么要手工控制迭代次数,比如b.lv <100,因为不知道几轮能移除完那些最终可移除的纸卷,只能放一个较大的值,而这样效率低很多,放小了又有移除不完的风险,所以不推荐。

with recursive t as(select '
..@@.@@@@.
@@@.@.@.@@
@@@@@.@.@@
@.@@@@..@.
@@.@@@@.@@
.@@@@@@@.@
.@.@.@.@@@
@.@@@.@@@@
.@@@@@@@@.
@.@.@@@.@.' t), 
b as(select row_number()over()rn,(rn-1)//10+1 r, (rn-1)%10+1 c,b from(select unnest(string_split(replace(t,chr(10), ''), ''))b from t))
,c as 
(
select 1 lv,b.rn,b.r,b.c,b.b from b
union all
select 1+any_value(b.lv),b.rn,b.r,b.c,b.b from c b,c b2 where b.b='@' and abs(b.r-b2.r)<=1 and abs(b.c-b2.c)<=1 and b.lv <100 group by b.rn,b.r,b.c,b.b having count(case when b2.b='@' then 1 end) >=5
)
select count(case when b='@' and lv=1 then 1 end)-count(case when b='@' and lv=(select max(lv) from c)then 1 end) mov from c;
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值