From d19192b95b1ee5aa8e28925aa4e2cd4d868c6092 Mon Sep 17 00:00:00 2001 From: Bauke Date: Mon, 3 Oct 2022 18:02:40 +0200 Subject: [PATCH] Import 2020 solutions. --- source/main.rs | 1 + source/utilities.rs | 4 +- source/year_2020/day_01/mod.rs | 39 +++++++ source/year_2020/day_02/mod.rs | 60 ++++++++++ source/year_2020/day_03/mod.rs | 43 +++++++ source/year_2020/day_04/mod.rs | 164 +++++++++++++++++++++++++++ source/year_2020/day_05/mod.rs | 75 ++++++++++++ source/year_2020/day_06/mod.rs | 46 ++++++++ source/year_2020/day_07/mod.rs | 79 +++++++++++++ source/year_2020/day_08/mod.rs | 105 +++++++++++++++++ source/year_2020/day_09/mod.rs | 77 +++++++++++++ source/year_2020/day_10/mod.rs | 51 +++++++++ source/year_2020/day_11/grid.rs | 162 ++++++++++++++++++++++++++ source/year_2020/day_11/mod.rs | 37 ++++++ source/year_2020/day_12/extra.rs | 42 +++++++ source/year_2020/day_12/mod.rs | 93 +++++++++++++++ source/year_2020/day_13/mod.rs | 74 ++++++++++++ source/year_2020/day_14/mod.rs | 123 ++++++++++++++++++++ source/year_2020/day_14/operation.rs | 21 ++++ source/year_2020/day_15/mod.rs | 42 +++++++ source/year_2020/day_16/mod.rs | 142 +++++++++++++++++++++++ source/year_2020/mod.rs | 39 +++++++ 22 files changed, 1517 insertions(+), 2 deletions(-) create mode 100644 source/year_2020/day_01/mod.rs create mode 100644 source/year_2020/day_02/mod.rs create mode 100644 source/year_2020/day_03/mod.rs create mode 100644 source/year_2020/day_04/mod.rs create mode 100644 source/year_2020/day_05/mod.rs create mode 100644 source/year_2020/day_06/mod.rs create mode 100644 source/year_2020/day_07/mod.rs create mode 100644 source/year_2020/day_08/mod.rs create mode 100644 source/year_2020/day_09/mod.rs create mode 100644 source/year_2020/day_10/mod.rs create mode 100644 source/year_2020/day_11/grid.rs create mode 100644 source/year_2020/day_11/mod.rs create mode 100644 source/year_2020/day_12/extra.rs create mode 100644 source/year_2020/day_12/mod.rs create mode 100644 source/year_2020/day_13/mod.rs create mode 100644 source/year_2020/day_14/mod.rs create mode 100644 source/year_2020/day_14/operation.rs create mode 100644 source/year_2020/day_15/mod.rs create mode 100644 source/year_2020/day_16/mod.rs create mode 100644 source/year_2020/mod.rs diff --git a/source/main.rs b/source/main.rs index 20d559c..f671a5f 100644 --- a/source/main.rs +++ b/source/main.rs @@ -14,6 +14,7 @@ pub mod prelude; pub mod solution; pub mod templates; pub mod utilities; +pub mod year_2020; pub mod year_2021; #[derive(Debug, Parser)] diff --git a/source/utilities.rs b/source/utilities.rs index 954f411..f0d780e 100644 --- a/source/utilities.rs +++ b/source/utilities.rs @@ -5,10 +5,10 @@ use std::{ use {color_eyre::Result, dialoguer::Password, rand::seq::IteratorRandom}; -use crate::{solution::Solution, year_2021}; +use crate::{solution::Solution, year_2020, year_2021}; pub fn get_solutions() -> Vec> { - vec![year_2021::get_solutions()] + vec![year_2020::get_solutions(), year_2021::get_solutions()] } pub fn random_emoji() -> String { diff --git a/source/year_2020/day_01/mod.rs b/source/year_2020/day_01/mod.rs new file mode 100644 index 0000000..6830670 --- /dev/null +++ b/source/year_2020/day_01/mod.rs @@ -0,0 +1,39 @@ +use crate::prelude::*; + +pub fn solution() -> Solution { + Solution::new(Day::new(1, 2020), part_1, part_2) + .with_expected(605364, 128397680) +} + +const TARGET: i32 = 2020; + +fn parse_lines(input: &str) -> Vec { + input + .lines() + .filter_map(|line| line.parse::().ok()) + .collect() +} + +fn part_1(input: &str) -> Result { + Ok( + parse_lines(input) + .into_iter() + .tuple_combinations() + .find(|(a, b)| a + b == TARGET) + .map(|(a, b)| a * b) + .unwrap() + .to_string(), + ) +} + +fn part_2(input: &str) -> Result { + Ok( + parse_lines(input) + .into_iter() + .tuple_combinations() + .find(|(a, b, c)| a + b + c == TARGET) + .map(|(a, b, c)| a * b * c) + .unwrap() + .to_string(), + ) +} diff --git a/source/year_2020/day_02/mod.rs b/source/year_2020/day_02/mod.rs new file mode 100644 index 0000000..74092ae --- /dev/null +++ b/source/year_2020/day_02/mod.rs @@ -0,0 +1,60 @@ +use crate::prelude::*; + +pub fn solution() -> Solution { + Solution::new(Day::new(2, 2020), part_1, part_2).with_expected(638, 699) +} + +#[derive(Debug)] +struct Item { + min: usize, + max: usize, + letter: String, + password: String, +} + +fn parse_items(input: &str) -> Vec { + let mut items = vec![]; + + for line in input.lines() { + // TODO: Replace with regex. + let min = &line[0..line.find('-').unwrap()]; + let max = &line[line.find('-').unwrap() + 1..line.find(' ').unwrap()]; + let letter = &line[line.find(' ').unwrap() + 1..line.find(':').unwrap()]; + let password = &line[line.find(": ").unwrap() + 2..]; + items.push(Item { + min: min.parse().unwrap(), + max: max.parse().unwrap(), + letter: letter.to_string(), + password: password.to_string(), + }) + } + + items +} + +fn part_1(input: &str) -> Result { + Ok( + parse_items(input) + .into_iter() + .filter(|item| item.password.matches(&item.letter).count() >= item.min) + .filter(|item| item.password.matches(&item.letter).count() <= item.max) + .count() + .to_string(), + ) +} + +fn part_2(input: &str) -> Result { + Ok( + parse_items(input) + .into_iter() + .filter(|item| { + let letter = item.letter.chars().next(); + let target_one = item.password.chars().nth(item.min - 1); + let target_two = item.password.chars().nth(item.max - 1); + target_one != target_two + && (target_one == letter || target_two == letter) + }) + .count() + .to_string(), + ) +} diff --git a/source/year_2020/day_03/mod.rs b/source/year_2020/day_03/mod.rs new file mode 100644 index 0000000..8b2f4b8 --- /dev/null +++ b/source/year_2020/day_03/mod.rs @@ -0,0 +1,43 @@ +use crate::prelude::*; + +pub fn solution() -> Solution { + Solution::new(Day::new(3, 2020), part_1, part_2) + .with_expected(198, 5140884672_i64) +} + +fn solve(data: &str, (horizontal, vertical): (usize, usize)) -> usize { + let line_length = data.find('\n').unwrap(); + let mut result = 0; + + let mut x_position = 0; + for (y_position, line) in data.lines().enumerate() { + if y_position % vertical != 0 { + continue; + } + + if line.chars().nth(x_position) == Some('#') { + result += 1; + } + + x_position += horizontal; + if x_position >= line_length { + x_position -= line_length; + } + } + + result +} + +fn part_1(input: &str) -> Result { + Ok(solve(input, (3, 1)).to_string()) +} + +fn part_2(input: &str) -> Result { + Ok( + [(1, 1), (3, 1), (5, 1), (7, 1), (1, 2)] + .into_iter() + .map(|increment| solve(input, increment)) + .product::() + .to_string(), + ) +} diff --git a/source/year_2020/day_04/mod.rs b/source/year_2020/day_04/mod.rs new file mode 100644 index 0000000..94a706d --- /dev/null +++ b/source/year_2020/day_04/mod.rs @@ -0,0 +1,164 @@ +use crate::prelude::*; + +pub fn solution() -> Solution { + Solution::new(Day::new(4, 2020), part_1, part_2).with_expected(237, 172) +} + +#[derive(Debug, Clone)] +struct Passport { + byr: Option, + iyr: Option, + eyr: Option, + hgt: Option, + hcl: Option, + ecl: Option, + pid: Option, + cid: Option, +} + +fn parse_passports(input: &str) -> Vec { + let mut passports = vec![]; + let blank = Passport { + byr: None, + iyr: None, + eyr: None, + hgt: None, + hcl: None, + ecl: None, + pid: None, + cid: None, + }; + let mut intermediate = blank.clone(); + + for line in input.lines() { + if line.is_empty() { + passports.push(intermediate.clone()); + intermediate = blank.clone(); + continue; + } + + let components = line.split(' '); + for component in components { + let colon_index = component.find(':').unwrap(); + let key = &component[0..colon_index]; + let value = &component[colon_index + 1..]; + match key { + "byr" => intermediate.byr = Some(value.to_string()), + "iyr" => intermediate.iyr = Some(value.to_string()), + "eyr" => intermediate.eyr = Some(value.to_string()), + "hgt" => intermediate.hgt = Some(value.to_string()), + "hcl" => intermediate.hcl = Some(value.to_string()), + "ecl" => intermediate.ecl = Some(value.to_string()), + "pid" => intermediate.pid = Some(value.to_string()), + "cid" => intermediate.cid = Some(value.to_string()), + _ => unreachable!("{key}"), + }; + } + } + + passports +} + +fn part_1(input: &str) -> Result { + Ok( + parse_passports(input) + .into_iter() + .filter(|passport| { + passport.byr.is_some() + && passport.iyr.is_some() + && passport.eyr.is_some() + && passport.hgt.is_some() + && passport.hcl.is_some() + && passport.ecl.is_some() + && passport.pid.is_some() + }) + .count() + .to_string(), + ) +} + +fn part_2(input: &str) -> Result { + let hcl_regex = Regex::new("^#[a-fA-F0-9]{6}$").unwrap(); + let pid_regex = Regex::new("^[0-9]{9}$").unwrap(); + + let ecl_values = vec![ + "amb".to_string(), + "blu".to_string(), + "brn".to_string(), + "gry".to_string(), + "grn".to_string(), + "hzl".to_string(), + "oth".to_string(), + ]; + + Ok( + parse_passports(input) + .into_iter() + .filter(|passport| { + passport.byr.is_some() + && passport.iyr.is_some() + && passport.eyr.is_some() + && passport.hgt.is_some() + && passport.hcl.is_some() + && passport.ecl.is_some() + && passport.pid.is_some() + }) + .filter(|passport| { + let byr = passport + .byr + .as_ref() + .unwrap() + .parse::() + .unwrap_or_default(); + (1920..=2002).contains(&byr) + }) + .filter(|passport| { + let iyr = passport + .iyr + .as_ref() + .unwrap() + .parse::() + .unwrap_or_default(); + (2010..=2020).contains(&iyr) + }) + .filter(|passport| { + let eyr = passport + .eyr + .as_ref() + .unwrap() + .parse::() + .unwrap_or_default(); + (2020..=2030).contains(&eyr) + }) + .filter(|passport| { + let hgt = passport.hgt.as_ref().unwrap(); + match &hgt[hgt.len() - 2..] { + "cm" => { + let value = + hgt[0..hgt.len() - 2].parse::().unwrap_or_default(); + (150..=193).contains(&value) + } + "in" => { + let value = + hgt[0..hgt.len() - 2].parse::().unwrap_or_default(); + (59..=76).contains(&value) + } + _ => false, + } + }) + .filter(|passport| { + let hcl = passport.hcl.as_ref().unwrap(); + hcl_regex.is_match(hcl) + }) + .filter(|passport| { + let ecl = passport.ecl.as_ref().unwrap(); + ecl_values.contains(ecl) + }) + .filter(|passport| { + let pid = passport.pid.as_ref().unwrap(); + pid_regex.is_match(pid) + }) + .count() + .to_string(), + ) +} diff --git a/source/year_2020/day_05/mod.rs b/source/year_2020/day_05/mod.rs new file mode 100644 index 0000000..deb3dfc --- /dev/null +++ b/source/year_2020/day_05/mod.rs @@ -0,0 +1,75 @@ +use crate::prelude::*; + +pub fn solution() -> Solution { + Solution::new(Day::new(5, 2020), part_1, part_2).with_expected(850, 599) +} + +const MAX_ROW_RANGE: i32 = 128; +const MAX_COLUMN_RANGE: i32 = 8; + +fn calculate_row_and_column(input: &str) -> (i32, i32) { + let mut row_range = 0..MAX_ROW_RANGE; + let mut column_range = 0..MAX_COLUMN_RANGE; + + for character in input.chars() { + let row_min = row_range.start; + let row_max = row_range.end; + let row_difference = (row_max - row_min) / 2; + + let column_min = column_range.start; + let column_max = column_range.end; + let column_difference = (column_max - column_min) / 2; + + match character { + 'F' => { + row_range = row_min..(row_max - row_difference); + } + 'B' => { + row_range = (row_min + row_difference)..row_max; + } + 'L' => { + column_range = column_min..(column_max - column_difference); + } + 'R' => { + column_range = (column_min + column_difference)..column_max; + } + _ => unreachable!("{character}"), + } + } + + (row_range.start, column_range.start) +} + +fn calculate_seat_id((row, column): (i32, i32)) -> i32 { + (row * 8) + column +} + +fn part_1(input: &str) -> Result { + Ok( + input + .lines() + .map(calculate_row_and_column) + .map(calculate_seat_id) + .max() + .unwrap() + .to_string(), + ) +} + +fn part_2(input: &str) -> Result { + let seat_ids = input + .lines() + .map(calculate_row_and_column) + .map(calculate_seat_id) + .collect::>(); + + let max_seat_id = seat_ids.iter().max().unwrap(); + let min_seat_id = seat_ids.iter().min().unwrap(); + + Ok( + (*min_seat_id..*max_seat_id) + .find(|index| !seat_ids.contains(index)) + .map(|id| id.to_string()) + .unwrap(), + ) +} diff --git a/source/year_2020/day_06/mod.rs b/source/year_2020/day_06/mod.rs new file mode 100644 index 0000000..9c672f1 --- /dev/null +++ b/source/year_2020/day_06/mod.rs @@ -0,0 +1,46 @@ +use crate::prelude::*; + +pub fn solution() -> Solution { + Solution::new(Day::new(6, 2020), part_1, part_2).with_expected(6437, 3229) +} + +fn part_1(input: &str) -> Result { + Ok( + input + .split("\n\n") + .map(|group| { + group + .replace('\n', "") + .chars() + .collect::>() + .len() + }) + .sum::() + .to_string(), + ) +} + +fn part_2(input: &str) -> Result { + Ok( + input + .split("\n\n") + .map(|group| { + let mut count = 0; + let possibilities = + group.replace('\n', "").chars().collect::>(); + for character in possibilities { + if group + .trim() + .split('\n') + .all(|person| person.contains(character)) + { + count += 1; + } + } + + count + }) + .sum::() + .to_string(), + ) +} diff --git a/source/year_2020/day_07/mod.rs b/source/year_2020/day_07/mod.rs new file mode 100644 index 0000000..0751960 --- /dev/null +++ b/source/year_2020/day_07/mod.rs @@ -0,0 +1,79 @@ +use crate::prelude::*; + +pub fn solution() -> Solution { + Solution::new(Day::new(7, 2020), part_1, part_2).with_expected(169, 82372) +} + +const TARGET: &str = "shiny gold"; + +fn parse_bags(input: &str) -> Vec<(&str, Vec<(usize, &str)>)> { + let mut bags = vec![]; + + for line in input.lines() { + let main_bag = &line[0..line.find(" bags").unwrap()]; + let other_bags = line[line.find("contain ").unwrap() + "contain ".len()..] + .split(',') + .map(str::trim) + .map(|bag| { + let count = bag[0..bag.find(' ').unwrap()] + .parse::() + .unwrap_or_default(); + let bag = &bag[bag.find(' ').unwrap() + 1..bag.find(" bag").unwrap()]; + (count, bag) + }) + .collect::>(); + + bags.push((main_bag, other_bags)); + } + + bags +} + +fn can_hold_target_bag<'a>( + target: &'a str, + bags: &[(&'a str, Vec<(usize, &'a str)>)], +) -> Vec<&'a str> { + let mut can_hold = vec![]; + for (bag, children) in bags { + if children.iter().any(|(_, child)| *child == target) { + can_hold.push(*bag); + can_hold.append(&mut can_hold_target_bag(bag, bags)); + } + } + + can_hold +} + +fn target_bag_holds_amount<'a>( + target: &'a str, + bags: &[(&'a str, Vec<(usize, &'a str)>)], +) -> usize { + let mut count = 0; + + for (bag, children) in bags { + if bag == &target { + for (child_count, child_bag) in children { + if child_count != &0 { + count += child_count; + count += child_count * target_bag_holds_amount(child_bag, bags); + } + } + } + } + + count +} + +fn part_1(input: &str) -> Result { + Ok( + can_hold_target_bag(TARGET, &parse_bags(input)) + .into_iter() + .collect::>() + .len() + .to_string(), + ) +} + +fn part_2(input: &str) -> Result { + Ok(target_bag_holds_amount("shiny gold", &parse_bags(input)).to_string()) +} diff --git a/source/year_2020/day_08/mod.rs b/source/year_2020/day_08/mod.rs new file mode 100644 index 0000000..3d5b5bd --- /dev/null +++ b/source/year_2020/day_08/mod.rs @@ -0,0 +1,105 @@ +use crate::prelude::*; + +pub fn solution() -> Solution { + Solution::new(Day::new(8, 2020), part_1, part_2).with_expected(1814, 1056) +} + +#[derive(Debug, Eq, PartialEq, Hash, Clone)] +enum Operation { + Acc, + Jmp, + Nop, +} + +impl From<&str> for Operation { + fn from(input: &str) -> Self { + match input { + "acc" => Self::Acc, + "jmp" => Self::Jmp, + "nop" => Self::Nop, + _ => unreachable!("{input}"), + } + } +} + +type Argument = i32; + +#[derive(Debug, Eq, PartialEq, Hash, Clone)] +struct Instruction { + line: usize, + op: Operation, + arg: Argument, +} + +impl From<(usize, &str)> for Instruction { + fn from((line, input): (usize, &str)) -> Self { + let mut split = input.split(' '); + + let op = split.next().map(Into::into).unwrap(); + let arg = split.next().map(|arg| arg.parse().unwrap()).unwrap(); + + Self { line, op, arg } + } +} + +fn execute(instructions: &[Instruction]) -> (i32, bool) { + let mut seen_instructions = HashSet::new(); + + let mut accumulator = 0; + let mut encountered_infinite_loop = false; + let mut instruction_position = 0; + + while let Some(instruction) = instructions.get(instruction_position as usize) + { + if !seen_instructions.insert(instruction) { + encountered_infinite_loop = true; + break; + } + + let mut increment_position = 1; + match instruction.op { + Operation::Acc => accumulator += instruction.arg, + Operation::Jmp => increment_position = instruction.arg, + Operation::Nop => (), + }; + + instruction_position += increment_position; + } + + (accumulator, encountered_infinite_loop) +} + +fn parse_instructions(input: &str) -> Vec { + input.lines().enumerate().map(Into::into).collect() +} + +fn part_1(input: &str) -> Result { + Ok(execute(&parse_instructions(input)).0.to_string()) +} + +fn part_2(input: &str) -> Result { + let instructions = parse_instructions(input); + let mut part_2_result = None; + + for (index, instruction) in instructions.iter().enumerate() { + let mut new_instructions = instructions.clone(); + + if instruction.op == Operation::Jmp || instruction.op == Operation::Nop { + let mut new_instruction = instruction.clone(); + new_instruction.op = match instruction.op { + Operation::Jmp => Operation::Nop, + Operation::Nop => Operation::Jmp, + Operation::Acc => Operation::Acc, + }; + new_instructions[index] = new_instruction; + } + + let (result, encountered_infinite_loop) = execute(&new_instructions); + if !encountered_infinite_loop { + part_2_result = Some(result.to_string()); + break; + } + } + + Ok(part_2_result.unwrap()) +} diff --git a/source/year_2020/day_09/mod.rs b/source/year_2020/day_09/mod.rs new file mode 100644 index 0000000..af6d398 --- /dev/null +++ b/source/year_2020/day_09/mod.rs @@ -0,0 +1,77 @@ +use crate::prelude::*; + +pub fn solution() -> Solution { + Solution::new(Day::new(9, 2020), part_1, part_2) + .with_expected(22477624, 2980044) +} + +const PREAMBLE_LENGTH: usize = 25; + +fn parse_numbers(input: &str) -> Vec { + input + .lines() + .map(|number| number.parse().unwrap()) + .collect() +} + +fn part_1(input: &str) -> Result { + let numbers = parse_numbers(input); + let mut result_one = 0; + for (index, number) in numbers.iter().enumerate() { + if index < PREAMBLE_LENGTH { + continue; + } + + let preamble = { + let mut set = HashSet::new(); + let _numbers = numbers + .iter() + .skip(index - PREAMBLE_LENGTH) + .take(PREAMBLE_LENGTH); + + for a in _numbers.clone() { + for b in _numbers.clone() { + set.insert(a + b); + } + } + + set + }; + + if !preamble.contains(number) { + result_one = *number; + break; + } + } + + Ok(result_one.to_string()) +} + +fn part_2(input: &str) -> Result { + let numbers = parse_numbers(input); + let result_one = part_1(input)?; + let mut result_two = 0; + 'outer: for preamble_length in 2..numbers.len() { + for (index, _) in numbers.iter().enumerate() { + if index < preamble_length { + continue; + } + + let preamble = numbers + .iter() + .skip(index - preamble_length) + .take(preamble_length); + + let preamble_total = preamble.clone().sum::(); + + if preamble_total.to_string() == result_one { + let mut preamble = preamble.collect::>(); + preamble.sort_unstable(); + result_two = *preamble.first().unwrap() + *preamble.last().unwrap(); + break 'outer; + } + } + } + + Ok(result_two.to_string()) +} diff --git a/source/year_2020/day_10/mod.rs b/source/year_2020/day_10/mod.rs new file mode 100644 index 0000000..52a0b7d --- /dev/null +++ b/source/year_2020/day_10/mod.rs @@ -0,0 +1,51 @@ +use crate::prelude::*; + +pub fn solution() -> Solution { + Solution::new(Day::new(10, 2020), part_1, part_2) + .with_expected(2368, 1727094849536_i64) +} + +fn parse_numbers(input: &str) -> (HashMap, HashMap) { + let mut numbers = input + .lines() + .map(|line| line.parse().unwrap()) + .collect::>(); + + numbers.push(0); + numbers.sort_unstable(); + numbers.push(numbers.last().unwrap() + 3); + + let mut intervals = HashMap::new(); + let mut streaks = HashMap::new(); + let mut streak_length = 0; + for (index, a) in numbers.iter().enumerate() { + if let Some(b) = numbers.get(index + 1) { + let interval = b - a; + if interval == 1 { + streak_length += 1; + } else { + *streaks.entry(streak_length).or_insert(0) += 1; + streak_length = 0; + } + + *intervals.entry(interval).or_insert(0) += 1; + } + } + + (intervals, streaks) +} + +fn part_1(input: &str) -> Result { + let (intervals, _) = parse_numbers(input); + Ok((intervals.get(&1).unwrap() * intervals.get(&3).unwrap()).to_string()) +} + +fn part_2(input: &str) -> Result { + let (_, streaks) = parse_numbers(input); + Ok( + (7_usize.pow(*streaks.get(&4).unwrap()) + * 4_usize.pow(*streaks.get(&3).unwrap()) + * 2_usize.pow(*streaks.get(&2).unwrap())) + .to_string(), + ) +} diff --git a/source/year_2020/day_11/grid.rs b/source/year_2020/day_11/grid.rs new file mode 100644 index 0000000..bb5dc90 --- /dev/null +++ b/source/year_2020/day_11/grid.rs @@ -0,0 +1,162 @@ +#[derive(Debug, Eq, PartialEq, Clone)] +pub enum Cell { + Edge, + Floor, + Empty, + Occupied, +} + +#[derive(Debug, Eq, PartialEq)] +pub enum Ruleset { + PartOne, + PartTwo, +} + +impl From for Cell { + fn from(input: char) -> Self { + match input { + '@' => Self::Edge, + '.' => Self::Floor, + 'L' => Self::Empty, + '#' => Self::Occupied, + _ => unreachable!("{input}"), + } + } +} + +#[derive(Debug)] +pub struct Grid { + pub line_length: isize, + pub occupied_cell_count: usize, + pub occupied_cell_tolerance: usize, + pub cells: Vec, + pub ruleset: Ruleset, +} + +impl Grid { + pub fn new(input: &str, ruleset: Ruleset) -> Self { + let line_length = input.find('\n').unwrap() + 2; + + // Deal with edge cases by just adding an extra cell to the outer edges. + let extra_floor = "@".repeat(line_length - 2); + let data = format!("{}\n{}\n{}", extra_floor, input, extra_floor); + + let cells = data + .trim() + .replace('\n', "@@") + .chars() + .map(Into::into) + .collect(); + + let occupied_cell_tolerance = match ruleset { + Ruleset::PartOne => 4, + Ruleset::PartTwo => 5, + }; + + Self { + line_length: line_length as isize, + occupied_cell_count: 0, + occupied_cell_tolerance, + cells, + ruleset, + } + } + + pub fn simulate_step(&self) -> Vec { + let mut cells = self.cells.clone(); + + for (index, cell) in self.cells.iter().enumerate() { + let index = index as isize; + let mut occupied_cells = 0; + let adjacencies = vec![ + // Top Left + -self.line_length - 1, + // Above + -self.line_length, + // Top Right + -self.line_length + 1, + // Left + -1, + // Right + 1, + // Bottom Left + self.line_length - 1, + // Below + self.line_length, + // Bottom Right + self.line_length + 1, + ]; + + if self.ruleset == Ruleset::PartOne { + for mut adjacency in adjacencies { + adjacency += index; + if adjacency < 0 { + continue; + } + + if self.cells.get(adjacency as usize) == Some(&Cell::Occupied) { + occupied_cells += 1; + } + } + } else if self.ruleset == Ruleset::PartTwo { + for adjacency in adjacencies { + let mut target_index = index; + 'inner: loop { + target_index += adjacency; + if target_index < 0 || target_index as usize > self.cells.len() { + break 'inner; + } + + match self.cells.get(target_index as usize) { + Some(&Cell::Empty) | Some(&Cell::Edge) => break 'inner, + Some(&Cell::Occupied) => { + occupied_cells += 1; + break 'inner; + } + _ => (), + }; + } + } + } + + if cell == &Cell::Edge || cell == &Cell::Floor { + continue; + } + + if cell == &Cell::Empty && occupied_cells == 0 { + cells[index as usize] = Cell::Occupied; + } + + if cell == &Cell::Occupied + && occupied_cells >= self.occupied_cell_tolerance + { + cells[index as usize] = Cell::Empty; + } + } + + cells + } + + pub fn count_cells(&self, target: Cell) -> usize { + self.cells.iter().filter(|cell| cell == &&target).count() + } + + // Useful for debugging. + pub fn _draw(&self) -> String { + let mut result = String::new(); + for (index, cell) in self.cells.iter().enumerate() { + if index % self.line_length as usize == 0 { + result += "\n"; + } + + result += match cell { + Cell::Edge => "@", + Cell::Floor => ".", + Cell::Empty => "L", + Cell::Occupied => "#", + }; + } + + result + } +} diff --git a/source/year_2020/day_11/mod.rs b/source/year_2020/day_11/mod.rs new file mode 100644 index 0000000..f47f1f5 --- /dev/null +++ b/source/year_2020/day_11/mod.rs @@ -0,0 +1,37 @@ +use crate::prelude::*; + +mod grid; + +use grid::*; + +pub fn solution() -> Solution { + Solution::new(Day::new(11, 2020), part_1, part_2).with_expected(2243, 2027) +} + +fn calculate(input: &str, ruleset: Ruleset) -> usize { + let mut grid = Grid::new(input, ruleset); + + let edge_cell_count = grid.count_cells(Cell::Edge); + let floor_cell_count = grid.count_cells(Cell::Floor); + + let mut previous_occupied_cell_count = None; + while previous_occupied_cell_count != Some(grid.occupied_cell_count) { + previous_occupied_cell_count = Some(grid.occupied_cell_count); + grid.cells = grid.simulate_step(); + grid.occupied_cell_count = grid.count_cells(Cell::Occupied); + + // Sanity check, make sure no edge or floor cells are ever changed. + assert_eq!(edge_cell_count, grid.count_cells(Cell::Edge)); + assert_eq!(floor_cell_count, grid.count_cells(Cell::Floor)); + } + + previous_occupied_cell_count.unwrap() +} + +fn part_1(input: &str) -> Result { + Ok(calculate(input, Ruleset::PartOne).to_string()) +} + +fn part_2(input: &str) -> Result { + Ok(calculate(input, Ruleset::PartTwo).to_string()) +} diff --git a/source/year_2020/day_12/extra.rs b/source/year_2020/day_12/extra.rs new file mode 100644 index 0000000..19bbfa5 --- /dev/null +++ b/source/year_2020/day_12/extra.rs @@ -0,0 +1,42 @@ +#[derive(Debug, Eq, PartialEq)] +pub enum Action { + North, + East, + South, + West, + Left, + Right, + Forward, +} + +impl From for Action { + fn from(input: char) -> Self { + match input { + 'N' => Self::North, + 'E' => Self::East, + 'S' => Self::South, + 'W' => Self::West, + 'L' => Self::Left, + 'R' => Self::Right, + 'F' => Self::Forward, + _ => unreachable!("{input}"), + } + } +} + +#[derive(Debug)] +pub struct Instruction { + pub action: Action, + pub value: i32, +} + +impl From<&str> for Instruction { + fn from(input: &str) -> Self { + let mut chars = input.chars(); + + let action = chars.next().map(Into::into).unwrap(); + let value = chars.collect::().parse().unwrap(); + + Self { action, value } + } +} diff --git a/source/year_2020/day_12/mod.rs b/source/year_2020/day_12/mod.rs new file mode 100644 index 0000000..8c9e063 --- /dev/null +++ b/source/year_2020/day_12/mod.rs @@ -0,0 +1,93 @@ +use crate::prelude::*; + +mod extra; + +use extra::*; + +pub fn solution() -> Solution { + Solution::new(Day::new(12, 2020), part_1, part_2).with_expected(1496, 63843) +} + +fn parse_instructions(input: &str) -> Vec { + input.lines().map(Into::into).collect() +} + +fn part_1(input: &str) -> Result { + let instructions = parse_instructions(input); + let mut horizontal = 0; + let mut vertical = 0; + let mut direction: i32 = 90; + + for instruction in instructions { + match instruction.action { + Action::Forward => { + match direction { + 90 => horizontal += instruction.value, + 270 => horizontal -= instruction.value, + 180 => vertical += instruction.value, + 0 => vertical -= instruction.value, + _ => unreachable!("{direction}"), + }; + } + Action::Left => direction -= instruction.value, + Action::Right => direction += instruction.value, + Action::East => horizontal += instruction.value, + Action::West => horizontal -= instruction.value, + Action::South => vertical += instruction.value, + Action::North => vertical -= instruction.value, + }; + + direction = match direction % 360 { + -270 => 90, + -90 => 270, + 0 => 0, + value => value.abs(), + }; + } + + Ok((horizontal.abs() + vertical.abs()).to_string()) +} + +fn part_2(input: &str) -> Result { + let instructions = parse_instructions(input); + + let mut horizontal = 0; + let mut vertical = 0; + + let mut wp_horizontal = 10; + let mut wp_vertical = -1; + + for instruction in instructions { + match instruction.action { + Action::Forward => { + horizontal += wp_horizontal * instruction.value; + vertical += wp_vertical * instruction.value; + } + Action::East => wp_horizontal += instruction.value, + Action::West => wp_horizontal -= instruction.value, + Action::South => wp_vertical += instruction.value, + Action::North => wp_vertical -= instruction.value, + _ => (), + }; + + match (&instruction.action, instruction.value) { + (Action::Left, 90) | (Action::Right, 270) => { + let intermediate_vertical = wp_vertical; + wp_vertical = -wp_horizontal; + wp_horizontal = intermediate_vertical; + } + (Action::Left, 180) | (Action::Right, 180) => { + wp_horizontal = -wp_horizontal; + wp_vertical = -wp_vertical; + } + (Action::Left, 270) | (Action::Right, 90) => { + let intermediate_horizontal = wp_horizontal; + wp_horizontal = -wp_vertical; + wp_vertical = intermediate_horizontal; + } + _ => (), + }; + } + + Ok((horizontal.abs() + vertical.abs()).to_string()) +} diff --git a/source/year_2020/day_13/mod.rs b/source/year_2020/day_13/mod.rs new file mode 100644 index 0000000..93d8925 --- /dev/null +++ b/source/year_2020/day_13/mod.rs @@ -0,0 +1,74 @@ +use crate::prelude::*; + +pub fn solution() -> Solution { + Solution::new(Day::new(13, 2020), part_1, part_2) + .with_expected(1895, 840493039281088_i64) +} + +fn part_1(input: &str) -> Result { + let mut lines = input.lines(); + + let target = lines.next().map(str::parse::).unwrap().unwrap(); + let busses = lines + .next() + .unwrap() + .split(',') + .filter_map(|value| value.parse().ok()) + .collect::>(); + + let mut difference = 0; + let mut earliest_bus = 0; + let mut minutes = 0; + + for bus in busses { + let rest = target % bus; + + if minutes < rest { + difference = bus - rest; + earliest_bus = bus; + minutes = rest; + } + } + + Ok((earliest_bus * difference).to_string()) +} + +fn part_2(input: &str) -> Result { + // Skip the first line, as it isn't used for the second part of the puzzle. + let input = input.lines().nth(1).unwrap(); + + let busses = input + .split(',') + .enumerate() + .filter_map(|(offset, bus)| { + if let Ok(bus) = bus.parse() { + Some((offset, bus)) + } else { + None + } + }) + .collect::>(); + + let mut timestamp = 0; + // Start the increment at 1 as we'll be multiplying it with the bus numbers. + let mut increment = 1; + // Keep track of which busses we've gotten modulo equals 0 from. + let mut used_busses = vec![]; + + 'outer: loop { + timestamp += increment; + + for (offset, bus) in &busses { + if (timestamp + offset) % bus != 0 { + continue 'outer; + } else if !used_busses.contains(&(offset, bus)) { + used_busses.push((offset, bus)); + increment *= bus; + } + } + + break 'outer; + } + + Ok(timestamp.to_string()) +} diff --git a/source/year_2020/day_14/mod.rs b/source/year_2020/day_14/mod.rs new file mode 100644 index 0000000..1c1c946 --- /dev/null +++ b/source/year_2020/day_14/mod.rs @@ -0,0 +1,123 @@ +use crate::prelude::*; + +mod operation; + +use operation::*; + +pub fn solution() -> Solution { + Solution::new(Day::new(14, 2020), part_1, part_2) + .with_expected(9967721333886_i64, 4355897790573_i64) +} + +fn part_1(input: &str) -> Result { + let operations = input.lines().map(Into::into).collect::>(); + + let mut current_mask = "".to_string(); + let mut memory = HashMap::new(); + + for operation in operations { + match operation { + Operation::SetMask(mask) => current_mask = mask.clone(), + Operation::SetMemory(address, mut value) => { + for (index, character) in current_mask.chars().rev().enumerate() { + match character { + '0' => value &= !(1 << index), + '1' => value |= 1 << index, + _ => (), + } + } + + *memory.entry(address).or_insert(value) = value; + } + } + } + + Ok( + memory + .iter() + .map(|(_, value)| value) + .sum::() + .to_string(), + ) +} + +fn part_2(input: &str) -> Result { + let operations = input.lines().map(Into::into).collect::>(); + + let mut current_mask = "".to_string(); + let mut memory = HashMap::new(); + + for operation in operations { + match operation { + Operation::SetMask(mask) => current_mask = mask.clone(), + Operation::SetMemory(mut address, value) => { + for (index, character) in current_mask.chars().rev().enumerate() { + if character == '1' { + address |= 1 << index; + } + } + + let mut new_mask = String::new(); + for (index, character) in format!("{:036b}", address).char_indices() { + if current_mask.chars().nth(index) == Some('X') { + new_mask += "X"; + } else { + new_mask += &character.to_string(); + } + } + + let mut floating_addresses = HashSet::new(); + floating_addresses.insert(new_mask); + while floating_addresses + .iter() + .any(|address| address.contains('X')) + { + for address in floating_addresses.clone() { + for new_address in combine(address.clone()) { + floating_addresses.insert(new_address); + } + + floating_addresses.remove(&address); + } + } + + let floating_addresses = floating_addresses + .iter() + .map(|address| i64::from_str_radix(address, 2).unwrap()) + .collect::>(); + for mut address in floating_addresses { + for (index, character) in current_mask.chars().rev().enumerate() { + if character == '1' { + address |= 1 << index; + } + } + + *memory.entry(address).or_insert(value) = value; + } + } + } + } + + Ok( + memory + .iter() + .map(|(_, value)| *value) + .sum::() + .to_string(), + ) +} + +fn combine(input: String) -> Vec { + let mut result = vec![]; + + for (index, character) in input.char_indices() { + if character == 'X' { + let zero = format!("{}{}{}", &input[0..index], '0', &input[index + 1..]); + result.push(zero); + let one = format!("{}{}{}", &input[0..index], '1', &input[index + 1..]); + result.push(one); + } + } + + result +} diff --git a/source/year_2020/day_14/operation.rs b/source/year_2020/day_14/operation.rs new file mode 100644 index 0000000..5719b37 --- /dev/null +++ b/source/year_2020/day_14/operation.rs @@ -0,0 +1,21 @@ +#[derive(Debug)] +pub enum Operation { + SetMask(String), + SetMemory(i64, i64), +} + +impl From<&str> for Operation { + fn from(input: &str) -> Self { + let mut split = input.split(" = "); + + let operation = split.next().unwrap(); + if operation == "mask" { + return Self::SetMask(split.next().unwrap().to_string()); + } + + let address = operation[4..operation.len() - 1].parse().unwrap(); + let value = split.next().unwrap().parse().unwrap(); + + Self::SetMemory(address, value) + } +} diff --git a/source/year_2020/day_15/mod.rs b/source/year_2020/day_15/mod.rs new file mode 100644 index 0000000..53645c8 --- /dev/null +++ b/source/year_2020/day_15/mod.rs @@ -0,0 +1,42 @@ +use crate::prelude::*; + +pub fn solution() -> Solution { + Solution::new(Day::new(15, 2020), part_1, part_2).with_expected(441, 10613991) +} + +fn solve(input: &str, target: usize) -> isize { + let mut numbers = HashMap::new(); + let mut previous_number = (0, (0, 0)); + + for (index, number) in input + .trim() + .split(',') + .map(str::parse::) + .map(Result::unwrap) + .enumerate() + { + numbers.insert(number, (index, index)); + previous_number = (number, (index, index)); + } + + for index in numbers.len()..target { + let next_num = previous_number.1 .1 - previous_number.1 .0; + let num = numbers + .entry(next_num as isize) + .or_insert_with(|| (index, index)); + num.0 = num.1; + num.1 = index; + + previous_number = (next_num as isize, *num); + } + + *numbers.iter().max_by(|a, b| a.1 .1.cmp(&b.1 .1)).unwrap().0 +} + +fn part_1(input: &str) -> Result { + Ok(solve(input, 2020).to_string()) +} + +fn part_2(input: &str) -> Result { + Ok(solve(input, 30000000).to_string()) +} diff --git a/source/year_2020/day_16/mod.rs b/source/year_2020/day_16/mod.rs new file mode 100644 index 0000000..e21ace8 --- /dev/null +++ b/source/year_2020/day_16/mod.rs @@ -0,0 +1,142 @@ +use crate::prelude::*; + +pub fn solution() -> Solution { + Solution::new(Day::new(16, 2020), part_1, part_2) + .with_expected(26980, 3021381607403_i64) +} + +fn solve(input: &str, return_part_1: bool) -> Result { + let mut parts = input.trim().split("\n\n"); + let mut ranges = vec![]; + + for line in parts.next().unwrap().lines() { + let name = &line[0..line.find(':').unwrap()]; + + let mut first_range = line + [line.find(':').unwrap() + 2..line.find(" or").unwrap()] + .split('-') + .map(str::parse::) + .map(Result::unwrap); + + let first_range = first_range.next().unwrap()..=first_range.next().unwrap(); + + let mut second_range = line[line.find(" or ").unwrap() + 4..] + .split('-') + .map(str::parse::) + .map(Result::unwrap); + + let second_range = + second_range.next().unwrap()..=second_range.next().unwrap(); + + ranges.push((name, first_range, second_range)); + } + + let my_ticket = parts + .next() + .unwrap() + .lines() + .nth(1) + .unwrap() + .split(',') + .map(str::parse::<_>) + .map(Result::unwrap) + .collect::>(); + + let mut tickets = vec![]; + + // Skip the first line in the 3rd part, as it will say "nearby tickets:". + for line in parts.next().unwrap().lines().skip(1) { + tickets.push( + line + .split(',') + .map(str::parse::) + .map(Result::unwrap) + .collect::>(), + ); + } + + let mut result_one = 0; + let mut valid_tickets = vec![my_ticket.clone()]; + + for ticket in &tickets { + let mut ticket_is_valid = true; + + for field in ticket { + let field_is_invalid = ranges.iter().all(|(_, range_1, range_2)| { + !range_1.contains(field) && !range_2.contains(field) + }); + + if field_is_invalid { + result_one += field; + ticket_is_valid = false; + } + } + + if ticket_is_valid { + valid_tickets.push(ticket.clone()); + } + } + + if return_part_1 { + return Ok(result_one.to_string()); + } + + let mut possibilities = HashMap::new(); + let ticket_length = my_ticket.len(); + + for (name, range_1, range_2) in ranges { + for index in 0..ticket_length { + let mut fields = valid_tickets + .iter() + .map(|tickets| tickets.get(index)) + .map(Option::unwrap); + + if fields.all(|field| range_1.contains(field) || range_2.contains(field)) + { + let possibility = possibilities.entry(name).or_insert(vec![]); + possibility.push(index); + } + } + } + + let mut possibilities = possibilities.into_iter().collect::>(); + possibilities.sort_by(|a, b| a.1.len().cmp(&b.1.len())); + let mut indices = HashMap::::new(); + + for (name, possibilities) in possibilities { + 'inner: for possibility in possibilities { + if indices.contains_key(&possibility) { + continue 'inner; + } + + indices.insert(possibility, name); + } + } + + let result_two = my_ticket + .iter() + .enumerate() + .filter_map(|(index, number)| { + if let Some(name) = indices.get(&index) { + if name.starts_with("departure") { + Some(number) + } else { + None + } + } else { + None + } + }) + .product::() + .to_string(); + + Ok(result_two) +} + +fn part_1(input: &str) -> Result { + solve(input, true) +} + +fn part_2(input: &str) -> Result { + solve(input, false) +} diff --git a/source/year_2020/mod.rs b/source/year_2020/mod.rs new file mode 100644 index 0000000..ce9cb33 --- /dev/null +++ b/source/year_2020/mod.rs @@ -0,0 +1,39 @@ +use crate::solution::Solution; + +mod day_01; +mod day_02; +mod day_03; +mod day_04; +mod day_05; +mod day_06; +mod day_07; +mod day_08; +mod day_09; +mod day_10; +mod day_11; +mod day_12; +mod day_13; +mod day_14; +mod day_15; +mod day_16; + +pub fn get_solutions() -> Vec { + vec![ + day_01::solution(), + day_02::solution(), + day_03::solution(), + day_04::solution(), + day_05::solution(), + day_06::solution(), + day_07::solution(), + day_08::solution(), + day_09::solution(), + day_10::solution(), + day_11::solution(), + day_12::solution(), + day_13::solution(), + day_14::solution(), + day_15::solution(), + day_16::solution(), + ] +}