diff --git a/day07/Cargo.lock b/day07/Cargo.lock new file mode 100644 index 0000000..f28bfa9 --- /dev/null +++ b/day07/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "day07" +version = "0.1.0" +dependencies = [ + "rustc-hash", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" diff --git a/day07/Cargo.toml b/day07/Cargo.toml new file mode 100644 index 0000000..3a39fa2 --- /dev/null +++ b/day07/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "day07" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rustc-hash = "1.1.0" diff --git a/day07/src/main.rs b/day07/src/main.rs new file mode 100644 index 0000000..808468e --- /dev/null +++ b/day07/src/main.rs @@ -0,0 +1,134 @@ +use rustc_hash::FxHashMap; +use std::path::Path; + +const TOTAL_SPACE: usize = 70_000_000; +const REQUIRED_SPACE: usize = 30_000_000; + +fn main() { + let s = std::fs::read_to_string("/dev/stdin").unwrap(); + let mut commands = s.split("$ "); + commands.next(); // chuck out first empty string + let mut pwd: Option = None; + let mut file_tree: FxHashMap)>> = FxHashMap::default(); + let mut dir_totals: FxHashMap = FxHashMap::default(); + + for command in commands { + let lines = command.lines().collect::>(); + let cmd = lines.first().unwrap(); + let args = cmd.split(' ').collect::>(); + let cmd = args.first().unwrap(); + let args = args.get(1..).unwrap(); + + match *cmd { + "cd" => cd(&mut pwd, args), + "ls" => ls( + &pwd, + lines.get(1..).unwrap(), + &mut file_tree, + &mut dir_totals, + ), + invalid => panic!("unexpected command: {:?}", invalid), + } + } + + for directory in file_tree.iter() { + let mut total = 0; + let dir_str = Path::new(&directory.0); + + if dir_str.file_name().is_some() + && dir_totals.contains_key(&dir_str.to_str().unwrap().to_string()) + { + continue; + } + + for item in directory.1.iter() { + if let Some(file_size) = item.1 { + total += file_size; + } + } + dir_totals.insert(directory.0.to_string(), total); + } + + let mut dirs = file_tree.iter().collect::>(); + dirs.sort_by(|a, b| { + b.0.split('/') + .count() + .cmp(&a.0.split('/').count()) + .then(b.0.len().cmp(&a.0.len())) + }); + for dir in dirs.iter() { + for sub_dir in dir.1.iter().filter(|dir| dir.1.is_none()) { + let balls = (dir.0.to_owned() + &sub_dir.0.replace("dir ", "/")).replace("//", "/"); + + dir_totals.insert( + dir.0.to_owned(), + *dir_totals.get(&dir.0.to_owned()).unwrap() + *dir_totals.get(&balls).unwrap(), + ); + } + } + + let sum = dir_totals + .iter() + .filter(|dir| dir.1 < &100000) + .fold(0, |total, dir| total + dir.1); + + let free_space = TOTAL_SPACE - dir_totals.get("/").unwrap(); + let minimum_required = REQUIRED_SPACE - free_space; + + let mut deletable: Vec<(&String, &usize)> = dir_totals + .iter() + .filter(|dir| dir.1 >= &minimum_required) + .collect(); + deletable.sort_by(|a, b| a.1.cmp(b.1)); + println!("P1: {} P2: {}", sum, deletable.first().unwrap().1); +} + +fn cd(pwd: &mut Option, args: &[&str]) { + let path_str = String::new(); + let path = pwd.as_ref().unwrap_or(&path_str); + let dir = args.first().expect("cd requries an arg").to_string(); + if dir == ".." { + let mut split_path = path.split('/').collect::>(); + split_path.pop(); + if split_path.len() == 1 { + split_path.push(""); + } + *pwd = Some(split_path.join("/")); + } else if !path.is_empty() && path != "/" { + *pwd = Some(path.to_owned() + "/" + &dir); + } else { + *pwd = Some(path.to_owned() + &dir); + } +} + +fn ls( + pwd: &Option, + lines: &[&str], + file_tree: &mut FxHashMap)>>, + dir_totals: &mut FxHashMap, +) { + let mut contents: Vec<(String, Option)> = Vec::new(); + let mut contains_dirs = false; // if no inner dirs, work out total here + + for line in lines { + if line.starts_with("dir ") { + contents.push((line.to_string(), None)); + contains_dirs = true; + continue; + } + let file_data = line.split_once(' ').unwrap(); + let (file_size, file_name) = ( + file_data.0.parse::().unwrap(), + file_data.1.to_string(), + ); + contents.push((file_name, Some(file_size))); + } + if !contains_dirs { + let mut total = 0; + for (_, size) in contents.iter() { + total += size.unwrap(); + } + dir_totals.insert(pwd.as_ref().unwrap().to_string(), total); + } + file_tree.insert(pwd.as_ref().unwrap().to_string(), contents); +} diff --git a/day09/Cargo.lock b/day09/Cargo.lock new file mode 100644 index 0000000..7713d84 --- /dev/null +++ b/day09/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "day09" +version = "0.1.0" +dependencies = [ + "rustc-hash", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" diff --git a/day09/Cargo.toml b/day09/Cargo.toml new file mode 100644 index 0000000..dafe3a3 --- /dev/null +++ b/day09/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "day09" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rustc-hash = "1.1.0" diff --git a/day09/src/main.rs b/day09/src/main.rs new file mode 100644 index 0000000..cfb14fd --- /dev/null +++ b/day09/src/main.rs @@ -0,0 +1,73 @@ +use rustc_hash::FxHashSet; +use std::cmp::Ordering; +use std::io::{self, BufRead}; + +fn main() { + let lines = io::stdin().lock().lines().map(|l| l.unwrap()); + + let mut head = (0, 0); + let mut knots: Vec<(isize, isize)> = vec![(0, 0); 9]; + let mut visited_p1: FxHashSet<(isize, isize)> = FxHashSet::default(); + let mut visited_p2: FxHashSet<(isize, isize)> = FxHashSet::default(); + + visited_p1.insert((0, 0)); + visited_p2.insert((0, 0)); + + for line in lines { + let (dir, len) = line.split_once(' ').unwrap(); + let position = (dir, len.parse::().unwrap()); + let mut current; + + for _ in 0..position.1 { + step_head(&mut head, position.0); + current = head; + + for i in 0..9 { + let knot = knots.get_mut(i).unwrap(); + follow(i, current, knot, &mut visited_p1, &mut visited_p2); + current = *knot; + } + } + } + println!("P1: {}, P2: {}", visited_p1.len(), visited_p2.len()); +} + +fn step_head(head: &mut (isize, isize), dir: &str) { + match dir { + "L" => head.1 -= 1, + "R" => head.1 += 1, + "U" => head.0 -= 1, + "D" => head.0 += 1, + invalid => panic!("invalid direction '{}'", invalid), + } +} + +fn follow( + idx: usize, + head: (isize, isize), + tail: &mut (isize, isize), + visited_p1: &mut FxHashSet<(isize, isize)>, + visited_p2: &mut FxHashSet<(isize, isize)>, +) { + let diffx = (tail.0 - head.0).abs(); + let diffy = (tail.1 - head.1).abs(); + if (diffx == 1 || diffx == 0) && (diffy == 1 || diffy == 0) { + return; + } + match tail.0.cmp(&head.0) { + Ordering::Greater => tail.0 -= 1, + Ordering::Less => tail.0 += 1, + _ => (), + } + match tail.1.cmp(&head.1) { + Ordering::Greater => tail.1 -= 1, + Ordering::Less => tail.1 += 1, + _ => (), + } + if idx == 0 { + visited_p1.insert(*tail); + } + if idx == 8 { + visited_p2.insert(*tail); + } +} diff --git a/day10/Cargo.lock b/day10/Cargo.lock new file mode 100644 index 0000000..5f8ea61 --- /dev/null +++ b/day10/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "day10" +version = "0.1.0" diff --git a/day10/Cargo.toml b/day10/Cargo.toml new file mode 100644 index 0000000..40d2066 --- /dev/null +++ b/day10/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "day10" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/day10/src/main.rs b/day10/src/main.rs new file mode 100644 index 0000000..7627b97 --- /dev/null +++ b/day10/src/main.rs @@ -0,0 +1,83 @@ +use std::collections::VecDeque; +use std::io::{self, BufRead, Write}; + +#[derive(Debug)] +struct Operation { + opcode: String, + arg: Option, + cycles: usize, +} + +const SIGNAL_TICK_COUNT: usize = 220; +const DRAW_CRT: usize = 240; + +fn main() { + let lines = io::stdin().lock().lines().map(|l| l.unwrap()); + + let mut program: Vec = vec![]; + let mut queue: VecDeque<&Operation> = VecDeque::new(); + + for line in lines { + let cmd = line.split(' ').collect::>(); + let op = cmd.first().unwrap(); + let mut arg: Option = None; + + if let Some(num) = cmd.get(1) { + arg = Some(num.parse::().unwrap()); + } + let cycles = match op { + &"noop" => 1, + &"addx" => 2, + invalid => panic!("invalid opcode {}", invalid), + }; + program.push(Operation { + opcode: op.to_string(), + arg, + cycles, + }); + } + + // init counters, program and queue + let mut x = 1; + let mut current_op = program.first().unwrap(); + let mut cycles_remaining_for_current = current_op.cycles; + let mut signal_sum = 0; + let mut screen = [[false; 40]; 6]; + + for (screen_idx, i) in (1..DRAW_CRT + 1).enumerate() { + if cycles_remaining_for_current == 0 { + if current_op.opcode == "addx" { + x += current_op.arg.unwrap(); + } + current_op = queue.pop_front().unwrap(); + cycles_remaining_for_current = current_op.cycles; + } + let cmd = program.get(i % program.len()).unwrap(); + queue.push_back(cmd); + cycles_remaining_for_current -= 1; + match i { + 20 | 60 | 100 | 140 | 180 | 220 => signal_sum += x * i as isize, + _ => (), + } + + let (scrx, scry): (usize, usize) = (screen_idx / 40, screen_idx % 40); + screen[scrx][scry] = x == screen_idx as isize % 40 + || x - 1 == screen_idx as isize % 40 + || x + 1 == screen_idx as isize % 40; + + if i == SIGNAL_TICK_COUNT { + println!("{}", signal_sum); + } + } + draw_screen(&screen); +} + +fn draw_screen(screen: &[[bool; 40]; 6]) { + let mut lock = io::stdout().lock(); + for y in 0..6 { + for x in 0..40 { + write!(lock, "{}", if screen[y][x] { "#" } else { "." }).unwrap() + } + writeln!(lock).unwrap(); + } +}