Day 1: The Tyranny of the Rocket Equation
Rust solution to AoC|2019|1.
Not much to say today.
Input
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pub mod input {
#[derive(Debug)]
pub struct PuzzleData {
pub masses: Vec<usize>,
}
impl From<&str> for PuzzleData {
/// parse the puzzle input
fn from(s: &str) -> Self {
Self {
masses: s
.lines()
.map(|line| line.parse())
.collect::<Result<_, _>>()
.unwrap(),
}
}
}
}
Star 1
1
2
3
4
5
6
7
8
9
10
11
pub fn req_fuel(m: &usize) -> Option<usize> {
if *m >= 6 {
Some(m / 3 - 2)
} else {
None
}
}
pub fn star_1(data: &PuzzleData) -> SolType1 {
data.masses.iter().filter_map(req_fuel).sum()
}
Star 2
1
2
3
4
5
6
pub fn star_2(data: &PuzzleData) -> SolType2 {
data.masses
.iter()
.flat_map(|mass| successors(req_fuel(mass), req_fuel))
.sum()
}
Day 2: Program Alarm
Rust solution to AoC|2019|2.
All the logic is in the IntCode
implementation
Star 1
1
2
3
4
5
6
7
pub fn star_1(memory: &Memory) -> SolType {
let mut code = IntCode::new(memory);
code[1] = 12;
code[2] = 2;
code.run();
code[0]
}
Star 2
1
2
3
4
5
6
7
8
9
10
11
12
pub fn star_2(memory: &Memory) -> SolType {
let mut code = IntCode::new(vec![]);
(0..100 * 100)
.find(|res| {
code.reset(memory);
code[1] = res / 100;
code[2] = res % 100;
code.run();
code[0] == 19_690_720
})
.unwrap()
}
Day 3: Crossed Wires
Rust solution to AoC|2019|3.
Input
Quite a bit of the logic is already in the input parsing. The input is parsed into a sequence of points together with the length of the wire (poly-line) up to each point.
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
39
40
41
pub mod input {
use std::iter::once;
#[derive(Debug)]
pub struct PuzzleData {
pub wire_1: Vec<((isize, isize), isize)>,
pub wire_2: Vec<((isize, isize), isize)>,
}
/// parse poly-line into a sequence of points together with the cumulative
/// length to each of the points
fn parse_line(line: &str) -> Vec<((isize, isize), isize)> {
once(((0, 0), 0))
.chain(
line.split(',')
.map(|part| (part[1..].parse::<isize>().unwrap(), part.as_bytes()[0]))
.scan(((0, 0), 0), |((x, y), cum_steps), (steps, dir)| {
((*x, *y), *cum_steps) = match dir {
b'R' => ((*x + steps, *y), *cum_steps + steps),
b'U' => ((*x, *y - steps), *cum_steps + steps),
b'L' => ((*x - steps, *y), *cum_steps + steps),
b'D' => ((*x, *y + steps), *cum_steps + steps),
_ => panic!("Unexpected direction: {}", dir as char),
};
Some(((*x, *y), *cum_steps))
}),
)
.collect()
}
impl From<&str> for PuzzleData {
/// parse the puzzle input
fn from(s: &str) -> Self {
let mut lines = s.lines();
Self {
wire_1: parse_line(lines.next().unwrap()),
wire_2: parse_line(lines.next().unwrap()),
}
}
}
}
Star 1
What is not done in input parsing is in the intersections
function.
The rest is simple.
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/// Find intersections of two poly-lines (each segment is either horizontal
/// or vertical)
///
/// Assumption: there are only real intersections of a horizontal segment
/// with a vertical segments, no overlapping horizontal or vertical segments
///
/// Each intersection is represented by its coordinates and the steps from
/// the origin to the intersection
pub fn intersections(
wire_1: &[((isize, isize), isize)],
wire_2: &[((isize, isize), isize)],
) -> Vec<((isize, isize), isize, isize)> {
wire_1
.iter()
.zip(wire_1.iter().skip(1))
.flat_map(|(((x_1_a, y_1_a), l_1), ((x_1_b, y_1_b), _))| {
wire_2.iter().zip(wire_2.iter().skip(1)).filter_map(
move |(((x_2_a, y_2_a), l_2), ((x_2_b, y_2_b), _))| {
if (x_2_a != &0 || y_1_a != &0)
&& x_2_a == x_2_b
&& y_1_a == y_1_b
&& x_2_a >= x_1_a.min(x_1_b)
&& x_2_a <= x_1_a.max(x_1_b)
&& y_1_a >= y_2_a.min(y_2_b)
&& y_1_a <= y_2_a.max(y_2_b)
{
Some((
(*x_2_a, *y_1_a),
*l_1 + (x_2_a - x_1_a).abs(),
*l_2 + (y_1_a - y_2_a).abs(),
))
} else if (x_1_a != &0 || y_2_a != &0)
&& y_2_a == y_2_b
&& x_1_a == x_1_b
&& y_2_a >= y_1_a.min(y_1_b)
&& y_2_a <= y_1_a.max(y_1_b)
&& x_1_a >= x_2_a.min(x_2_b)
&& x_1_a <= x_2_a.max(x_2_b)
{
Some((
(*x_1_a, *y_2_a),
*l_1 + (y_2_a - y_1_a).abs(),
*l_2 + (x_1_a - x_2_a).abs(),
))
} else {
None
}
},
)
})
.collect()
}
pub fn star_1(data: &PuzzleData) -> SolType1 {
let xs = intersections(&data.wire_1, &data.wire_2);
xs.iter()
.map(|((x, y), _, _)| x.abs() + y.abs())
.min()
.unwrap()
}
Star 2
The intersections
function from part 1 also works for part 2 (the initial version obviously did not)
1
2
3
4
pub fn star_2(data: &PuzzleData) -> SolType2 {
let xs = intersections(&data.wire_1, &data.wire_2);
xs.iter().map(|(_, l_1, l_2)| *l_1 + *l_2).min().unwrap()
}
Tests
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#[cfg(test)]
mod tests {
use super::*;
const CONTENT: &str = r#"R98,U47,R26,D63,R33,U87,L62,D20,R33,U53,R51
U98,R91,D20,R16,D67,R40,U7,R15,U6,R7
"#;
#[test]
pub fn test_intersections() {
let content = "R8,U5,L5,D3\nU7,R6,D4,L4\n";
let data = PuzzleData::from(content);
let mut xs = intersections(&data.wire_1, &data.wire_2);
xs.sort_unstable();
assert_eq!(vec![((3, -3), 20, 20), ((6, -5), 15, 15)], xs);
}
#[test]
pub fn test_from() {
let content = "R8,U5,L5,D3\nU7,R6,D4,L4\n";
let data = PuzzleData::from(content);
assert_eq!(
vec![
((0, 0), 0),
((8, 0), 8),
((8, -5), 13),
((3, -5), 18),
((3, -2), 21)
],
data.wire_1
);
assert_eq!(
vec![
((0, 0), 0),
((0, -7), 7),
((6, -7), 13),
((6, -3), 17),
((2, -3), 21)
],
data.wire_2
);
}
#[test]
pub fn test_star_1() {
let data = PuzzleData::from(CONTENT);
assert_eq!(135, star_1(&data));
}
#[test]
pub fn test_star_2() {
let data = PuzzleData::from(CONTENT);
assert_eq!(410, star_2(&data));
}
}
Day 4: Secure Container
Rust solution to AoC|2019|4.
Input
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pub mod input {
#[derive(Debug)]
pub struct PuzzleData {
pub range: (usize, usize),
}
impl From<&str> for PuzzleData {
/// parse the puzzle input
fn from(s: &str) -> Self {
Self {
range: s
.trim()
.split_once('-')
.map(|(from, to)| (from.parse().unwrap(), to.parse().unwrap()))
.unwrap(),
}
}
}
}
Star 1
To avoid being forced to check the 'never decrease' condition, I increment digits in a way to always satisfy this condition implicitly.
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
pub const N: usize = 6;
/// Convert number to array of six (lowest) digits
///
/// # Examples
/// ```
/// # use mr_kaffee_2019_04::*;
/// let v = 123456;
/// let d = to_digits(v);
/// assert_eq!([1, 2, 3, 4, 5, 6], d);
/// ```
pub fn to_digits(mut v: usize) -> [u8; N] {
let mut d = [0; N];
for k in 0..N {
d[N - 1 - k] = (v % 10) as _;
v /= 10;
}
d
}
pub fn solve<F: Fn(&[u8; N]) -> bool>((from, to): (usize, usize), f: F) -> SolType1 {
let mut d = to_digits(from);
let to = to_digits(to);
// make sure d meets 'never decrease' condition
for k in 1..N {
if d[k] < d[k - 1] {
for l in k..N {
d[l] = d[k - 1];
}
break;
}
}
let mut count = 0;
while d < to {
// increment counter if condition is met
if f(&d) {
count += 1;
}
// increment digits respecting 'never decrease' rule
let (mut inc, mut k) = (1, N - 1);
while inc > 0 {
(inc, k) = if d[k] < 10 - inc {
d[k] += inc;
for l in k + 1..N {
d[l] = d[k];
}
(0, k)
} else {
(1, k - 1)
}
}
}
count
}
pub fn star_1(data: &PuzzleData) -> SolType1 {
solve(data.range, |d| (1..N).any(|k| d[k] == d[k - 1]))
}
Star 2
Just replace the condition by one which also checks that the digit before and after the duplicate (if any) are distinct.
1
2
3
4
5
6
7
pub fn star_2(data: &PuzzleData) -> SolType2 {
solve(data.range, |d| {
(1..N).any(|k| {
d[k] == d[k - 1] && (k == 1 || d[k - 2] != d[k]) && (k == N - 1 || d[k + 1] != d[k])
})
})
}
Day 5: Sunny with a Chance of Asteroids
Rust solution to AoC|2019|5.
All the logic is in the IntCode
implementation
Star 1
1
2
3
4
5
6
pub fn star_1(memory: &Memory) -> SolType {
let mut code = IntCode::<isize,isize>::from(memory);
code.input = 1;
code.run();
code.output
}
Star 2
1
2
3
4
5
6
pub fn star_2(memory: &Memory) -> SolType {
let mut code = IntCode::<isize,isize>::from(memory);
code.input = 5;
code.run();
code.output
}
Day 6: Universal Orbit Map
Rust solution to AoC|2019|6.
Input
Parse data into map with center objects as key and list of orbiting objects as value and inverse map with orbiting objects as key and center objects as values.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pub mod input {
use std::collections::{HashMap, HashSet};
#[derive(Debug)]
pub struct PuzzleData<'a> {
pub map: HashMap<&'a str, HashSet<&'a str>>,
pub inv_map: HashMap<&'a str, &'a str>,
}
impl<'a> From<&'a str> for PuzzleData<'a> {
/// parse the puzzle input
fn from(s: &'a str) -> Self {
let (map, inv_map) = s.lines().map(|l| l.split_once(')').unwrap()).fold(
(HashMap::new(), HashMap::new()),
|(mut map, mut inv_map), (c, o)| {
map.entry(c).or_insert_with(HashSet::new).insert(o);
inv_map.insert(o, c);
(map, inv_map)
},
);
Self { map, inv_map }
}
}
}
Star 1
Recursively count orbiting objects and orbits. The function count_orbits
returns a pair, where the first value is the number of distinct orbiting objects and the second value is the total number of direct and indirect orbits. The first value is required for the aggregation.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fn count_orbits(map: &HashMap<&str, HashSet<&str>>, center: &str) -> (usize, usize) {
let (mut cnt_obj, mut cnt_orb) = (0, 0);
if let Some(orbits) = map.get(center) {
for orbit in orbits {
// everything that orbits around orbit, indirectly orbits around
// center, orbit also orbits directly around center
let (cnt_obj_, cnt_orb_) = count_orbits(map, orbit);
(cnt_obj, cnt_orb) = (cnt_obj + cnt_obj_ + 1, cnt_orb + cnt_orb_ + cnt_obj_ + 1);
}
}
(cnt_obj, cnt_orb)
}
pub fn star_1(data: &PuzzleData) -> SolType1 {
count_orbits(&data.map, "COM").1
}
Star 2
Find the path from YOU and SAN to COM and remove the common part.
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
pub fn star_2(data: &PuzzleData) -> SolType2 {
// path from YOU to COM
let mut path_you = VecDeque::new();
let mut cur = "YOU";
while let Some(&obj) = data.inv_map.get(cur) {
path_you.push_back(obj);
cur = obj;
}
// path from SAN to COM
let mut path_san = VecDeque::new();
let mut cur = "SAN";
while let Some(&obj) = data.inv_map.get(cur) {
path_san.push_back(obj);
cur = obj;
}
// remove common parts
while path_you.back() == path_san.back() {
path_you.pop_back();
path_san.pop_back();
}
path_you.len() + path_san.len()
}
Tests
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#[cfg(test)]
mod tests {
use super::*;
const CONTENT_1: &str = r#"COM)B
B)C
C)D
D)E
E)F
B)G
G)H
D)I
E)J
J)K
K)L
"#;
const CONTENT_2: &str = r#"COM)B
B)C
C)D
D)E
E)F
B)G
G)H
D)I
E)J
J)K
K)L
K)YOU
I)SAN
"#;
#[test]
pub fn test_from() {
let data = PuzzleData::from(CONTENT_1);
assert_eq!(
HashMap::from([
("COM", HashSet::from(["B"])),
("B", HashSet::from(["G", "C"])),
("G", HashSet::from(["H"])),
("C", HashSet::from(["D"])),
("D", HashSet::from(["E", "I"])),
("E", HashSet::from(["J", "F"])),
("J", HashSet::from(["K"])),
("K", HashSet::from(["L"]))
]),
data.map
);
assert_eq!(
HashMap::from([
("B", "COM"),
("C", "B"),
("D", "C"),
("E", "D"),
("F", "E"),
("G", "B"),
("H", "G"),
("I", "D"),
("J", "E"),
("K", "J"),
("L", "K")
]),
data.inv_map
);
}
#[test]
pub fn test_count_orbits() {
let data = PuzzleData::from(CONTENT_1);
assert_eq!((0, 0), count_orbits(&data.map, "L"));
assert_eq!((1, 1), count_orbits(&data.map, "K"));
assert_eq!((2, 3), count_orbits(&data.map, "J"));
assert_eq!((4, 7), count_orbits(&data.map, "E"));
}
#[test]
pub fn test_star_1() {
let data = PuzzleData::from(CONTENT_1);
assert_eq!(42, star_1(&data));
}
#[test]
pub fn test_star_2() {
let data = PuzzleData::from(CONTENT_2);
assert_eq!(4, star_2(&data));
}
}
Day 7: Amplification Circuit
Rust solution to AoC|2019|7.
Calculate best amplification using IntCode
Star 1
To generate permutations, I use Heap’s a algorithm.
1
2
3
4
5
6
7
8
9
10
11
12
13
pub fn star_1(memory: &Memory) -> SolType {
let mut max_out = 0;
for phases in Permutations::from(0..5) {
let mut out = 0;
for phase in phases {
let mut code = IntCode::from((memory, VecDeque::from([phase, out]), 0));
code.run();
out = code.output;
}
max_out = max_out.max(out);
}
max_out
}
Star 2
Implement the feedback loop. One code’s output is use as another code’s input repeatedly. Each code runs until a new input is required.
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
pub fn star_2(memory: &Memory) -> SolType {
let mut max_out = 0;
for phases in Permutations::from(5..10) {
// initialize codes
let mut codes = phases
.iter()
.map(|&phase| {
let mut code = IntCode::from((memory, (), 0));
code.run();
code.run_with_input(phase);
code
})
.collect::<Vec<_>>();
'outer: loop {
for k in 0..phases.len() {
// get output from previous, this also works for initial output,
// since outputs are initialized to 0
let out = codes[(k + phases.len() - 1) % phases.len()].output;
if !codes[k].run_with_input(out) {
// if code is not expecting inputs anymore, stop
break 'outer;
}
}
}
max_out = max_out.max(codes.last().unwrap().output);
}
max_out
}
Day 8: Space Image Format
Rust solution to AoC|2019|8.
Input
Just use the input as is.
1
2
3
4
5
6
7
8
9
10
11
pub mod input {
#[derive(Debug)]
pub struct PuzzleData<'a>(pub &'a [u8]);
impl<'a> From<&'a str> for PuzzleData<'a> {
/// parse the puzzle input
fn from(s: &'a str) -> Self {
Self(s.trim().as_bytes())
}
}
}
Star 1
Well, this code does exactly what the puzzle description asks for: count zeros, ones and twos in each layer and return the product of the number of ones and twos in the layer with the lowest number of zeros.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pub const W: usize = 25;
pub const H: usize = 6;
pub fn star_1(PuzzleData(data): &PuzzleData) -> SolType1 {
(0..data.len())
.step_by(W * H)
.map(|k| {
(k..k + W * H).fold((0, 0, 0), |(zeros, ones, twos), l| {
(
zeros + (data[l] == b'0') as usize,
ones + (data[l] == b'1') as usize,
twos + (data[l] == b'2') as usize,
)
})
})
.min_by_key(|(zeros, _, _)| *zeros)
.map(|(_, ones, twos)| ones * twos)
.unwrap()
}
Star 2
Since the first layer is on top, and the last one on bottom, for each pixel, the first non-transparent pixel found in any layer, determines the color in the final image. Black is converted to letters::DARK
and white is converted to letters::LIT
on the fly. The result is decoded into a string using Letters::decode
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pub fn star_2(PuzzleData(data): &PuzzleData) -> SolType2 {
let image = (0..data.len())
.step_by(W * H)
.fold(vec![b'2'; W * H], |mut image, k| {
image
.iter_mut()
.enumerate()
.filter(|(_, v)| **v == b'2')
.for_each(|(l, v)| {
*v = match data[k + l] {
b'0' => DARK as u8,
b'1' => LIT as u8,
v => v,
}
});
image
});
image.decode(0).unwrap()
}
Day 9: Sensor Boost
Rust solution to AoC|2019|9.
All the logic is in the IntCode
implementation
Star 1
1
2
3
4
5
pub fn star_1(memory: &Memory) -> SolType {
let mut code = IntCode::from((memory, 1, 0));
code.run();
code.output
}
Star 2
1
2
3
4
5
pub fn star_2(memory: &Memory) -> SolType {
let mut code = IntCode::from((memory, 2, 0));
code.run();
code.output
}
Day 10: Monitoring Station
Rust solution to AoC|2019|10.
Input
Parse input into asteroid coordinates.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pub mod input {
use crate::Asteroid;
#[derive(Debug)]
pub struct PuzzleData(pub Vec<Asteroid>);
impl From<&str> for PuzzleData {
/// parse the puzzle input
fn from(s: &str) -> Self {
let w = s.find('\n').unwrap();
Self(
s.bytes()
.enumerate()
.filter(|(_, v)| *v == b'#')
.map(|(p, _)| (p % (w + 1), p / (w + 1)).into())
.collect(),
)
}
}
}
Star 1
Group asteroids by direction. The number of distinct directions is the number of asteroids seen.
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
pub fn gcd(mut a: isize, mut b: isize) -> isize {
while b != 0 {
(a, b) = (b, a % b);
}
a
}
pub fn get_in_sight(
asteroids: &[Asteroid],
k_1: usize,
) -> HashMap<(isize, isize), BTreeSet<(isize, Asteroid)>> {
let mut directions = HashMap::new();
let a_1 = asteroids[k_1];
for (_, a_2) in asteroids.iter().enumerate().filter(|(k_2, _)| k_1 != *k_2) {
let (d_c, d_r) = (
a_2.col as isize - a_1.col as isize,
a_2.row as isize - a_1.row as isize,
);
let q = gcd(d_c.abs(), d_r.abs());
directions
.entry((d_c / q, d_r / q))
.or_insert(BTreeSet::new())
.insert((d_c.abs() + d_r.abs(), *a_2));
}
directions
}
pub fn star_1(PuzzleData(asteroids): &PuzzleData) -> SolType1 {
(0..asteroids.len())
.map(|k_1| get_in_sight(asteroids, k_1).len())
.max()
.unwrap()
}
Star 2
Sort directions by angle (atan2
). Find first asteroid that is seen in the 200th direction.
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
pub fn star_2(PuzzleData(asteroids): &PuzzleData) -> SolType2 {
let (_, map) = (0..asteroids.len())
.map(|k_1| (k_1, get_in_sight(asteroids, k_1)))
.max_by_key(|(_, map)| map.len())
.unwrap();
// add directions to vec and sort by angle
// 0.5 0.0
// (-/+) | (+/+) (-/-) | (+/-)
// 1.0 ------+------ 0.0 1.5 ------+------ 0.5
// (-/-) | (+/-) (-/+) | (+/+)
// -0.5 1.0
// standard math laser beam rotation
// => have to use atan2(col, -row)
let mut dirs = map
.keys()
.map(|(col, row)| {
(
(f64::atan2(*col as _, -*row as _) + 2.0 * PI) % (2.0 * PI),
(col, row),
)
})
.collect::<Vec<_>>();
dirs.sort_unstable_by(|(a_1, _), (a_2, _)| a_1.partial_cmp(a_2).unwrap());
// get 200th direction
let (_, (&d_c, &d_r)) = dirs[199];
// get first asteroid in that direction
let (_, asteroid) = map.get(&(d_c, d_r)).unwrap().first().copied().unwrap();
100 * asteroid.col + asteroid.row
}
Tests
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
39
40
41
42
43
44
45
46
47
48
49
#[cfg(test)]
mod tests {
use super::*;
const CONTENT: &str = r#"......#.#.
#..#.#....
..#######.
.#.#.###..
.#..#.....
..#....#.#
#..#....#.
.##.#..###
##...#..#.
.#....####
"#;
const CONTENT_SMALL: &str = r#".#..#
.....
#####
....#
...##
"#;
#[test]
pub fn test_from() {
let data = PuzzleData::from(CONTENT_SMALL);
assert_eq!(
vec![
Asteroid::from((1, 0)),
Asteroid::from((4, 0)),
Asteroid::from((0, 2)),
Asteroid::from((1, 2)),
Asteroid::from((2, 2)),
Asteroid::from((3, 2)),
Asteroid::from((4, 2)),
Asteroid::from((4, 3)),
Asteroid::from((3, 4)),
Asteroid::from((4, 4))
],
data.0
);
}
#[test]
pub fn test_star_1() {
let data = PuzzleData::from(CONTENT);
assert_eq!(33, star_1(&data));
}
}
Day 11: Space Police
Rust solution to AoC|2019|11.
Hull Painting robot runs on IntCode
Robot
The robot is modeled by position, direction, map of tiles painted and the next expected output produced by the int code.
The robot implements intcode::Input
and intcode::Output
to interact with the intcode::IntCode
.
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
39
40
41
42
#[derive(Debug)]
pub struct HullPaintingRobot {
pub pos: (isize, isize),
pub dir: (isize, isize),
pub hull: HashMap<(isize, isize), bool>,
pub exp_direction: bool,
}
impl Default for HullPaintingRobot {
fn default() -> Self {
Self {
pos: (0, 0),
dir: (0, -1),
hull: HashMap::new(),
exp_direction: false,
}
}
}
impl Input for HullPaintingRobot {
fn read(&mut self) -> isize {
self.hull.get(&self.pos).copied().unwrap_or(false) as _
}
}
impl Output for HullPaintingRobot {
fn write(&mut self, value: isize) {
if self.exp_direction {
match value {
1 => self.dir = (-self.dir.1, self.dir.0),
_ => self.dir = (self.dir.1, -self.dir.0),
}
self.pos = (self.pos.0 + self.dir.0, self.pos.1 + self.dir.1);
} else {
match value {
1 => self.hull.insert(self.pos, true),
_ => self.hull.insert(self.pos, false),
};
}
self.exp_direction = !self.exp_direction;
}
}
Star 1
Straight forward.
Robot is wrapped in a RefCell
because it is passed to the IntCode
as input and output at the same time, and mutable access to both references is required. This uses the blanket trait implementations of Input
and Output
for &RefCell<T>
where T
is an Input
or Output
respectively.
1
2
3
4
5
6
7
pub fn star_1(memory: &Memory) -> SolType1 {
let robot = HullPaintingRobot::default();
let wrapper = RefCell::new(robot);
let mut code = IntCode::from((memory, &wrapper, &wrapper));
code.run();
wrapper.take().hull.len()
}
Star 2
Simulation is identical to part 1. Afterwards, I calculate the bounding box to be able to assemble a 'display' that can be decoded using mr_kaffee_aoc::letters::Letters
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
pub fn star_2(memory: &Memory) -> SolType2 {
// simulate robot
let robot = HullPaintingRobot {
hull: HashMap::from([((0, 0), true)]),
..Default::default()
};
let wrapper = RefCell::new(robot);
let mut code = IntCode::from((memory, &wrapper, &wrapper));
code.run();
// calculate bounding box
let hull = wrapper.take().hull;
let (col_min, row_min, col_max, row_max) = hull.iter().filter(|&(_, &c)| c).fold(
(isize::MAX, isize::MAX, isize::MIN, isize::MIN),
|(c_mn, r_mn, c_mx, r_mx), ((c, r), _)| {
(c_mn.min(*c), r_mn.min(*r), c_mx.max(c + 1), r_mx.max(r + 1))
},
);
// interpret result
use letters::{DARK, HEIGHT, LIT, WIDTH};
let w = ((col_max - col_min) as usize).div_ceil(WIDTH) * WIDTH;
let h = ((row_max - row_min) as usize).div_ceil(HEIGHT) * HEIGHT;
let mut display = vec![DARK as u8; w * h];
for ((col, row), _) in hull.iter().filter(|&(_, &c)| c) {
display[(col - col_min) as usize + w * (row - row_min) as usize] = LIT as u8;
}
display.decode(0).unwrap()
}
Day 12: The N-Body Problem
Rust solution to AoC|2019|12.
Input
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
pub mod input {
#[derive(Debug)]
pub struct PuzzleData(pub Vec<[isize; 6]>);
impl From<&str> for PuzzleData {
/// parse the puzzle input
fn from(s: &str) -> Self {
Self(
s.lines()
.map(|line| {
line.strip_prefix('<')
.and_then(|line| line.strip_suffix('>'))
.unwrap()
.split(", ")
.map(|p| p[2..].parse().unwrap())
})
.map(|mut it| {
[
it.next().unwrap(),
it.next().unwrap(),
it.next().unwrap(),
0,
0,
0,
]
})
.collect(),
)
}
}
}
Star 1
Straight forward simulation
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
39
pub fn simulate(moons: &[[isize; 6]], steps: usize) -> SolType {
let mut moons = moons.to_owned();
for _ in 0..steps {
for m_1 in 0..moons.len() - 1 {
for m_2 in m_1..moons.len() {
for d in 0..3 {
match moons[m_1][d].cmp(&moons[m_2][d]) {
Ordering::Less => {
moons[m_1][d + 3] += 1;
moons[m_2][d + 3] -= 1;
}
Ordering::Greater => {
moons[m_1][d + 3] -= 1;
moons[m_2][d + 3] += 1;
}
_ => (),
}
}
}
}
for moon in &mut moons {
for d in 0..3 {
moon[d] += moon[d + 3];
}
}
}
moons
.iter()
.map(|moon| {
moon[0..3].iter().map(|p| p.abs()).sum::<isize>()
* moon[3..6].iter().map(|v| v.abs()).sum::<isize>()
})
.sum::<isize>() as _
}
pub fn star_1(PuzzleData(moons): &PuzzleData) -> SolType {
simulate(moons, 1_000)
}
Star 2
Since dimensions are independent, the period can be determined independently for the three dimensions. The overall period is the least common multiple
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
pub fn find_period(moons: &[[isize; 6]], dim: usize) -> usize {
let mut moons = moons
.iter()
.map(|moon| [moon[dim], moon[dim + 3]])
.collect::<Vec<_>>();
let moons_0 = moons.clone();
for k in 1..usize::MAX {
for m_1 in 0..moons.len() - 1 {
for m_2 in m_1..moons.len() {
match moons[m_1][0].cmp(&moons[m_2][0]) {
Ordering::Less => {
moons[m_1][1] += 1;
moons[m_2][1] -= 1;
}
Ordering::Greater => {
moons[m_1][1] -= 1;
moons[m_2][1] += 1;
}
_ => (),
}
}
}
for moon in &mut moons {
moon[0] += moon[1];
}
if moons_0 == moons {
return k;
}
}
unreachable!()
}
pub fn gcd(mut a: usize, mut b: usize) -> usize {
while b != 0 {
(a, b) = (b, a % b);
}
a
}
pub fn star_2(PuzzleData(moons): &PuzzleData) -> SolType {
let period_x = find_period(moons, 0);
let period_y = find_period(moons, 1);
let period_z = find_period(moons, 2);
let q = gcd(period_x, period_y);
let lcm = period_x * period_y / q;
let q = gcd(lcm, period_z);
lcm * period_z / q
}
Tests
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
#[cfg(test)]
mod tests {
use super::*;
const CONTENT: &str = r#"<x=-8, y=-10, z=0>
<x=5, y=5, z=10>
<x=2, y=-7, z=3>
<x=9, y=-8, z=-3>
"#;
#[test]
pub fn test_from() {
let data = PuzzleData::from(CONTENT);
assert_eq!(
vec![
[-8, -10, 0, 0, 0, 0],
[5, 5, 10, 0, 0, 0],
[2, -7, 3, 0, 0, 0],
[9, -8, -3, 0, 0, 0]
],
data.0
);
}
#[test]
pub fn test_simulate() {
let data = PuzzleData::from(CONTENT);
assert_eq!(1_940, simulate(&data.0, 100));
}
#[test]
pub fn test_star_2() {
let data = PuzzleData::from(CONTENT);
assert_eq!(4_686_774_924, star_2(&data));
}
}
Day 13: Care Package
Rust solution to AoC|2019|13.
Arcade runs IntCode
Arcade
The main logic is in the implementations of intcode::Input
(calculating joystick commands for part 2) and intcode::Output
(update state based on int code’s output) implementations for the Arcade
struct.
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#[derive(Debug, Default)]
pub struct Arcade {
map: HashMap<(isize, isize), u8>,
output_counter: u8,
col: isize,
row: isize,
score: isize,
cur_ball: Option<(isize, isize)>,
prev_ball: Option<(isize, isize)>,
paddle: Option<(isize, isize)>,
}
impl Display for Arcade {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let (c_mn, r_mn, c_mx, r_mx) = self.map.iter().fold(
(isize::MAX, isize::MAX, isize::MIN, isize::MIN),
|(c_mn, r_mn, c_mx, r_mx), ((c, r), _)| {
(c_mn.min(*c), r_mn.min(*r), c_mx.max(c + 1), r_mx.max(r + 1))
},
);
for r in r_mn..r_mx {
for c in c_mn..c_mx {
match self.map.get(&(c, r)).unwrap_or(&0) {
0 => ' '.fmt(f)?,
1 => '#'.fmt(f)?,
2 => '$'.fmt(f)?,
3 => '='.fmt(f)?,
4 => 'O'.fmt(f)?,
_ => unreachable!(),
}
}
'\n'.fmt(f)?;
}
Ok(())
}
}
impl Input for Arcade {
fn read(&mut self) -> isize {
#[cfg(feature = "animate")]
{
print!("\x1B[1;1H\x1B[J"); // clear console
println!("Score: {:>8}", self.score);
print!("{}", self);
std::thread::sleep(std::time::Duration::from_millis(3));
}
if self.cur_ball.is_none() || self.paddle.is_none() {
return 0;
}
let (c_cur_ball, r_cur_ball) = self.cur_ball.unwrap();
let (c_prev_ball, r_prev_ball) = self.prev_ball.unwrap_or((c_cur_ball, r_cur_ball));
let (c_paddle, r_paddle) = self.paddle.unwrap();
let c_tar = if r_prev_ball < r_cur_ball {
// predict where the ball will be once it is just above the paddle
c_cur_ball
+ (r_paddle - 1 - r_cur_ball) * (c_cur_ball - c_prev_ball)
/ (r_cur_ball - r_prev_ball)
} else {
// just follow the ball
c_cur_ball
};
match c_tar.cmp(&c_paddle) {
Ordering::Less => -1,
Ordering::Equal => 0,
Ordering::Greater => 1,
}
}
}
impl Output for Arcade {
fn write(&mut self, value: isize) {
match self.output_counter {
0 => self.col = value,
1 => self.row = value,
2 => {
if self.col == -1 && self.row == 0 {
self.score = value;
} else {
match value {
3 => self.paddle = Some((self.col, self.row)), // paddle
4 => {
// ball
self.prev_ball = self.cur_ball;
self.cur_ball = Some((self.col, self.row))
}
_ => (),
}
self.map.insert((self.col, self.row), value as _);
}
}
_ => unreachable!(),
}
self.output_counter = (self.output_counter + 1) % 3;
}
}
Star 1
Just run the program and let the arcade state be updated.
1
2
3
4
5
6
pub fn star_1(memory: &Memory) -> SolType1 {
let arcade = Arcade::default();
let mut code = IntCode::from((memory, (), arcade));
code.run();
code.output.map.iter().filter(|&(_, &t)| t == 2).count()
}
Star 2
Pretend to have coins inserted and re-run the program.
You can see the game processing using cargo run --release --features animate
.
1
2
3
4
5
6
7
8
pub fn star_2(memory: &Memory) -> SolType2 {
let arcade = Arcade::default();
let wrapper = RefCell::new(arcade);
let mut code = IntCode::from((memory, &wrapper, &wrapper));
code[0] = 2;
code.run();
wrapper.take().score
}
Day 14: Space Stoichiometry
Rust solution to AoC|2019|14.
Input
Parse reactions in map with output name as key and reaction as value.
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
39
40
41
42
43
44
45
pub mod input {
use std::collections::HashMap;
#[derive(Debug)]
pub struct Reaction<'a> {
pub inputs: Vec<(usize, &'a str)>,
pub output: (usize, &'a str),
}
impl<'a> From<&'a str> for Reaction<'a> {
fn from(s: &'a str) -> Self {
s.split_once(" => ")
.map(|(inputs, output)| Self {
inputs: inputs
.split(", ")
.map(|v| {
v.split_once(' ')
.map(|(q, n)| (q.parse().unwrap(), n))
.unwrap()
})
.collect(),
output: output
.split_once(' ')
.map(|(q, n)| (q.parse().unwrap(), n))
.unwrap(),
})
.unwrap()
}
}
#[derive(Debug)]
pub struct PuzzleData<'a>(pub HashMap<&'a str, Reaction<'a>>);
impl<'a> From<&'a str> for PuzzleData<'a> {
/// parse the puzzle input
fn from(s: &'a str) -> Self {
Self(
s.lines()
.map(Reaction::from)
.map(|r| (r.output.1, r))
.collect(),
)
}
}
}
Star 1
The catch is to handle excess material correctly. I do this with an 'inventory' which stores any excess material and uses it first before producing more using reactions.
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
pub fn required_ore<'a>(
reactions: &HashMap<&'a str, Reaction<'a>>,
inventory: &mut HashMap<&'a str, usize>,
mut quantity: usize,
name: &'a str,
) -> usize {
if let Entry::Occupied(mut o) = inventory.entry(name) {
if *o.get() >= quantity {
*o.get_mut() -= quantity;
return 0;
} else {
quantity -= *o.get();
o.remove();
}
}
if name == "ORE" {
return quantity;
}
let reaction = reactions.get(name).unwrap();
let n = quantity.div_ceil(reaction.output.0);
if n * reaction.output.0 > quantity {
*inventory.entry(name).or_insert(0) += n * reaction.output.0 - quantity;
}
reaction
.inputs
.iter()
.map(|&(quantity, name)| required_ore(reactions, inventory, n * quantity, name))
.sum()
}
pub fn star_1(PuzzleData(reactions): &PuzzleData) -> SolType {
let mut inventory = HashMap::new();
required_ore(reactions, &mut inventory, 1, "FUEL")
}
Star 2
Because of excess material, the amount of ORE required per FUEL is a decreasing function of the number of FUEL to be produced. A simple iteration finds the result quickly.
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
pub fn star_2(PuzzleData(reactions): &PuzzleData) -> SolType {
let avl = 1_000_000_000_000;
let (mut fuel_l, mut fuel_h) = (0, 1);
let mut inventory = HashMap::new();
let mut req = required_ore(reactions, &mut inventory, fuel_h, "FUEL");
while req <= avl {
(fuel_l, fuel_h) = (fuel_h, (fuel_h + 1).max(avl / (req / fuel_h)));
inventory.clear();
req = required_ore(reactions, &mut inventory, fuel_h, "FUEL");
}
while fuel_h - fuel_l > 1 {
let fuel = (fuel_l + fuel_h) / 2;
inventory.clear();
let req = required_ore(reactions, &mut inventory, fuel, "FUEL");
*(if req > avl { &mut fuel_h } else { &mut fuel_l }) = fuel;
}
fuel_l
}
Tests
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
#[cfg(test)]
mod tests {
use super::*;
const CONTENT: &str = r#"157 ORE => 5 NZVS
165 ORE => 6 DCFZ
44 XJWVT, 5 KHKGT, 1 QDVJ, 29 NZVS, 9 GPVTF, 48 HKGWZ => 1 FUEL
12 HKGWZ, 1 GPVTF, 8 PSHF => 9 QDVJ
179 ORE => 7 PSHF
177 ORE => 5 HKGWZ
7 DCFZ, 7 PSHF => 2 XJWVT
165 ORE => 2 GPVTF
3 DCFZ, 7 NZVS, 5 HKGWZ, 10 PSHF => 8 KHKGT
"#;
#[test]
pub fn test_from() {
let data = PuzzleData::from(CONTENT);
println!("{data:?}");
}
#[test]
pub fn test_star_1() {
let data = PuzzleData::from(CONTENT);
assert_eq!(13_312, star_1(&data));
}
#[test]
pub fn test_star_2() {
let data = PuzzleData::from(CONTENT);
assert_eq!(82_892_753, star_2(&data));
}
}
Day 15: Oxygen System
Rust solution to AoC|2019|15.
Repair droid running IntCode.
Solution
The droid explores the area using depth first search.
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
#[derive(Debug)]
struct RepairDroid {
/// map of the area
map: HashMap<(isize, isize), u8>,
/// status received
status: isize,
/// current position
pos: (isize, isize),
/// requested next position
req_pos: (isize, isize),
/// path traveled so far
path: Vec<Direction>,
/// minimum moves to oxygen
min_moves: usize,
/// coordinate of oxygen
oxygen: Option<(isize, isize)>,
}
impl Default for RepairDroid {
fn default() -> Self {
Self {
map: HashMap::new(),
status: 1,
pos: (0, 0),
req_pos: (0, 0),
path: Vec::new(),
min_moves: usize::MAX,
oxygen: None,
}
}
}
The main logic is in the RepairDroid::update
function which processes a response from the droid and calculates the next command based on that response.
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
impl RepairDroid {
const EMPTY: u8 = b'.';
const WALL: u8 = b'#';
const OXYGEN: u8 = b'O';
/// process response ([`RepairDroid::status']) and calculate next command
///
/// Return `None` to stop the int code program.
pub fn update(&mut self) -> Option<isize> {
// process response
match self.status {
0 => {
self.path.pop();
self.map.insert(self.req_pos, Self::WALL);
}
1 => {
self.pos = self.req_pos;
self.map.insert(self.pos, Self::EMPTY);
}
2 => {
self.min_moves = self.min_moves.min(self.path.len());
self.pos = self.req_pos;
self.oxygen = Some(self.pos);
self.map.insert(self.pos, Self::OXYGEN);
}
_ => panic!("Unexpected status: {}", self.status),
}
// calculate command
Direction::DIRECTIONS
.iter()
.find(|d| !self.map.contains_key(&d.adjacent(self.pos)))
.copied()
.or(self.path.last().map(Direction::reverse))
.map(|dir| {
// set requested next position
self.req_pos = dir.adjacent(self.pos);
// update path (push or pop if reversing last element)
match self.path.last() {
Some(last) if last.is_reverse(&dir) => {
self.path.pop();
}
_ => self.path.push(dir),
}
// send command
dir.command()
})
}
}
impl Output for RepairDroid {
fn write(&mut self, status: isize) {
self.status = status;
}
}
impl Input for RepairDroid {
fn read(&mut self) -> isize {
self.try_read().unwrap()
}
fn try_read(&mut self) -> Option<isize> {
self.update()
}
}
To keep code clean, a helper enum Direction
is used:
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
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Direction {
East = 0,
North = 1,
West = 2,
South = 3,
}
impl Direction {
pub const DIRECTIONS: [Self; 4] = [Self::East, Self::North, Self::West, Self::South];
pub const DELTAS: [(isize, isize); 4] = [(1, 0), (0, -1), (-1, 0), (0, 1)];
pub const COMMANDS: [isize; 4] = [4, 1, 3, 2];
pub fn command(&self) -> isize {
Self::COMMANDS[*self as usize]
}
pub fn delta(&self) -> (isize, isize) {
Self::DELTAS[*self as usize]
}
pub fn reverse(&self) -> Self {
Self::DIRECTIONS[(*self as usize + 2) & 3]
}
pub fn is_reverse(&self, other: &Self) -> bool {
((*self as usize) + 2) & 3 == (*other as usize)
}
pub fn adjacent(&self, pos: (isize, isize)) -> (isize, isize) {
let delta = self.delta();
(pos.0 + delta.0, pos.1 + delta.1)
}
}
Star 1 & 2
The solution first runs the depth first search to explore the area and find the shortest path to oxygen. Then it uses breadth first search on the map created by the droid to determine how long it takes for the complete area to be filled with oxygen.
Run it with cargo run --release --features display
to see the map the droid creates printed.
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
pub fn star_1_2(memory: &Memory) -> SolType {
let droid = RepairDroid::default();
let wrapper = RefCell::new(droid);
let mut code = IntCode::from((memory, &wrapper, &wrapper));
code.run();
let mut droid = wrapper.take();
#[cfg(feature = "display")]
println!("{}", droid);
let mut queue = Vec::from([(0, droid.oxygen.unwrap())]);
let mut max_dist = 0;
while let Some((dist, p)) = queue.pop() {
for a in Direction::DIRECTIONS.iter().map(|d| d.adjacent(p)) {
let Entry::Occupied(mut e) = droid.map.entry(a) else { panic!() };
if e.get() == &RepairDroid::EMPTY {
*e.get_mut() = RepairDroid::OXYGEN;
queue.push((dist + 1, a));
max_dist = max_dist.max(dist + 1);
}
}
}
(droid.min_moves, max_dist).into()
}
Day 16: Flawed Frequency Transmission
Rust solution to AoC|2019|16.
Input
1
2
3
4
5
6
7
8
9
10
11
12
13
pub mod input {
use crate::SolType;
#[derive(Debug)]
pub struct PuzzleData(pub Vec<SolType>);
impl From<&str> for PuzzleData {
/// parse the puzzle input
fn from(s: &str) -> Self {
Self(s.trim().bytes().map(|b| (b - b'0') as SolType).collect())
}
}
}
Star 1
Straight forward 'FFT' implementation
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
pub const PHASES: usize = 100;
pub fn star_1(PuzzleData(digits): &PuzzleData) -> SolType {
// allocate buffers
let mut digits = digits.clone();
let mut update = vec![0; digits.len()];
// update in each phase
for _ in 0..PHASES {
for (out_pos, upd) in update.iter_mut().enumerate() {
// for out_pos in 0..digits.len() {
*upd = 0;
for (in_pos, digit) in digits.iter().enumerate() {
match ((in_pos + 1) / (out_pos + 1)) & 3 {
1 => *upd += digit, // pattern: 1
3 => *upd -= digit, // pattern: -1
_ => (), // pattern: 0
}
}
// only use unit digit ignoring sign
*upd = upd.abs() % 10;
}
(digits, update) = (update, digits);
}
digits.iter().take(8).fold(0, |r, d| 10 * r + d)
}
Star 2
Use the fact that in the second half of the signal, the output of a phase only depends on the the input digits at the same or later positions
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
pub const REPETITIONS: usize = 10_000;
pub fn star_2(PuzzleData(digits): &PuzzleData) -> SolType {
let len = REPETITIONS * digits.len();
let off = digits.iter().take(7).fold(0, |r, d| 10 * r + d) as usize;
assert!(
off > len / 2,
"Algorithm only implemented for 2nd half of signal"
);
// init tail
let mut val = (off..len)
.map(|k| digits[k % digits.len()])
.collect::<Vec<_>>();
// update in each phase (in place)
for _ in 0..PHASES {
// in second half of signal:
// d_{k,upd} = sum_{i=k}^{n-1} d_i
// = d_{k+1,upd} + d_k
for k in (0..len - 1 - off).rev() {
val[k] = (val[k] + val[k + 1]).abs() % 10;
}
}
val.iter().take(8).fold(0, |r, d| 10 * r + d)
}
Tests
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#[cfg(test)]
mod tests {
use super::*;
#[test]
pub fn test_star_1() {
let data = PuzzleData::from("80871224585914546619083218645595");
assert_eq!(24_176_176, star_1(&data));
}
#[test]
pub fn test_star_2() {
let data = PuzzleData::from("03036732577212944063491565474664");
assert_eq!(84_462_026, star_2(&data));
}
}
Day 17: Set and Forget
Rust solution to AoC|2019|17.
IntCode again. This time to operate a vacuum robot and some cameras.
Star 1 & 2
Get a map of the scaffolds:
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
39
40
41
42
43
pub fn star_1_2(memory: &Memory) -> SolType1 {
let mut code = IntCode::from((memory, (), VecDeque::new()));
code.run();
let data = code
.output
.iter()
.map(|&v| (v as u8) as char)
.collect::<String>()
.trim()
.to_string();
let w = data.find('\n').unwrap();
let h = (data.len() + w) / (w + 1);
let mut result_1 = 0;
for r in 1..h - 1 {
for c in 1..w - 1 {
if [(c, r), (c + 1, r), (c, r + 1), (c - 1, r), (c, r - 1)]
.iter()
.all(|(c, r)| data.as_bytes()[c + r * (w + 1)] == b'#')
{
result_1 += c * r;
}
}
}
let routine = pattern::get_movement_routine(&data, w, h);
// println!("{}", routine);
let mut code = IntCode::from((
memory,
routine
.as_bytes()
.iter()
.chain([b'n', b'\n'].iter())
.map(|&b| b as isize)
.collect::<VecDeque<_>>(),
VecDeque::new(),
));
code[0] = 2;
code.run();
(result_1, code.output.back().copied().unwrap()).into()
}
For part 2, find path and determine patterns that can be repeated in main routine
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
mod pattern {
const ORIENTATIONS: [u8; 4] = [b'>', b'^', b'<', b'v'];
pub fn get_movement_routine(data: &str, w: usize, h: usize) -> String {
let data = data.as_bytes();
let p = data.iter().position(|v| ORIENTATIONS.contains(v)).unwrap();
// find path
let (mut col, mut row) = (p % (w + 1), p / (w + 1));
let mut o = ORIENTATIONS.iter().position(|&o| data[p] == o).unwrap();
let mut steps = Vec::new();
loop {
let mut n = 0;
while let Some((c_upd, row_upd)) = next_pos(o, (col, row), data, w, h) {
(col, row) = (c_upd, row_upd);
n += 1;
}
if n > 0 {
steps.push(n.to_string());
}
if next_pos((o + 1) & 3, (col, row), data, w, h).is_some() {
o = (o + 1) & 3;
steps.push("L".to_string());
continue;
} else if next_pos((o + 3) & 3, (col, row), data, w, h).is_some() {
o = (o + 3) & 3;
steps.push("R".to_string());
continue;
}
break;
}
let mut offs = [0, 1, 1]; // offsets
let mut lens = [1, 0, 0]; // lengths
find_patterns(&steps, &mut offs, &mut lens, Vec::new(), 0).unwrap()
}
// next position if any for given orientation
fn next_pos(
orientation: usize,
(col, row): (usize, usize),
data: &[u8],
w: usize,
h: usize,
) -> Option<(usize, usize)> {
match orientation {
0 if col < w - 1 && data[col + 1 + (w + 1) * row] == b'#' => Some((col + 1, row)),
1 if row > 0 && data[col + (w + 1) * (row - 1)] == b'#' => Some((col, row - 1)),
2 if col > 0 && data[col - 1 + (w + 1) * row] == b'#' => Some((col - 1, row)),
3 if row < h - 1 && data[col + (w + 1) * (row + 1)] == b'#' => Some((col, row + 1)),
_ => None,
}
}
// recursively find patterns to build main- and sub-routines
fn find_patterns(
steps: &[String],
offs: &mut [usize; 3],
lens: &mut [usize; 3],
main: Vec<String>,
k: usize,
) -> Option<String> {
// iterate over length of kth pattern
let mut routine_len = if offs[k] + lens[k] <= steps.len() {
steps[offs[k]..offs[k] + lens[k]]
.iter()
.map(String::len)
.sum::<usize>()
+ lens[k]
- 1
} else {
usize::MAX
};
while routine_len < 20 {
// find matching patterns so far
let mut main = main.clone();
let mut off = offs[k];
while main.len() < 10 {
if let Some(k) = (0..=k).find(|&k| {
steps.len() >= off + lens[k]
&& steps[offs[k]..offs[k] + lens[k]] == steps[off..off + lens[k]]
}) {
off += lens[k];
main.push(((b'A' + k as u8) as char).to_string());
} else {
break;
}
}
// if matching patterns make the full path, return them
if off == steps.len() {
return Some(format!(
"{}\n{}\n",
main.join(","),
(0..offs.len())
.map(|k| steps[offs[k]..offs[k] + lens[k]].join(","))
.collect::<Vec<_>>()
.join("\n")
));
}
// recurse to define more patterns
if k < offs.len() - 1 {
(offs[k + 1], lens[k + 1]) = (off, 1);
if let Some(routines) = find_patterns(steps, offs, lens, main, k + 1) {
return Some(routines);
}
}
// increase length
lens[k] += 1;
routine_len = if offs[k] + lens[k] <= steps.len() {
routine_len + 1 + steps[offs[k] + lens[k]].len()
} else {
usize::MAX
};
}
None
}
}
Day 18: Many-Worlds Interpretation
Rust solution to AoC|2019|18.
Path finding in a grid with keys and doors
Star 1
Parse the input in a map and find the shortest path with a Dijkstra like algorithm. The shortest path from one key to another is calculated on demand and cached for later use. The search state consists of the current key (position) and the available keys.
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#[derive(Debug)]
pub struct Map {
pub grid: Vec<char>,
pub width: usize,
pub keys: HashMap<char, usize>,
adjacents: HashMap<char, Vec<(char, usize, usize)>>,
}
impl From<&str> for Map {
/// parse the puzzle input
fn from(s: &str) -> Self {
let width = s.find('\n').unwrap();
let grid = s
.trim()
.chars()
.filter(|b| !b.is_ascii_whitespace())
.collect::<Vec<_>>();
let mut start = 0;
let keys = grid
.iter()
.cloned()
.enumerate()
.fold(HashMap::new(), |mut keys, (p, c)| {
match c {
'a'..='z' => _ = keys.insert(c, p),
'@' => {
keys.insert((b'0' + start) as char, p);
start += 1;
}
_ => (),
}
keys
});
Self {
grid,
width,
keys,
adjacents: Default::default(),
}
}
}
impl Map {
fn calc_adjacents(
key: char,
keys: &HashMap<char, usize>,
grid: &[char],
width: usize,
) -> Vec<(char, usize, usize)> {
let start = *keys.get(&key).unwrap();
let mut queue = VecDeque::from([(start, 0, 0)]);
let mut seen = HashSet::from([start]);
let mut list = Vec::new();
while let Some((p, dist, req_keys)) = queue.pop_front() {
if grid[p] >= 'a' && grid[p] <= 'z' {
list.push((grid[p], dist, req_keys));
}
let (x, y) = (p % width, p / width);
for (x, y) in [
(x + 1, y),
(x, y.wrapping_sub(1)),
(x.wrapping_sub(1), y),
(x, y + 1),
] {
if x >= width || y * width >= grid.len() {
continue;
}
let p = x + y * width;
let v = grid[p];
if v == '#' {
continue;
}
if seen.insert(p) {
let mut req_keys = req_keys;
if v.is_ascii_uppercase() {
req_keys |= 1 << (v as u8 - b'A')
}
queue.push_back((p, dist + 1, req_keys));
}
}
}
list
}
pub fn adjacents(
&mut self,
key: char,
avl_keys: usize,
) -> impl Iterator<Item = (char, usize)> + '_ {
self.adjacents
.entry(key)
.or_insert_with(|| Self::calc_adjacents(key, &self.keys, &self.grid, self.width))
.iter()
.filter(move |(key, _, req_keys)| {
*req_keys & avl_keys == *req_keys && (avl_keys >> (*key as u8 - b'a')) & 1 == 0
})
.map(|(key, dist, _)| (*key, *dist))
}
}
pub fn star_1(&input: &&str) -> SolType1 {
let mut map = Map::from(input);
let start = ('0', 0);
let mut seen = HashMap::from([(start, usize::MAX)]);
let mut queue = BinaryHeap::from([(usize::MAX, start)]);
let all_keys = map
.keys
.keys()
.filter(|&&v| v.is_ascii_lowercase())
.fold(0, |all_keys, &key| all_keys | (1 << (key as u8 - b'a')));
while let Some((not_d, (key, avl_keys))) = queue.pop() {
if avl_keys == all_keys {
return !not_d;
}
for (adj, d) in map.adjacents(key, avl_keys) {
let avl_keys = avl_keys | (1 << (adj as u8 - b'a'));
let cur_best = seen.entry((adj, avl_keys)).or_insert(0);
if *cur_best < not_d - d {
*cur_best = not_d - d;
queue.push((*cur_best, (adj, avl_keys)));
}
}
}
panic!("No solution!");
}
Star 2
Part 2 is essentially the same as part 1. The main difference is that the search state now consists of four current keys (positions) and the available keys. In an adjacent search state, only one of the current keys (positions) must change.
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
39
40
41
42
43
44
45
46
47
48
49
50
pub fn star_2(&input: &&str) -> SolType2 {
// replace center in input string by new center
let start = input.find('@').unwrap();
let width = input.find('\n').unwrap() + 1;
let input = [
&input[0..start - width - 1],
"@#@",
&input[start - width + 2..start - 1],
"###",
&input[start + 2..start + width - 1],
"@#@",
&input[start + width + 2..],
]
.concat();
// parse map
let mut map = Map::from(input.as_str());
// state now consists of for positions and available keys
let start = (['0', '1', '2', '3'], 0);
let mut seen = HashMap::from([(start, usize::MAX)]);
let mut queue = BinaryHeap::from([(usize::MAX, start)]);
let all_keys = map
.keys
.keys()
.filter(|&&v| v.is_ascii_lowercase())
.fold(0, |all_keys, &key| all_keys | (1 << (key as u8 - b'a')));
while let Some((not_d, (pos, avl_keys))) = queue.pop() {
if avl_keys == all_keys {
return !not_d;
}
// each of the four positions needs to be considered to find adjacents
for k in 0..pos.len() {
for (adj, d) in map.adjacents(pos[k], avl_keys) {
let avl_keys = avl_keys | (1 << (adj as u8 - b'a'));
let mut adjs = pos;
adjs[k] = adj;
let cur_best = seen.entry((adjs, avl_keys)).or_insert(0);
if *cur_best < not_d - d {
*cur_best = not_d - d;
queue.push((*cur_best, (adjs, avl_keys)));
}
}
}
}
panic!("No solution!");
}
Tests
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#[cfg(test)]
mod tests {
use super::*;
const CONTENT_1: &str = r#"########################
#f.D.E.e.C.b.A.@.a.B.c.#
######################.#
#d.....................#
########################
"#;
const CONTENT_2: &str = r#"#############
#DcBa.#.GhKl#
#.###...#I###
#e#d#-@.#j#k#
###C#...###J#
#fEbA.#.FgHi#
#############
"#;
#[test]
pub fn test_from() {
let map = Map::from(CONTENT_1);
println!("{:?}", map);
assert_eq!(24, map.width);
assert_eq!(
HashMap::from([
('f', 25),
('e', 31),
('b', 35),
('0', 39),
('a', 41),
('c', 45),
('d', 73)
]),
map.keys
);
}
#[test]
pub fn test_adjacents() {
let map = Map::from(CONTENT_1);
let list = Map::calc_adjacents('0', &map.keys, &map.grid, map.width);
println!("{:?}", list);
assert_eq!(
vec![
('a', 2, 0b000_000),
('b', 4, 0b000_001),
('c', 6, 0b000_010),
('e', 8, 0b000_101),
('f', 14, 0b011_101),
('d', 30, 0b000_010)
],
list
);
}
#[test]
pub fn test_star_1() {
assert_eq!(86, star_1(&CONTENT_1));
}
#[test]
pub fn test_star_2() {
assert_eq!(32, star_2(&CONTENT_2));
}
}
Day 19: Tractor Beam
Rust solution to AoC|2019|19.
A drone discovers a tractor beam running IntCode
Star 1
Trivial…
1
2
3
4
5
6
7
8
9
10
11
pub fn is_pulled(memory: &Memory, col: isize, row: isize) -> bool {
let mut code = IntCode::from((memory, VecDeque::from([col, row]), 0));
code.run();
code.output == 1
}
pub fn star_1(memory: &Memory) -> SolType1 {
(0..50 * 50)
.filter(|&p| is_pulled(memory, p % 50, p / 50))
.count()
}
Star 2
The only interesting thing here was to think about how to find the place where the ship fits with not too many measurements…
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
39
40
41
42
43
44
45
46
47
48
pub fn does_fit(memory: &Memory, row: isize, col_0: isize, w: isize) -> (bool, isize) {
// find first coordinate that is pulled in lower row
let mut col = col_0;
while !is_pulled(memory, col, row + w - 1) {
col += 1;
}
(
// check other corners of square
is_pulled(memory, col, row)
&& is_pulled(memory, col + w - 1, row)
&& is_pulled(memory, col + w - 1, row + w - 1),
col,
)
}
pub fn star_2(memory: &Memory) -> SolType2 {
let w = 100;
// double row while square does not fit
let (mut row_low, mut row_high) = (0, 50);
let mut col_low = 0;
let mut col_0 = 0;
let mut fits = false;
while !fits {
col_low = col_0;
(fits, col_0) = does_fit(memory, row_high, col_0, w);
if !fits {
(row_low, row_high) = (row_high, row_high << 1);
}
}
// half search interval in each step
let mut col_fit = col_low;
while row_high - row_low > 1 {
let row_mid = (row_low + row_high) / 2;
(fits, col_0) = does_fit(memory, row_mid, col_low, w);
if fits {
row_high = row_mid;
col_fit = col_0;
} else {
row_low = row_mid;
col_low = col_0;
}
}
col_fit * 10_000 + row_high
}
Day 20: Donut Maze
Rust solution to AoC|2019|20.
Exploring fractal space
Input
Input parsing was tedious today.
As part of the input parsing, I find all the portals and store them in three maps:
-
Two mapping labels to coordinates, one for inner and one outer portals
-
One mapping coordinates (the coordinate just outside the grid) to labels and a flag indicating whether the coordinate is an inner or an outer portal
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
pub mod input {
use std::collections::HashMap;
#[derive(Debug)]
pub struct DonutMaze<'a> {
pub grid: &'a [u8],
pub w: usize,
pub ports_o: HashMap<u16, (usize, usize)>,
pub ports_i: HashMap<u16, (usize, usize)>,
pub ports: HashMap<(usize, usize), (u16, bool)>,
}
impl<'a> From<&'a str> for DonutMaze<'a> {
/// parse the puzzle input
fn from(s: &'a str) -> Self {
let grid = s.as_bytes();
let w = s.find('\n').unwrap() + 1;
let h = grid.len() / w;
debug_assert_eq!(w * h, grid.len());
// find outer portals
let mut ports = HashMap::new();
let mut ports_o = HashMap::new();
for col in 2..w - 2 {
if grid[col] != b' ' {
let label = (grid[col] as u16) << 8 | grid[col + w] as u16;
ports_o.insert(label, (col, 2));
ports.insert((col, 1), (label, false));
}
if grid[col + w * (h - 1)] != b' ' {
let label =
(grid[col + w * (h - 2)] as u16) << 8 | grid[col + w * (h - 1)] as u16;
ports_o.insert(label, (col, h - 3));
ports.insert((col, h - 2), (label, false));
}
}
for row in 2..h - 2 {
if grid[w * row] != b' ' {
let label = (grid[w * row] as u16) << 8 | grid[1 + w * row] as u16;
ports_o.insert(label, (2, row));
ports.insert((1, row), (label, false));
}
if grid[w - 2 + w * row] != b' ' {
let label = (grid[w - 3 + w * row] as u16) << 8 | grid[w - 2 + w * row] as u16;
ports_o.insert(label, (w - 4, row));
ports.insert((w - 3, row), (label, false));
}
}
// find inner portals
let (col_in_mn, row_in_mn) = (2..h - 2)
.filter_map(|row| {
(2..w - 3)
.find(|col| grid[col + w * row] != b'.' && grid[col + w * row] != b'#')
.map(|col| (col, row))
})
.next()
.unwrap();
let mut ports_i = HashMap::new();
let mut col = col_in_mn;
while grid[col + w * row_in_mn] != b'.' && grid[col + w * row_in_mn] != b'#' {
if grid[col + w * row_in_mn] != b' ' {
let label = (grid[col + w * row_in_mn] as u16) << 8
| grid[col + w * (row_in_mn + 1)] as u16;
ports_i.insert(label, (col, row_in_mn - 1));
ports.insert((col, row_in_mn), (label, true));
}
col += 1;
}
let col_in_mx = col - 1;
let mut row = row_in_mn;
while grid[col_in_mn + w * row] != b'.' && grid[col_in_mn + w * row] != b'#' {
if grid[col_in_mn + w * row] != b' ' {
let label = (grid[col_in_mn + w * row] as u16) << 8
| grid[col_in_mn + 1 + w * row] as u16;
ports_i.insert(label, (col_in_mn - 1, row));
ports.insert((col_in_mn, row), (label, true));
}
if grid[col_in_mx + w * row] != b' ' {
let label = (grid[col_in_mx - 1 + w * row] as u16) << 8
| grid[col_in_mx + w * row] as u16;
ports_i.insert(label, (col_in_mx + 1, row));
ports.insert((col_in_mx, row), (label, true));
}
row += 1;
}
let row_in_mx = row - 1;
for col in col_in_mn..=col_in_mx {
if grid[col + w * row_in_mx] != b' ' {
let label = (grid[col + w * (row_in_mx - 1)] as u16) << 8
| grid[col + w * row_in_mx] as u16;
ports_i.insert(label, (col, row_in_mx + 1));
ports.insert((col, row_in_mx), (label, true));
}
}
Self {
grid,
w,
ports_o,
ports_i,
ports,
}
}
}
}
Star 1
Breadth first traversal, using portals to warp from inner to outer and vice versa.
The function shortest_path
is made to also solve part 2. The level l
is only needed for part 2 and is always 0
in part 1.
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
pub fn shortest_path<F>(maze: &DonutMaze, f: F) -> SolType1
where
F: Fn(u16, bool, usize) -> Option<(usize, (usize, usize))>,
{
let start_label = (b'A' as u16) << 8 | b'A' as u16;
let start = (0, maze.ports_o.get(&start_label).copied().unwrap());
let target_label = (b'Z' as u16) << 8 | b'Z' as u16;
let target = (0, maze.ports_o.get(&target_label).copied().unwrap());
let mut seen = HashSet::from([start]);
let mut queue = VecDeque::from([(0, start)]);
while let Some((d, (l, (col, row)))) = queue.pop_front() {
if (l, (col, row)) == target {
return d;
}
for (mut adj_col, mut adj_row) in [
(col + 1, row),
(col, row - 1),
(col - 1, row),
(col, row + 1),
] {
if maze.grid[adj_col + maze.w * adj_row] == b'#' {
continue;
}
// move through portal
let mut adj_l = l;
if maze.grid[adj_col + maze.w * adj_row] != b'.' {
let (label, inner) = maze.ports.get(&(adj_col, adj_row)).copied().unwrap();
if label == start_label || label == target_label {
continue;
}
if let Some(conn) = f(label, inner, adj_l) {
(adj_l, (adj_col, adj_row)) = conn;
} else {
continue;
}
}
if seen.insert((adj_l, (adj_col, adj_row))) {
debug_assert!(
maze.grid[adj_col + maze.w * adj_row] == b'.',
"from ({}, {}) at {} to ({}, {}) at {}!?",
col,
row,
d,
adj_col,
adj_row,
d + 1
);
queue.push_back((d + 1, (adj_l, (adj_col, adj_row))));
}
}
}
panic!("No solution!")
}
pub fn star_1(maze: &DonutMaze) -> SolType1 {
shortest_path(maze, |label, inner, _| {
Some((
0,
if inner { &maze.ports_o } else { &maze.ports_i }
.get(&label)
.copied()
.unwrap(),
))
})
}
Star 2
Breadth first traversal, using portals to switch between levels.
The only change compared to part 1 is the closure that determines how to use the portals.
1
2
3
4
5
6
7
8
9
10
11
pub fn star_2(maze: &DonutMaze) -> SolType2 {
shortest_path(maze, |label, inner, l| {
if l == 0 && !inner {
None
} else if inner {
Some((l + 1, maze.ports_o.get(&label).copied().unwrap()))
} else {
Some((l - 1, maze.ports_i.get(&label).copied().unwrap()))
}
})
}
Tests
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
39
40
41
42
43
44
45
46
47
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use super::*;
const CONTENT_1: &str = include_str!("../test_1.txt");
const CONTENT_2: &str = include_str!("../test_2.txt");
#[test]
pub fn test_from() {
let data = DonutMaze::from(CONTENT_1);
println!("{:?}", data);
assert_eq!(10, data.ports_i.len());
assert_eq!(12, data.ports_o.len());
assert_eq!(
HashSet::from([
(b'A' as u16) << 8 | b'A' as u16,
(b'Z' as u16) << 8 | b'Z' as u16
]),
data.ports_o
.keys()
.cloned()
.filter(|k| !data.ports_i.contains_key(k))
.collect::<HashSet<_>>()
);
assert_eq!(
0,
data.ports_i
.keys()
.filter(|k| !data.ports_o.contains_key(k))
.count()
);
}
#[test]
pub fn test_star_1() {
let data = DonutMaze::from(CONTENT_1);
assert_eq!(58, star_1(&data));
}
#[test]
pub fn test_star_2() {
let data = DonutMaze::from(CONTENT_2);
assert_eq!(396, star_2(&data));
}
}
Day 21: Springdroid Adventure
Rust solution to AoC|2019|21.
Springdroid running IntCode
Would be nice to have an algorithm to find the spring droid logic. I did it manually. The idea for both, part 1 and 2, is to jump if it is safely possible and the jump makes it possible to access ground that would not be accessible if the droid chose to walk instead
Star 1
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
pub fn star_1(memory: &Memory) -> SolType1 {
// jump width is 4
// jump if !(A & B & C) & D, i.e.,
// one of the next three steps is not solid
// and the jump target is solid, i.e., it is
// possible to jump and it is not possible to
// reach D by walking
// set J to true
// set J to J & A
// set J to J & B
// set J to J & C
// set J to !J
// set J to J & D
let inputs = [
"NOT J J", "AND A J", "AND B J", "AND C J", "NOT J J", "AND D J", "WALK",
];
let mut code = IntCode::<IterInput<_>, _>::from((
memory,
inputs
.iter()
.flat_map(|l| l.bytes().chain(once(b'\n')))
.map(|b| b as isize)
.into(),
VecDeque::new(),
));
code.run();
code.output.back().copied().unwrap()
}
Star 2
Am I just lucky or is my solution using 14 instructions and not using register 'I' generic?
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
pub fn star_2(memory: &Memory) -> SolType2 {
// Idea: Jump late!
// If a destination can also be reached by walking, do so, because it
// increases options.
// D & (E | H)
// @
// ---^=--=
// ABCDEFGH
// It is only possible to jump if the jump target D is solid and the next
// step is possible (step: E or jump: H)
// A & B & C
// @
// ===^----
// ABCDEFGH
// If it is possible to jump, it is also possible to walk to D -> jumping
// brings no advantage
// A & E & F & G
// @
// =--^===-
// ABCDEFGH
// If it is possible to jump first and walk second, it is also possible to
// walk first and jump second; if it is possible to jump twice in a row,
// it is also possible to walk first, then jump, then walk three times
// -> jumping brings no advantage
// => !(A & B & C) & !(A & E & F & G) & D & (E | H)
// set J to E
// set J to J & F = E & F
// set J to J & G = E & F & G
// set T to B
// set T to T & C = B & C
// set J to J | T = (B & C) | (E & F & G)
// set J to J & A = A & ((B & C) | (E & F & G)) = (A & B & C) | (A & E & F & G)
// set J to !J = !(A & B & C) & !(A & E & F & G)
// set T to !E
// set T to !T = E
// set T to T | H = E | H
// set T to T & D = D & (E | H)
// set J to J & T = !(A & B & C) & !(A & E & F & G) & D & (E | H)
let inputs = [
"OR E J", "AND F J", "AND G J", "OR B T", "AND C T", "OR T J", "AND A J", "NOT J J",
"NOT E T", "NOT T T", "OR H T", "AND D T", "AND T J", "RUN",
];
let mut code = IntCode::<IterInput<_>, _>::from((
memory,
inputs
.iter()
.flat_map(|l| l.bytes().chain(once(b'\n')))
.map(|b| b as isize)
.into(),
VecDeque::new(),
));
code.run();
code.output.back().copied().unwrap()
}
Day 22: Slam Shuffle
Rust solution to AoC|2019|22.
One of my all time favorites.
I learned a lot solving this puzzle.
Input
Parse input into a list of BasicShuffle
enums
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
pub mod input {
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum BasicShuffle {
DealNewStack,
Cut(i64),
DealWithInc(i64),
}
#[derive(Debug)]
pub struct PuzzleData {
shuffles: Vec<BasicShuffle>,
}
impl PuzzleData {
pub fn shuffles(&self) -> &[BasicShuffle] {
&self.shuffles
}
}
impl From<&str> for PuzzleData {
fn from(s: &str) -> Self {
use BasicShuffle::*;
Self {
shuffles: s
.lines()
.map(|l| {
l.strip_prefix("deal with increment ")
.map(|v| DealWithInc(v.parse().unwrap()))
.or_else(|| l.strip_prefix("cut ").map(|v| Cut(v.parse().unwrap())))
.unwrap_or(DealNewStack)
})
.collect(),
}
}
}
}
Solution
The key idea of the solution is that each basic shuffle as well as the concatenation of any number of basic shuffles can be represented as an affine map pos = fac * card + off (mod len)
where len
is the size of the card deck and pos
is the position of a given card
.
For part 2, this needs to be inverted to card = fac_inv * (pos - off) (mod len)
, which is possible because len
is prime (more generally, it is possible if fac
and len
are co-prime). The inverse is calculated using the Extended Euclidean algorithm.
Another ingredient to the solution is, that the concatenation of shuffles corresponds to a multiplication of shuffles. Hence, applying the same shuffle N times corresponds to taking the N-th power of a shuffle, which can be efficiently calculated using Exponentiation by squaring
All this is put together in struct Shuffle
representing an affine, reversible shuffle and initialized from the basic shuffles from the puzzle input.
Since intermediate results can grow big, I use u128
internally. Everything at the external interfaces fits in u64
.
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/// an affine shuffle for a deck of a given size
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Shuffle {
len: u64,
fac: u64,
off: u64,
fac_inv: Option<u64>,
}
impl From<(u64, &BasicShuffle)> for Shuffle {
fn from((len, shuffle): (u64, &BasicShuffle)) -> Self {
match shuffle {
// Example: deal into new stack
// [0 1 2 3 4 5 6] -> [6 5 4 3 2 1 0]
// card: 0 -> pos: 6
// card: 1 -> pos: 5
// ...
// card: 6 -> pos: 0
BasicShuffle::DealNewStack => Self {
fac: (len - 1),
off: (len - 1),
len,
fac_inv: None,
},
// Example: cut 2
// [0 1 2 3 4 5 6] -> [2 3 4 5 6 0 1]
// card: 0 -> pos: 5
// card: 1 -> pos: 6
// card: 2 -> pos: 0
// ...
// card: 6 -> pos: 4
BasicShuffle::Cut(off) => Self {
fac: 1,
off: (-off).rem_euclid(len as _) as _,
len,
fac_inv: None,
},
// Example: deal with inc 3
// [0 1 2 3 4 5 6] -> [0 5 3 1 6 4 2]
// card: 0 -> pos: 0
// card: 1 -> pos: 3
// ...
BasicShuffle::DealWithInc(fac) => Self {
fac: (fac).rem_euclid(len as _) as _,
off: 0,
len,
fac_inv: None,
},
}
}
}
impl From<(u64, &[BasicShuffle])> for Shuffle {
fn from((len, shuffles): (u64, &[BasicShuffle])) -> Self {
shuffles
.iter()
.fold(Self::identity(len), |result, shuffle| {
result.and(&(len, shuffle).into())
})
}
}
impl Shuffle {
/// get an identity shuffle
pub fn identity(len: u64) -> Self {
Self {
fac: 1,
off: 0,
len,
fac_inv: Some(1),
}
}
/// Combine this shuffle with another one and return the updated shuffle as result
///
/// Note that in general `a.and(b) != b.and(a)`
///
/// Panics if `len` of `self` and `other` differ
pub fn and(mut self, other: &Self) -> Self {
assert!(self.len == other.len);
// pos = other.fac * (self.fac * card + self.off) + other.off
// pos = other.fac * self.fac * card + other.fac * self.off + other.off
self.off =
((other.fac as u128 * self.off as u128 + other.off as u128) % self.len as u128) as _;
self.fac = ((other.fac as u128 * self.fac as u128) % self.len as u128) as _;
self.fac_inv = None;
self
}
/// take this shuffle to the given power
///
/// the power is calculated recursively using exponentiation by squaring
pub fn power(mut self, n: u64) -> Self {
if n == 0 {
(self.fac, self.off, self.fac_inv) = (1, 0, Some(1));
} else if n > 1 {
let (off, fac) = (self.off as u128, self.fac as u128);
// calculate power for n / 2
self = self.power(n >> 1);
// square result
self.off =
((self.off as u128 * self.fac as u128 + self.off as u128) % self.len as u128) as _;
self.fac = ((self.fac as u128 * self.fac as u128) % self.len as u128) as _;
// if n is odd, multiply one more time by original offset and facto
if n & 1 == 1 {
self.off = ((self.off as u128 * fac + off) % self.len as u128) as _;
self.fac = ((self.fac as u128 * fac) % self.len as u128) as _;
}
// reset inverse
self.fac_inv = None;
}
self
}
/// get position of given card
pub fn pos_of(&self, card: u64) -> u64 {
((self.fac as u128 * card as u128 + self.off as u128) % self.len as u128) as _
}
/// get card at given index
///
/// calculate and cache inverse if needed
/// panics if this shuffle is not invertible, i.e., if `self.fac` and
/// `self.len` are not co-prime.
pub fn card_at(&mut self, pos: u64) -> u64 {
let delta = pos as u128 + self.len as u128 - self.off as u128;
((self.fac_inv_cached() * delta) % self.len as u128) as _
}
fn fac_inv_cached(&mut self) -> u128 {
*self.fac_inv.get_or_insert_with(|| {
let (mut t, mut new_t) = (0, 1);
let (mut r, mut new_r) = (self.len as i64, self.fac as i64);
while new_r != 0 {
let q = r / new_r;
(t, new_t) = (new_t, t - q * new_t);
(r, new_r) = (new_r, r - q * new_r);
}
assert_eq!(1, r, "cannot invert {} mod {}", self.fac, self.len);
t.rem_euclid(self.len as _) as _
}) as _
}
}
Star 1
Just use the above…
1
2
3
pub fn star_1(data: &PuzzleData) -> u64 {
Shuffle::from((10_007, data.shuffles())).pos_of(2_019)
}
Star 2
All the work has been done in the Shuffle
struct…
1
2
3
4
5
pub fn star_2(data: &PuzzleData) -> u64 {
Shuffle::from((119_315_717_514_047, data.shuffles()))
.power(101_741_582_076_661)
.card_at(2_020)
}
Tests
... and the tests
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#[cfg(test)]
mod tests {
use super::*;
const CONTENT_1: &str = r#"deal with increment 7
deal into new stack
deal into new stack
"#;
const RESULT_1: &[u64] = &[0, 3, 6, 9, 2, 5, 8, 1, 4, 7];
const CONTENT_2: &str = r#"cut 6
deal with increment 7
deal into new stack
"#;
const RESULT_2: &[u64] = &[3, 0, 7, 4, 1, 8, 5, 2, 9, 6];
const CONTENT_3: &str = r#"deal with increment 7
deal with increment 9
cut -2
"#;
const RESULT_3: &[u64] = &[6, 3, 0, 7, 4, 1, 8, 5, 2, 9];
const CONTENT_4: &str = r#"deal into new stack
cut -2
deal with increment 7
cut 8
cut -4
deal with increment 7
cut 3
deal with increment 9
deal with increment 3
cut -1
"#;
const RESULT_4: &[u64] = &[9, 2, 5, 8, 1, 4, 7, 0, 3, 6];
const TESTS: [(&str, &[u64]); 4] = [
(CONTENT_1, RESULT_1),
(CONTENT_2, RESULT_2),
(CONTENT_3, RESULT_3),
(CONTENT_4, RESULT_4),
];
fn get_shuffled_1(len: u64, shuffle: &Shuffle) -> Vec<u64> {
let mut v = vec![0; len as _];
for card in 0..len {
v[shuffle.pos_of(card) as usize] = card
}
v
}
fn get_shuffled_2(len: u64, shuffle: &mut Shuffle) -> Vec<u64> {
(0..len).map(|idx| shuffle.card_at(idx)).collect()
}
#[test]
pub fn test_shuffle_from_single() {
let mut shuffle_1 = Shuffle::from((10, [BasicShuffle::DealWithInc(3)].as_slice()));
let shuffle_2 = Shuffle::from((10, &BasicShuffle::DealWithInc(3)));
assert_eq!(shuffle_1, shuffle_2);
let deck_1 = get_shuffled_1(10, &shuffle_1);
let deck_2 = get_shuffled_2(10, &mut shuffle_1);
assert_eq!([0, 7, 4, 1, 8, 5, 2, 9, 6, 3].as_slice(), deck_1);
assert_eq!([0, 7, 4, 1, 8, 5, 2, 9, 6, 3].as_slice(), deck_2);
let mut shuffle_1 = Shuffle::from((10, [BasicShuffle::Cut(-4)].as_slice()));
let shuffle_2 = Shuffle::from((10, &BasicShuffle::Cut(-4)));
assert_eq!(shuffle_1, shuffle_2);
let deck_1 = get_shuffled_1(10, &shuffle_1);
let deck_2 = get_shuffled_2(10, &mut shuffle_1);
assert_eq!([6, 7, 8, 9, 0, 1, 2, 3, 4, 5].as_slice(), deck_1);
assert_eq!([6, 7, 8, 9, 0, 1, 2, 3, 4, 5].as_slice(), deck_2);
let mut shuffle_1 = Shuffle::from((10, [BasicShuffle::DealNewStack].as_slice()));
let shuffle_2 = Shuffle::from((10, &BasicShuffle::DealNewStack));
assert_eq!(shuffle_1, shuffle_2);
let deck_1 = get_shuffled_1(10, &shuffle_1);
let deck_2 = get_shuffled_2(10, &mut shuffle_1);
assert_eq!([9, 8, 7, 6, 5, 4, 3, 2, 1, 0].as_slice(), deck_1);
assert_eq!([9, 8, 7, 6, 5, 4, 3, 2, 1, 0].as_slice(), deck_2);
}
#[test]
pub fn test_shuffle_from() {
for (k, (content, expected)) in TESTS.into_iter().enumerate() {
println!("\nTest {:2}\n=======\n{content}", k + 1);
let data = PuzzleData::from(content);
let shuffles = data.shuffles();
let mut deck_1 = vec![];
let mut deck_2 = vec![];
for n in 0..shuffles.len() {
let mut shuffle = Shuffle::from((10, &shuffles[0..=n]));
deck_1 = get_shuffled_1(10, &shuffle);
deck_2 = get_shuffled_2(10, &mut shuffle);
println!(" {:2}) {shuffle:?} => {deck_1:?} / {deck_2:?}", n + 1);
}
assert_eq!(expected, deck_1);
assert_eq!(expected, deck_2);
}
}
#[test]
pub fn test_power() {
let shuffle_0: Shuffle = (10, PuzzleData::from(CONTENT_3).shuffles()).into();
let shuffle_1 = shuffle_0
.clone()
.and(&shuffle_0)
.and(&shuffle_0)
.and(&shuffle_0)
.and(&shuffle_0);
let shuffle_2 = shuffle_0.clone().power(5);
assert_eq!(shuffle_1, shuffle_2);
}
}
Day 23: Category Six
Rust solution to AoC|2019|23.
Another day of IntCode; this time 50 computers run Network Interface Control (NIC) in parallel.
Solution
The solution uses a struct PostOffice
which takes care of delivering packets between the computers:
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#[derive(Debug, Clone)]
pub struct PostOffice {
boxes: RefCell<Vec<VecDeque<isize>>>,
nat: RefCell<Option<(isize, isize)>>,
misses: RefCell<Vec<usize>>,
last_y: RefCell<Option<isize>>,
y: RefCell<Option<isize>>,
}
impl PostOffice {
/// NAT address
pub const NAT: usize = 255;
/// create instance of size `n`
pub fn new(n: usize) -> Self {
Self {
boxes: RefCell::new(vec![VecDeque::new(); n]),
nat: RefCell::new(None),
misses: RefCell::new(vec![0; n]),
last_y: RefCell::new(None),
y: RefCell::new(None),
}
}
/// deliver packet for `recipient`
pub fn deliver(&self, recipient: usize, x: isize, y: isize) {
if recipient == Self::NAT {
self.nat.replace(Some((x, y)));
} else {
let mut boxes = self.boxes.borrow_mut();
boxes[recipient].push_back(x);
boxes[recipient].push_back(y);
};
}
/// get packet for `receiver` if available
pub fn get_packet(&self, receiver: usize) -> Option<(isize, isize)> {
if receiver == Self::NAT {
return self.get_nat();
}
if self.boxes.borrow()[receiver].len() < 2 {
self.miss(receiver);
None
} else {
self.hit(receiver);
let mut b = self.boxes.borrow_mut();
let x = b[receiver].pop_front().unwrap();
let y = b[receiver].pop_front().unwrap();
Some((x, y))
}
}
/// get last packet received by NAT if any
pub fn get_nat(&self) -> Option<(isize, isize)> {
self.nat.replace(None)
}
/// get repeated `y` sent by NAT if any
pub fn get_y(&self) -> Option<isize> {
self.y.borrow().to_owned()
}
/// minimum number of misses over all receiver
/// (miss = request packet for receiver on empty queue)
pub fn min_misses(&self) -> usize {
self.misses.borrow().iter().min().copied().unwrap_or(0)
}
/// count a miss for `receiver`
fn miss(&self, receiver: usize) {
self.misses.borrow_mut()[receiver] += 1;
if self.nat.borrow().is_some() && self.min_misses() > 1 {
let (x, y) = self.get_nat().unwrap();
self.deliver(0, x, y);
if self.last_y.borrow_mut().replace(y) == Some(y) {
self.y.borrow_mut().replace(y);
}
}
}
/// reset misses for `receiver`
fn hit(&self, receiver: usize) {
self.misses.borrow_mut()[receiver] = 0;
}
}
The output of the NICs is send to a queue; once three elements are in the queue, the packet is delivered using the post office:
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
#[derive(Debug, Clone)]
pub struct OutQueue<'a> {
post_office: &'a PostOffice,
queue: VecDeque<isize>,
}
impl<'a> OutQueue<'a> {
pub fn new(post_office: &'a PostOffice) -> Self {
Self {
post_office,
queue: VecDeque::new(),
}
}
}
impl Output for OutQueue<'_> {
fn write(&mut self, value: isize) {
self.queue.push_back(value);
while self.queue.len() >= 3 {
let recipient = self.queue.pop_front().unwrap() as usize;
let x = self.queue.pop_front().unwrap();
let y = self.queue.pop_front().unwrap();
self.post_office.deliver(recipient, x, y);
}
}
}
The solution for parts one and two is the same except for the stopping condition.
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
pub fn solve<F: Fn(&PostOffice) -> Option<SolType>>(memory: &Memory, f: F) -> SolType {
const N: usize = 50;
let post_office = PostOffice::new(N);
let mut computers = vec![IntCode::from((memory, (), OutQueue::new(&post_office))); N];
// boot computers
for (k, computer) in computers.iter_mut().enumerate() {
computer.run();
computer.run_with_input(k as isize);
}
// run computers in a loop
loop {
for (k, computer) in computers.iter_mut().enumerate() {
if let Some((x, y)) = post_office.get_packet(k) {
computer.run_with_input(x);
computer.run_with_input(y);
} else {
computer.run_with_input(-1);
}
if let Some(sol) = f(&post_office) {
return sol;
}
}
}
}
Star 1
In part 1, the loop stops once the NAT receives a packet. The solution is the y
value of that packet.
1
2
3
pub fn star_1(memory: &Memory) -> SolType {
solve(memory, |post_office| post_office.get_nat().map(|(_, y)| y))
}
Star 2
In part 2, the loop stops once the NAT sends the same y
value twice in a row. The solution is that y
value.
1
2
3
pub fn star_2(memory: &Memory) -> SolType {
solve(memory, PostOffice::get_y)
}
Day 24: Planet of Discord
Rust solution to AoC|2019|24.
Today, we count bugs…
Input
I parse the input in the bits of a u32
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pub mod input {
use crate::eris;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Eris(pub u32);
impl From<&str> for Eris {
/// parse the puzzle input
fn from(s: &str) -> Self {
let s = s.as_bytes();
Self((0..eris::N * eris::N).fold(0, |area, p| {
if s[p + p / eris::N] == b'#' {
area | (1 << p)
} else {
area
}
}))
}
}
}
Star 1
Do the updates, store previously seen configurations in a set and return the first configuration that repeats (since I already store the configurations in a u32
, the configuration equals its 'biodiversity rating')
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
39
40
41
42
43
44
45
46
47
48
49
50
pub mod eris {
pub const N: usize = 5;
pub fn get(v: u32, x: usize, y: usize) -> u32 {
(v >> (x + N * y)) & 1
}
pub fn set(v: &mut u32, x: usize, y: usize) {
*v |= 1 << (x + N * y);
}
pub fn count_adjacents(v: u32, x: usize, y: usize) -> u32 {
let mut cnt = 0;
if x > 0 {
cnt += get(v, x - 1, y);
};
if x < N - 1 {
cnt += get(v, x + 1, y);
}
if y > 0 {
cnt += get(v, x, y - 1);
}
if y < N - 1 {
cnt += get(v, x, y + 1);
}
cnt
}
pub fn update(v: u32) -> u32 {
let mut upd = 0;
for x in 0..N {
for y in 0..N {
let a = count_adjacents(v, x, y);
if a == 1 || a == 2 && get(v, x, y) == 0 {
set(&mut upd, x, y)
}
}
}
upd
}
}
pub fn star_1(data: &Eris) -> SolType {
let mut set = HashSet::new();
let mut data = data.0;
while set.insert(data) {
data = eris::update(data);
}
data
}
Star 2
Essentially change the update
and count_adjacents
functions to take into account the fractal structure of the problem and keep a list of levels that increases when the first bugs appear on a new inner or outer level.
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
pub mod eris_2 {
use crate::eris::{get, set, N};
pub const MID: usize = 2;
pub fn count_adjacents(v: u32, x: usize, y: usize, inner: u32, outer: u32) -> u32 {
let mut cnt = 0;
if x == 0 {
cnt += get(outer, MID - 1, MID);
cnt += get(v, x + 1, y);
} else if x == N - 1 {
cnt += get(outer, MID + 1, MID);
cnt += get(v, x - 1, y);
} else {
if x == MID + 1 && y == MID {
cnt += (0..N).map(|y| get(inner, N - 1, y)).sum::<u32>();
} else {
cnt += get(v, x - 1, y);
}
if x == MID - 1 && y == MID {
cnt += (0..N).map(|y| get(inner, 0, y)).sum::<u32>();
} else {
cnt += get(v, x + 1, y);
}
}
if y == 0 {
cnt += get(outer, MID, MID - 1);
cnt += get(v, x, y + 1);
} else if y == N - 1 {
cnt += get(outer, MID, MID + 1);
cnt += get(v, x, y - 1);
} else {
if y == MID + 1 && x == MID {
cnt += (0..N).map(|x| get(inner, x, N - 1)).sum::<u32>();
} else {
cnt += get(v, x, y - 1);
}
if y == MID - 1 && x == MID {
cnt += (0..N).map(|x| get(inner, x, 0)).sum::<u32>();
} else {
cnt += get(v, x, y + 1);
}
}
cnt
}
pub fn update(v: u32, inner: u32, outer: u32) -> u32 {
let mut upd = 0;
for x in 0..N {
for y in 0..N {
if x == MID && y == MID {
continue;
}
let a = count_adjacents(v, x, y, inner, outer);
if a == 1 || a == 2 && get(v, x, y) == 0 {
set(&mut upd, x, y)
}
}
}
upd
}
}
pub fn star_2(data: &Eris) -> SolType {
let mut data = VecDeque::from([data.0]);
let mut upd = VecDeque::with_capacity(data.len() + 2);
for _ in 0..200 {
upd.resize(data.len(), 0);
for k in 0..data.len() {
upd[k] = eris_2::update(
data[k],
if k == 0 { 0 } else { data[k - 1] },
if k == data.len() - 1 { 0 } else { data[k + 1] },
);
}
let a = eris_2::update(0, 0, *data.front().unwrap());
if a != 0 {
upd.push_front(a);
}
let b = eris_2::update(0, *data.back().unwrap(), 0);
if b != 0 {
upd.push_back(b);
}
data.truncate(0);
(data, upd) = (upd, data);
}
data.iter().map(|v| v.count_ones()).sum()
}
Tests
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#[cfg(test)]
mod tests {
use super::*;
const CONTENT: &str = r#"....#
#..#.
#..##
..#..
#....
"#;
#[test]
pub fn test_from() {
let data = Eris::from(CONTENT);
assert_eq!(0b100100110010100110000, data.0);
}
#[test]
pub fn test_star_1() {
let data = Eris::from(CONTENT);
assert_eq!(0b1000001000000000000000, star_1(&data));
}
}
Day 25: Cryostasis
Rust solution to AoC|2019|25.
One more time a Droid moves on IntCode and tries to figure out the password for an airlock.
Solution
The solution consists of the following steps:
-
Explore the environment to create a map of all rooms and collect all available items (but do not take any of 'escape pod', 'giant electromagnet', 'infinite loop', 'molten lava', or 'photons').
-
Move on the shortest possible path to the pressure-sensitive floor which is just behind the security checkpoint
-
Try different combinations of items until the weight is correct
-
Get the password
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
pub const FORBIDDEN: [&str; 5] = [
"escape pod",
"giant electromagnet",
"infinite loop",
"molten lava",
"photons",
];
pub const DIRECTIONS: [&str; 4] = ["east", "north", "west", "south"];
pub const CHECKPOINT: &str = "Security Checkpoint";
pub const HELLO: &str = "Oh, hello! You should be able to get in by typing ";
pub fn room_name(prompt: &str) -> (Option<&str>, impl Iterator<Item = &str>) {
let mut prompt_lines = prompt.trim().lines();
let name = prompt_lines
.next()
.and_then(|line| line.strip_prefix("== "))
.and_then(|line| line.strip_suffix(" =="));
(name, prompt_lines)
}
pub fn map_and_collect(game: &mut Game) -> (HashMap<String, Room>, Vec<String>, String) {
let forbidden = HashSet::from(FORBIDDEN);
let mut rooms: HashMap<String, Room> = HashMap::new();
let mut path = Vec::new();
let mut items = Vec::new();
let mut prev: Option<(String, usize)> = None;
loop {
let prompt = game.get_prompt();
let (name, prompt_lines) = room_name(&prompt);
let Some(name) = name else {
panic!("Unexpected prompt: {}\nPrevious: {:?}", prompt, prev);
};
debug_assert!(
prev.as_ref()
.map(|(prev_name, _)| prev_name != name)
.unwrap_or(true),
"Did not move!"
);
if let Some((prev_name, direction)) = prev.as_ref() {
rooms.entry(name.to_owned()).or_insert_with(|| {
let mut room = Room::parse(name, prompt_lines);
room.adjacents
.insert((direction + 2) & 3, prev_name.to_string());
room
});
rooms
.get_mut(prev_name.as_str())
.map(|room| room.adjacents.insert(*direction, name.to_string()));
prev = None;
} else {
rooms
.entry(name.to_owned())
.or_insert_with(|| Room::parse(name, prompt_lines));
}
{
let room = rooms.get_mut(name).unwrap();
// take all the items
for k in (0..room.items.len()).rev() {
if forbidden.contains::<&str>(&room.items[k].as_ref()) {
continue;
}
let item = room.items.remove(k);
game.take(&item);
items.push(item);
}
if name != CHECKPOINT {
if let Some(&direction) = room
.directions
.iter()
.find(|direction| !room.adjacents.contains_key(direction))
{
prev = Some((room.name.clone(), direction));
path.push(direction);
game.set_command(DIRECTIONS[direction]);
continue;
}
}
}
if let Some(direction) = path.pop() {
game.set_command(DIRECTIONS[(direction + 2) & 3]);
} else {
return (rooms, items, name.to_string());
}
}
}
pub fn star_1(memory: &Memory) -> SolType1 {
let mut game = Game::from(memory);
game.start();
// build map and collect items
let (map, items, room) = map_and_collect(&mut game);
// determine shortest path to checkpoint
let mut queue = VecDeque::from([room.clone()]);
let mut seen = HashSet::from([room.clone()]);
let mut parents = HashMap::new();
while let Some(name) = queue.pop_front() {
if name == CHECKPOINT {
break;
}
for (direction, target) in map.get(&name).unwrap().adjacents.iter() {
if seen.insert(target.clone()) {
queue.push_back(target.clone());
parents.insert(target.clone(), (*direction, name.clone()));
}
}
}
let mut current = parents.get(CHECKPOINT);
let mut path = Vec::new();
while let Some((direction, parent)) = current {
path.push(*direction);
current = parents.get(parent);
}
// move to checkpoint
let mut room = room;
while let Some(direction) = path.pop() {
game.set_command(DIRECTIONS[direction]);
let prompt = game.get_prompt();
let (name, _) = room_name(&prompt);
debug_assert_eq!(
map.get(&room).unwrap().adjacents.get(&direction).unwrap(),
name.unwrap()
);
room = name.unwrap().to_string();
}
// get command to move to Pressure-Sensitive Floor
let checkpoint = map.get(CHECKPOINT).unwrap();
let direction = checkpoint
.directions
.iter()
.find(|direction| !checkpoint.adjacents.contains_key(direction))
.copied()
.unwrap();
let command = DIRECTIONS[direction];
// move to Pressure-Sensitive Floor with various combinations of items
// until check passes
let mut m = (1 << items.len()) - 1;
for n in 1..1 << items.len() {
for (k, item) in items.iter().enumerate() {
if (n >> k) & 1 == 1 && (m >> k) & 1 == 0 {
game.take(item);
}
if (n >> k) & 1 == 0 && (m >> k) & 1 == 1 {
game.drop(item);
}
}
game.set_command(command);
let prompt = game.get_prompt();
if let Some(idx) = prompt.find(HELLO) {
return prompt[idx + HELLO.len()..]
.split_ascii_whitespace()
.next()
.unwrap()
.parse()
.unwrap();
}
m = n;
}
panic!("No solution");
}
#[derive(Debug)]
pub struct Room {
pub name: String,
pub directions: Vec<usize>,
pub adjacents: HashMap<usize, String>,
pub items: Vec<String>,
}
impl Room {
pub fn parse<'a, I: Iterator<Item = &'a str>>(name: &str, prompt: I) -> Self {
let mut mode = 0;
let mut directions = Vec::new();
let mut items = Vec::new();
for line in prompt {
match line {
"Doors here lead:" => mode = 1,
"- east" if mode == 1 => directions.push(0),
"- north" if mode == 1 => directions.push(1),
"- west" if mode == 1 => directions.push(2),
"- south" if mode == 1 => directions.push(3),
"Items here:" => mode = 2,
v if v.starts_with("- ") && mode == 2 => {
items.push(v.strip_prefix("- ").unwrap().to_string());
}
_ => mode = 0,
}
}
Self {
name: name.to_owned(),
directions,
adjacents: HashMap::new(),
items,
}
}
}
pub struct Game {
code: IntCode<(), VecDeque<isize>>,
}
impl From<&Memory<'_>> for Game {
fn from(memory: &Memory) -> Self {
Self {
code: memory.into(),
}
}
}
impl Game {
pub fn start(&mut self) {
self.code.run();
}
pub fn get_prompt(&mut self) -> String {
self.code
.output
.drain(0..)
.map(|v| (v as u8) as char)
.collect()
}
pub fn set_command(&mut self, command: &str) {
for b in command.trim().bytes().chain(once(b'\n')) {
self.code.run_with_input(b as _);
}
}
pub fn take(&mut self, item: &str) {
self.set_command(&format!("take {}", item));
let prompt = self.get_prompt();
debug_assert_eq!(
Some(format!("You take the {}.", item).as_str()),
prompt.trim().lines().next()
);
}
pub fn drop(&mut self, item: &str) {
self.set_command(&format!("drop {}", item));
let prompt = self.get_prompt();
debug_assert_eq!(
Some(format!("You drop the {}.", item).as_str()),
prompt.trim().lines().next()
);
}
}
Int-Code Computer
The intcode computer used in several puzzles is put in a separate library.
IntCode
The key part is the IntCode
structure with its implementation:
1
2
3
4
5
6
7
8
9
10
11
12
13
#[derive(Clone)]
pub struct IntCode<I: Input, O: Output> {
/// The computer's memory
pub memory: Vec<isize>,
/// The instruction pointer
pub ptr: usize,
/// The relative base
pub rel_base: isize,
/// A source of input values
pub input: I,
/// A sink for output values
pub output: O,
}
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
impl IntCode<(), ()> {
pub fn new<'a, M: Into<Memory<'a>>>(memory: M) -> Self {
Self::from(memory)
}
}
impl<I: Input, O: Output> IntCode<I, O> {
pub const ADD: isize = 1;
pub const MUL: isize = 2;
pub const IN: isize = 3;
pub const OUT: isize = 4;
pub const JIT: isize = 5;
pub const JIF: isize = 6;
pub const LT: isize = 7;
pub const EQ: isize = 8;
pub const RBO: isize = 9;
pub const EOF: isize = 99;
pub const MOD_OP: isize = 100;
pub const POSITION: usize = 0;
pub const IMMEDIATE: usize = 1;
pub const RELATIVE: usize = 2;
/// Run the current instruction
pub fn instruction(&mut self) -> bool {
match self[self.ptr] % Self::MOD_OP {
Self::ADD => {
// add
*self.get_param_mut(3) = self.get_param(1) + self.get_param(2);
self.ptr += 4;
true
}
Self::MUL => {
// multiply
*self.get_param_mut(3) = self.get_param(1) * self.get_param(2);
self.ptr += 4;
true
}
Self::IN => {
// input
match self.input.try_read() {
Some(value) => {
*self.get_param_mut(1) = value;
self.ptr += 2;
true
}
None => false,
}
}
Self::OUT => {
// output
self.output.write(self.get_param(1));
self.ptr += 2;
true
}
Self::JIT => {
// jump-if-true
self.ptr = if self.get_param(1) > 0 {
self.get_param(2) as _
} else {
self.ptr + 3
};
true
}
Self::JIF => {
// jump-if-false
self.ptr = if self.get_param(1) == 0 {
self.get_param(2) as _
} else {
self.ptr + 3
};
true
}
Self::LT => {
// less than
*self.get_param_mut(3) = (self.get_param(1) < self.get_param(2)) as _;
self.ptr += 4;
true
}
Self::EQ => {
// equals
*self.get_param_mut(3) = (self.get_param(1) == self.get_param(2)) as _;
self.ptr += 4;
true
}
Self::RBO => {
// relative base offset
self.rel_base += self.get_param(1);
self.ptr += 2;
true
}
Self::EOF => false,
opcode => panic!("Unexpected opcode: {} at {}", opcode, self.ptr),
}
}
/// Provide an input to the program and continue to run it
///
/// If the program is not in a position, where it accepts an input,
/// nothing happens and the function returns false
///
/// Use with `()` as `Input` to stop execution whenever an input is requested
pub fn run_with_input(&mut self, value: isize) -> bool {
if self[self.ptr] % Self::MOD_OP == Self::IN {
*self.get_param_mut(1) = value;
self.ptr += 2;
self.run();
true
} else {
false
}
}
/// Run program until it halts or waits for input
pub fn run(&mut self) {
while self.instruction() {}
}
/// Reset memory; instruction pointer and relative base are reset to default values
pub fn reset<M: AsRef<[isize]>>(&mut self, memory: &M) {
let memory = memory.as_ref();
self.ptr = 0;
self.rel_base = 0;
self.memory.resize(memory.len(), 0);
self.memory.copy_from_slice(memory);
}
/// Get a parameter for the current instruction
pub fn get_param(&self, k: usize) -> isize {
match (self[self.ptr] as usize / usize::pow(10, k as u32 + 1)) % 10 {
Self::POSITION => self[self[self.ptr + k] as _],
Self::IMMEDIATE => self[self.ptr + k],
Self::RELATIVE => self[(self.rel_base + self[self.ptr + k]) as _],
m => panic!(
"Unexpected mode {} from {} for offset {} at {}",
m, self[self.ptr], k, self.ptr
),
}
}
/// Get a mutable reference to a parameter for the current instruction
pub fn get_param_mut(&mut self, k: usize) -> &mut isize {
let tar = match (self[self.ptr] as usize / usize::pow(10, k as u32 + 1)) % 10 {
Self::POSITION => self[self.ptr + k] as usize,
Self::IMMEDIATE => unreachable!("Immediate mode for write."),
Self::RELATIVE => (self.rel_base + self[self.ptr + k]) as usize,
m => panic!(
"Unexpected mode {} from {} for offset {} at {}",
m, self[self.ptr], k, self.ptr
),
};
&mut self[tar]
}
}
Memory elements are accessed using Index
and IndexMut
traits, which will produce valid results even if the index exceeds the current memory size.
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
impl<I: Input, O: Output> Index<usize> for IntCode<I, O> {
type Output = isize;
/// Get the value at the given `idx`
///
/// If `idx` exceeds the current memory size, the function
/// returns `0` without increasing memory size.
fn index(&self, idx: usize) -> &Self::Output {
if idx >= self.memory.len() {
&0
} else {
&self.memory[idx]
}
}
}
impl<I: Input, O: Output> IndexMut<usize> for IntCode<I, O> {
/// Get a mutable reference to the value at the given `idx`.
///
/// If `idx` exceeds the current memory size, memory size
/// is increased
fn index_mut(&mut self, idx: usize) -> &mut Self::Output {
if idx >= self.memory.len() {
self.memory.resize(idx + 1, 0);
}
&mut self.memory[idx]
}
}
Conversions
To create an IntCode
, use one of the conversions
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#[derive(Debug)]
pub enum Memory<'a> {
Owned(Vec<isize>),
Ref(&'a [isize]),
}
pub mod memory {
use crate::Memory;
impl<'a> Memory<'a> {
/// Converts a `&Self::Owned(Vec<isize>)` to a `Self::Ref(&[isize])`
pub fn as_ref(&'a self) -> Self {
match self {
Self::Owned(val) => Self::Ref(val),
Self::Ref(val) => Self::Ref(val),
}
}
/// Consumes self and returns the data as vector.
/// If the data is not owned, it is copied using `to_owned`
pub fn get_owned(self) -> Vec<isize> {
match self {
Memory::Owned(val) => val,
Memory::Ref(val) => val.to_owned(),
}
}
/// Get a mutable reference to the data
///
/// This will first convert `self` into an owned variant, if required.
pub fn get_mut(&mut self) -> &mut [isize] {
if let Memory::Ref(val) = self {
*self = Memory::Owned(val.to_owned());
}
let Memory::Owned(val) = self else { unreachable!() };
val
}
}
impl Clone for Memory<'_> {
/// Clone the `Memory`.
///
/// This always yields an owned variant
fn clone(&self) -> Self {
match self {
Self::Owned(val) => Self::Owned(val.clone()),
&Self::Ref(val) => Self::Owned(val.to_owned()),
}
}
}
impl AsRef<[isize]> for Memory<'_> {
fn as_ref(&self) -> &[isize] {
match self {
Memory::Owned(val) => val,
Memory::Ref(val) => val,
}
}
}
impl<'a> From<&'a Memory<'a>> for Memory<'a> {
fn from(value: &'a Memory<'a>) -> Self {
value.as_ref()
}
}
impl<'a> From<&'a [isize]> for Memory<'a> {
fn from(value: &'a [isize]) -> Self {
Self::Ref(value)
}
}
impl From<Vec<isize>> for Memory<'_> {
fn from(value: Vec<isize>) -> Self {
Self::Owned(value)
}
}
impl From<&str> for Memory<'_> {
fn from(value: &str) -> Self {
Self::Owned(
value
.trim()
.split(',')
.map(str::parse)
.collect::<Result<_, _>>()
.unwrap(),
)
}
}
}
impl<'a, I: Input + Default, O: Output + Default, M: Into<Memory<'a>>> From<M> for IntCode<I, O> {
fn from(memory: M) -> Self {
Self {
memory: memory.into().get_owned(),
..Default::default()
}
}
}
impl<'a, I: Input, O: Output, M: Into<Memory<'a>>> From<(M, I, O)> for IntCode<I, O> {
fn from((memory, input, output): (M, I, O)) -> Self {
Self {
memory: memory.into().get_owned(),
input,
output,
ptr: 0,
rel_base: 0,
}
}
}
or create the a default instance (which can be manipulated later on)
1
2
3
4
5
6
7
8
9
10
11
impl<I: Input + Default, O: Output + Default> Default for IntCode<I, O> {
fn default() -> Self {
Self {
input: Default::default(),
output: Default::default(),
memory: Vec::new(),
rel_base: 0,
ptr: 0,
}
}
}
IO
Inputs can be provided or outputs consumed using the Input
and Output
traits:
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/// IO for [`IntCode`]
pub mod io {
use std::{cell::RefCell, collections::VecDeque};
pub trait Input {
/// Read a value
///
/// # Panics
/// If no value available
///
/// The default implementation unwraps the result of [`try_read`].
/// Implement your own behavior if read does not panic.
fn read(&mut self) -> isize {
self.try_read().unwrap()
}
/// Read a value. Return `None` if no value available.
///
/// This function shall never panic.
///
/// The default implementation wraps the result of [`read`] in an `Option`.
/// Implement your own behavior if there are instances when `None` shall be returned.
fn try_read(&mut self) -> Option<isize> {
Some(self.read())
}
}
pub trait Output {
/// Write a value.
///
/// # Panics
/// If output cannot be written
fn write(&mut self, value: isize);
}
impl Input for () {
fn try_read(&mut self) -> Option<isize> {
None
}
}
impl Output for () {
fn write(&mut self, value: isize) {
panic!("Writing output not supported! Attempted to write {}", value);
}
}
impl Input for isize {
fn read(&mut self) -> isize {
*self
}
}
impl Output for isize {
fn write(&mut self, value: isize) {
*self = value;
}
}
pub struct IterInput<T: Iterator<Item = isize>>(pub T);
impl<I, T> From<T> for IterInput<I>
where
I: Iterator<Item = isize>,
T: IntoIterator<Item = isize, IntoIter = I>,
{
fn from(value: T) -> Self {
Self(value.into_iter())
}
}
impl<I: Iterator<Item = isize>> Input for IterInput<I> {
fn try_read(&mut self) -> Option<isize> {
self.0.next()
}
}
impl Input for VecDeque<isize> {
fn try_read(&mut self) -> Option<isize> {
self.pop_front()
}
}
impl Output for VecDeque<isize> {
fn write(&mut self, value: isize) {
self.push_back(value);
}
}
impl<I: Input> Input for &RefCell<I> {
fn read(&mut self) -> isize {
self.borrow_mut().read()
}
fn try_read(&mut self) -> Option<isize> {
self.borrow_mut().try_read()
}
}
impl<O: Output> Output for &RefCell<O> {
fn write(&mut self, value: isize) {
self.borrow_mut().write(value);
}
}
}
Tests
Examples from various days are used to create tests:
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#[cfg(test)]
mod tests {
use std::collections::VecDeque;
use super::*;
#[test]
fn test_int_code() {
let mut int_code = IntCode::new("1,9,10,3,2,3,11,0,99,30,40,50");
int_code.run();
assert_eq!(
vec![3500, 9, 10, 70, 2, 3, 11, 0, 99, 30, 40, 50],
int_code.memory
);
}
#[test]
fn test_int_code_io() {
let mut int_code: IntCode<VecDeque<isize>, VecDeque<isize>> = "3,0,4,0,99".into();
int_code.input.push_back(100);
int_code.run();
assert_eq!(VecDeque::from([100]), int_code.output);
let mut int_code: IntCode<isize, isize> = "3,0,4,0,99".into();
int_code.input = 100;
int_code.run();
assert_eq!(100, int_code.output);
}
#[test]
fn test_int_code_immediate_mode() {
let mut int_code = IntCode::new("1002,4,3,4,33");
int_code.run();
assert_eq!(vec![1002, 4, 3, 4, 99], int_code.memory);
}
#[test]
fn test_int_code_jumps() {
let mut int_code: IntCode<isize, isize> = "3,9,8,9,10,9,4,9,99,-1,8".into();
int_code.input = 8;
int_code.output = -1;
int_code.run();
assert_eq!(1, int_code.output);
let mut int_code: IntCode<isize, isize> = "3,9,8,9,10,9,4,9,99,-1,8".into();
int_code.input = 12;
int_code.output = -1;
int_code.run();
assert_eq!(0, int_code.output);
let mut int_code: IntCode<isize, isize> = "3,9,7,9,10,9,4,9,99,-1,8".into();
int_code.input = 7;
int_code.output = -1;
int_code.run();
assert_eq!(1, int_code.output);
let mut int_code: IntCode<isize, isize> = "3,9,7,9,10,9,4,9,99,-1,8".into();
int_code.input = 8;
int_code.output = -1;
int_code.run();
assert_eq!(0, int_code.output);
}
#[test]
pub fn test_rel_base() {
let mut int_code: IntCode<(), VecDeque<isize>> =
"109,1,204,-1,1001,100,1,100,1008,100,16,101,1006,101,0,99".into();
int_code.run();
assert_eq!(
VecDeque::from([
109, 1, 204, -1, 1_001, 100, 1, 100, 1_008, 100, 16, 101, 1_006, 101, 0, 99
]),
int_code.output
);
let mut int_code = IntCode::from((
&[1_102isize, 34_915_192, 34_915_192, 7, 4, 7, 99, 0] as &[_],
(),
0,
));
int_code.run();
assert!(int_code.output >= isize::pow(10, 15));
let mut int_code = IntCode::from((&[104isize, 1_125_899_906_842_624, 99] as &[_], (), 0));
int_code.run();
assert_eq!(int_code.output, 1_125_899_906_842_624);
}
}