Compare commits
2 Commits
2e0eab1c03
...
d19192b95b
Author | SHA1 | Date |
---|---|---|
Bauke | d19192b95b | |
Bauke | 7aa3960b47 |
|
@ -30,9 +30,19 @@ dependencies = [
|
||||||
"itertools",
|
"itertools",
|
||||||
"pathfinding",
|
"pathfinding",
|
||||||
"rand",
|
"rand",
|
||||||
|
"regex",
|
||||||
"ureq",
|
"ureq",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "0.7.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "askama"
|
name = "askama"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
|
@ -720,6 +730,23 @@ dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.6.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "remove_dir_all"
|
name = "remove_dir_all"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
|
|
|
@ -24,4 +24,5 @@ emojis = "0.4.0"
|
||||||
itertools = "0.10.5"
|
itertools = "0.10.5"
|
||||||
pathfinding = "3.0.13"
|
pathfinding = "3.0.13"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
regex = "1.6.0"
|
||||||
ureq = { version = "2.5.0", features = ["cookie", "cookie_store"] }
|
ureq = { version = "2.5.0", features = ["cookie", "cookie_store"] }
|
||||||
|
|
|
@ -14,6 +14,7 @@ pub mod prelude;
|
||||||
pub mod solution;
|
pub mod solution;
|
||||||
pub mod templates;
|
pub mod templates;
|
||||||
pub mod utilities;
|
pub mod utilities;
|
||||||
|
pub mod year_2020;
|
||||||
pub mod year_2021;
|
pub mod year_2021;
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
|
|
|
@ -12,6 +12,7 @@ pub use {
|
||||||
},
|
},
|
||||||
itertools::Itertools,
|
itertools::Itertools,
|
||||||
pathfinding::prelude::astar,
|
pathfinding::prelude::astar,
|
||||||
|
regex::Regex,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use crate::solution::{Day, Solution};
|
pub use crate::solution::{Day, Solution};
|
||||||
|
|
|
@ -5,10 +5,10 @@ use std::{
|
||||||
|
|
||||||
use {color_eyre::Result, dialoguer::Password, rand::seq::IteratorRandom};
|
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>> {
|
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 {
|
pub fn random_emoji() -> String {
|
||||||
|
|
|
@ -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(),
|
||||||
|
)
|
||||||
|
}
|
|
@ -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(),
|
||||||
|
)
|
||||||
|
}
|
|
@ -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(),
|
||||||
|
)
|
||||||
|
}
|
|
@ -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(),
|
||||||
|
)
|
||||||
|
}
|
|
@ -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(),
|
||||||
|
)
|
||||||
|
}
|
|
@ -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(),
|
||||||
|
)
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
|
@ -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(),
|
||||||
|
)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
|
@ -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 }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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(),
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue