mirror of
https://github.com/Smaug123/advent-of-code-2021
synced 2025-10-05 20:18:41 +00:00
Day 17 in Rust (#17)
This commit is contained in:
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -220,6 +220,13 @@ dependencies = [
|
||||
"criterion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "day_17"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"criterion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "day_2"
|
||||
version = "0.1.0"
|
||||
|
@@ -15,4 +15,5 @@ members = [
|
||||
"day_13",
|
||||
"day_14",
|
||||
"day_16",
|
||||
"day_17",
|
||||
]
|
||||
|
15
day_17/Cargo.toml
Normal file
15
day_17/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "day_17"
|
||||
version = "0.1.0"
|
||||
authors = ["Smaug123 <patrick+github@patrickstevens.co.uk>"]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
[dev-dependencies]
|
||||
criterion = "0.3"
|
||||
|
||||
[[bench]]
|
||||
name = "bench"
|
||||
harness = false
|
19
day_17/benches/bench.rs
Normal file
19
day_17/benches/bench.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use day_17::day_17::{input, part_1, part_2};
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let input = input();
|
||||
c.bench_function("day 17 part 1", |b| {
|
||||
b.iter(|| {
|
||||
black_box(part_1(&input));
|
||||
})
|
||||
});
|
||||
c.bench_function("day 17 part 2", |b| {
|
||||
b.iter(|| {
|
||||
black_box(part_2(&input));
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
34
day_17/day17.m
Normal file
34
day_17/day17.m
Normal file
@@ -0,0 +1,34 @@
|
||||
(* ::Package:: *)
|
||||
|
||||
nums=With[{chars=Characters@StringTrim@ReadString[StringJoin[NotebookDirectory[], "/input.txt"]]},Flatten[IntegerDigits[FromDigits[#,16],2,4]&/@chars]];
|
||||
|
||||
|
||||
consumePacket[{}]={};
|
||||
consumePacket[{v1_,v2_,v3_,t1_,t2_,t3_,otherBits___}]:=With[{packetVersion=Sow@FromDigits[{v1,v2,v3},2],typeId=FromDigits[{t1,t2,t3},2], rest={otherBits}},
|
||||
Switch[typeId,
|
||||
4,With[{thisPacket=TakeWhile[Partition[rest,UpTo@5],#[[1]]==1&]},
|
||||
{FromDigits[Flatten[Join[Rest/@thisPacket,rest[[5 Length@thisPacket+2;;5 Length@thisPacket+5]]]],2],rest[[5 Length@thisPacket+6;;]]}
|
||||
],
|
||||
|
||||
_,
|
||||
With[{operator=
|
||||
Switch[typeId,0,Plus,1,Times,2,Min,3,Max,5,Boole@*Greater,6,Boole@*Less,7,Boole@*Equal]
|
||||
},
|
||||
Switch[rest[[1]],
|
||||
0,With[{subPacketLen=FromDigits[rest[[2;;16]],2]},
|
||||
{operator@@consumePackets[rest[[17;;16+subPacketLen]]],rest[[17+subPacketLen;;]]}
|
||||
],
|
||||
1,With[{subPackets=FromDigits[rest[[2;;12]],2]},
|
||||
With[{result=Nest[With[{c=consumePacket[#[[2]]]},{Join[#[[1]],{c[[1]]}],c[[2]]}]&,{{},rest[[13;;]]},subPackets]},
|
||||
{operator@@result[[1]],result[[2]]}
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
]
|
||||
]]
|
||||
|
||||
consumePackets[l_]:=First@NestWhile[With[{c=consumePacket[#[[2]]]},{Join[#[[1]],{c[[1]]}],c[[2]]}]&,{{},l},Not@AllTrue[#[[2]],#==0&]&]
|
||||
|
||||
|
||||
MapAt[Total@*Flatten,Reap@consumePackets[nums],{2}]
|
1
day_17/input.txt
Normal file
1
day_17/input.txt
Normal file
@@ -0,0 +1 @@
|
||||
target area: x=269..292, y=-68..-44
|
219
day_17/src/lib.rs
Normal file
219
day_17/src/lib.rs
Normal file
@@ -0,0 +1,219 @@
|
||||
pub mod day_17 {
|
||||
|
||||
use std::cmp::{max, Ordering};
|
||||
|
||||
pub struct Data<T> {
|
||||
min_x: T,
|
||||
min_y: T,
|
||||
max_x: T,
|
||||
max_y: T,
|
||||
}
|
||||
|
||||
fn chomp_str<I>(chars: &mut I, expected: &str)
|
||||
where
|
||||
I: Iterator<Item = char>,
|
||||
{
|
||||
for e in expected.chars() {
|
||||
let actual = chars.next();
|
||||
match actual {
|
||||
None => {
|
||||
panic!("Expected a char, got none");
|
||||
}
|
||||
Some(actual) => {
|
||||
if actual != e {
|
||||
panic!("Expected {}, got {}", e, actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn chomp_int<I>(chars: &mut I) -> i32
|
||||
where
|
||||
I: Iterator<Item = char>,
|
||||
{
|
||||
let fst = chars.next().unwrap();
|
||||
let (sign, start) = if fst == '-' {
|
||||
let fst = chars.next().unwrap();
|
||||
(-1, fst.to_digit(10).unwrap())
|
||||
} else {
|
||||
(1, fst.to_digit(10).unwrap())
|
||||
};
|
||||
let mut ans = start as i32;
|
||||
|
||||
for c in chars {
|
||||
if ('0'..='9').contains(&c) {
|
||||
ans = ans * 10 + (c as u8 - b'0') as i32;
|
||||
} else {
|
||||
return ans * sign;
|
||||
}
|
||||
}
|
||||
|
||||
ans * sign
|
||||
}
|
||||
|
||||
pub(crate) fn parse(s: &str) -> Data<i32> {
|
||||
let mut chars = s.chars();
|
||||
chomp_str(&mut chars, "target area: x=");
|
||||
let min_x = chomp_int(&mut chars);
|
||||
chomp_str(&mut chars, ".");
|
||||
let max_x = chomp_int(&mut chars);
|
||||
chomp_str(&mut chars, " y=");
|
||||
let min_y = chomp_int(&mut chars);
|
||||
chomp_str(&mut chars, ".");
|
||||
let max_y = chomp_int(&mut chars);
|
||||
|
||||
Data {
|
||||
min_x,
|
||||
max_x,
|
||||
min_y,
|
||||
max_y,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn input() -> Data<i32> {
|
||||
parse(include_str!("../input.txt"))
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Vector<T> {
|
||||
x: T,
|
||||
y: T,
|
||||
}
|
||||
|
||||
struct State<T> {
|
||||
position: Vector<T>,
|
||||
velocity: Vector<T>,
|
||||
}
|
||||
|
||||
fn step(state: &State<i32>) -> State<i32> {
|
||||
State {
|
||||
position: Vector {
|
||||
x: state.position.x + state.velocity.x,
|
||||
y: state.position.y + state.velocity.y,
|
||||
},
|
||||
velocity: Vector {
|
||||
x: match 0.cmp(&state.velocity.x) {
|
||||
Ordering::Less => state.velocity.x - 1,
|
||||
Ordering::Greater => state.velocity.x + 1,
|
||||
Ordering::Equal => 0,
|
||||
},
|
||||
y: state.velocity.y - 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn is_in_area<T>(data: &Data<T>, position: &Vector<T>) -> bool
|
||||
where
|
||||
T: Ord,
|
||||
{
|
||||
data.min_x <= position.x
|
||||
&& position.x <= data.max_x
|
||||
&& data.min_y <= position.y
|
||||
&& position.y <= data.max_y
|
||||
}
|
||||
|
||||
fn hits_target(data: &Data<i32>, velocity: &Vector<i32>) -> Option<i32> {
|
||||
// Continue until the x-velocity becomes zero.
|
||||
let mut state = State {
|
||||
position: Vector { x: 0, y: 0 },
|
||||
velocity: velocity.clone(),
|
||||
};
|
||||
|
||||
let mut max_y_coord = 0;
|
||||
let mut hit_area = false;
|
||||
|
||||
while state.velocity.x != 0 || state.position.y >= data.min_y {
|
||||
state = step(&state);
|
||||
if !hit_area && is_in_area(data, &state.position) {
|
||||
hit_area = true;
|
||||
}
|
||||
max_y_coord = max(max_y_coord, state.position.y);
|
||||
if (state.velocity.x > 0 && state.position.x > data.max_x)
|
||||
|| (state.velocity.x < 0 && state.position.x < data.min_x)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if hit_area {
|
||||
Some(max_y_coord)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn sqrt(n: i32) -> i32 {
|
||||
(n as f32).sqrt() as i32
|
||||
}
|
||||
|
||||
pub fn part_1(data: &Data<i32>) -> u32 {
|
||||
let mut max_y = 0;
|
||||
// Which x-direction do we need to fire in?
|
||||
if data.min_x > 0 {
|
||||
// If we start with x-velocity n, then we reach at most coordinate n(n+1)/2
|
||||
// so we only need to start from sqrt(2 v)
|
||||
for x in sqrt(2 * data.min_x)..=data.max_x {
|
||||
for y in data.min_y..=data.max_x {
|
||||
match hits_target(data, &Vector { x, y }) {
|
||||
None => {}
|
||||
Some(max_y_new) => {
|
||||
max_y = max(max_y_new, max_y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("Expected positive x - please implement if this happens");
|
||||
}
|
||||
|
||||
max_y as u32
|
||||
}
|
||||
|
||||
pub fn part_2(data: &Data<i32>) -> u64 {
|
||||
let mut count = 0;
|
||||
// Which x-direction do we need to fire in?
|
||||
if data.min_x > 0 {
|
||||
for x in sqrt(2 * data.min_x)..=data.max_x {
|
||||
for y in data.min_y..=data.max_x {
|
||||
match hits_target(data, &Vector { x, y }) {
|
||||
None => {}
|
||||
Some(_) => {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("Expected positive x - please implement if this happens");
|
||||
}
|
||||
|
||||
count
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::day_17::*;
|
||||
|
||||
static TEST_INPUT: &str = "target area: x=20..30, y=-10..-5";
|
||||
|
||||
#[test]
|
||||
fn part1_known() {
|
||||
let data = parse(&TEST_INPUT);
|
||||
assert_eq!(part_1(&data), 45);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_known() {
|
||||
let data = parse(&TEST_INPUT);
|
||||
assert_eq!(part_2(&data), 112);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_day_17() {
|
||||
let input = input();
|
||||
assert_eq!(part_1(&input), 2278);
|
||||
assert_eq!(part_2(&input), 996);
|
||||
}
|
||||
}
|
7
day_17/src/main.rs
Normal file
7
day_17/src/main.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
use day_17::day_17::{input, part_1, part_2};
|
||||
|
||||
fn main() {
|
||||
let input = input();
|
||||
println!("part 1 => {}", part_1(&input));
|
||||
println!("part 2 => {}", part_2(&input));
|
||||
}
|
Reference in New Issue
Block a user