1
Fork 0

Import 2020 solutions.

This commit is contained in:
Bauke 2022-10-03 18:02:40 +02:00
parent 7aa3960b47
commit d19192b95b
Signed by: Bauke
GPG Key ID: C1C0F29952BCF558
22 changed files with 1517 additions and 2 deletions

View File

@ -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)]

View File

@ -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<Solution>> {
vec![year_2021::get_solutions()]
vec![year_2020::get_solutions(), year_2021::get_solutions()]
}
pub fn random_emoji() -> String {

View File

@ -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<i32> {
input
.lines()
.filter_map(|line| line.parse::<i32>().ok())
.collect()
}
fn part_1(input: &str) -> Result<String> {
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<String> {
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(),
)
}

View File

@ -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<Item> {
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<String> {
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<String> {
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(),
)
}

View File

@ -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<String> {
Ok(solve(input, (3, 1)).to_string())
}
fn part_2(input: &str) -> Result<String> {
Ok(
[(1, 1), (3, 1), (5, 1), (7, 1), (1, 2)]
.into_iter()
.map(|increment| solve(input, increment))
.product::<usize>()
.to_string(),
)
}

View File

@ -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<String>,
iyr: Option<String>,
eyr: Option<String>,
hgt: Option<String>,
hcl: Option<String>,
ecl: Option<String>,
pid: Option<String>,
cid: Option<String>,
}
fn parse_passports(input: &str) -> Vec<Passport> {
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<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()
})
.count()
.to_string(),
)
}
fn part_2(input: &str) -> Result<String> {
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::<i32>()
.unwrap_or_default();
(1920..=2002).contains(&byr)
})
.filter(|passport| {
let iyr = passport
.iyr
.as_ref()
.unwrap()
.parse::<i32>()
.unwrap_or_default();
(2010..=2020).contains(&iyr)
})
.filter(|passport| {
let eyr = passport
.eyr
.as_ref()
.unwrap()
.parse::<i32>()
.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::<i32>().unwrap_or_default();
(150..=193).contains(&value)
}
"in" => {
let value =
hgt[0..hgt.len() - 2].parse::<i32>().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(),
)
}

View File

@ -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<String> {
Ok(
input
.lines()
.map(calculate_row_and_column)
.map(calculate_seat_id)
.max()
.unwrap()
.to_string(),
)
}
fn part_2(input: &str) -> Result<String> {
let seat_ids = input
.lines()
.map(calculate_row_and_column)
.map(calculate_seat_id)
.collect::<Vec<i32>>();
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(),
)
}

View File

@ -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<String> {
Ok(
input
.split("\n\n")
.map(|group| {
group
.replace('\n', "")
.chars()
.collect::<HashSet<char>>()
.len()
})
.sum::<usize>()
.to_string(),
)
}
fn part_2(input: &str) -> Result<String> {
Ok(
input
.split("\n\n")
.map(|group| {
let mut count = 0;
let possibilities =
group.replace('\n', "").chars().collect::<HashSet<char>>();
for character in possibilities {
if group
.trim()
.split('\n')
.all(|person| person.contains(character))
{
count += 1;
}
}
count
})
.sum::<usize>()
.to_string(),
)
}

View File

@ -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::<usize>()
.unwrap_or_default();
let bag = &bag[bag.find(' ').unwrap() + 1..bag.find(" bag").unwrap()];
(count, bag)
})
.collect::<Vec<(usize, &str)>>();
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<String> {
Ok(
can_hold_target_bag(TARGET, &parse_bags(input))
.into_iter()
.collect::<HashSet<&str>>()
.len()
.to_string(),
)
}
fn part_2(input: &str) -> Result<String> {
Ok(target_bag_holds_amount("shiny gold", &parse_bags(input)).to_string())
}

View File

@ -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<Instruction> {
input.lines().enumerate().map(Into::into).collect()
}
fn part_1(input: &str) -> Result<String> {
Ok(execute(&parse_instructions(input)).0.to_string())
}
fn part_2(input: &str) -> Result<String> {
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())
}

View File

@ -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<usize> {
input
.lines()
.map(|number| number.parse().unwrap())
.collect()
}
fn part_1(input: &str) -> Result<String> {
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<String> {
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::<usize>();
if preamble_total.to_string() == result_one {
let mut preamble = preamble.collect::<Vec<&usize>>();
preamble.sort_unstable();
result_two = *preamble.first().unwrap() + *preamble.last().unwrap();
break 'outer;
}
}
}
Ok(result_two.to_string())
}

View File

@ -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<usize, i32>, HashMap<i32, u32>) {
let mut numbers = input
.lines()
.map(|line| line.parse().unwrap())
.collect::<Vec<usize>>();
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<String> {
let (intervals, _) = parse_numbers(input);
Ok((intervals.get(&1).unwrap() * intervals.get(&3).unwrap()).to_string())
}
fn part_2(input: &str) -> Result<String> {
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(),
)
}

View File

@ -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<char> 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<Cell>,
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<Cell> {
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
}
}

View File

@ -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<String> {
Ok(calculate(input, Ruleset::PartOne).to_string())
}
fn part_2(input: &str) -> Result<String> {
Ok(calculate(input, Ruleset::PartTwo).to_string())
}

View File

@ -0,0 +1,42 @@
#[derive(Debug, Eq, PartialEq)]
pub enum Action {
North,
East,
South,
West,
Left,
Right,
Forward,
}
impl From<char> 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::<String>().parse().unwrap();
Self { action, value }
}
}

View File

@ -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<Instruction> {
input.lines().map(Into::into).collect()
}
fn part_1(input: &str) -> Result<String> {
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<String> {
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())
}

View File

@ -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<String> {
let mut lines = input.lines();
let target = lines.next().map(str::parse::<i32>).unwrap().unwrap();
let busses = lines
.next()
.unwrap()
.split(',')
.filter_map(|value| value.parse().ok())
.collect::<Vec<i32>>();
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<String> {
// 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::<Vec<(usize, usize)>>();
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())
}

View File

@ -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<String> {
let operations = input.lines().map(Into::into).collect::<Vec<Operation>>();
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::<i64>()
.to_string(),
)
}
fn part_2(input: &str) -> Result<String> {
let operations = input.lines().map(Into::into).collect::<Vec<Operation>>();
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::<Vec<i64>>();
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::<i64>()
.to_string(),
)
}
fn combine(input: String) -> Vec<String> {
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
}

View File

@ -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)
}
}

View File

@ -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::<isize>)
.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<String> {
Ok(solve(input, 2020).to_string())
}
fn part_2(input: &str) -> Result<String> {
Ok(solve(input, 30000000).to_string())
}

View File

@ -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<String> {
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::<usize>)
.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::<usize>)
.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::<Vec<_>>();
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::<usize>)
.map(Result::unwrap)
.collect::<Vec<_>>(),
);
}
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::<Vec<_>>();
possibilities.sort_by(|a, b| a.1.len().cmp(&b.1.len()));
let mut indices = HashMap::<usize, &str>::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::<usize>()
.to_string();
Ok(result_two)
}
fn part_1(input: &str) -> Result<String> {
solve(input, true)
}
fn part_2(input: &str) -> Result<String> {
solve(input, false)
}

39
source/year_2020/mod.rs Normal file
View File

@ -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<Solution> {
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(),
]
}