Skip to content

Commit eaacdeb

Browse files
committed
Day 17
1 parent 2b20f7d commit eaacdeb

File tree

8 files changed

+1039
-19
lines changed

8 files changed

+1039
-19
lines changed

Cargo.lock

Lines changed: 772 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ edition = "2021"
88
[dependencies]
99
clap = { version = "4.4", features = ["derive"] }
1010
itertools = "0.14.0"
11+
plotters = "0.3.7"
1112
rayon = "1.8"
1213
static_assertions = "1.1.0"
1314
tailcall = "1.0.1"

flake.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
in
1919
{
2020
devShells.default = pkgs.mkShell rec {
21-
nativeBuildInputs = [ pkgs.pkg-config ];
21+
nativeBuildInputs = with pkgs; [ pkg-config expat fontconfig ];
2222
buildInputs = with pkgs; [
2323
clang
2424
llvmPackages.bintools

src/day16.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ fn part1(start: Pose, end: Index, map: &Grid<Thing>) -> (Direction, usize) {
7676
.map(|e| {
7777
(
7878
e.heading,
79-
dijkstra(start.clone(), |p| movements(p, map), e)
79+
dijkstra(start.clone(), |p| movements(p, map), |n| *n == e)
8080
.unwrap() // we're sure there's a way to the end
8181
.collect::<Vec<_>>(),
8282
)

src/day17.rs

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
use crate::graph::dijkstra;
2+
3+
pub fn solve(_: &str) -> (usize, usize) {
4+
(
5+
to_usize_digits(&execute1(17323786)),
6+
inverse(&[2, 4, 1, 1, 7, 5, 1, 5, 4, 1, 5, 5, 0, 3, 3, 0]),
7+
)
8+
}
9+
10+
// by hand,
11+
fn execute1(mut a: usize) -> Vec<u8> {
12+
let m = 0b111; // for mod 8
13+
let mut res = vec![];
14+
// Note: this loop will run ~ a.log2() / 3 times, producing one digit per iteration.
15+
// This tells us the range of a we need to search for the fixed-point
16+
while a != 0 {
17+
let b = a & m ^ 1;
18+
// because of this look-ahead, later digits influence earlier digits.
19+
let c = (a >> b) & m;
20+
res.push((b ^ c ^ 5) as u8);
21+
a >>= 3;
22+
}
23+
res.reverse();
24+
res
25+
}
26+
27+
#[allow(dead_code)]
28+
fn execute2(a: usize) -> Vec<u8> {
29+
let d = to_binary_digits(a);
30+
let get_3_bits_starting_at = |i| to_usize_binary(d.get(i..i + 3).unwrap_or(&[]));
31+
32+
(0..d.len())
33+
.step_by(3)
34+
.map(|i| {
35+
let shift = 1 ^ get_3_bits_starting_at(i);
36+
let lookahead = get_3_bits_starting_at(i + shift);
37+
(shift ^ lookahead ^ 5) as u8
38+
})
39+
.rev()
40+
.collect()
41+
}
42+
43+
#[allow(dead_code)]
44+
fn execute3(a: usize) -> Vec<u8> {
45+
(0..(to_binary_digits(a).len() as f32 / 3.0).ceil() as usize)
46+
.map(|i| {
47+
let i = i * 3;
48+
let shift = 1 ^ get_3_bits_starting_at(a, i);
49+
let lookahead = get_3_bits_starting_at(a, i + shift);
50+
(shift ^ lookahead ^ 5) as u8
51+
})
52+
.rev()
53+
.collect()
54+
}
55+
56+
fn get_3_bits_starting_at(v: usize, i: usize) -> usize {
57+
(v >> i) & 0b111
58+
}
59+
60+
fn replace_3_bits_starting_at(v: usize, i: usize, chunk: usize) -> usize {
61+
let mask = 0b111 << i;
62+
(v & !mask) | ((chunk & 0b111) << i)
63+
}
64+
65+
// more like some pseudo-inverse because `execute` is not injective
66+
fn inverse(r: &[u8]) -> usize {
67+
// strategy: build `a` in reverse
68+
// Because of the look-ahead, the value of the current digit of the output could
69+
// be generated by different values of the current digit of the input.
70+
// Solution: Explore all possible values; use Dijkstra for that.
71+
// Could also be done (possibly simpler) by doing a DFS and looking at all possible
72+
// solutions (or being careful about the order of traversal)
73+
74+
fn get_next_possible_a_values(
75+
a_curr: usize,
76+
j: usize,
77+
target: u8,
78+
) -> impl Iterator<Item = usize> {
79+
(0..=0b111).filter_map(move |maybe_chunk| {
80+
let a = replace_3_bits_starting_at(a_curr, j, maybe_chunk);
81+
let shift = 1 ^ get_3_bits_starting_at(a, j);
82+
let lookahead = get_3_bits_starting_at(a, j + shift);
83+
if shift ^ lookahead ^ 5 == target as usize {
84+
Some(a)
85+
} else {
86+
None
87+
}
88+
})
89+
}
90+
91+
dijkstra(
92+
// Node: (Option<index into `r`>, `a` so far)
93+
// If Node.0.is_none() we're done
94+
(Some(r.len() - 1), 0usize),
95+
|n| {
96+
if let (Some(i), a) = *n {
97+
get_next_possible_a_values(a, i * 3, r[i])
98+
// want smallest a, so we set a as edge-weight
99+
.map(|a| (a, (i.checked_sub(1), a)))
100+
.collect::<Vec<_>>()
101+
} else {
102+
vec![]
103+
}
104+
},
105+
|&n| n.0.is_none(),
106+
)
107+
.map(|s| s.last().unwrap().1)
108+
.expect("No inverse found!")
109+
}
110+
111+
// lsb
112+
fn to_usize_binary(x: &[bool]) -> usize {
113+
x.iter().enumerate().map(|(i, &v)| (v as usize) << i).sum()
114+
}
115+
116+
fn to_usize_digits(x: &[u8]) -> usize {
117+
x.iter()
118+
.enumerate()
119+
.map(|(i, &v)| v as usize * 10usize.pow(i as u32))
120+
.sum()
121+
}
122+
123+
// lsb
124+
fn to_binary_digits(mut x: usize) -> Vec<bool> {
125+
let mut result = vec![];
126+
while x > 0 {
127+
result.push((x & 1) == 1);
128+
x >>= 1;
129+
}
130+
result
131+
}
132+
133+
#[cfg(test)]
134+
mod tests {
135+
use super::*;
136+
137+
#[test]
138+
fn test_replace_and_read_3_bits() {
139+
let original = 0b11111111;
140+
let new_chunk = 0b000;
141+
let modified = replace_3_bits_starting_at(original, 3, new_chunk);
142+
let read_back = get_3_bits_starting_at(modified, 3);
143+
144+
assert_eq!(read_back, new_chunk);
145+
assert_eq!(modified, 0b11000111);
146+
}
147+
148+
#[test]
149+
fn test_executes() {
150+
let a = 17323786;
151+
assert_eq!(execute1(a), execute2(a));
152+
assert_eq!(execute1(a), execute3(a));
153+
}
154+
155+
#[test]
156+
fn test_roundtrip() {
157+
for n in 0..100 {
158+
let binary = to_binary_digits(n);
159+
let result = to_usize_binary(&binary);
160+
assert_eq!(result, n, "Roundtrip failed for {}", n);
161+
}
162+
}
163+
}

src/day17.ua

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Basically all code thanks to Reto
2+
3+
# [A,B,C,PC]
4+
729_0_0_0
5+
$ 0,1,5,4,3,0
6+
7+
⋕⊜□≠@,.
8+
↯∞_2
9+
10+
Combo ← ⊡⊙(⊂0_1_2_3)
11+
12+
👆 ← ⍜⊡(+2)3
13+
14+
Xdv ← (
15+
⊙(
16+
# evaluate the combo operand (is this needed?)
17+
# ⊸Combo
18+
# put the value of the A register 2nd from the top
19+
⊙⊸⊡₀
20+
# right-shift
21+
⍜⊙⋯↘
22+
:
23+
)
24+
# put back into X register
25+
⍜⊡◌
26+
👆
27+
)
28+
29+
Bxl ← (
30+
⊙:⊡1.:
31+
⍜∩⋯⬚0≠
32+
⍜⊡◌1:
33+
👆
34+
)
35+
36+
Bst ← (
37+
⊸Combo
38+
⍜⋯(⬚0↙3)
39+
⍜⊡◌1:
40+
👆
41+
)
42+
43+
Jnz ← (
44+
⊙:⊡0.:
45+
≠0
46+
⨬(⊡3.◌|-2)
47+
⍜⊡◌3:
48+
👆
49+
)
50+
51+
Bxc ← (
52+
53+
⊸∩⌟Combo 5 6
54+
⍜∩⋯⬚0≠
55+
⍜⊡◌1:
56+
👆
57+
)
58+
59+
Out ← (
60+
⊸Combo
61+
⍜⋯(⬚0↙3)
62+
&pf@,&pf
63+
👆
64+
)
65+
66+
# takes code + state returns state
67+
f ← (
68+
# get state + current instruction + keep full instructions
69+
◡(⊡÷2⊡3)
70+
°⊟
71+
⨬(Xdv 0|Bxl|Bst|Jnz|Bxc|Out|Xdv 1|Xdv 2)
72+
)
73+
:
74+
75+
⍢(f|>⊓(÷2⊡3)⧻)

0 commit comments

Comments
 (0)