Day 17 in Rust (#17)

This commit is contained in:
Patrick Stevens
2021-12-17 17:35:22 +00:00
committed by GitHub
parent ae25038131
commit f8eb377f69
8 changed files with 303 additions and 0 deletions

7
Cargo.lock generated
View File

@@ -220,6 +220,13 @@ dependencies = [
"criterion", "criterion",
] ]
[[package]]
name = "day_17"
version = "0.1.0"
dependencies = [
"criterion",
]
[[package]] [[package]]
name = "day_2" name = "day_2"
version = "0.1.0" version = "0.1.0"

View File

@@ -15,4 +15,5 @@ members = [
"day_13", "day_13",
"day_14", "day_14",
"day_16", "day_16",
"day_17",
] ]

15
day_17/Cargo.toml Normal file
View 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
View 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
View 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
View File

@@ -0,0 +1 @@
target area: x=269..292, y=-68..-44

219
day_17/src/lib.rs Normal file
View 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
View 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));
}