利用Duckdb求解Advent of Code 2021第8题 七笔划数码显示屏

原题地址:https://adventofcode.com/2021/day/8

第 8 天:七笔划数码管搜索

你刚刚到达洞穴的安全地带,鲸鱼就撞上了洞口,导致洞口坍塌。传感器显示这个洞穴在更深的地方有另一个出口,所以你除了继续前进别无选择。

当你的潜水艇缓慢地穿过洞穴系统时,你注意到潜艇上的四位数七笔划数码管显示器出现了故障;它们一定是在逃跑过程中损坏了。没有它们你会遇到很多麻烦,所以你最好弄清楚哪里出了问题。

七笔划数码管的每个数字通过点亮或熄灭名为 a 到 g 的七个笔划中的任意几个来显示:

  0:      1:      2:      3:      4:
 aaaa    ....    aaaa    aaaa    ....
b    c  .    c  .    c  .    c  b    c
b    c  .    c  .    c  .    c  b    c
 ....    ....    dddd    dddd    dddd
e    f  .    f  e    .  .    f  .    f
e    f  .    f  e    .  .    f  .    f
 gggg    ....    gggg    gggg    ....

  5:      6:      7:      8:      9:
 aaaa    aaaa    aaaa    aaaa    aaaa
b    .  b    .  .    c  b    c  b    c
b    .  b    .  .    c  b    c  b    c
 dddd    dddd    ....    dddd    dddd
.    f  e    f  .    f  e    f  .    f
.    f  e    f  .    f  e    f  .    f
 gggg    gggg    ....    gggg    gggg

因此,要显示数字 1,只会点亮笔划 c 和 f;其余笔划熄灭。要显示数字 7,只会点亮笔划 a, c, 和 f。

问题在于控制这些笔划的信号在每个显示器上都被弄混了。潜水艇仍然试图通过在信号线 a 到 g 上产生输出来显示数字,但这些电线被随机连接到了笔划上。更糟糕的是,电线/笔划的连接关系对每个四位数显示器是独立混用的!(不过,同一个显示器内的所有数字使用相同的连接关系。)

所以,你可能知道只有信号线 b 和 g 被点亮了,但这并不意味着笔划 b 和 g 被点亮了:唯一使用两个笔划的数字是 1,所以这必然意味着笔划 c 和 f 应该是点亮的。仅凭这个信息,你仍然无法分辨哪根电线(b/g)连接到哪个笔划(c/f)。为此,你需要收集更多信息。

对于每个显示器,你观察一会儿变化的信号,记下你看到的所有十个唯一的信号模式,然后写下一个四位的输出值(你的谜题输入)。利用这些信号模式,你应该能推断出哪种模式对应哪个数字。

例如,以下可能是你在笔记中看到的单个条目:

acedgfb cdfbe gcdfa fbcad dab cefabd cdfgeb eafb cagedb ab |
cdfeb fcadb cdfeb cdbaf

(此条目在此处换行以适合显示;在你的笔记中,它全都在一行上。)

每个条目由十个唯一的信号模式、一个 | 分隔符以及最后的四位输出值组成。在一个条目内,使用相同的电线/笔划连接关系(但你不知道实际的连接关系是什么)。唯一的信号模式对应着潜水艇使用当前电线/笔划连接关系尝试显示数字的十种不同方式。因为 7 是唯一使用三个笔划的数字,所以上面例子中的 dab 意味着要显示数字 7,信号线 d, a, 和 b 是点亮的。因为 4 是唯一使用四个笔划的数字,所以 eafb 意味着要显示数字 4,信号线 e, a, f, 和 b 是点亮的。

利用这些信息,你应该能推断出哪组信号线对应那十个数字中的每一个。然后,你就可以解码那个四位输出值。不幸的是,在上面的例子中,输出值中的所有数字(cdfeb fcadb cdfeb cdbaf)都使用了五个笔划,因此更难推断。

现在,请先关注容易识别的数字。考虑下面这个更大的例子:

