diff --git a/.gitignore b/.gitignore index 0db84f1..6e2786d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,10 @@ # temporary files /target +/input +session.txt # secrets .env -# don't include inputs -/input -session.txt +# editors +.vscode diff --git a/Cargo.lock b/Cargo.lock index 043b8f9..1c0a7be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,7 +12,7 @@ dependencies = [ ] [[package]] -name = "aoc-2022" +name = "aoc" version = "0.1.0" dependencies = [ "eyes", diff --git a/Cargo.toml b/Cargo.toml index 4248e9f..e8549b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "aoc-2022" +name = "aoc" version = "0.1.0" edition = "2021" @@ -8,6 +8,4 @@ regex = "1" itertools = "0.10" rayon = "1" eyes = "1" - -[build-dependencies] reqwest = { version = "0.11", features = ["blocking", "cookies"] } diff --git a/build.rs b/build.rs deleted file mode 100644 index ba2cffb..0000000 --- a/build.rs +++ /dev/null @@ -1,39 +0,0 @@ -use reqwest::{blocking::Client, cookie::Jar}; -use std::{collections::HashMap, env, fs, path::PathBuf, sync::Arc}; - -const BASE_URL: &str = "https://adventofcode.com"; -const YEAR: &str = "2022"; - -fn main() { - // get env and dotenv into hashmap - let file = fs::read_to_string(".env").unwrap_or(String::new()); - let env = file - .lines() - .filter_map(|line| line.split_once('=')) - .map(|(key, value)| (key.to_string(), value.to_string())) - .chain(env::vars()) - .collect::>(); - - // create session cookie - let session = env - .get("SESSION") - .expect("SESSION environment variable not set, retrieve it from https://adventofcode.com"); - let cookie = format!("session={session}"); - - // setup client - let jar = Arc::new(Jar::default()); - jar.add_cookie_str(&cookie, &BASE_URL.parse().unwrap()); - let client = Client::builder().cookie_provider(jar).build().unwrap(); - - // download until day fails - for day in 1..=25 { - let path = PathBuf::from(format!("input/day{day}.txt")); - if path.try_exists().is_err() { - let url = format!("{BASE_URL}/{YEAR}/day/{day}/input"); - let Ok(res) = client.get(url).send() else { - break - }; - fs::write(path, res.text().unwrap()).unwrap(); - } - } -} diff --git a/src/bin/day1.rs b/src/bin/2022-1.rs similarity index 93% rename from src/bin/day1.rs rename to src/bin/2022-1.rs index c3f30df..075bd55 100644 --- a/src/bin/day1.rs +++ b/src/bin/2022-1.rs @@ -1,7 +1,7 @@ use itertools::Itertools; fn main() { - let input = std::fs::read_to_string("input/day1.txt").unwrap(); + let input = aoc::input!(); println!("part 1: {}", part1(&input)); println!("part 1: {}", part2(&input)); } diff --git a/src/bin/day2.rs b/src/bin/2022-2.rs similarity index 93% rename from src/bin/day2.rs rename to src/bin/2022-2.rs index c135b93..5778a1d 100644 --- a/src/bin/day2.rs +++ b/src/bin/2022-2.rs @@ -1,7 +1,7 @@ use eyes::parse; fn main() { - let input = std::fs::read_to_string("input/day2.txt").unwrap(); + let input = aoc::input!(); println!("part 1: {}", part1(&input)); println!("part 1: {}", part2(&input)); } diff --git a/src/bin/day3.rs b/src/bin/2022-3.rs similarity index 95% rename from src/bin/day3.rs rename to src/bin/2022-3.rs index 10dd0b5..f6294ec 100644 --- a/src/bin/day3.rs +++ b/src/bin/2022-3.rs @@ -2,7 +2,7 @@ use itertools::Itertools; use std::collections::HashSet; fn main() { - let input = std::fs::read_to_string("input/day3.txt").unwrap(); + let input = aoc::input!(); println!("part 1: {}", part1(&input)); println!("part 1: {}", part2(&input)); } diff --git a/src/bin/day4.rs b/src/bin/2022-4.rs similarity index 92% rename from src/bin/day4.rs rename to src/bin/2022-4.rs index 66d0c19..5c1e9ff 100644 --- a/src/bin/day4.rs +++ b/src/bin/2022-4.rs @@ -1,7 +1,7 @@ use eyes::parse; fn main() { - let input = std::fs::read_to_string("input/day4.txt").unwrap(); + let input = aoc::input!(); println!("part 1: {}", part1(&input)); println!("part 1: {}", part2(&input)); } diff --git a/src/bin/2022-5.rs b/src/bin/2022-5.rs new file mode 100644 index 0000000..d965105 --- /dev/null +++ b/src/bin/2022-5.rs @@ -0,0 +1,109 @@ +use eyes::parse; +use itertools::Itertools; + +fn main() { + let input = aoc::input!(); + println!("part 1: {}", part1(&input)); + println!("part 1: {}", part2(&input)); +} + +fn part1(input: &str) -> String { + // parsing challenge..... yay...... + let (crates, instructions) = input.split_once("\n\n").unwrap(); + let mut stacks = crates + .lines() + .rev() + .skip(1) + .map(|line| { + line.chars() + .chunks(4) + .into_iter() + .map(|mut cr| (cr.next() == Some('[')).then(|| cr.next().unwrap())) + .collect::>() + }) + .fold(vec![vec![]; 9], |mut stacks, crates| { + for (i, c) in crates.into_iter().enumerate() { + if let Some(c) = c { + stacks[i].push(c) + } + } + stacks + }); + + // perform instructions + instructions + .lines() + .map(|line| parse!(line, "move {} from {} to {}", usize, usize, usize)) + .for_each(|(n, from, to)| { + (0..n).for_each(|_| { + if let Some(c) = stacks[from - 1].pop() { + stacks[to - 1].push(c) + } + }) + }); + + // get message + stacks + .into_iter() + .filter_map(|mut stack| stack.pop()) + .collect::() +} + +fn part2(input: &str) -> String { + let (crates, instructions) = input.split_once("\n\n").unwrap(); + let mut stacks = crates + .lines() + .rev() + .skip(1) + .map(|line| { + line.chars() + .chunks(4) + .into_iter() + .map(|mut cr| (cr.next() == Some('[')).then(|| cr.next().unwrap())) + .collect::>() + }) + .fold(vec![vec![]; 9], |mut stacks, crates| { + for (i, c) in crates.into_iter().enumerate() { + if let Some(c) = c { + stacks[i].push(c) + } + } + stacks + }); + + // perform instructions + instructions + .lines() + .map(|line| parse!(line, "move {} from {} to {}", usize, usize, usize)) + .for_each(|(n, from, to)| { + let len = stacks[from - 1].len(); + let split = stacks[from - 1].split_off(len - n); + stacks[to - 1].extend(split); + }); + + // get message + stacks + .into_iter() + .filter_map(|mut stack| stack.pop()) + .collect::() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn examples() { + let example = r#" [D] +[N] [C] +[Z] [M] [P] + 1 2 3 + +move 1 from 2 to 1 +move 3 from 1 to 3 +move 2 from 2 to 1 +move 1 from 1 to 2"#; + assert_eq!(part1(example), "CMZ".to_string()); + assert_eq!(part2(example), "MCD".to_string()); + } +} diff --git a/src/lib.rs b/src/lib.rs index 8b13789..6399f19 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,53 @@ +use reqwest::{blocking::Client, cookie::Jar}; +use std::{env, error::Error, fs, path::PathBuf, sync::Arc}; +const BASE_URL: &str = "https://adventofcode.com"; + +#[macro_export] +macro_rules! input { + () => {{ + let stem = std::path::Path::new(file!()) + .file_stem() + .unwrap() + .to_string_lossy(); + let (year, day) = stem.split_once('-').unwrap(); + $crate::get_day(year, day).unwrap() + }}; +} + +pub fn get_day(year: &str, day: &str) -> Result> { + let path = PathBuf::from(format!("input/{year}/day{day}.txt")); + fs::create_dir_all(path.parent().unwrap())?; + + if let Ok(input) = fs::read_to_string(&path) { + Ok(input) + } else { + // get session token + let file = fs::read_to_string(".env").unwrap_or(String::new()); + let session = file + .lines() + .filter_map(|line| line.split_once('=')) + .map(|(key, value)| (key.to_string(), value.to_string())) + .chain(env::vars()) + .find_map(|(k, v)| (k == "SESSION").then_some(v)) + .ok_or( + "SESSION environment variable not set, retrieve it from https://adventofcode.com", + )?; + + // create session cookie + let cookie = format!("session={session}"); + + // setup client + let jar = Arc::new(Jar::default()); + jar.add_cookie_str(&cookie, &BASE_URL.parse()?); + let client = Client::builder().cookie_provider(jar).build()?; + + // download until day fails + let url = format!("{BASE_URL}/{year}/day/{day}/input"); + let text = client.get(url).send()?.text()?; + fs::create_dir_all(path.parent().unwrap())?; + fs::write(path, &text)?; + + Ok(text) + } +}