Compare commits
No commits in common. "d19192b95b1ee5aa8e28925aa4e2cd4d868c6092" and "2e0eab1c03bef2bf34e6ecd6798db96184c9d522" have entirely different histories.
d19192b95b
...
2e0eab1c03
|
@ -30,19 +30,9 @@ 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"
|
||||||
|
@ -730,23 +720,6 @@ 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,5 +24,4 @@ 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,7 +14,6 @@ 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,7 +12,6 @@ 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_2020, year_2021};
|
use crate::{solution::Solution, year_2021};
|
||||||
|
|
||||||
pub fn get_solutions() -> Vec<Vec<Solution>> {
|
pub fn get_solutions() -> Vec<Vec<Solution>> {
|
||||||
vec![year_2020::get_solutions(), year_2021::get_solutions()]
|
vec![year_2021::get_solutions()]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn random_emoji() -> String {
|
pub fn random_emoji() -> String {
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
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(),
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
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(),
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
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(),
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,164 +0,0 @@
|
||||||
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(),
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,75 +0,0 @@
|
||||||
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(),
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
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(),
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,79 +0,0 @@
|
||||||
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())
|
|
||||||
}
|
|
|
@ -1,105 +0,0 @@
|
||||||
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())
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
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())
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
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(),
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,162 +0,0 @@
|
||||||
#[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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
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())
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
#[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 }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,93 +0,0 @@
|
||||||
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())
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
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())
|
|
||||||
}
|
|
|
@ -1,123 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
#[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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
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())
|
|
||||||
}
|
|
|
@ -1,142 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
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