Advent Of Code 2022 第二天 - 剪刀石头布

本文是关于 Advent Of Code 2022 的。
编程圣诞日历,圣诞节前每天一个编程问题。

有兴趣的朋友可以在官网答题,用自己喜欢的编程方式去收集星星⭐️。在官网答题需要登陆并下载为你单独生成的谜题输入,每个人的都不一样。

问题

问题是剪刀石头布,总分由两部分组成,第一部分是你出的手势。石头是1,剪刀是2,布是3。

第二部分是输赢。赢了是6,输了是0,平局为3。

最终输出就是每一场总分之和。

第一部分

精灵们开始在海滩上安营扎寨。为了决定谁的帐篷能离零食仓库最近,一场巨大的石头剪刀布
比赛已经开始了。

剪刀石头布
是两个玩家之间的游戏。每场比赛包含很多轮;在每轮比赛中,选手们同时用手形选择石头、布或剪刀中的一种。然后,选出该轮的赢家。石头击败剪刀,剪刀击败布,而布击败石头。如果两个人都选择了相同的形状,那么这一轮就以平局结束。

一个精灵为了感谢你昨天的帮助,给了你一份加密的策略指南
(你的谜题输入),他们说这一定会帮助你获胜。“第一栏是你的对手要玩的东西:A代表石头,B代表布,C
代表剪刀。第二栏……”突然,精灵被叫走了,去帮别人搭帐篷。

你觉得,第二栏一定是你应该回应的手势,作为回应。 X 代表石头, Y 代表布, Z 代表剪刀。每次都赢了会让人怀疑,所以这些回应一定是经过精心挑选的。

整个比赛的赢家是得分最高的选手。你的总分是你每一轮的分数之和。单一回合的分数是你选择的 形状的分数
(1代表石头,2代表布,3代表剪刀)加上该回合的 结果的分数(如果你输了,则为0,如果该回合是平局,则为6)。

由于你不能确定精灵是在帮助你还是在欺骗你,所以你应该计算出如果你按照策略指南的要求你会得到的分数。

例如,假设你得到了以下的策略指南:

1
2
3
A Y
B X
C Z

本战略指南预测并建议如下。

  • 在第一轮,你的对手将选择石头( A ),而你应该选择布(Y)。这以你的胜利而告终,分数为 8 (2 因为你选择了布牌 + 6 因为你赢了)。
  • 在第二轮中,你的对手将选择布牌( B ),而你应该选择石头( X )。这以你的失败告终,分数为 1 (1 + 0)。
  • 第三轮是平局,双方都选择剪刀,你的得分是 3 + 3 = 6

在这个例子中,如果你按照策略指南,你的总分将是 15 (8 + 1 + 6)。

如果一切完全按照你的策略指南进行,你的总分会是多少?

第二部分

精灵完成了对帐篷的帮助,又偷偷地走到你身边。“总之,第二栏说的是这一轮需要如何结束。 X 意味着你需要输掉比赛, Y
意味着你需要以平局结束比赛,而 Z 意味着你需要获胜。祝你好运!“

总分仍然以同样的方式计算,但现在你需要弄清楚选择什么样的形状,以使回合按指示结束。上面的例子现在是这样的。

  • 在第一轮,你的对手会选择石(A),而你需要这一轮以平局结束(Y),所以你也选择石。这样你的得分是1 + 3 = 4
  • 在第二轮,你的对手将选择布(B),而你选择石,所以你输了(X),得分是1 + 0 = 1
  • 在第三轮,你将用石头击败对手的剪刀,得分是1 + 6 = 7

现在你正确地解密了超绝密战略指南,你会得到一个总分 12

按照精灵的指示进行第二栏, 如果一切完全按照你的策略指南进行,你的总分会是多少?

代码

完整的代码可以在 这里 找到。

02.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
type Input = Vec<(i8, i8)>;

fn parse(input: &str) -> Input {
input.lines()
.filter_map(|l| {
let byte = l.as_bytes();
Some(((byte.first()? - 'A' as u8) as i8, (byte.last()? - 'X' as u8) as i8))
})
.collect()
}

fn result_score(their: i8, mine: i8) -> i8 {
(3 - (2 + their - mine) % 3) % 3 * 3
}

pub fn part_one(input: Input) -> Option<u32> {
Some(
input.iter()
.map(|&(x, y)| (result_score(x, y) + y + 1) as u32)
.sum()
)
}

pub fn part_two(input: Input) -> Option<u32> {
Some(
input.iter()
.map(|&(x, y)| {
let mine = match y {
0 => (x + 2) % 3,
1 => x,
2 => (x + 1) % 3,
_ => unreachable!()
};
(result_score(x, mine) + mine + 1) as u32
})
.sum()
)
}

解析器

每一行是一组数据,把剪刀石头布变成比较简单的 (0, 1, 2)

第一部分

这里使用的是找出一个集合中元素的顺时针距距离。
例如 [1, 2, 3, 4, 5, 6, 7, 8 , 9, 10, 11, 12]111 的距离就是 2。

这个和剪刀石头布的关系在于,如果你将 Rock, Paper, Scissors 比作 [0, 1, 2] ,往前一个的永远自己是输的,例如石头前面是布。而后一位自然自己永远是赢的,相同就代表平局。

(3 + their - mine) % 3 就是计算这个距离的方法,而 3-(x-1)%3 的原因是为了把结果偏移,因为如果按照上面的说法,1
就是输了,但是我们要的是 0
这样就可以用这个方法找到结果 (0, 1, 2)。再将这个数字乘三就是该局的分数。

第二部分

使用上述的方法只不过反过来,根据结果判断你需要出什么手势。

运行时间

所有时间由我这垃圾笔记本电脑进行(不科学的)测试,均以 --release 启动。

1
2
3
4
5
Day 2
------
Parser: ✓ (28.0µs)
Part 1: 12276 (9.4µs)
Part 2: 9975 (14.2µs)