add day 12+13+14
This commit is contained in:
parent
ff54ef107a
commit
4de7ad785e
43
Cargo.lock
generated
43
Cargo.lock
generated
@ -1,5 +1,10 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.40"
|
||||
@ -48,6 +53,24 @@ dependencies = [
|
||||
"shared 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "day12"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "day13"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"shared 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "day14"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "day2"
|
||||
version = "0.1.0"
|
||||
@ -131,6 +154,23 @@ name = "libc"
|
||||
version = "0.2.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.6"
|
||||
@ -186,6 +226,7 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[metadata]
|
||||
"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
|
||||
"checksum backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea"
|
||||
"checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491"
|
||||
"checksum cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76"
|
||||
@ -195,6 +236,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
"checksum failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08"
|
||||
"checksum itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484"
|
||||
"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
|
||||
"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09"
|
||||
"checksum num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4"
|
||||
"checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27"
|
||||
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
|
||||
"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
|
||||
|
@ -11,5 +11,8 @@ members = [
|
||||
"day9",
|
||||
"day10",
|
||||
"day11",
|
||||
"day12",
|
||||
"day13",
|
||||
"day14",
|
||||
"shared",
|
||||
]
|
||||
|
10
day12/Cargo.toml
Normal file
10
day12/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "day12"
|
||||
version = "0.1.0"
|
||||
authors = ["Stefan Bühler <stbuehler@web.de>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
num-integer = "0.1.41"
|
4
day12/src/input.txt
Normal file
4
day12/src/input.txt
Normal file
@ -0,0 +1,4 @@
|
||||
<x=-2, y=9, z=-5>
|
||||
<x=16, y=19, z=9>
|
||||
<x=0, y=3, z=6>
|
||||
<x=11, y=0, z=11>
|
166
day12/src/main.rs
Normal file
166
day12/src/main.rs
Normal file
@ -0,0 +1,166 @@
|
||||
fn strip_prefix<'a>(prefix: &str, input: &'a str) -> &'a str {
|
||||
assert!(input.starts_with(prefix));
|
||||
&input[prefix.len()..]
|
||||
}
|
||||
|
||||
fn strip_suffix<'a>(suffix: &str, input: &'a str) -> &'a str {
|
||||
assert!(input.ends_with(suffix));
|
||||
&input[..input.len() - suffix.len()]
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
|
||||
pub struct Position {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub z: i32,
|
||||
}
|
||||
|
||||
impl Position {
|
||||
pub fn parse(input: &str) -> Self {
|
||||
let input = strip_suffix(">", strip_prefix("<", input.trim()));
|
||||
let mut parts = input.split(",");
|
||||
let x = strip_prefix("x=", parts.next().expect("need x component").trim()).parse().unwrap();
|
||||
let y = strip_prefix("y=", parts.next().expect("need y component").trim()).parse().unwrap();
|
||||
let z = strip_prefix("z=", parts.next().expect("need z component").trim()).parse().unwrap();
|
||||
assert!(parts.next().is_none(), "unexpected data");
|
||||
Position { x, y, z }
|
||||
}
|
||||
|
||||
pub fn potential_energy(self) -> u32 {
|
||||
self.x.abs() as u32 + self.y.abs() as u32 + self.z.abs() as u32
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
|
||||
pub struct Velocity {
|
||||
pub dx: i32,
|
||||
pub dy: i32,
|
||||
pub dz: i32,
|
||||
}
|
||||
|
||||
impl Velocity {
|
||||
pub fn kinetic_energy(self) -> u32 {
|
||||
self.dx.abs() as u32 + self.dy.abs() as u32 + self.dz.abs() as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::AddAssign<Velocity> for Position {
|
||||
fn add_assign(&mut self, rhs: Velocity) {
|
||||
self.x += rhs.dx;
|
||||
self.y += rhs.dy;
|
||||
self.z += rhs.dz;
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_moons(input: &str) -> Vec<(Position, Velocity)> {
|
||||
input.lines().map(Position::parse).map(|p| (p, Velocity::default())).collect()
|
||||
}
|
||||
|
||||
fn step(moons: &mut [(Position, Velocity)]) {
|
||||
for i in 0..moons.len() - 1 {
|
||||
// change velocity based on "gravity"
|
||||
for j in i + 1..moons.len() {
|
||||
let dx = (moons[j].0.x - moons[i].0.x).signum();
|
||||
moons[i].1.dx += dx; moons[j].1.dx -= dx;
|
||||
let dy = (moons[j].0.y - moons[i].0.y).signum();
|
||||
moons[i].1.dy += dy; moons[j].1.dy -= dy;
|
||||
let dz = (moons[j].0.z - moons[i].0.z).signum();
|
||||
moons[i].1.dz += dz; moons[j].1.dz -= dz;
|
||||
}
|
||||
}
|
||||
for moon in moons {
|
||||
moon.0 += moon.1;
|
||||
}
|
||||
}
|
||||
|
||||
fn energy(moons: &[(Position, Velocity)]) -> u32 {
|
||||
moons.iter().map(|(p, v)| p.potential_energy() * v.kinetic_energy()).sum()
|
||||
}
|
||||
|
||||
fn energy_after_steps(mut moons: Vec<(Position, Velocity)>, steps: u32) -> u32 {
|
||||
for _ in 0..steps {
|
||||
step(&mut moons);
|
||||
}
|
||||
energy(&moons)
|
||||
}
|
||||
|
||||
type ExtractedAxis = Vec<(i32, i32)>;
|
||||
fn extract_x(moons: &[(Position, Velocity)]) -> ExtractedAxis {
|
||||
moons.iter().map(|(p, v)| (p.x, v.dx)).collect()
|
||||
}
|
||||
fn extract_y(moons: &[(Position, Velocity)]) -> ExtractedAxis {
|
||||
moons.iter().map(|(p, v)| (p.y, v.dy)).collect()
|
||||
}
|
||||
fn extract_z(moons: &[(Position, Velocity)]) -> ExtractedAxis {
|
||||
moons.iter().map(|(p, v)| (p.z, v.dz)).collect()
|
||||
}
|
||||
|
||||
fn repeats_after_steps(mut moons: Vec<(Position, Velocity)>) -> u64 {
|
||||
// as `step` is reversible the first repeated state will always be the initial state
|
||||
|
||||
// find cycle per axis
|
||||
let mut result_x = None;
|
||||
let initial_x = extract_x(&moons);
|
||||
let mut result_y = None;
|
||||
let initial_y = extract_y(&moons);
|
||||
let mut result_z = None;
|
||||
let initial_z = extract_z(&moons);
|
||||
|
||||
for current in 1u64.. {
|
||||
step(&mut moons);
|
||||
if result_x.is_none() && initial_x == extract_x(&moons) {
|
||||
result_x = Some(current);
|
||||
}
|
||||
if result_y.is_none() && initial_y == extract_y(&moons) {
|
||||
result_y = Some(current);
|
||||
}
|
||||
if result_z.is_none() && initial_z == extract_z(&moons) {
|
||||
result_z = Some(current);
|
||||
}
|
||||
if let (Some(x), Some(y), Some(z)) = (result_x, result_y, result_z) {
|
||||
return num_integer::lcm(num_integer::lcm(x, y), z);
|
||||
}
|
||||
}
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let moons = parse_moons(include_str!("input.txt"));
|
||||
let energy = energy_after_steps(moons.clone(), 1000);
|
||||
println!("Energy after 1000 steps: {}", energy);
|
||||
println!("Repeats initial state after {} steps", repeats_after_steps(moons));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod day12test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn examples1() {
|
||||
assert_eq!(179, energy_after_steps(parse_moons(
|
||||
"<x=-1, y=0, z=2>
|
||||
<x=2, y=-10, z=-7>
|
||||
<x=4, y=-8, z=8>
|
||||
<x=3, y=5, z=-1>"), 10));
|
||||
assert_eq!(1940, energy_after_steps(parse_moons(
|
||||
"<x=-8, y=-10, z=0>
|
||||
<x=5, y=5, z=10>
|
||||
<x=2, y=-7, z=3>
|
||||
<x=9, y=-8, z=-3>
|
||||
"), 100));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn examples2() {
|
||||
assert_eq!(2772, repeats_after_steps(parse_moons(
|
||||
"<x=-1, y=0, z=2>
|
||||
<x=2, y=-10, z=-7>
|
||||
<x=4, y=-8, z=8>
|
||||
<x=3, y=5, z=-1>")));
|
||||
assert_eq!(4686774924, repeats_after_steps(parse_moons(
|
||||
"<x=-8, y=-10, z=0>
|
||||
<x=5, y=5, z=10>
|
||||
<x=2, y=-7, z=3>
|
||||
<x=9, y=-8, z=-3>")));
|
||||
}
|
||||
}
|
10
day13/Cargo.toml
Normal file
10
day13/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "day13"
|
||||
version = "0.1.0"
|
||||
authors = ["Stefan Bühler <stbuehler@web.de>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
shared = { path = "../shared" }
|
1
day13/src/input.txt
Normal file
1
day13/src/input.txt
Normal file
File diff suppressed because one or more lines are too long
147
day13/src/main.rs
Normal file
147
day13/src/main.rs
Normal file
@ -0,0 +1,147 @@
|
||||
use shared::intcode::{IntCode, SimulateStep, CELL};
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub enum Tile {
|
||||
Empty,
|
||||
Wall,
|
||||
Block,
|
||||
HorizontalPaddle,
|
||||
Ball,
|
||||
}
|
||||
|
||||
impl Tile {
|
||||
pub fn new(t: CELL) -> Self {
|
||||
match t {
|
||||
0 => Self::Empty,
|
||||
1 => Self::Wall,
|
||||
2 => Self::Block,
|
||||
3 => Self::HorizontalPaddle,
|
||||
4 => Self::Ball,
|
||||
_ => panic!("Unknown tile {}", t),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub struct Position {
|
||||
pub x: CELL,
|
||||
pub y: CELL,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub enum Update {
|
||||
Tile {
|
||||
position: Position,
|
||||
tile: Tile,
|
||||
},
|
||||
Score {
|
||||
score: CELL,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
#[repr(i8)]
|
||||
pub enum Joystick {
|
||||
Left = -1,
|
||||
Neutral = 0,
|
||||
Right = 1,
|
||||
}
|
||||
|
||||
fn view_game(mut ic: IntCode) -> impl Iterator<Item=(Position, Tile)> {
|
||||
std::iter::from_fn(move || {
|
||||
let x = match ic.simulate_step() {
|
||||
SimulateStep::Finished => return None,
|
||||
SimulateStep::WaitForInput => panic!("no input for game"),
|
||||
SimulateStep::Output(n) => n,
|
||||
};
|
||||
let y = ic.simulate_step().expect_output();
|
||||
let p = Position { x, y };
|
||||
let tile = Tile::new(ic.simulate_step().expect_output());
|
||||
Some((p, tile))
|
||||
})
|
||||
}
|
||||
|
||||
fn find_blocks<I>(tiles: I) -> HashSet<Position>
|
||||
where
|
||||
I: Iterator<Item=(Position, Tile)>,
|
||||
{
|
||||
let mut blocks = HashSet::new();
|
||||
for (pos, tile) in tiles {
|
||||
match tile {
|
||||
Tile::Block => { blocks.insert(pos); }
|
||||
_ => { blocks.remove(&pos); }
|
||||
}
|
||||
}
|
||||
blocks
|
||||
}
|
||||
|
||||
fn get_update(ic: &mut IntCode, joystick: Joystick) -> Option<Update> {
|
||||
let joystick = joystick as i8 as CELL;
|
||||
ic.next_input = Some(joystick);
|
||||
let x = match ic.simulate_step() {
|
||||
SimulateStep::Finished => {
|
||||
ic.next_input = None;
|
||||
return None;
|
||||
},
|
||||
SimulateStep::WaitForInput => panic!("no input for game"),
|
||||
SimulateStep::Output(n) => n,
|
||||
};
|
||||
let y = ic.simulate_step().expect_output();
|
||||
if x == -1 && y == 0 {
|
||||
Some(Update::Score { score: ic.simulate_step().expect_output() })
|
||||
} else {
|
||||
let position = Position { x, y };
|
||||
let tile = Tile::new(ic.simulate_step().expect_output());
|
||||
Some(Update::Tile { position, tile })
|
||||
}
|
||||
}
|
||||
|
||||
fn break_all_blocks(mut ic: IntCode) -> CELL {
|
||||
ic.data[0] = 2; // "play for free"
|
||||
let mut last_score = None;
|
||||
let mut last_paddle = None;
|
||||
let mut last_ball = None;
|
||||
let mut blocks = HashSet::new();
|
||||
let mut had_blocks = false;
|
||||
let mut joystick = Joystick::Neutral;
|
||||
while let Some(update) = get_update(&mut ic, joystick) {
|
||||
match update {
|
||||
Update::Score { score } => last_score = Some(score),
|
||||
Update::Tile { position, tile } => {
|
||||
blocks.remove(&position);
|
||||
match tile {
|
||||
Tile::Empty => (),
|
||||
Tile::Wall => (),
|
||||
Tile::Block => { blocks.insert(position); had_blocks = true; },
|
||||
Tile::HorizontalPaddle => last_paddle = Some(position),
|
||||
Tile::Ball => last_ball = Some(position),
|
||||
}
|
||||
}
|
||||
}
|
||||
if let (Some(paddle), Some(ball)) = (&last_paddle, &last_ball) {
|
||||
let diff_x = ball.x - paddle.x;
|
||||
joystick = if diff_x > 0 {
|
||||
Joystick::Right
|
||||
} else if diff_x < 0 {
|
||||
Joystick::Left
|
||||
} else {
|
||||
Joystick::Neutral
|
||||
};
|
||||
}
|
||||
}
|
||||
if had_blocks && blocks.is_empty() {
|
||||
println!("Won game, all blocks destroyed");
|
||||
} else {
|
||||
println!("Lost game");
|
||||
}
|
||||
last_score.expect("no score written")
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let ic = include_str!("input.txt").parse::<IntCode>().unwrap();
|
||||
let blocks = find_blocks(view_game(ic.clone()));
|
||||
println!("Number of blocks: {}", blocks.len());
|
||||
let score = break_all_blocks(ic);
|
||||
println!("Score: {}", score);
|
||||
}
|
9
day14/Cargo.toml
Normal file
9
day14/Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "day14"
|
||||
version = "0.1.0"
|
||||
authors = ["Stefan Bühler <stbuehler@web.de>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
63
day14/src/input.txt
Normal file
63
day14/src/input.txt
Normal file
@ -0,0 +1,63 @@
|
||||
12 VJWQR, 1 QTBC => 6 BGXJV
|
||||
12 BGTMN, 2 DRKQR => 2 TVSF
|
||||
2 FTFK => 2 THNDN
|
||||
13 LKRTN, 7 MDPN, 12 NZKQZ => 5 LPWPD
|
||||
1 HDKX, 3 DWZS, 1 RCBQS => 1 DCRK
|
||||
14 ZCMF => 6 ZKHLC
|
||||
3 ZFVH, 2 ZCMF, 1 SCJG, 1 LQWJ, 4 BGBJ, 1 NHPR, 3 VKZFJ, 7 FWFXZ => 4 QVJMP
|
||||
11 TNMLB => 7 NVDCR
|
||||
1 LPWPD, 1 BGBJ => 2 SCJG
|
||||
3 DFCVF, 1 QGSN => 6 PQXG
|
||||
1 BGXJV, 1 THNDN => 4 BCQN
|
||||
3 LKRTN => 9 MDPN
|
||||
2 THNDN, 13 RCKZ, 10 FQSLN => 8 VKZFJ
|
||||
4 LBCZ, 9 LWHS => 1 FQSLN
|
||||
6 WSRVZ => 9 TNMLB
|
||||
8 FQSLN, 14 JQRF, 4 BGTMN => 5 QGSN
|
||||
4 ZCMF, 4 PLSM, 2 ZHTX => 8 TDHPM
|
||||
2 RSKC, 10 SHBC, 8 MDPN => 6 FMSZ
|
||||
2 VJWQR => 2 FPTV
|
||||
12 DRKQR => 6 NHPR
|
||||
35 QJLF, 22 BGTMN, 11 VJWTR, 1 QVJMP, 8 LQWJ, 1 TWLC, 16 NXZCH, 18 THKF, 42 JBLM => 1 FUEL
|
||||
2 BGTMN, 4 XJKN => 8 ZCMF
|
||||
4 TVSF => 3 RSKC
|
||||
7 HRWS, 1 TVSF => 3 ZHTX
|
||||
134 ORE => 4 WSRVZ
|
||||
1 VKZFJ, 1 TWLC, 4 ZHTX, 5 THNDN, 12 PLVN, 1 ZFXNP, 1 PQXG, 6 CWHV => 7 VJWTR
|
||||
20 XJKN, 1 LCKW, 3 NZKQZ => 7 HDKX
|
||||
1 LPWPD => 8 RCKZ
|
||||
4 RCBQS, 1 NVDCR => 5 BGBJ
|
||||
8 BGXJV => 4 BGTMN
|
||||
13 QBDX, 16 BGXJV => 6 NZKQZ
|
||||
2 LPWPD => 3 DRKQR
|
||||
4 QBDX => 7 XJKN
|
||||
12 LCKW, 9 NVDCR => 3 RCBQS
|
||||
142 ORE => 3 QBDX
|
||||
1 WXHJF => 1 XKDJ
|
||||
2 RSKC => 2 CWHV
|
||||
2 ZHTX, 1 ZFXNP => 6 JQRF
|
||||
1 FTFK, 1 TVSF, 1 QBDX => 2 JBLM
|
||||
1 TDHPM, 14 NHPR, 3 QPSF => 5 ZFVH
|
||||
3 GDTPC, 1 ZKHLC => 8 ZFXNP
|
||||
5 DWZS => 3 LQWJ
|
||||
1 FTFK, 4 LBCZ, 13 NHPR => 1 FWFXZ
|
||||
1 RCBQS, 12 SHBC => 9 FTFK
|
||||
1 WSRVZ, 1 XKDJ => 5 LKRTN
|
||||
2 BGTMN, 1 MDPN => 5 PLSM
|
||||
2 BGXJV, 17 XKDJ, 4 FPTV => 9 LCKW
|
||||
148 ORE => 2 QTBC
|
||||
110 ORE => 2 VJWQR
|
||||
42 ZFXNP, 15 RCKZ, 8 GDTPC => 3 QJLF
|
||||
13 HRWS => 4 GDTPC
|
||||
34 HRWS => 4 DFCVF
|
||||
2 VKZFJ, 2 NHPR, 16 PLVN, 1 QPSF, 13 LBCZ, 4 DCRK, 10 LWHS => 7 NXZCH
|
||||
3 CWHV, 1 THNDN => 7 LWHS
|
||||
1 BGXJV, 2 QBDX => 5 DWZS
|
||||
9 LQWJ => 8 QPSF
|
||||
21 BCQN, 3 FMSZ, 1 RSKC => 5 THKF
|
||||
118 ORE => 6 WXHJF
|
||||
11 FMSZ => 9 TWLC
|
||||
28 PLSM => 5 SHBC
|
||||
1 ZKHLC, 23 SCJG => 7 LBCZ
|
||||
17 DWZS, 16 THNDN => 9 PLVN
|
||||
7 HDKX => 9 HRWS
|
195
day14/src/main.rs
Normal file
195
day14/src/main.rs
Normal file
@ -0,0 +1,195 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub struct ChemicalAmount<'a> {
|
||||
pub amount: u64,
|
||||
pub name: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> ChemicalAmount<'a> {
|
||||
pub fn parse(input: &'a str) -> Self {
|
||||
let input = input.trim();
|
||||
let mut elems = input.split_whitespace();
|
||||
let amount = elems.next().expect("need amount").trim().parse::<u64>().expect("number for amount");
|
||||
let name = elems.next().expect("need amount").trim();
|
||||
assert!(elems.next().is_none(), "unexpected data");
|
||||
ChemicalAmount {
|
||||
amount,
|
||||
name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Inputs<'a> = Vec<ChemicalAmount<'a>>;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub struct Reaction<'a> {
|
||||
pub inputs: Inputs<'a>,
|
||||
pub output: ChemicalAmount<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Reaction<'a> {
|
||||
pub fn parse(input: &'a str) -> Self {
|
||||
let mut parts = input.split("=>");
|
||||
let inputs = parts.next().expect("need inputs").trim().split(",").map(ChemicalAmount::parse).collect();
|
||||
let output = ChemicalAmount::parse(parts.next().expect("need output").trim());
|
||||
assert!(parts.next().is_none(), "unexpected data");
|
||||
Reaction {
|
||||
inputs,
|
||||
output,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn runs_for(&self, required_output: u64) -> u64 {
|
||||
(required_output + self.output.amount - 1) / self.output.amount
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Reactions<'a>(pub HashMap<&'a str, Reaction<'a>>);
|
||||
|
||||
impl<'a> Reactions<'a> {
|
||||
pub fn parse(input: &'a str) -> Self {
|
||||
Self(input.lines().map(Reaction::parse).map(|reaction| (reaction.output.name.into(), reaction)).collect())
|
||||
}
|
||||
|
||||
fn gather_ore(&self, needs: &mut HashMap<&'a str, u64>, name: &'a str, additional: u64) -> u64 {
|
||||
if name == "ORE" { return additional; }
|
||||
let previous_need;
|
||||
let updated_need;
|
||||
{
|
||||
let need = needs.entry(&name).or_default();
|
||||
previous_need = *need;
|
||||
updated_need = previous_need + additional;
|
||||
*need = updated_need;
|
||||
}
|
||||
let reaction = &self.0[name];
|
||||
let previous_runs = reaction.runs_for(previous_need);
|
||||
let updated_runs = reaction.runs_for(updated_need);
|
||||
let mut ore = 0;
|
||||
if updated_runs > previous_runs {
|
||||
let new_runs = updated_runs - previous_runs;
|
||||
for input in &reaction.inputs {
|
||||
ore += self.gather_ore(needs, input.name, input.amount * new_runs);
|
||||
}
|
||||
}
|
||||
ore
|
||||
}
|
||||
|
||||
fn ore_for_fuel(&self, fuel: u64) -> u64 {
|
||||
let mut needs = HashMap::new();
|
||||
self.gather_ore(&mut needs, "FUEL", fuel)
|
||||
}
|
||||
}
|
||||
|
||||
// check must be monotone, there must be a `n` such that for all `i`: `i <= n <=> check(i)`
|
||||
// returns `None` if this `n` is less than 0 (i.e. check is never true)
|
||||
fn binary_search_max<F>(check: F, start: u64, max_step: u64) -> Option<u64>
|
||||
where
|
||||
F: Fn(u64) -> bool,
|
||||
{
|
||||
let mut upper;
|
||||
let mut lower;
|
||||
|
||||
// find boundaries
|
||||
if !check(start) {
|
||||
// find lower limit
|
||||
if start == 0 { return None; }
|
||||
upper = start - 1;
|
||||
loop {
|
||||
lower = start / 2;
|
||||
if check(lower) { break; }
|
||||
if lower == 0 { return None; }
|
||||
upper = lower - 1;
|
||||
}
|
||||
} else {
|
||||
lower = start;
|
||||
// find upper limit
|
||||
upper = start;
|
||||
loop {
|
||||
upper += max_step;
|
||||
if !check(upper) {
|
||||
upper -= 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// invariant: check(lower) && !check(upper + 1)
|
||||
while lower != upper {
|
||||
debug_assert!(lower < upper);
|
||||
let mid = (lower + upper + 1) / 2;
|
||||
debug_assert!(mid > lower);
|
||||
if check(mid) {
|
||||
lower = mid;
|
||||
} else {
|
||||
upper = mid - 1;
|
||||
}
|
||||
}
|
||||
Some(lower)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let reactions = Reactions::parse(include_str!("input.txt"));
|
||||
let ore_one_fuel;
|
||||
{
|
||||
ore_one_fuel = reactions.ore_for_fuel(1);
|
||||
println!("Need {} ORE for one FUEL", ore_one_fuel);
|
||||
}
|
||||
|
||||
let have_ore = 1000000000000u64;
|
||||
let max_fuel = binary_search_max(|fuel| {
|
||||
reactions.ore_for_fuel(fuel) <= have_ore
|
||||
}, have_ore / ore_one_fuel, have_ore / ore_one_fuel).unwrap();
|
||||
println!("Can get {} fuel for {} ore", max_fuel, have_ore);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod day14test {
|
||||
use super::*;
|
||||
|
||||
fn calculate_needed_ore(input: &str) -> u64 {
|
||||
let reactions = Reactions::parse(input);
|
||||
reactions.ore_for_fuel(1)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn examples1() {
|
||||
assert_eq!(165, calculate_needed_ore(
|
||||
"9 ORE => 2 A
|
||||
8 ORE => 3 B
|
||||
7 ORE => 5 C
|
||||
3 A, 4 B => 1 AB
|
||||
5 B, 7 C => 1 BC
|
||||
4 C, 1 A => 1 CA
|
||||
2 AB, 3 BC, 4 CA => 1 FUEL"));
|
||||
assert_eq!(13312, calculate_needed_ore(
|
||||
"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"));
|
||||
assert_eq!(2210736, calculate_needed_ore(
|
||||
"171 ORE => 8 CNZTR
|
||||
7 ZLQW, 3 BMBT, 9 XCVML, 26 XMNCP, 1 WPTQ, 2 MZWV, 1 RJRHP => 4 PLWSL
|
||||
114 ORE => 4 BHXH
|
||||
14 VRPVC => 6 BMBT
|
||||
6 BHXH, 18 KTJDG, 12 WPTQ, 7 PLWSL, 31 FHTLT, 37 ZDVW => 1 FUEL
|
||||
6 WPTQ, 2 BMBT, 8 ZLQW, 18 KTJDG, 1 XMNCP, 6 MZWV, 1 RJRHP => 6 FHTLT
|
||||
15 XDBXC, 2 LTCX, 1 VRPVC => 6 ZLQW
|
||||
13 WPTQ, 10 LTCX, 3 RJRHP, 14 XMNCP, 2 MZWV, 1 ZLQW => 1 ZDVW
|
||||
5 BMBT => 4 WPTQ
|
||||
189 ORE => 9 KTJDG
|
||||
1 MZWV, 17 XDBXC, 3 XCVML => 2 XMNCP
|
||||
12 VRPVC, 27 CNZTR => 2 XDBXC
|
||||
15 KTJDG, 12 BHXH => 5 XCVML
|
||||
3 BHXH, 2 VRPVC => 7 MZWV
|
||||
121 ORE => 7 VRPVC
|
||||
7 XCVML => 6 RJRHP
|
||||
5 BHXH, 4 VRPVC => 5 LTCX"));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user