本文是关于 Advent Of Code 2022 的。
编程圣诞日历,圣诞节前每天一个编程问题。
有兴趣的朋友可以在官网答题,用自己喜欢的编程方式去收集星星⭐️。在官网答题需要登陆并下载为你单独生成的谜题输入,每个人的都不一样。
问题
移动箱子部分不是很复杂,只要按照指示操作就好,这题比较有趣的地方在于写出解析初始状态的解析器。
第一部分
一旦最后的物资从船上卸下,探险队就可以出发了。补给品被存放在一堆有标记的 箱 中,但由于需要的补给品被埋在许多其他箱子下面,所以箱子需要重新排列。
船上有一个 巨大的货物起重机 ,能够在堆栈之间移动板条箱。为了确保没有一个板条箱被压碎或倒下,起重机操作员将通过一系列精心策划的步骤重新安排它们。在板条箱被重新排列后,所需的板条箱将在每个堆栈的顶部。
精灵们不想在这个微妙的过程中打断起重机操作员,但他们忘了问她 哪个 板条箱最后会在哪里,而且他们想尽快准备好卸货,以便他们能够登船。
然而,他们确实有一张开始堆放的板条箱的图纸 和 重新排列的程序(你的谜题输入)。比如说:
1 2 3 4 5 6 7 8 9
| [D] [N] [C] [Z] [M] [P] 1 2 3
move 1 from 2 to 1 move 3 from 1 to 3 move 2 from 2 to 1 move 1 from 1 to 2
|
在这个例子中,有三个堆栈的板条箱。堆栈1包含两个箱子:箱子Z
在底部,箱子N
在顶部。堆栈2包含三个箱子;从下到上,它们是箱子M
,C
,和D
。最后,堆栈3包含一个板条箱,P
。
然后,给出了重新排列的程序。在程序的每一步中,一定数量的板条箱被从一个堆栈移到另一个堆栈。在上述重排程序的第一步中,一个板条箱从堆栈2移到堆栈1,形成了这种配置。
1 2 3 4
| [D] [N] [C] [Z] [M] [P] 1 2 3
|
在第二步中,三个板条箱被从堆栈1移到堆栈3。板条箱是 一个个移动 的,所以第一个被移动的板条箱(D
)最后会在第二和第三个板条箱下面。
1 2 3 4 5
| [Z] [N] [C] [D] [M] [P] 1 2 3
|
然后,两个板条箱都从堆栈2移到堆栈1。同样,因为箱子是 一个个移动 的,所以箱子C
最后会在箱子M
下面。
1 2 3 4 5
| [Z] [N] [M] [D] [C] [P] 1 2 3
|
最后,一个板条箱从堆栈1移到堆栈2。
1 2 3 4 5
| [Z] [N] [D] [C] [M] [P] 1 2 3
|
精灵们只需要知道 哪个板条箱最终会在每个堆栈的顶部 ;在这个例子中,顶部的板条箱是堆栈1中的C
,堆栈2中的M
,以及堆栈3中的Z
,所以你应该把这些结合起来,给精灵们一个信息 CMZ
。
在重新排列程序完成后,每个堆栈顶部的板条箱最终是什么?
第二部分
当你看着起重机操作员熟练地重新排列板条箱时,你注意到这个过程并没有按照你的预测进行。
一些泥巴覆盖在起重机侧面的字迹上,你赶紧把它擦掉。这台起重机不是 箱子移动机9000 - 它是 箱子移动机9001 。
箱子移动机9001值得注意的是许多新的和令人兴奋的功能:空调、皮革座椅、一个额外的杯架,以及 能够同时拿起和移动多个箱子 。
再次考虑上面的例子,板条箱开始时的配置是一样的。
1 2 3 4
| [D] [N] [C] [Z] [M] [P] 1 2 3
|
将单个板条箱从堆栈2移到堆栈1的行为与以前一样。
1 2 3 4
| [D] [N] [C] [Z] [M] [P] 1 2 3
|
然而,将三个板条箱从堆栈1移到堆栈3的动作意味着这三个移动的板条箱*保持相同的顺序,结果是这样的新配置。
1 2 3 4 5
| [D] [N] [C] [Z] [M] [P] 1 2 3
|
接下来,当两个箱子从堆栈2移到堆栈1时,它们也 保持它们的顺序 。
1 2 3 4 5
| [D] [N] [C] [Z] [M] [P] 1 2 3
|
最后,一个板条箱仍然从堆栈1移到堆栈2,但现在是板条箱C
被移动。
1 2 3 4 5
| [D] [N] [Z] [M] [C] [P] 1 2 3
|
在这个例子中,箱子移动机9001把板条箱放在一个完全不同的顺序。 MCD
。
在重排过程结束前,更新你的模拟,让精灵们知道他们应该站在哪里,以准备卸下最后的物资。
在重新安排程序完成后,什么板条箱最终会在每个堆栈的顶部?
代码
完整的代码可以在 这里 找到。
05.rs1 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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| use itertools::Itertools;
type Input = (Stack, Vec<Instruction>);
type Stack = Vec<Vec<char>>; type Instruction = (usize, usize, usize);
fn move_stacks(stack: &mut Stack, instruction: Vec<Instruction>, grouped: bool) { instruction.iter().for_each(|&(count, from, to)| { let pillar = &mut stack[from - 1]; let boxes = pillar.split_off(pillar.len() - count); if grouped { stack[to - 1].extend(boxes.iter()) } else { stack[to - 1].extend(boxes.iter().rev()) } }) }
fn top_row_string(stack: Stack) -> String { stack.iter().filter_map(|pillar| pillar.last()).join("") }
fn parse(input: &str) -> Input { let (stack_input, instruction_input) = input.split_once("\n\n").unwrap(); let mut stack_iter = stack_input.lines().rev(); let mut stack = vec![vec![]; stack_iter.next().unwrap().len() / 4 + 1];
stack_iter.for_each(|line| { line.chars() .skip(1) .step_by(4) .enumerate() .for_each(|(i, x)| if x != ' ' { stack[i].push(x) }) });
let instructions = instruction_input .lines() .map(|x| { let k = x.split_ascii_whitespace().collect::<Vec<&str>>(); (k[1].parse().unwrap(), k[3].parse().unwrap(), k[5].parse().unwrap()) }).collect(); (stack, instructions) }
pub fn part_one(input: Input) -> Option<String> { let (mut stack, instruction) = input; move_stacks(&mut stack, instruction, false); Some( top_row_string(stack) ) }
pub fn part_two(input: Input) -> Option<String> { let (mut stack, instruction) = input; move_stacks(&mut stack, instruction, true); Some( top_row_string(stack) ) }
|
解析器
这个是这天的重点,很多人都没写解析器就靠手输入,毕竟这初始状态一看就不好做。 我这解析器是把输入分为两部分解决。
初始状态先获取有多少个柱子,然后从下往上每一行从第二个字符开始跳过四个,正好就是每一个柱子的输入,如果是空格就无视。
指示的部分就很简单了,只需要用空格分割然后找出对的地方就可以了。
第一部分
一个个拿起来如果你想象一下结果,就跟拿起来了反转一下一样。
第二部分
整块拿起来连反转都不需要。
运行时间
所有时间由我这垃圾笔记本电脑进行(不科学的)测试,均以 --release
启动。
1 2 3 4 5
| Day 5 ------ Parser: ✓ (211.9µs) Part 1: TDCHVHJTG (57.6µs) Part 2: NGCMPJLHV (51.8µs)
|