be cfbegad cbdgef fgaecd cgeb fdcge agebfd fecdb fabcd edb |
fdgacbe cefdb cefbgd gcbe
edbfga begcd cbg gc gcadebf fbgde acbgfd abcde gfcbed gfec |
fcgedb cgb dgebacf gc
fgaebd cg bdaec gdafb agbcfd gdcbef bgcad gfac gcb cdgabef |
cg cg fdcagb cbg
fbegcd cbd adcefb dageb afcb bc aefdc ecdab fgdeca fcdbega |
efabcd cedba gadfec cb
aecbfdg fbg gf bafeg dbefa fcge gcbea fcaegb dgceab fcbdga |
gecf egdcabf bgf bfgea
fgeab ca afcebg bdacfeg cfaedg gcfdb baec bfadeg bafgc acf |
gebdcfa ecba ca fadegcb
dbcfg fgd bdegcaf fgec aegbdf ecdfab fbedc dacgb gdcebf gf |
cefg dcbef fcge gbcadfe
bdfegc cbegaf gecbf dfcage bdacg ed bedf ced adcbefg gebcd |
ed bcgafe cdgba cbgef
egadfb cdbfeg cegd fecab cgb gbdefca cg fgcdab egfdb bfceg |
gbdfcae bgc cg cgb
gcafb gcf dcaebfg ecagb gf abcdeg gaef cafbge fdbac fegbdc |
fgae cfgab fg bagce

因为数字 1, 4, 7, 和 8 各自使用了唯一数量的笔划,所以你应该能够分辨出哪些信号组合对应这些数字。仅统计输出值中的数字(每行 | 之后的部分),在上面的例子中,有 26 个使用了唯一笔划数的数字实例(在上面已高亮显示)。

在输出值中,数字 1、4、7 或 8 出现了多少次?

我的代码如下:

with t as(select 'be cfbegad cbdgef fgaecd cgeb fdcge agebfd fecdb fabcd edb |
fdgacbe cefdb cefbgd gcbe
edbfga begcd cbg gc gcadebf fbgde acbgfd abcde gfcbed gfec |
fcgedb cgb dgebacf gc
fgaebd cg bdaec gdafb agbcfd gdcbef bgcad gfac gcb cdgabef |
cg cg fdcagb cbg
fbegcd cbd adcefb dageb afcb bc aefdc ecdab fgdeca fcdbega |
efabcd cedba gadfec cb
aecbfdg fbg gf bafeg dbefa fcge gcbea fcaegb dgceab fcbdga |
gecf egdcabf bgf bfgea
fgeab ca afcebg bdacfeg cfaedg gcfdb baec bfadeg bafgc acf |
gebdcfa ecba ca fadegcb
dbcfg fgd bdegcaf fgec aegbdf ecdfab fbedc dacgb gdcebf gf |
cefg dcbef fcge gbcadfe
bdfegc cbegaf gecbf dfcage bdacg ed bedf ced adcbefg gebcd |
ed bcgafe cdgba cbgef
egadfb cdbfeg cegd fecab cgb gbdefca cg fgcdab egfdb bfceg |
gbdfcae bgc cg cgb
gcafb gcf dcaebfg ecagb gf abcdeg gaef cafbge fdbac fegbdc |
fgae cfgab fg bagce' t),
--b as(select unnest(string_split(replace(t,' | ','|'),chr(10)))b from t),
b as(select unnest(string_split(replace(t,' |'||chr(10),'|'),chr(10)))b from t),
line as(select row_number()over() rn,l,l2 from(select string_split(substr(b, 1, instr(b, '|')-1), ' ')l, string_split(substr(b, instr(b, '|')+1), ' ')l2 from b)), 
pat as(select rn, array_to_string(array_sort(string_split(unnest(l), '')),'')pat from line),
outp as(select rn, array_to_string(array_sort(string_split(unnest(l2), '')),'')outp from line)
--select count(*)from outp where exists (select 1 from pat where pat.rn=outp.rn and pat=outp)
select * from outp where length(outp)in(2, 3, 4, 7) and outp in (select pat from pat where pat.rn=outp.rn)
;

思路是:
将每行分成模式和输出两部分,这在示例中是|加换行符,在正式输入中没有换行符,所以上述代码提供了正式和示例两种的语句,注释掉一行就能适用其中一种。
将模式和输出都执行空格分隔的字符串转列表,再对列表中的字符串也逐个字符转列表,然后对字符列表排序,再转回字符串。这是因为,有时候模式和输出的字母顺序不同,无法直接比较,排序后就完全一致了。
然后在输出中找到同样行号的模式中长度为2, 3, 4, 7的字符串,对应1、7、4、8四个数字的笔划数。如果在模式中有,那就计数,其实输出中所有的字符串都能在模式中找到,而且不存在无效的字符串,比如aaaa这种,所以只要清点长度是2、3、4、7的字符串就可得到正确答案,不过严谨起见,就按现在这么写。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值