利用Duckdb求解Advent of Code 2020第9题 编码错误

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

原题地址
— 第9天:编码错误 —

当你的邻座正开心地玩着电子游戏时,你把注意力转向了面前座位小屏幕上一个开放的数据端口。

尽管这个端口是非标准的,但你巧妙地用几个回形针成功地将它连接到了你的电脑上。连接后,端口输出了一系列数字(你的谜题输入)。

这些数据似乎是用XMAS(交换掩码加法系统)加密的,对你来说幸运的是,这是一种存在重要弱点的旧密码。

XMAS 首先传输一个包含25个数字的前导码。之后,你接收到的每个数字都应该是紧邻的前25个数字中任意两个数字的和。这两个数字的值必须不同,并且可能不止存在一对这样的数字。

例如,假设你的前导码由1到25的数字随机排列组成。那么,为了有效,下一个数字必须是这些数字中某两个数字的和:

  • 26 将是一个有效的下一个数字,因为它可以是 125(或者许多其他组合,如 224)。
  • 49 将是一个有效的下一个数字,因为它是 2425 的和。
  • 100 将是无效的;前25个数字中没有两个数字的和是 100
  • 50 也是无效的;尽管 25 出现在前25个数字中,但这对数字中的两个数必须是不同的。

假设第26个数字是 45,并且第一个数字(由于已超过25个数字之前的范围,不再是可选项)是 20。那么,为了使下一个数字有效,需要在 1-1921-2545 这些可用的数字中,存在一对数字的和等于它:

  • 26 仍然是一个有效的下一个数字,因为 125 仍然在前25个数字的范围内。
  • 65 将是无效的,因为没有两个可用数字的和等于它。
  • 6466 都是有效的,因为它们分别是 19+4521+45 的结果。

下面是一个更大的例子,它只考虑前5个数字(并且前导码长度为5):

35
20
15
25
47
40
62
55
65
95
102
117
150
182
127
219
299
277
309
576

在这个例子中,在5个数字的前导码之后,几乎每个数字都是前5个数字中某两个数字的和;唯一不遵循此规则的数字是 127

攻击XMAS数据弱点的第一步是找出列表中(前导码之后)第一个不是其前25个数字中某两个数字之和的数字。第一个不具备此属性的数字是什么?

— 第二部分 —

破解XMAS加密的最后一步依赖于你刚刚找到的无效数字:你必须在列表中找到一个连续的、至少包含两个数字的集合,这些数字的和等于第一步中找到的无效数字。

再次考虑上面的例子:

35
20
15
25
47
40
62
55
65
95
102
117
150
182
127
219
299
277
309
576

在这个列表中,将从 1540 的所有数字相加,就得到了第一步中的无效数字 127。(当然,你实际列表中的连续数字集合可能更长。)

为了找到加密弱点,将这个连续范围内最小和最大的数字相加;在这个例子中,它们是 1547,得到 62

在你的XMAS加密数字列表中,加密弱点是什么?

解题思路:
第一部分就是穷举每个数字之前所有相邻的五个数字两两相加之和,用标量子查询标记出不在前面五个数字之和之中的那些。 注意必须包含select 1 where exists,才能保证返回一行。
第二部分要求算出每个数字之前所有相邻的n个数字相加之和, 这可以用带范围的分析函数完成,从current row 开始,用following控制数字的个数,然后找所有n个数字和中能和刚才第一部分找到的数相等的组合,用开始位置和长度确定范围,再找出其中的最大和最小值加起来。
两部分的代码写在了一起,第一部分的结果保存在 fd子查询中。

with recursive t as(select '35
20
15
25
47
40
62
55
65
95
102
117
150
182
127
219
299
277
309
576' t), 
b as(select row_number()over()rn,b::bigint b from(select unnest(string_split(t,chr(10)))b from t))
, a as(select bm.rn , bm.b,
(select 1 where exists
(select 1 from (select b2.b from b b2 where b2.rn between bm.rn-5 and bm.rn-1) ta, 
                           (select b2.b from b b2 where b2.rn between bm.rn-5 and bm.rn-1) tb where bm.b=ta.b+tb.b and ta.b>tb.b)
) can
from b bm
) 
, fd as(select * from a where can is null and rn>5 order by rn limit 1)

, d as(select 0 lv,0 rn,0::bigint sumc
union all
select lv+1,bm.rn, sum(bm.b)over(order by bm.rn rows between current row and lv+1 following) sumc from b bm,(select * from d limit 1) where 
not exists(select 1 from d where sumc=(select b from fd))
)
,fd2 as(select rn,rn+lv rn2 from d where sumc=(select b from fd))
select min(b)+max(b) from b where rn between (select rn from fd2) and (select rn2 from fd2);

以下是输出

--显示从当前位置开始连续4个数的和
memory D .read 2009.txt 
┌───────┬───────┬──────────────────────────────────────────────────────────────────────────┐
│  rn   │   b   │ sum(bm.b) OVER (ORDER BY bm.rn ROWS BETWEEN CURRENT ROW AND 3 FOLLOWING) │
│ int64 │ int64 │                                  int128                                  │
├───────┼───────┼──────────────────────────────────────────────────────────────────────────┤
│     13595 │
│     220107 │
│     315127<----425174 │
│     547204 │
│     640222 │
│     762277 │
│     855317 │
│     965379 │
│    1095464 │
│    11102551 │
│    12117576 │
│    13150678 │
│    14182827 │
│    15127922 │
│    162191104 │
│    172991461 │
│    182771162 │
│    19309885 │
│    20576576 │
├───────┴───────┴──────────────────────────────────────────────────────────────────────────┤
│ 20 rows                                                                        3 columns │
└──────────────────────────────────────────────────────────────────────────────────────────┘

memory D .read 2009.txt
┌───────┬───────┬───────┐
│  lv   │  rn   │ sumc  │
│ int32 │ int32 │ int32 │
├───────┼───────┼───────┤
│     33127 │
└───────┴───────┴───────┘

memory D .read 2009.txt
┌───────────────────┐
│ (min(b) + max(b)) │
│       int64       │
├───────────────────┤
│                62 │
└───────────────────┘
memory D .read 2009.txt
┌────────┬────────┐
│ min(b)max(b) │
│ int64  │ int64  │
├────────┼────────┤
│     1547 │
└────────┴────────┘
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值