Import 2020 solutions.
This commit is contained in:
parent
7aa3960b47
commit
d19192b95b
|
@ -14,6 +14,7 @@ pub mod prelude;
|
|||
pub mod solution;
|
||||
pub mod templates;
|
||||
pub mod utilities;
|
||||
pub mod year_2020;
|
||||
pub mod year_2021;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
|
|
|
@ -5,10 +5,10 @@ use std::{
|
|||
|
||||
use {color_eyre::Result, dialoguer::Password, rand::seq::IteratorRandom};
|
||||
|
||||
use crate::{solution::Solution, year_2021};
|
||||
use crate::{solution::Solution, year_2020, year_2021};
|
||||
|
||||
pub fn get_solutions() -> Vec<Vec<Solution>> {
|
||||
vec![year_2021::get_solutions()]
|
||||
vec![year_2020::get_solutions(), year_2021::get_solutions()]
|
||||
}
|
||||
|
||||
pub fn random_emoji() -> String {
|
||||
|
|
|
@ -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