Created
December 25, 2020 23:35
-
-
Save forrestthewoods/a46ea526af8e3d951c6992c793711926 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
mod data; | |
/* | |
pub mod day00 { | |
use std::fmt::Write; | |
pub fn run() -> String { | |
let mut result = String::with_capacity(128); | |
let answer_part1 = part1(crate::data::DAY00); | |
writeln!(&mut result, "Day 00, Problem 1 - [{}]", answer_part1).unwrap(); | |
let answer_part2 = part2(crate::data::DAY00); | |
writeln!(&mut result, "Day 00, Problem 2 - [{}]", answer_part2).unwrap(); | |
result | |
} | |
fn part1(_input: &str) -> usize { | |
0 | |
} | |
fn part2(_input: &str) -> usize { | |
0 | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn examples() { | |
} | |
#[test] | |
fn verify() { | |
} | |
} | |
} | |
*/ | |
mod util { | |
pub trait StripCarriageReturn { | |
fn strip_carriage_return(&self) -> String; | |
} | |
impl StripCarriageReturn for &str { | |
fn strip_carriage_return(&self) -> String { | |
self.replace("\r", "") | |
} | |
} | |
} | |
pub mod game_console { | |
use itertools::Itertools; | |
use std::collections::HashSet; | |
#[derive(Copy, Clone)] | |
pub enum Op { | |
Acc(isize), | |
Jmp(isize), | |
Nop(isize), | |
} | |
#[derive(Copy, Clone)] | |
pub enum RunResult { | |
Loop(isize), | |
Finish(isize), | |
} | |
#[derive(Clone)] | |
pub struct GameConsole { | |
instruction_ptr: isize, | |
accumulator: isize, | |
pub ops: Vec<Op>, | |
} | |
impl From<&str> for Op { | |
#[inline] | |
fn from(line: &str) -> Op { | |
let (op, arg) = line.split(" ").collect_tuple().unwrap(); | |
let arg: isize = arg.parse().unwrap(); | |
match op { | |
"acc" => Op::Acc(arg), | |
"jmp" => Op::Jmp(arg), | |
"nop" => Op::Nop(arg), | |
_ => unreachable!("Unexpected op string [{}]", op), | |
} | |
} | |
} | |
impl GameConsole { | |
pub fn new(input: &str) -> GameConsole { | |
GameConsole { | |
instruction_ptr: 0, | |
accumulator: 0, | |
ops: input.lines().map_into::<Op>().collect(), | |
} | |
} | |
pub fn run(&mut self) -> RunResult { | |
let mut visited: HashSet<isize> = Default::default(); | |
loop { | |
// Test for end of program | |
if self.instruction_ptr == self.ops.len() as isize { | |
return RunResult::Finish(self.accumulator); | |
} | |
// Test for infinite loop | |
if !visited.insert(self.instruction_ptr) { | |
return RunResult::Loop(self.accumulator); | |
} | |
// Execute next instruction | |
match &self.ops[self.instruction_ptr as usize] { | |
Op::Acc(value) => { | |
self.accumulator += value; | |
self.instruction_ptr += 1; | |
} | |
Op::Jmp(value) => { | |
self.instruction_ptr += value; | |
} | |
Op::Nop(_) => self.instruction_ptr += 1, | |
} | |
} | |
} | |
} | |
} | |
pub mod day01 { | |
use itertools::Itertools; | |
use std::fmt::Write; | |
pub fn run() -> String { | |
let mut result = String::with_capacity(128); | |
let nums = parse_data(crate::data::DAY01); | |
let answer_part1 = part1(&nums); | |
writeln!(&mut result, "Day 1, Problem 1 - [{}]", answer_part1).unwrap(); | |
let answer_part2 = part2(&nums); | |
writeln!(&mut result, "Day 1, Problem 2 - [{}]", answer_part2).unwrap(); | |
result | |
} | |
fn parse_data(data: &str) -> Vec<i32> { | |
data.lines() | |
.map(|s| s.parse::<i32>().unwrap()) | |
.sorted() | |
.collect() | |
} | |
// nums must be sorted | |
fn part1(nums: &[i32]) -> i32 { | |
let mut lo = 0; | |
let mut hi = nums.len() - 1; | |
while nums[lo] + nums[hi] != 2020 { | |
if nums[lo] + nums[hi] < 2020 { | |
lo += 1; | |
} else { | |
hi -= 1; | |
} | |
} | |
nums[lo] * nums[hi] | |
} | |
// nums must be sorted | |
fn part2(nums: &[i32]) -> i32 { | |
let mut hi = 2; | |
loop { | |
let mut lo = 0; | |
while lo < hi - 1 { | |
let a = nums[lo]; | |
let c = nums[hi]; | |
// Too far | |
if a + c > 2020 { | |
break; | |
} | |
// Not far enough | |
let b = 2020 - a - c; | |
if b > c { | |
break; | |
} | |
// Does B exist? | |
if let Ok(_) = &nums[lo + 1..hi].binary_search(&b) { | |
return a * b * c; | |
} | |
lo += 1; | |
} | |
hi += 1; | |
} | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn examples() { | |
let test_data = "1721\n979\n366\n299\n675\n1456"; | |
let test_nums = parse_data(test_data); | |
assert_eq!(part1(&test_nums), 514579); // 299 * 1721 = 514579 | |
assert_eq!(part2(&test_nums), 241861950); // 366 * 675 * 979 = 241861950 | |
} | |
#[test] | |
fn verify() { | |
let data = crate::data::DAY01; | |
let nums = parse_data(data); | |
assert_eq!(part1(&nums), 121396); | |
assert_eq!(part2(&nums), 73616634); // 117 * 426 * 1477 | |
} | |
} | |
} | |
pub mod day02 { | |
use lazy_static::lazy_static; | |
use regex::Regex; | |
use std::fmt::Write; | |
struct Entry { | |
lo: usize, | |
hi: usize, | |
letter: char, | |
password: String, | |
} | |
pub fn run() -> String { | |
let mut result = String::with_capacity(128); | |
let entries = parse_data(crate::data::DAY02); | |
let answer_part1 = part1(&entries); | |
writeln!(&mut result, "Day 2, Problem 1 - [{}]", answer_part1).unwrap(); | |
let answer_part2 = part2(&entries); | |
writeln!(&mut result, "Day 2, Problem 2 - [{}]", answer_part2).unwrap(); | |
result | |
} | |
fn parse_data(data: &str) -> Vec<Entry> { | |
lazy_static! { | |
static ref REGEX: Regex = | |
Regex::new(r"(?P<lo>\d+)-(?P<hi>\d+) (?P<letter>[a-z]): (?P<password>[a-z]+)") | |
.unwrap(); | |
} | |
data.lines() | |
.map(|line| { | |
let caps = REGEX.captures(line).unwrap(); | |
Entry { | |
lo: caps["lo"].parse().unwrap(), | |
hi: caps["hi"].parse().unwrap(), | |
letter: caps["letter"].parse().unwrap(), | |
password: caps["password"].parse().unwrap(), | |
} | |
}) | |
.collect() | |
} | |
fn part1(entries: &[Entry]) -> usize { | |
entries | |
.iter() | |
.filter(|entry| { | |
let char_count = entry | |
.password | |
.chars() | |
.filter(|&c| c == entry.letter) | |
.count(); | |
char_count >= entry.lo && char_count <= entry.hi | |
}) | |
.count() | |
} | |
fn part2(entries: &[Entry]) -> usize { | |
entries | |
.iter() | |
.filter(|entry| { | |
let bytes = entry.password.as_bytes(); | |
let lo = bytes[entry.lo - 1] as char; | |
let hi = bytes[entry.hi - 1] as char; | |
(lo == entry.letter) ^ (hi == entry.letter) | |
}) | |
.count() | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn examples() { | |
let test_data = "1-3 a: abcde\n1-3 b: cdefg\n2-9 c: ccccccccc"; | |
let data = parse_data(test_data); | |
assert_eq!(part1(&data), 2); | |
assert_eq!(part2(&data), 1); | |
} | |
#[test] | |
fn verify() { | |
let data = parse_data(crate::data::DAY02); | |
assert_eq!(part1(&data), 398); | |
assert_eq!(part2(&data), 562); | |
} | |
} | |
} | |
pub mod day03 { | |
use std::fmt::Write; | |
pub fn run() -> String { | |
let mut result = String::with_capacity(128); | |
let (trees, width) = parse_data(crate::data::DAY03); | |
let answer_part1 = part1(&trees, width); | |
writeln!(&mut result, "Day 3, Problem 1 - [{}]", answer_part1).unwrap(); | |
let answer_part2 = part2(&trees, width); | |
writeln!(&mut result, "Day 3, Problem 2 - [{}]", answer_part2).unwrap(); | |
result | |
} | |
fn parse_data(data: &str) -> (Vec<bool>, usize) { | |
// Width of first line | |
let width = data.lines().next().unwrap().len(); | |
// # == tree | |
let trees = data | |
.lines() | |
.flat_map(|line| line.chars()) | |
.map(|c| c == '#') | |
.collect(); | |
(trees, width) | |
} | |
fn part1(trees: &[bool], width: usize) -> usize { | |
const X_STEP: usize = 3; | |
const Y_STEP: usize = 1; | |
test_slope(trees, width, X_STEP, Y_STEP) | |
} | |
fn part2(trees: &[bool], width: usize) -> usize { | |
let slopes: Vec<(usize, usize)> = vec![(1, 1), (3, 1), (5, 1), (7, 1), (1, 2)]; | |
let result = slopes | |
.iter() | |
.map(|(slope_x, slope_y)| test_slope(trees, width, *slope_x, *slope_y)) | |
.product(); | |
result | |
} | |
fn test_slope(trees: &[bool], width: usize, slope_x: usize, slope_y: usize) -> usize { | |
let height = trees.len() / width; | |
let num_steps = height / slope_y; | |
(1..num_steps) | |
.map(|step| step * slope_y * width + (step * slope_x) % width) | |
.filter(|&idx| trees[idx]) | |
.count() | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn examples() { | |
let test_data = "..##.......\n#...#...#..\n.#....#..#.\n..#.#...#.#\n.#...##..#.\n..#.##.....\n.#.#.#....#\n.#........#\n#.##...#...\n#...##....#\n.#..#...#.#"; | |
let (trees, width) = parse_data(test_data); | |
assert_eq!(part1(&trees, width), 7); | |
assert_eq!(part2(&trees, width), 336); | |
} | |
#[test] | |
fn verify() { | |
let (trees, width) = parse_data(crate::data::DAY03); | |
assert_eq!(part1(&trees, width), 184); | |
assert_eq!(part2(&trees, width), 2431272960); | |
} | |
} | |
} | |
pub mod day04 { | |
use itertools::Itertools; | |
use std::collections::{HashMap, HashSet}; | |
use std::fmt::Write; | |
pub fn run() -> String { | |
let mut result = String::with_capacity(128); | |
let answer_part1 = part1(crate::data::DAY04); | |
writeln!(&mut result, "Day 4, Problem 1 - [{}]", answer_part1).unwrap(); | |
let answer_part2 = part2(crate::data::DAY04); | |
writeln!(&mut result, "Day 4, Problem 2 - [{}]", answer_part2).unwrap(); | |
result | |
} | |
fn part1(raw_data: &str) -> usize { | |
let required_keys: HashSet<_> = vec!["byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"] | |
.into_iter() | |
.collect(); | |
raw_data | |
.lines() | |
.group_by(|line| line.trim().is_empty()) | |
.into_iter() | |
.filter(|(empty, _)| !empty) | |
.map(|(_, group)| { | |
group | |
.into_iter() | |
.flat_map(|l| l.split_whitespace()) | |
.map(|kvp| kvp.split(":").next().unwrap()) | |
.collect::<HashSet<&str>>() | |
}) | |
.filter(|keys| required_keys.iter().all(|key| keys.contains(key))) | |
.count() | |
} | |
fn part2(raw_data: &str) -> usize { | |
let required_keys: HashSet<_> = ["byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"] | |
.iter() | |
.collect(); | |
let valid_eye_colors: HashSet<_> = ["amb", "blu", "brn", "gry", "grn", "hzl", "oth"] | |
.iter() | |
.collect(); | |
raw_data | |
.lines() | |
.group_by(|line| line.trim().is_empty()) | |
.into_iter() | |
.filter(|(empty, _)| !empty) | |
.map(|(_, group)| { | |
group | |
.into_iter() | |
.flat_map(|l| l.split_whitespace()) | |
.map(|kvp| kvp.split(":").tuples().next().unwrap()) | |
.collect::<HashMap<&str, &str>>() | |
}) | |
.filter(|keys| { | |
required_keys.iter().all(|&key| { | |
match keys.get(key) { | |
Some(value) => { | |
let valid = match *key { | |
"byr" => value | |
.parse::<i32>() | |
.map_or(false, |year| (1920..=2002).contains(&year)), | |
"iyr" => value | |
.parse::<i32>() | |
.map_or(false, |year| (2010..=2020).contains(&year)), | |
"eyr" => value | |
.parse::<i32>() | |
.map_or(false, |year| (2020..=2030).contains(&year)), | |
"hgt" => { | |
let n = value.len() - 2; | |
let height = (&value[..n]).parse::<i32>().unwrap_or(0); | |
let height_str = &value[n..]; | |
match height_str { | |
"cm" => (150..=193).contains(&height), | |
"in" => (59..=76).contains(&height), | |
_ => false, | |
} | |
} | |
"hcl" => { | |
let value_bytes = value.as_bytes(); | |
value_bytes.len() == 7 | |
&& value_bytes[0] == '#' as u8 | |
&& value_bytes | |
.iter() | |
.skip(1) | |
.all(|&b| b.is_ascii_hexdigit()) | |
} | |
"ecl" => valid_eye_colors.contains(value), | |
"pid" => { | |
value.len() == 9 | |
&& value.as_bytes().iter().all(|&v| v.is_ascii_digit()) | |
} | |
"cid" => true, // always valid | |
_ => false, // required key not present | |
}; | |
valid | |
} | |
None => false, | |
} | |
}) | |
}) | |
.count() | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn examples() { | |
assert_eq!(part1(crate::data::_DAY04_EXAMPLE_1), 2); | |
assert_eq!(part2(crate::data::_DAY04_EXAMPLE_2), 0); | |
assert_eq!(part2(crate::data::_DAY04_EXAMPLE_3), 4); | |
} | |
#[test] | |
fn verify() { | |
assert_eq!(part1(crate::data::DAY04), 247); | |
assert_eq!(part2(crate::data::DAY04), 145); | |
} | |
} | |
} | |
pub mod day05 { | |
use itertools::Itertools; | |
use std::collections::HashSet; | |
use std::fmt::Write; | |
pub fn run() -> String { | |
let mut result = String::with_capacity(128); | |
let answer_part1 = part1(crate::data::DAY05); | |
writeln!(&mut result, "Day 5, Problem 1 - [{}]", answer_part1).unwrap(); | |
let answer_part2 = part2(crate::data::DAY05); | |
writeln!(&mut result, "Day 5, Problem 2 - [{}]", answer_part2).unwrap(); | |
result | |
} | |
fn pos_from_pass(pass: &str) -> (u32, u32) { | |
let row = pass | |
.chars() | |
.take(7) | |
.map(|c| if c == 'B' { 1 } else { 0 }) | |
.fold(0, |acc, v| acc << 1 | v); | |
let col = pass | |
.chars() | |
.skip(7) | |
.map(|c| if c == 'R' { 1 } else { 0 }) | |
.fold(0, |acc, v| acc << 1 | v); | |
(row, col) | |
} | |
fn id_from_pos(pos: (u32, u32)) -> u32 { | |
pos.0 * 8 + pos.1 | |
} | |
fn id_from_pass(pass: &str) -> u32 { | |
let pos = pos_from_pass(pass); | |
id_from_pos(pos) | |
} | |
fn part1(input: &str) -> u32 { | |
input.lines().map(|line| id_from_pass(line)).max().unwrap() | |
} | |
fn part2(input: &str) -> u32 { | |
let mut seats: HashSet<u32> = Default::default(); | |
let mut min_row = 127; | |
let mut max_row = 0; | |
for line in input.lines() { | |
let pos = pos_from_pass(line); | |
min_row = std::cmp::min(pos.0, min_row); | |
max_row = std::cmp::max(pos.0, max_row); | |
seats.insert(id_from_pos(pos)); | |
} | |
let all_seats: HashSet<u32> = (min_row + 1..=max_row - 1) | |
.cartesian_product(0..=7) | |
.map(|pos| id_from_pos(pos)) | |
.collect(); | |
let missing_seats: Vec<_> = all_seats.difference(&seats).collect(); | |
assert!(missing_seats.len() == 1); | |
*missing_seats[0] | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn examples() { | |
assert_eq!(id_from_pass("FBFBBFFRLR"), 357); | |
assert_eq!(id_from_pass("BFFFBBFRRR"), 567); | |
assert_eq!(id_from_pass("FFFBBBFRRR"), 119); | |
assert_eq!(id_from_pass("BBFFBBFRLL"), 820); | |
} | |
#[test] | |
fn verify() { | |
assert_eq!(part1(crate::data::DAY05), 980); | |
assert_eq!(part2(crate::data::DAY05), 607); | |
} | |
} | |
} | |
pub mod day06 { | |
use crate::util::*; | |
use std::fmt::Write; | |
pub fn run() -> String { | |
let mut result = String::with_capacity(128); | |
let data = crate::data::DAY06.strip_carriage_return(); | |
let answer_part1 = part1(&data); | |
writeln!(&mut result, "Day 6, Problem 1 - [{}]", answer_part1).unwrap(); | |
let answer_part2 = part2(&data); | |
writeln!(&mut result, "Day 6, Problem 2 - [{}]", answer_part2).unwrap(); | |
result | |
} | |
fn mask_from_char(c: char) -> u32 { | |
1u32 << (c as u32 - 'a' as u32) | |
} | |
fn part1(data: &str) -> u32 { | |
data.split("\n\n") | |
.map(|group| { | |
group | |
.split_whitespace() | |
.flat_map(|person| person.chars()) | |
.map(|c| mask_from_char(c)) | |
.fold(0u32, |acc, v| acc | v) | |
.count_ones() | |
}) | |
.sum() | |
} | |
fn part2(data: &str) -> u32 { | |
data.split("\n\n") | |
.map(|group| { | |
group | |
.split_whitespace() | |
.map(|person| { | |
person | |
.chars() | |
.map(|c| mask_from_char(c)) | |
.fold(0, |acc, v| acc | v) | |
}) | |
.fold(std::u32::MAX, |acc, p| acc & p) | |
.count_ones() | |
}) | |
.sum() | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn examples() { | |
let data = crate::data::_DAY06_EXAMPLE_1.strip_carriage_return(); | |
assert_eq!(part1(&data), 11); | |
assert_eq!(part2(&data), 6); | |
} | |
#[test] | |
fn verify() { | |
let data = crate::data::DAY06.strip_carriage_return(); | |
assert_eq!(part1(&data), 6549); | |
assert_eq!(part2(&data), 3466); | |
} | |
} | |
} | |
pub mod day07 { | |
use petgraph::prelude::*; | |
use regex::Regex; | |
use std::collections::HashMap; | |
use std::fmt::Write; | |
type BagGraph<'a> = petgraph::Graph<&'a str, usize, petgraph::Directed, u32>; | |
pub fn run() -> String { | |
let mut result = String::with_capacity(128); | |
let graph = build_graph(crate::data::DAY07); | |
let answer_part1 = part1(&graph); | |
writeln!(&mut result, "Day 7, Problem 1 - [{}]", answer_part1).unwrap(); | |
let answer_part2 = part2(&graph); | |
writeln!(&mut result, "Day 7, Problem 2 - [{}]", answer_part2).unwrap(); | |
result | |
} | |
fn build_graph(data: &str) -> BagGraph { | |
let bag_re = Regex::new(r"(.+) bags contain").unwrap(); | |
let children_re = Regex::new(r"(\d+) (.+?) bags?[,.]").unwrap(); | |
let mut graph = BagGraph::new(); | |
let mut nodes: HashMap<&str, NodeIndex<u32>> = Default::default(); | |
for line in data.lines() { | |
let caps = bag_re.captures(line).unwrap(); | |
let bag = &caps.get(1).unwrap().as_str(); | |
let parent_idx = *nodes.entry(bag).or_insert_with(|| graph.add_node(bag)); | |
for child_cap in children_re.captures_iter(line) { | |
let child_count: usize = child_cap.get(1).unwrap().as_str().parse().unwrap(); | |
let child_name: &str = child_cap.get(2).unwrap().as_str(); | |
let child_idx = *nodes | |
.entry(child_name) | |
.or_insert_with(|| graph.add_node(child_name)); | |
graph.add_edge(parent_idx, child_idx, child_count); | |
} | |
} | |
graph | |
} | |
fn part1(graph: &BagGraph) -> usize { | |
let shiny_gold_idx = graph | |
.node_indices() | |
.find(|i| graph[*i] == "shiny gold") | |
.unwrap(); | |
let mut space = petgraph::algo::DfsSpace::new(&graph); | |
graph | |
.node_indices() | |
.filter(|&idx| idx != shiny_gold_idx) | |
.filter(|&idx| { | |
petgraph::algo::has_path_connecting(&graph, idx, shiny_gold_idx, Some(&mut space)) | |
}) | |
.count() | |
} | |
fn part2(graph: &BagGraph) -> usize { | |
let shiny_gold_idx = graph | |
.node_indices() | |
.find(|i| graph[*i] == "shiny gold") | |
.unwrap(); | |
let mut nodes = vec![(1, shiny_gold_idx)]; | |
let mut sum = 0; | |
while !nodes.is_empty() { | |
let (parent_count, node_idx) = nodes.pop().unwrap(); | |
for edge in graph.edges_directed(node_idx, petgraph::Direction::Outgoing) { | |
sum += parent_count * edge.weight(); | |
nodes.push((parent_count * edge.weight(), edge.target())); | |
} | |
} | |
sum | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn examples() { | |
let graph1 = build_graph(crate::data::_DAY07_EXAMPLE_1); | |
let graph2 = build_graph(crate::data::_DAY07_EXAMPLE_2); | |
assert_eq!(part1(&graph1), 4); | |
assert_eq!(part2(&graph1), 32); | |
assert_eq!(part2(&graph2), 126); | |
} | |
#[test] | |
fn verify() { | |
let graph = build_graph(crate::data::DAY07); | |
assert_eq!(part1(&graph), 335); | |
assert_eq!(part2(&graph), 2431); | |
} | |
} | |
} | |
pub mod day08 { | |
use crate::game_console::*; | |
use std::fmt::Write; | |
pub fn run() -> String { | |
let mut result = String::with_capacity(128); | |
let answer_part1 = part1(crate::data::DAY08); | |
writeln!(&mut result, "Day 8, Problem 1 - [{}]", answer_part1).unwrap(); | |
let answer_part2 = part2(crate::data::DAY08); | |
writeln!(&mut result, "Day 8, Problem 2 - [{}]", answer_part2).unwrap(); | |
result | |
} | |
fn part1(data: &str) -> isize { | |
let mut game_console = GameConsole::new(data); | |
let result = game_console.run(); | |
match result { | |
RunResult::Loop(value) => return value, | |
_ => unreachable!("Day08 part1 expected an infinite loop"), | |
} | |
} | |
fn part2(data: &str) -> isize { | |
let base_console = GameConsole::new(data); | |
let mut console = base_console.clone(); | |
for i in 0..console.ops.len() { | |
let op = &mut console.ops[i]; | |
match op { | |
Op::Jmp(value) => *op = Op::Nop(*value), | |
Op::Nop(value) => *op = Op::Jmp(*value), | |
_ => continue, | |
} | |
match console.run() { | |
RunResult::Finish(value) => return value, | |
_ => (), | |
} | |
console = base_console.clone() | |
} | |
unreachable!("Day08 Part2 failed to find a solution"); | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn examples() { | |
assert_eq!(part1(crate::data::_DAY08_EXAMPLE_1), 5); | |
assert_eq!(part2(crate::data::_DAY08_EXAMPLE_1), 8); | |
} | |
#[test] | |
fn verify() { | |
assert_eq!(part1(crate::data::DAY08), 1446); | |
assert_eq!(part2(crate::data::DAY08), 1403); | |
} | |
} | |
} | |
pub mod day09 { | |
use itertools::Itertools; | |
use std::fmt::Write; | |
pub fn run() -> String { | |
let mut result = String::with_capacity(128); | |
let nums = parse_input(crate::data::DAY09); | |
let answer_part1 = part1(&nums, 25); | |
writeln!(&mut result, "Day 9, Problem 1 - [{}]", answer_part1).unwrap(); | |
let answer_part2 = part2(&nums, 69316178); | |
writeln!(&mut result, "Day 9, Problem 2 - [{}]", answer_part2).unwrap(); | |
result | |
} | |
fn parse_input(input: &str) -> Vec<i64> { | |
input.lines().map(|line| line.parse().unwrap()).collect() | |
} | |
fn part1(data: &[i64], n: usize) -> i64 { | |
// returns true if sorted nums contains pair that sums to value | |
let check_slice = |nums: &[i64], v: i64| -> bool { | |
let mut lo = 0; | |
let mut hi = nums.len() - 1; | |
while lo < hi { | |
let a = nums[lo]; | |
let b = nums[hi]; | |
let s = a + b; | |
if s == v { | |
return true; | |
} else if s > v { | |
hi -= 1; | |
} else { | |
lo += 1; | |
} | |
// sum can not possibly be found within [lo..=hi] | |
if a > v || b < v / 2 { | |
break; | |
} | |
} | |
false | |
}; | |
// removes a number and adds a number. slice sorted before and after | |
let slide_slice = |nums: &mut [i64], to_remove: i64, to_add: i64| { | |
let idx = nums.binary_search(&to_remove).unwrap(); | |
nums[idx] = to_add; | |
nums.sort_unstable(); | |
}; | |
// initialize window | |
let mut nums: Vec<i64> = data[..n].iter().cloned().sorted().collect(); | |
for i in n..data.len() { | |
let v = data[i]; | |
if !check_slice(&nums, v) { | |
return v; | |
} | |
let to_remove = data[i - n]; | |
slide_slice(&mut nums, to_remove, v); | |
} | |
unreachable!("Failed to find answer"); | |
} | |
fn part2(data: &[i64], sum: i64) -> i64 { | |
for i in 0..data.len() - 2 { | |
let mut j = i + 1; | |
let mut slice_sum: i64 = data[i]; | |
while j < data.len() { | |
// grow slice | |
slice_sum += data[j]; | |
// found slice | |
if slice_sum == sum { | |
let (min, max) = data[i..j + 1].iter().minmax().into_option().unwrap(); | |
return min + max; | |
} | |
// slice sum is too large, early out | |
if slice_sum >= sum { | |
break; | |
} | |
j += 1; | |
} | |
} | |
unreachable!("Failed to find answer"); | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn example() { | |
let nums = parse_input(crate::data::_DAY09_EXAMPLE_1); | |
assert_eq!(part1(&nums, 5), 127); | |
assert_eq!(part2(&nums, 127), 62); | |
} | |
#[test] | |
fn verify() { | |
let nums = parse_input(crate::data::DAY09); | |
assert_eq!(part1(&nums, 25), 69316178); | |
assert_eq!(part2(&nums, 69316178), 9351526); | |
} | |
} | |
} | |
pub mod day10 { | |
use std::collections::HashMap; | |
use std::fmt::Write; | |
pub fn run() -> String { | |
let mut result = String::with_capacity(128); | |
let nums = parse_input(crate::data::DAY10); | |
let answer_part1 = part1(&nums); | |
writeln!(&mut result, "Day 10, Problem 1 - [{}]", answer_part1).unwrap(); | |
let answer_part2 = part2(&nums); | |
writeln!(&mut result, "Day 10, Problem 2 - [{}]", answer_part2).unwrap(); | |
result | |
} | |
fn parse_input(input: &str) -> Vec<u8> { | |
let mut nums: Vec<_> = Default::default(); | |
nums.push(0); | |
nums.extend(input.lines().map(|l| l.parse::<u8>().unwrap())); | |
nums.push(*nums.iter().max().unwrap() + 3); | |
nums.sort(); | |
nums | |
} | |
fn part1(nums: &[u8]) -> usize { | |
let counts = nums | |
.windows(2) | |
.map(|window| window[1] - window[0]) | |
.fold((0, 0), |acc, diff| { | |
(acc.0 + (diff == 1) as usize, acc.1 + (diff == 3) as usize) | |
}); | |
counts.0 * counts.1 | |
} | |
fn part2(nums: &[u8]) -> usize { | |
fn recursive_calc(jolts: u8, nums: &[u8], memo: &mut HashMap<u8, usize>) -> usize { | |
if let Some(count) = memo.get(&jolts) { | |
*count | |
} else { | |
let sum = (1..=3) | |
.map(|offset| jolts + offset) | |
.filter(|next_jolts| nums.binary_search(&next_jolts).is_ok()) | |
.map(|next_jolts| recursive_calc(next_jolts, nums, memo)) | |
.sum::<usize>() | |
.max(1); | |
memo.insert(jolts, sum); | |
sum | |
} | |
} | |
let mut memo: HashMap<u8, usize> = Default::default(); | |
let result = recursive_calc(nums[0], nums, &mut memo); | |
result | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn examples() { | |
assert_eq!(part1(&parse_input(crate::data::_DAY10_EXAMPLE_1)), 35); | |
assert_eq!(part1(&parse_input(crate::data::_DAY10_EXAMPLE_2)), 220); | |
assert_eq!(part2(&parse_input(crate::data::_DAY10_EXAMPLE_1)), 8); | |
assert_eq!(part2(&parse_input(crate::data::_DAY10_EXAMPLE_2)), 19208); | |
} | |
#[test] | |
fn verify() { | |
let nums = parse_input(crate::data::DAY10); | |
assert_eq!(part1(&nums), 1656); | |
assert_eq!(part2(&nums), 56693912375296); | |
} | |
} | |
} | |
pub mod day11 { | |
use fts_vecmath::point2::*; | |
use fts_vecmath::vector2::*; | |
use std::collections::hash_map::DefaultHasher; | |
use std::collections::HashSet; | |
use std::fmt::Write; | |
use std::hash::Hasher; | |
const FLOOR: u8 = '.' as u8; | |
const EMPTY: u8 = 'L' as u8; | |
const OCCUPIED: u8 = '#' as u8; | |
type Pos = Point2<isize>; | |
type Offset = Vector2<isize>; | |
const OFFSETS: [Offset; 8] = [ | |
Offset { x: -1, y: -1 }, | |
Offset { x: 0, y: -1 }, | |
Offset { x: 1, y: -1 }, | |
Offset { x: -1, y: 0 }, | |
Offset { x: 1, y: 0 }, | |
Offset { x: -1, y: 1 }, | |
Offset { x: 0, y: 1 }, | |
Offset { x: 1, y: 1 }, | |
]; | |
pub fn run() -> String { | |
let mut result = String::with_capacity(128); | |
let (grid, width) = parse_input(crate::data::DAY11); | |
let answer_part1 = part1(&grid, width); | |
writeln!(&mut result, "Day 11, Problem 1 - [{}]", answer_part1).unwrap(); | |
let answer_part2 = part2(&grid, width); | |
writeln!(&mut result, "Day 11, Problem 2 - [{}]", answer_part2).unwrap(); | |
result | |
} | |
fn parse_input(input: &str) -> (Vec<u8>, isize) { | |
let width = input.lines().next().unwrap().chars().count(); | |
let grid: Vec<u8> = input | |
.lines() | |
.flat_map(|l| l.chars()) | |
.map(|c| c as u8) | |
.collect(); | |
(grid, width as isize) | |
} | |
fn part1(grid: &[u8], width: isize) -> usize { | |
let mut states: HashSet<u64> = Default::default(); | |
let height = grid.len() as isize / width; | |
let mut grid: Vec<u8> = grid.iter().cloned().collect(); | |
let mut next_grid: Vec<u8> = grid.clone(); | |
loop { | |
let mut hasher = DefaultHasher::new(); | |
hasher.write(&grid); | |
let hash = hasher.finish(); | |
if !states.insert(hash) { | |
return grid.iter().filter(|&&seat| seat == OCCUPIED).count(); | |
} | |
for row in 0..height { | |
for col in 0..width { | |
let idx = (row * width + col) as usize; | |
if grid[idx] == FLOOR { | |
continue; | |
} | |
let pos = Pos::new(col, row); | |
let num_occupied = OFFSETS | |
.iter() | |
.map(|&offset| pos + offset) | |
.filter(|p| (0..width).contains(&p.x) && (0..height).contains(&p.y)) | |
.map(|p| (p.y * width + p.x) as usize) | |
.filter(|&idx| grid[idx] == OCCUPIED) | |
.count(); | |
if grid[idx] == EMPTY && num_occupied == 0 { | |
next_grid[idx] = OCCUPIED; | |
} else if grid[idx] == OCCUPIED && num_occupied >= 4 { | |
next_grid[idx] = EMPTY; | |
} else { | |
next_grid[idx] = grid[idx]; | |
} | |
} | |
} | |
std::mem::swap(&mut grid, &mut next_grid); | |
} | |
} | |
fn part2(grid: &[u8], width: isize) -> usize { | |
let mut states: HashSet<u64> = Default::default(); | |
let height = grid.len() as isize / width; | |
let mut grid: Vec<u8> = grid.iter().cloned().collect(); | |
let mut next_grid: Vec<u8> = grid.clone(); | |
let is_valid_pos = |p: Pos| (0..width).contains(&p.x) && (0..height).contains(&p.y); | |
loop { | |
let mut hasher = DefaultHasher::new(); | |
hasher.write(&grid); | |
let hash = hasher.finish(); | |
if !states.insert(hash) { | |
return grid.iter().filter(|&&seat| seat == OCCUPIED).count(); | |
} | |
for row in 0..height { | |
for col in 0..width { | |
let idx = (row * width + col) as usize; | |
if grid[idx] == FLOOR { | |
continue; | |
} | |
let pos = Pos::new(col, row); | |
// This is the simple version that is easy to understand and is fast | |
let mut num_occupied = 0; | |
for &offset in &OFFSETS { | |
let mut p = pos; | |
loop { | |
p += offset; | |
if !is_valid_pos(p) { | |
break; | |
} | |
let idx = (p.y * width + p.x) as usize; | |
let tile = grid[idx]; | |
if tile == OCCUPIED { | |
num_occupied += 1; | |
break; | |
} else if tile == EMPTY { | |
break; | |
} | |
} | |
} | |
/* | |
// This is the overcomplicated iterator version that is twice as slow | |
let num_occupied = OFFSETS | |
.iter() | |
.filter_map(|&offset| { | |
[offset] | |
.iter() | |
.cycle() | |
.enumerate() | |
.map(|(i, &offset)| pos + offset * (i + 1) as isize) | |
.take_while(|&pos| is_valid_pos(pos)) | |
.map(|pos| grid[(pos.y * width + pos.x) as usize]) | |
.filter(|&tile| tile != FLOOR) | |
.take_while(|&tile| tile == OCCUPIED) | |
.next() | |
}) | |
.count(); | |
*/ | |
// This is a still overcomplicated version that is also twice as slow | |
/* | |
let num_occupied = OFFSETS | |
.iter() | |
.filter_map(|&offset| { | |
(1..).map(|i| pos + offset * i) | |
.take_while(|&pos| is_valid_pos(pos)) | |
.map(|pos| grid[(pos.y * width + pos.x) as usize]) | |
.filter(|&tile| tile != FLOOR) | |
.take_while(|&tile| tile == OCCUPIED) | |
.next() | |
}) | |
.count(); | |
*/ | |
if grid[idx] == EMPTY && num_occupied == 0 { | |
next_grid[idx] = OCCUPIED; | |
} else if grid[idx] == OCCUPIED && num_occupied >= 5 { | |
next_grid[idx] = EMPTY; | |
} else { | |
next_grid[idx] = grid[idx]; | |
} | |
} | |
} | |
std::mem::swap(&mut grid, &mut next_grid); | |
} | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn examples() { | |
let (grid, width) = parse_input(crate::data::_DAY11_EXAMPLE_1); | |
assert_eq!(part1(&grid, width), 37); | |
assert_eq!(part2(&grid, width), 26); | |
} | |
#[test] | |
fn verify() { | |
let (grid, width) = parse_input(crate::data::DAY11); | |
assert_eq!(part1(&grid, width), 2361); | |
assert_eq!(part2(&grid, width), 2119); | |
} | |
} | |
} | |
pub mod day12 { | |
use fts_vecmath::point2::*; | |
use fts_vecmath::vector2::*; | |
use std::fmt::Write; | |
type Pos = Point2<i32>; | |
type Dir = Vector2<i32>; | |
type RotMat = (i32, i32, i32, i32); | |
#[derive(Copy, Clone)] | |
enum Action { | |
North = 0, | |
East, | |
South, | |
West, | |
Left, | |
Right, | |
Forward, | |
} | |
const COMPASS: [Action; 4] = [Action::North, Action::East, Action::South, Action::West]; | |
const FACING_DIR: [Dir; 4] = [ | |
Dir { x: 0, y: 1 }, | |
Dir { x: 1, y: 0 }, | |
Dir { x: 0, y: -1 }, | |
Dir { x: -1, y: 0 }, | |
]; | |
// Rotation Matrix | |
// cosθ -sinθ -> 0 1 | |
// sin0 cos0 2 3 | |
const ROTATE_LEFT: [RotMat; 3] = [ | |
(0, -1, 1, 0), // 90 | |
(-1, 0, 0, -1), // 180 | |
(0, 1, -1, 0), // 270 | |
]; | |
const ROTATE_RIGHT: [RotMat; 3] = [ | |
(0, 1, -1, 0), // -90 | |
(-1, 0, 0, -1), // -180 | |
(0, -1, 1, 0), // -270 | |
]; | |
fn rotate(rot: RotMat, dir: Dir) -> Dir { | |
Dir::new(dir.x * rot.0 + dir.y * rot.1, dir.x * rot.2 + dir.y * rot.3) | |
} | |
impl From<char> for Action { | |
fn from(c: char) -> Action { | |
match c { | |
'N' => Action::North, | |
'E' => Action::East, | |
'S' => Action::South, | |
'W' => Action::West, | |
'L' => Action::Left, | |
'R' => Action::Right, | |
'F' => Action::Forward, | |
_ => unreachable!("Unexpected value [{}]", c), | |
} | |
} | |
} | |
pub fn run() -> String { | |
let mut result = String::with_capacity(128); | |
let data = parse_input(crate::data::DAY12); | |
let answer_part1 = part1(&data); | |
writeln!(&mut result, "Day 12, Problem 1 - [{}]", answer_part1).unwrap(); | |
let answer_part2 = part2(&data); | |
writeln!(&mut result, "Day 12, Problem 2 - [{}]", answer_part2).unwrap(); | |
result | |
} | |
fn parse_input(input: &str) -> Vec<(Action, i32)> { | |
input | |
.lines() | |
.map(|line| { | |
( | |
line.chars().next().unwrap().into(), | |
line[1..].parse().unwrap(), | |
) | |
}) | |
.collect() | |
} | |
fn part1(data: &[(Action, i32)]) -> i32 { | |
let mut pos = Pos::new(0, 0); | |
let mut facing = Action::East; | |
for (action, value) in data { | |
match action { | |
Action::North => pos.y += value, | |
Action::South => pos.y -= value, | |
Action::East => pos.x += value, | |
Action::West => pos.x -= value, | |
Action::Forward => pos += FACING_DIR[facing as usize] * value, | |
Action::Left => { | |
facing = COMPASS[(facing as i32 - value / 90).rem_euclid(4) as usize] | |
} | |
Action::Right => { | |
facing = COMPASS[(facing as i32 + value / 90).rem_euclid(4) as usize] | |
} | |
} | |
} | |
pos.x.abs() + pos.y.abs() | |
} | |
fn part2(data: &[(Action, i32)]) -> i32 { | |
let mut pos = Pos::new(0, 0); | |
let mut waypoint = Dir::new(10, 1); | |
for (action, value) in data { | |
match action { | |
Action::North => waypoint.y += value, | |
Action::South => waypoint.y -= value, | |
Action::East => waypoint.x += value, | |
Action::West => waypoint.x -= value, | |
Action::Forward => pos += waypoint * value, | |
Action::Left => { | |
waypoint = rotate(ROTATE_LEFT[(*value / 90) as usize - 1], waypoint) | |
} | |
Action::Right => { | |
waypoint = rotate(ROTATE_RIGHT[(*value / 90) as usize - 1], waypoint) | |
} | |
} | |
} | |
pos.x.abs() + pos.y.abs() | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn examples() { | |
let data = parse_input(crate::data::_DAY12_EXAMPLE_1); | |
assert_eq!(part1(&data), 25); | |
assert_eq!(part2(&data), 286); | |
} | |
#[test] | |
fn verify() { | |
let data = parse_input(crate::data::DAY12); | |
assert_eq!(part1(&data), 1589); | |
assert_eq!(part2(&data), 23960); | |
} | |
} | |
} | |
pub mod day13 { | |
use std::fmt::Write; | |
pub fn run() -> String { | |
let mut result = String::with_capacity(128); | |
let answer_part1 = part1(crate::data::DAY13); | |
writeln!(&mut result, "Day 0, Problem 1 - [{}]", answer_part1).unwrap(); | |
let answer_part2 = part2(crate::data::DAY13); | |
writeln!(&mut result, "Day 0, Problem 2 - [{}]", answer_part2).unwrap(); | |
result | |
} | |
fn part1(input: &str) -> i32 { | |
let mut lines = input.lines(); | |
let earliest_time: i32 = lines.next().unwrap().parse().unwrap(); | |
let buses: Vec<i32> = lines | |
.next() | |
.unwrap() | |
.split(",") | |
.filter(|&v| v != "x") | |
.map(|v| v.parse().unwrap()) | |
.collect(); | |
let (bus, time) = buses | |
.iter() | |
.map(|bus| { | |
( | |
bus, | |
(earliest_time as f32 / *bus as f32).ceil() as i32 * bus, | |
) | |
}) | |
.min_by_key(|(_, time)| *time) | |
.unwrap(); | |
(time - earliest_time) * bus | |
} | |
fn part2(input: &str) -> usize { | |
let line = input.lines().skip(1).next().unwrap(); | |
let buses: Vec<(usize, usize)> = line | |
.split(",") | |
.enumerate() | |
.filter(|(_, s)| *s != "x") | |
.map(|(i, bus)| (i, bus.parse().unwrap())) | |
.collect(); | |
let mut n = buses[0].1; | |
let mut step = buses[0].1; | |
for (i, bus) in &buses[1..] { | |
let mut tn = n + i; | |
while tn % bus != 0 { | |
tn += step; | |
} | |
n = tn - i; | |
step *= bus; | |
} | |
n | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn examples() { | |
assert_eq!(part1(crate::data::_DAY13_EXAMPLE_1), 295); | |
assert_eq!(part2(crate::data::_DAY13_EXAMPLE_1), 1068781); | |
assert_eq!(part2("\n17,x,13,19"), 3417); | |
assert_eq!(part2("\n67,7,59,61"), 754018); | |
assert_eq!(part2("\n67,x,7,59,61"), 779210); | |
assert_eq!(part2("\n67,7,x,59,61"), 1261476); | |
assert_eq!(part2("\n1789,37,47,1889"), 1202161486); | |
} | |
#[test] | |
fn verify() { | |
assert_eq!(part1(crate::data::DAY13), 333); | |
assert_eq!(part2(crate::data::DAY13), 690123192779524); | |
} | |
} | |
} | |
pub mod day14 { | |
use lazy_static::lazy_static; | |
use regex::Regex; | |
use std::collections::HashMap; | |
use std::fmt::Write; | |
pub fn run() -> String { | |
let mut result = String::with_capacity(128); | |
let answer_part1 = part1(crate::data::DAY14); | |
writeln!(&mut result, "Day 14, Problem 1 - [{}]", answer_part1).unwrap(); | |
let answer_part2 = part2(crate::data::DAY14); | |
writeln!(&mut result, "Day 14, Problem 2 - [{}]", answer_part2).unwrap(); | |
result | |
} | |
fn part1(input: &str) -> u64 { | |
lazy_static! { | |
static ref MASK_REGEX: Regex = Regex::new(r"mask = (.+)").unwrap(); | |
} | |
lazy_static! { | |
static ref WRITE_REGEX: Regex = Regex::new(r"mem\[(\d+)\] = (\d+)").unwrap(); | |
} | |
let mut memory: HashMap<usize, u64> = Default::default(); | |
let mut set_mask: u64 = 0; | |
let mut clear_mask: u64 = std::u64::MAX; | |
for line in input.lines() { | |
if let Some(caps) = MASK_REGEX.captures(line) { | |
let mask = caps.get(1).unwrap().as_str(); | |
set_mask = mask | |
.chars() | |
.rev() | |
.enumerate() | |
.filter(|(_, c)| *c == '1') | |
.fold(0, |acc, (i, _)| acc | (1 << i)); | |
clear_mask = mask | |
.chars() | |
.rev() | |
.enumerate() | |
.filter(|(_, c)| *c == '0') | |
.fold(std::u64::MAX, |acc, (i, _)| acc & !(1 << i)); | |
} else if let Some(caps) = WRITE_REGEX.captures(line) { | |
let register: usize = caps.get(1).unwrap().as_str().parse().unwrap(); | |
let mut value: u64 = caps.get(2).unwrap().as_str().parse().unwrap(); | |
value |= set_mask; | |
value &= clear_mask; | |
*memory.entry(register).or_default() = value; | |
} else { | |
unreachable!("Did not match line: [{}]", line); | |
} | |
} | |
memory.iter().map(|(_, v)| v).sum() | |
} | |
fn part2(input: &str) -> u64 { | |
lazy_static! { | |
static ref MASK_REGEX: Regex = Regex::new(r"mask = (.+)").unwrap(); | |
} | |
lazy_static! { | |
static ref WRITE_REGEX: Regex = Regex::new(r"mem\[(\d+)\] = (\d+)").unwrap(); | |
} | |
let mut memory: HashMap<u64, u64> = Default::default(); | |
let mut set_mask: u64 = 0; | |
let mut floating_bits: Vec<u64> = Default::default(); | |
for line in input.lines() { | |
if let Some(caps) = MASK_REGEX.captures(line) { | |
let mask = caps.get(1).unwrap().as_str(); | |
set_mask = mask | |
.chars() | |
.rev() | |
.enumerate() | |
.filter(|(_, c)| *c == '1') | |
.fold(0, |acc, (i, _)| acc | (1 << i)); | |
let floating_mask: u64 = mask | |
.chars() | |
.rev() | |
.enumerate() | |
.filter(|(_, c)| *c == 'X') | |
.fold(0, |acc, (i, _)| acc | (1 << i)); | |
floating_bits.clear(); | |
for i in 0..36 { | |
if (floating_mask & (1 << i)) != 0 { | |
floating_bits.push(i); | |
} | |
} | |
} else if let Some(caps) = WRITE_REGEX.captures(line) { | |
let base_register: u64 = caps.get(1).unwrap().as_str().parse().unwrap(); | |
let value: u64 = caps.get(2).unwrap().as_str().parse().unwrap(); | |
let pow = 2_usize.pow(floating_bits.len() as u32); | |
for permutation in 0..pow { | |
let mut final_register = base_register | set_mask; | |
for idx in 0..floating_bits.len() { | |
if permutation & (1 << idx) != 0 { | |
final_register |= 1 << floating_bits[idx]; | |
} else { | |
final_register &= !(1 << floating_bits[idx]); | |
} | |
} | |
*memory.entry(final_register).or_default() = value; | |
} | |
} else { | |
unreachable!("Did not match line: [{}]", line); | |
} | |
} | |
memory.iter().map(|(_, v)| v).sum() | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn examples() { | |
assert_eq!(part1(crate::data::_DAY14_EXAMPLE_1), 165); | |
assert_eq!(part2(crate::data::_DAY14_EXAMPLE_2), 208); | |
} | |
#[test] | |
fn verify() { | |
assert_eq!(part1(crate::data::DAY14), 15018100062885); | |
assert_eq!(part2(crate::data::DAY14), 5724245857696); | |
} | |
} | |
} | |
pub mod day15 { | |
use std::collections::HashMap; | |
use std::fmt::Write; | |
pub fn run() -> String { | |
let mut result = String::with_capacity(128); | |
let answer_part1 = solve(crate::data::DAY15, 2020); | |
writeln!(&mut result, "Day 15, Problem 1 - [{}]", answer_part1).unwrap(); | |
let answer_part2 = solve(crate::data::DAY15, 30_000_000); | |
writeln!(&mut result, "Day 15, Problem 2 - [{}]", answer_part2).unwrap(); | |
result | |
} | |
fn solve(input: &str, target_turn: usize) -> usize { | |
let mut prev_num = 0; | |
let mut history: HashMap<usize, (usize, usize)> = input | |
.split(",") | |
.map(|n| n.parse::<usize>().unwrap()) | |
.enumerate() | |
.map(|(i, v)| { | |
prev_num = v; | |
let turn = i + 1; | |
(v, (turn, turn)) | |
}) | |
.collect(); | |
let mut turn = history.len() + 1; | |
loop { | |
let prev_turns = history.get(&prev_num).unwrap(); | |
let next_num = (turn - 1) - prev_turns.0; | |
if turn == target_turn { | |
return next_num; | |
} | |
let turns = history.entry(next_num).or_insert((turn, turn)); | |
*turns = (turns.1, turn); | |
prev_num = next_num; | |
turn += 1; | |
} | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn examples() { | |
assert_eq!(solve("0,3,6", 2020), 436); | |
assert_eq!(solve("1,3,2", 2020), 1); | |
assert_eq!(solve("2,1,3", 2020), 10); | |
assert_eq!(solve("1,2,3", 2020), 27); | |
assert_eq!(solve("2,3,1", 2020), 78); | |
assert_eq!(solve("3,2,1", 2020), 438); | |
assert_eq!(solve("3,1,2", 2020), 1836); | |
} | |
#[test] | |
fn verify() { | |
assert_eq!(solve(crate::data::DAY15, 2020), 273); | |
assert_eq!(solve(crate::data::DAY15, 30_000_000), 47205); | |
} | |
} | |
} | |
pub mod day16 { | |
use std::collections::HashSet; | |
use std::fmt::Write; | |
pub fn run() -> String { | |
let mut result = String::with_capacity(128); | |
let (answer_part1, answer_part2) = solve(crate::data::DAY16); | |
writeln!(&mut result, "Day 16, Problem 1 - [{}]", answer_part1).unwrap(); | |
writeln!(&mut result, "Day 16, Problem 2 - [{}]", answer_part2).unwrap(); | |
result | |
} | |
fn solve(input: &str) -> (usize, usize) { | |
let my_ticket_idx = input.find("your ticket").unwrap(); | |
let nearby_tickets_idx = input.find("nearby tickets").unwrap(); | |
// Parse rules | |
let rules: Vec<_> = input[..my_ticket_idx] | |
.lines() | |
.filter(|line| !line.is_empty()) | |
.map(|line| { | |
let parts: Vec<_> = line.split(":").collect(); | |
let key = parts[0]; | |
let parts: Vec<_> = parts[1].split(" ").collect(); | |
let split = parts[1].find("-").unwrap(); | |
let lo: usize = parts[1][..split].parse().unwrap(); | |
let hi: usize = parts[1][split + 1..].parse().unwrap(); | |
let range_a = lo..=hi; | |
let split = parts[3].find("-").unwrap(); | |
let lo: usize = parts[3][..split].parse().unwrap(); | |
let hi: usize = parts[3][split + 1..].parse().unwrap(); | |
let range_b = lo..=hi; | |
(key, range_a, range_b) | |
}) | |
.collect(); | |
// Parse all tickets | |
let tickets: Vec<Vec<usize>> = input[nearby_tickets_idx..] | |
.lines() | |
.skip(1) | |
.map(|line| { | |
line.split(",") | |
.map(|num| num.parse::<_>().unwrap()) | |
.collect() | |
}) | |
.collect(); | |
// Solve part 1 | |
let part1 = tickets | |
.iter() | |
.flat_map(|ticket| ticket.iter()) | |
.filter(|num| { | |
rules | |
.iter() | |
.all(|rule| !rule.1.contains(num) && !rule.2.contains(num)) | |
}) | |
.sum(); | |
// Compute valid tickets | |
let valid_tickets: Vec<_> = tickets | |
.iter() | |
.filter(|ticket| { | |
ticket.iter().all(|num| { | |
rules | |
.iter() | |
.any(|rule| rule.1.contains(num) || rule.2.contains(num)) | |
}) | |
}) | |
.collect(); | |
// Build container with all possibile matches | |
let num_rules = rules.len(); | |
// Rule to index | |
let mut possible_positions: Vec<HashSet<usize>> = | |
(0..num_rules).map(|_| (0..num_rules).collect()).collect(); | |
// First pass | |
for ticket_value_idx in 0..num_rules { | |
for ticket in &valid_tickets { | |
let value = ticket[ticket_value_idx]; | |
for rule_idx in 0..num_rules { | |
let rule = &rules[rule_idx]; | |
if !rule.1.contains(&value) && !rule.2.contains(&value) { | |
possible_positions[rule_idx].remove(&ticket_value_idx); | |
} | |
} | |
} | |
} | |
// Loop and removed solved one-by-one | |
let mut solved: HashSet<usize> = Default::default(); | |
while solved.len() != num_rules { | |
for rule_idx in 0..num_rules { | |
if !solved.contains(&rule_idx) && possible_positions[rule_idx].len() == 1 { | |
let solved_pos = *possible_positions[rule_idx].iter().next().unwrap(); | |
for idx in 0..num_rules { | |
if rule_idx != idx { | |
possible_positions[idx].remove(&solved_pos); | |
} | |
} | |
solved.insert(rule_idx); | |
} | |
} | |
} | |
// Each set should have exactly one remaining possibility | |
let solved_positions: Vec<usize> = possible_positions | |
.iter() | |
.map(|set| *set.iter().next().unwrap()) | |
.collect(); | |
// Parse my ticket | |
let my_ticket: Vec<usize> = input[my_ticket_idx..nearby_tickets_idx] | |
.lines() | |
.skip(1) | |
.filter(|line| !line.is_empty()) | |
.flat_map(|line| line.split(",").map(|num| num.parse::<usize>().unwrap())) | |
.collect(); | |
// Multiple the correct 6 numbers | |
let part2 = rules | |
.iter() | |
.enumerate() | |
.filter(|(_, rule)| rule.0.contains("departure")) | |
.map(|(i, _)| solved_positions[i]) | |
.map(|idx| my_ticket[idx]) | |
.product(); | |
(part1, part2) | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn examples() { | |
let (part1, _) = solve(crate::data::_DAY16_EXAMPLE_1); | |
assert_eq!(part1, 71); | |
} | |
#[test] | |
fn verify() { | |
let (part1, part2) = solve(crate::data::DAY16); | |
assert_eq!(part1, 21071); | |
assert_eq!(part2, 3429967441937); | |
} | |
} | |
} | |
pub mod day17 { | |
use fts_vecmath::point3::*; | |
use fts_vecmath::vector3::*; | |
use fts_vecmath::vector4::*; | |
use itertools::Itertools; | |
use std::collections::HashSet; | |
use std::fmt::Write; | |
pub fn run() -> String { | |
let mut result = String::with_capacity(128); | |
let answer_part1 = part1(crate::data::DAY17); | |
writeln!(&mut result, "Day 17, Problem 1 - [{}]", answer_part1).unwrap(); | |
let answer_part2 = part2(crate::data::DAY17); | |
writeln!(&mut result, "Day 17, Problem 2 - [{}]", answer_part2).unwrap(); | |
result | |
} | |
fn part1(input: &str) -> usize { | |
type Pos = Point3<i32>; | |
type Offset = Vector3<i32>; | |
let offsets: Vec<Offset> = (-1..=1) | |
.cartesian_product(-1..=1) | |
.cartesian_product(-1..=1) | |
.filter_map(|((x, y), z)| match (x, y, z) { | |
(0, 0, 0) => None, | |
_ => Some(Offset::new(x, y, z)), | |
}) | |
.collect(); | |
let dim = input.lines().count() as i32; | |
let mut grid: HashSet<Pos> = input | |
.lines() | |
.enumerate() | |
.flat_map(|(row, line)| { | |
line.chars() | |
.enumerate() | |
.filter_map(move |(col, c)| match c { | |
'#' => Some(Pos::new(col as i32 - dim / 2, row as i32 - dim / 2, 0)), | |
'.' => None, | |
_ => unreachable!("Unexpected input char [{}]", c), | |
}) | |
}) | |
.collect(); | |
let mut min_pos = Pos::new(-dim / 2 - 1, -dim / 2 - 1, -1); | |
let mut max_pos = Pos::new(dim / 2 + 1, dim / 2 + 1, 1); | |
for _ in 0..6 { | |
let mut next_grid: HashSet<Pos> = Default::default(); | |
for x in min_pos.x..=max_pos.x { | |
for y in min_pos.y..=max_pos.y { | |
for z in min_pos.z..=max_pos.z { | |
let pos = Pos::new(x, y, z); | |
let mut count = 0; | |
for offset in &offsets { | |
let neighbor = pos + *offset; | |
if grid.contains(&neighbor) { | |
count += 1; | |
} | |
if count > 3 { | |
break; | |
} | |
} | |
let active = grid.contains(&pos); | |
if (active && (count == 2 || count == 3)) || (!active && count == 3) { | |
min_pos = fts_vecmath::point3::min(min_pos, pos - Offset::one()); | |
max_pos = fts_vecmath::point3::max(max_pos, pos + Offset::one()); | |
next_grid.insert(pos); | |
} | |
} | |
} | |
} | |
std::mem::swap(&mut grid, &mut next_grid); | |
} | |
grid.len() | |
} | |
fn part2(input: &str) -> usize { | |
type Pos = Vector4<i32>; | |
type Offset = Vector4<i32>; | |
let offsets: Vec<Offset> = (-1..=1) | |
.cartesian_product(-1..=1) | |
.cartesian_product(-1..=1) | |
.cartesian_product(-1..=1) | |
.filter_map(|(((x, y), z), w)| match (x, y, z, w) { | |
(0, 0, 0, 0) => None, | |
_ => Some(Offset::new(x, y, z, w)), | |
}) | |
.collect(); | |
let dim = input.lines().count() as i32; | |
let mut grid: HashSet<Pos> = input | |
.lines() | |
.enumerate() | |
.flat_map(|(row, line)| { | |
line.chars() | |
.enumerate() | |
.filter_map(move |(col, c)| match c { | |
'#' => Some(Pos::new(col as i32 - dim / 2, row as i32 - dim / 2, 0, 0)), | |
'.' => None, | |
_ => unreachable!("Unexpected input char [{}]", c), | |
}) | |
}) | |
.collect(); | |
for _ in 0..6 { | |
let mut next_grid: HashSet<Pos> = Default::default(); | |
let maybe_pos: HashSet<Pos> = grid | |
.iter() | |
.flat_map(|&pos| offsets.iter().map(move |offset| pos + *offset)) | |
.collect(); | |
for pos in maybe_pos { | |
let mut count = 0; | |
for offset in &offsets { | |
let neighbor = pos + *offset; | |
if grid.contains(&neighbor) { | |
count += 1; | |
} | |
if count > 3 { | |
break; | |
} | |
} | |
let active = grid.contains(&pos); | |
if (active && (count == 2 || count == 3)) || (!active && count == 3) { | |
next_grid.insert(pos); | |
} | |
} | |
std::mem::swap(&mut grid, &mut next_grid); | |
} | |
grid.len() | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn examples() { | |
assert_eq!(part1(crate::data::_DAY17_EXAMPLE_1), 112); | |
assert_eq!(part2(crate::data::_DAY17_EXAMPLE_1), 848); | |
} | |
#[test] | |
fn verify() { | |
assert_eq!(part1(crate::data::DAY17), 211); | |
assert_eq!(part2(crate::data::DAY17), 1952); | |
} | |
} | |
} | |
pub mod day18 { | |
use std::fmt::Write; | |
pub fn run() -> String { | |
let mut result = String::with_capacity(128); | |
let answer_part1 = part1(crate::data::DAY18); | |
writeln!(&mut result, "Day 18, Problem 1 - [{}]", answer_part1).unwrap(); | |
let answer_part2 = part2(crate::data::DAY18); | |
writeln!(&mut result, "Day 18, Problem 2 - [{}]", answer_part2).unwrap(); | |
result | |
} | |
#[derive(Copy, Clone, Eq, PartialEq, Debug)] | |
enum Token { | |
LeftParen, | |
RightParen, | |
Plus, | |
Multiply, | |
Num(usize), | |
Space, | |
} | |
impl std::fmt::Display for Token { | |
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
match self { | |
Token::LeftParen => write!(f, "("), | |
Token::RightParen => write!(f, ")"), | |
Token::Plus => write!(f, "+"), | |
Token::Multiply => write!(f, "*"), | |
Token::Num(v) => write!(f, "{}", v), | |
Token::Space => write!(f, " "), | |
} | |
} | |
} | |
fn parse_input(input: &str) -> Vec<Vec<Token>> { | |
let mut result: Vec<_> = Default::default(); | |
for line in input.lines().map(|line| line.as_bytes()) { | |
let mut line_result: Vec<Token> = Default::default(); | |
let mut idx = 0; | |
while idx < line.len() { | |
let token = match line[idx] as char { | |
'(' => Token::LeftParen, | |
')' => Token::RightParen, | |
'+' => Token::Plus, | |
'*' => Token::Multiply, | |
' ' => Token::Space, | |
_ => { | |
let end = line[idx..] | |
.iter() | |
.enumerate() | |
.find_map(|(i, v)| match v.is_ascii_digit() { | |
false => Some(idx + i), | |
true => None, | |
}) | |
.unwrap_or(line.len()); | |
let span = &line[idx..end]; | |
let s: &str = std::str::from_utf8(span).unwrap(); | |
idx = end; | |
Token::Num(s.parse().unwrap()) | |
} | |
}; | |
// Increment non-nums | |
match token { | |
Token::Num(_) => (), | |
_ => idx += 1, | |
} | |
// Push non-white space | |
match token { | |
Token::Space => (), | |
_ => line_result.push(token), | |
} | |
} | |
result.push(line_result); | |
} | |
result | |
} | |
fn part1(input: &str) -> usize { | |
let mut data = parse_input(input); | |
fn value_from_slice2(slice: &[Token]) -> usize { | |
let mut sum = true; | |
slice.iter().fold(0, |mut acc, token| { | |
match token { | |
Token::Plus => sum = true, | |
Token::Multiply => sum = false, | |
Token::Num(n) => { | |
if sum { | |
acc += n; | |
} else { | |
acc *= n; | |
} | |
} | |
_ => unreachable!(), | |
} | |
acc | |
}) | |
} | |
data.iter_mut() | |
.map(|entry| { | |
while let Some(end) = entry.iter().position(|v| *v == Token::RightParen) { | |
let pos = entry[..end] | |
.iter() | |
.rev() | |
.position(|v| *v == Token::LeftParen) | |
.unwrap(); | |
let begin = end - pos - 1; | |
let slice = &entry[begin + 1..end]; | |
let value = value_from_slice2(slice); | |
entry.drain(begin..=end).last(); | |
entry.insert(begin, Token::Num(value)); | |
} | |
value_from_slice2(&entry) | |
}) | |
.sum() | |
} | |
fn part2(input: &str) -> usize { | |
let mut data = parse_input(input); | |
// inclusive [begin, end] | |
fn collapse_range(tokens: &mut Vec<Token>, begin: usize, mut end: usize) -> usize { | |
// Collapse sums | |
while let Some(idx) = &tokens[begin..=end].iter().position(|v| *v == Token::Plus) { | |
let lo = begin + idx - 1; | |
let hi = begin + idx + 1; | |
let (a, b) = match (&tokens[lo], &tokens[hi]) { | |
(Token::Num(a), Token::Num(b)) => (*a, *b), | |
_ => unreachable!(), | |
}; | |
// Replace three values with one | |
tokens.drain(lo..=hi).last(); | |
tokens.insert(lo, Token::Num(a + b)); | |
end -= 2; | |
} | |
// Collapse multiplies | |
while let Some(idx) = &tokens[begin..=end] | |
.iter() | |
.position(|v| *v == Token::Multiply) | |
{ | |
let lo = begin + idx - 1; | |
let hi = begin + idx + 1; | |
let (a, b) = match (&tokens[lo], &tokens[hi]) { | |
(Token::Num(a), Token::Num(b)) => (*a, *b), | |
_ => unreachable!(), | |
}; | |
// Replace three values with one | |
tokens.drain(lo..=hi).last(); | |
tokens.insert(lo, Token::Num(a * b)); | |
end -= 2; | |
} | |
// Collapse parens | |
if end - begin + 1 == 3 { | |
tokens.remove(end); | |
tokens.remove(begin); | |
end -= 2; | |
} | |
assert_eq!(tokens[begin..=end].len(), 1); | |
match tokens[begin] { | |
Token::Num(v) => v, | |
_ => unreachable!(), | |
} | |
} | |
data.iter_mut() | |
.map(|entry| { | |
while let Some(end) = entry.iter().position(|v| *v == Token::RightParen) { | |
let pos = entry[..end] | |
.iter() | |
.rev() | |
.position(|v| *v == Token::LeftParen) | |
.unwrap(); | |
let begin = end - pos - 1; | |
collapse_range(entry, begin, end); | |
} | |
let end = entry.len() - 1; | |
collapse_range(entry, 0, end) | |
}) | |
.sum() | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn examples() { | |
assert_eq!(part1("1 + 2 * 3 + 4 * 5 + 6"), 71); | |
assert_eq!(part1("1 + (2 * 3) + (4 * (5 + 6))"), 51); | |
assert_eq!(part1("2 * 3 + (4 * 5)"), 26); | |
assert_eq!(part1("5 + (8 * 3 + 9 + 3 * 4 * 3)"), 437); | |
assert_eq!(part1("5 * 9 * (7 * 3 * 3 + 9 * 3 + (8 + 6 * 4))"), 12240); | |
assert_eq!( | |
part1("((2 + 4 * 9) * (6 + 9 * 8 + 6) + 6) + 2 + 4 * 2"), | |
13632 | |
); | |
assert_eq!(part2("1 + 2 * 3 + 4 * 5 + 6"), 231); | |
assert_eq!(part2("1 + (2 * 3) + (4 * (5 + 6))"), 51); | |
assert_eq!(part2("2 * 3 + (4 * 5)"), 46); | |
assert_eq!(part2("5 + (8 * 3 + 9 + 3 * 4 * 3)"), 1445); | |
assert_eq!(part2("5 * 9 * (7 * 3 * 3 + 9 * 3 + (8 + 6 * 4))"), 669060); | |
assert_eq!( | |
part2("((2 + 4 * 9) * (6 + 9 * 8 + 6) + 6) + 2 + 4 * 2"), | |
23340 | |
); | |
} | |
#[test] | |
fn verify() { | |
assert_eq!(part1(crate::data::DAY18), 31142189909908); | |
assert_eq!(part2(crate::data::DAY18), 323912478287549); | |
} | |
} | |
} | |
pub mod day19 { | |
use crate::util::*; | |
use std::collections::HashMap; | |
use std::fmt::Write; | |
pub fn run() -> String { | |
let mut result = String::with_capacity(128); | |
let input = crate::data::DAY19.strip_carriage_return(); | |
let (rules, messages) = parse_input(&input); | |
let answer_part1 = part1(&rules, &messages); | |
writeln!(&mut result, "Day 19, Problem 1 - [{}]", answer_part1).unwrap(); | |
let answer_part2 = part2(&rules, &messages); | |
writeln!(&mut result, "Day 19, Problem 2 - [{}]", answer_part2).unwrap(); | |
result | |
} | |
#[derive(Debug, Clone)] | |
enum Rule { | |
Subrules(Vec<Vec<usize>>), | |
Char(char), | |
} | |
fn parse_input(input: &str) -> (HashMap<usize, Rule>, Vec<&str>) { | |
let sections: Vec<&str> = input.split("\n\n").collect(); | |
let rules: HashMap<usize, Rule> = sections[0] | |
.lines() | |
.map(|line| { | |
let parts: Vec<_> = line.split(":").map(|part| part.trim()).collect(); | |
let id: usize = parts[0].parse().unwrap(); | |
let rule = match parts[1] { | |
"\"a\"" => Rule::Char('a'), | |
"\"b\"" => Rule::Char('b'), | |
_ => { | |
let subrules: Vec<_> = parts[1].split("|").map(|s| s.trim()).collect(); | |
let subrules = subrules | |
.iter() | |
.map(|subrule| { | |
subrule | |
.trim() | |
.split(" ") | |
.map(|n| n.parse::<usize>().unwrap()) | |
.collect() | |
}) | |
.collect(); | |
Rule::Subrules(subrules) | |
} | |
}; | |
(id, rule) | |
}) | |
.collect(); | |
let messages: Vec<&str> = sections[1].lines().collect(); | |
(rules, messages) | |
} | |
fn part1(rules: &HashMap<usize, Rule>, messages: &[&str]) -> usize { | |
fn validate_message(msg: &str, rules: &HashMap<usize, Rule>) -> bool { | |
let mut open: Vec<(&str, Vec<usize>, Vec<usize>)> = vec![(msg, vec![0], vec![])]; | |
while !open.is_empty() { | |
let (msg, mut path, mut visited) = open.pop().unwrap(); | |
if msg.is_empty() || path.is_empty() { | |
continue; | |
} | |
let next_rule = path.remove(0); | |
visited.push(next_rule); | |
let rule = rules.get(&next_rule).unwrap(); | |
match rule { | |
Rule::Char(c) => { | |
if msg.chars().next().unwrap() == *c { | |
if path.is_empty() && msg.len() == 1 { | |
// End of the line! | |
return true; | |
} else if msg.len() > 1 { | |
open.push((&msg[1..], path.clone(), visited.clone())); | |
} | |
} | |
} | |
Rule::Subrules(branches) => { | |
for branch in branches { | |
let new_path: Vec<_> = | |
branch.iter().chain(path.iter()).copied().collect(); | |
open.push((msg, new_path, visited.clone())) | |
} | |
} | |
} | |
} | |
false | |
} | |
messages | |
.iter() | |
.filter(|msg| validate_message(msg, &rules)) | |
.count() | |
} | |
fn part2(orig_rules: &HashMap<usize, Rule>, messages: &[&str]) -> usize { | |
let mut rules: HashMap<usize, Rule> = orig_rules.clone(); | |
rules.insert(8, Rule::Subrules(vec![vec![42], vec![42, 8]])); | |
rules.insert(11, Rule::Subrules(vec![vec![42, 31], vec![42, 11, 31]])); | |
part1(&rules, messages) | |
// 214 too low | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn examples() { | |
let data_example1 = crate::data::_DAY19_EXAMPLE_1.strip_carriage_return(); | |
let (rules, messages) = parse_input(&data_example1); | |
assert_eq!(part1(&rules, &messages), 2); | |
let data_example1 = crate::data::_DAY19_EXAMPLE_2.strip_carriage_return(); | |
let (rules, messages) = parse_input(&data_example1); | |
assert_eq!(part1(&rules, &messages), 3); | |
assert_eq!(part2(&rules, &messages), 12); | |
} | |
#[test] | |
fn verify() { | |
let input = crate::data::DAY19.strip_carriage_return(); | |
let (rules, messages) = parse_input(&input); | |
assert_eq!(part1(&rules, &messages), 136); | |
assert_eq!(part2(&rules, &messages), 256); | |
} | |
} | |
} | |
pub mod day20 { | |
use crate::util::*; | |
use fts_vecmath::point2::*; | |
use fts_vecmath::vector2::*; | |
use regex::Regex; | |
use std::collections::{HashMap, HashSet}; | |
use std::fmt::Write; | |
type Pos = Point2<i8>; | |
type Offset = Vector2<i8>; | |
type TileId = usize; | |
type BorderId = usize; | |
type Orient = usize; | |
const TOP: usize = 0; | |
const RIGHT: usize = 1; | |
const BOTTOM: usize = 2; | |
const LEFT: usize = 3; | |
const ORIENTS: [[usize; 4]; 8] = [ | |
[0, 1, 2, 3], | |
[3, 0, 1, 2], | |
[2, 3, 0, 1], | |
[1, 2, 3, 0], | |
[4, 5, 6, 7], | |
[7, 4, 5, 6], | |
[6, 7, 4, 5], | |
[5, 6, 7, 4], | |
]; | |
pub fn run() -> String { | |
let mut result = String::with_capacity(128); | |
let input = crate::data::DAY20.strip_carriage_return(); | |
let (answer_part1, answer_part2) = solve(&input); | |
writeln!(&mut result, "Day 20, Problem 1 - [{}]", answer_part1).unwrap(); | |
writeln!(&mut result, "Day 20, Problem 2 - [{}]", answer_part2).unwrap(); | |
result | |
} | |
fn parse(input: &str) -> Vec<(TileId, Vec<Vec<u8>>)> { | |
let id_re = Regex::new(r"Tile (\d+):").unwrap(); | |
input | |
.split("\n\n") | |
.map(|section| { | |
// omg this is gross | |
let id: usize = id_re | |
.captures(section.lines().next().unwrap()) | |
.unwrap() | |
.get(1) | |
.unwrap() | |
.as_str() | |
.parse() | |
.unwrap(); | |
let tile: Vec<Vec<u8>> = section | |
.lines() | |
.skip(1) | |
.map(|line| line.as_bytes().to_vec()) | |
.collect(); | |
(id, tile) | |
}) | |
.collect() | |
} | |
// rotate right 90 degrees | |
fn rotate(tile: &mut Vec<Vec<u8>>) { | |
let width = tile[0].len(); | |
let mut temp = tile.clone(); | |
for row in 0..width { | |
for col in 0..width { | |
let r = col; | |
let c = width - row - 1; | |
temp[r][c] = tile[row][col]; | |
} | |
} | |
*tile = temp; | |
} | |
// flip about Y axis | |
fn flip(tile: &mut Vec<Vec<u8>>) { | |
let width = tile[0].len(); | |
for row in 0..width { | |
let tile_row = &mut tile[row]; | |
for col in 0..width / 2 { | |
let c = width - col - 1; | |
let left = tile_row[col]; | |
let right = tile_row[c]; | |
tile_row[col] = right; | |
tile_row[c] = left; | |
} | |
} | |
} | |
fn solve(input: &str) -> (usize, usize) { | |
let tiles = parse(input); | |
// Extract borders for each tile | |
let tile_borders: Vec<(TileId, [Vec<u8>; 8])> = tiles | |
.iter() | |
.map(|(id, tile)| { | |
let top: Vec<u8> = tile[0].clone(); | |
let right: Vec<u8> = tile.iter().map(|row| *row.last().unwrap()).collect(); | |
let bottom: Vec<u8> = tile.last().unwrap().iter().rev().copied().collect(); | |
let left: Vec<u8> = tile.iter().rev().map(|row| *row.first().unwrap()).collect(); | |
let t2: Vec<u8> = top.iter().rev().copied().collect(); | |
let r2: Vec<u8> = left.iter().rev().copied().collect(); | |
let b2: Vec<u8> = bottom.iter().rev().copied().collect(); | |
let l2: Vec<u8> = right.iter().rev().copied().collect(); | |
(*id, [top, right, bottom, left, t2, r2, b2, l2]) | |
}) | |
.collect(); | |
let tile_borders2: HashMap<TileId, &[Vec<u8>; 8]> = tile_borders | |
.iter() | |
.map(|(id, borders)| (*id, borders)) | |
.collect(); | |
let mut unplaced_tiles: HashSet<TileId> = | |
tile_borders.iter().map(|(tile_id, _)| *tile_id).collect(); | |
let mut board: HashMap<Pos, (TileId, Orient)> = Default::default(); | |
let mut open_tiles: HashMap<Pos, Vec<(BorderId, &Vec<u8>)>> = Default::default(); | |
// Initialize | |
let tile = &tile_borders[0]; | |
unplaced_tiles.remove(&tile.0); | |
board.insert(Pos::new(0, 0), (tile.0, 0)); | |
open_tiles.insert(Pos::new(0, 1), vec![(BOTTOM, &tile.1[TOP])]); | |
open_tiles.insert(Pos::new(1, 0), vec![(LEFT, &tile.1[RIGHT])]); | |
open_tiles.insert(Pos::new(0, -1), vec![(TOP, &tile.1[BOTTOM])]); | |
open_tiles.insert(Pos::new(-1, 0), vec![(RIGHT, &tile.1[LEFT])]); | |
let mut closed: HashSet<Pos> = Default::default(); | |
while !unplaced_tiles.is_empty() { | |
// Pick a spot that hasn't been filled | |
let pos = *open_tiles.iter().next().unwrap().0; | |
let constraints = open_tiles.remove(&pos).unwrap(); | |
// Test all unplaced tiles | |
let mut placed_tile: Option<TileId> = None; | |
for &unplaced_tile in &unplaced_tiles { | |
let unplaced_tile_borders = *tile_borders2.get(&unplaced_tile).unwrap(); | |
// Does it have all borders | |
for orient_idx in 0..8 { | |
let orient = &ORIENTS[orient_idx]; | |
let matches = constraints.iter().all(|constraint| { | |
constraint | |
.1 | |
.iter() | |
.rev() | |
.zip(unplaced_tile_borders[orient[constraint.0]].iter()) | |
.all(|(a, b)| a == b) | |
}); | |
if matches { | |
// Success! | |
board.insert(pos, (unplaced_tile, orient_idx)); | |
let neighbor = pos + Offset::new(0, 1); | |
if !board.contains_key(&neighbor) && !closed.contains(&neighbor) { | |
let border = &unplaced_tile_borders[orient[TOP]]; | |
open_tiles | |
.entry(neighbor) | |
.or_default() | |
.push((BOTTOM, border)); | |
} | |
// These four blocks could probably be generalized | |
let neighbor = pos + Offset::new(1, 0); | |
if !board.contains_key(&neighbor) && !closed.contains(&neighbor) { | |
let border = &unplaced_tile_borders[orient[RIGHT]]; | |
open_tiles.entry(neighbor).or_default().push((LEFT, border)); | |
} | |
let neighbor = pos + Offset::new(0, -1); | |
if !board.contains_key(&neighbor) && !closed.contains(&neighbor) { | |
let border = &unplaced_tile_borders[orient[BOTTOM]]; | |
open_tiles.entry(neighbor).or_default().push((TOP, border)); | |
} | |
let neighbor = pos + Offset::new(-1, 0); | |
if !board.contains_key(&neighbor) && !closed.contains(&neighbor) { | |
let border = &unplaced_tile_borders[orient[LEFT]]; | |
open_tiles | |
.entry(neighbor) | |
.or_default() | |
.push((RIGHT, border)); | |
} | |
placed_tile = Some(unplaced_tile); | |
break; | |
} | |
} | |
if placed_tile.is_some() { | |
break; | |
} | |
} | |
if let Some(tile_to_remove) = placed_tile { | |
unplaced_tiles.remove(&tile_to_remove); | |
} else { | |
closed.insert(pos); | |
} | |
} | |
let mut corner = Pos::new(0, 0); | |
for (&pos, _) in &board { | |
corner = fts_vecmath::point2::min(corner, pos); | |
} | |
let dim = (tile_borders.len() as f32).sqrt() as i8; | |
let offset = dim - 1; | |
// Find corners | |
let ll = board.get(&corner).unwrap().0; | |
let lr = board.get(&(corner + Offset::new(offset, 0))).unwrap().0; | |
let ul = board.get(&(corner + Offset::new(0, offset))).unwrap().0; | |
let ur = board | |
.get(&(corner + Offset::new(offset, offset))) | |
.unwrap() | |
.0; | |
// part1 is product of corners | |
let part1 = ul * ur * ll * lr; | |
let mut ocean: Vec<Vec<u8>> = Default::default(); | |
// Correctly orient tiles | |
let ul_pos = corner + Offset::new(0, offset); | |
let lr_pos = ul_pos + Offset::new(offset, -offset); | |
let edge_len = tiles[0].1.len(); | |
let mut oriented_tiles: HashMap<Pos, Vec<Vec<u8>>> = Default::default(); | |
for tile_row in (lr_pos.y..=ul_pos.y).rev() { | |
for tile_col in ul_pos.x..=lr_pos.x { | |
let tile_pos = Pos::new(tile_col, tile_row); | |
let &(tile_id, mut tile_orient) = board.get(&tile_pos).unwrap(); | |
let mut tile = tiles | |
.iter() | |
.find(|(id, _)| tile_id == *id) | |
.unwrap() | |
.1 | |
.clone(); | |
// Flip and rotate | |
if tile_orient > 3 { | |
flip(&mut tile); | |
tile_orient -= 4; | |
} | |
while tile_orient > 0 { | |
rotate(&mut tile); | |
tile_orient -= 1; | |
} | |
oriented_tiles.insert(tile_pos, tile); | |
} | |
} | |
// Build ocean with borders removed | |
for tile_row in (lr_pos.y..=ul_pos.y).rev() { | |
for row in 1..edge_len - 1 { | |
let mut ocean_row: Vec<u8> = Default::default(); | |
for tile_col in ul_pos.x..=lr_pos.x { | |
let tile_pos = Pos::new(tile_col, tile_row); | |
let tile = oriented_tiles.get(&tile_pos).unwrap(); | |
let tile_row = &tile[row]; | |
for col in 1..edge_len - 1 { | |
ocean_row.push(tile_row[col]); | |
} | |
} | |
ocean.push(ocean_row); | |
} | |
} | |
let sea_monster: Vec<Offset> = vec![ | |
Offset::new(18, 0), | |
Offset::new(0, 1), | |
Offset::new(5, 1), | |
Offset::new(6, 1), | |
Offset::new(11, 1), | |
Offset::new(12, 1), | |
Offset::new(17, 1), | |
Offset::new(18, 1), | |
Offset::new(19, 1), | |
Offset::new(1, 2), | |
Offset::new(4, 2), | |
Offset::new(7, 2), | |
Offset::new(10, 2), | |
Offset::new(13, 2), | |
Offset::new(16, 2), | |
]; | |
// Look for sea monster | |
let r: Box<dyn Fn(&mut Vec<Vec<u8>>)> = | |
Box::new(|mut ocean: &mut Vec<Vec<u8>>| rotate(&mut ocean)); | |
let f: Box<dyn Fn(&mut Vec<Vec<u8>>)> = | |
Box::new(|mut ocean: &mut Vec<Vec<u8>>| flip(&mut ocean)); | |
let transforms: Vec<_> = vec![&r, &r, &r, &f, &r, &r, &r]; | |
let ocean_dim = ocean.len(); | |
assert_eq!(ocean[0].len(), ocean_dim); | |
let mut transform_iter = transforms.iter(); | |
let part2 = loop { | |
// Check for sea monster | |
let mut num_sea_monsters = 0; | |
for row in 0..ocean_dim - 3 { | |
for col in 0..ocean_dim - 19 { | |
let pos = Pos::new(col as i8, row as i8); | |
let is_sea_monster = sea_monster | |
.iter() | |
.map(|offset| pos + *offset) | |
.all(|p| ocean[p.y as usize][p.x as usize] == '#' as u8); | |
if is_sea_monster { | |
num_sea_monsters += 1; | |
} | |
} | |
} | |
// Count number of non-sea monster waves | |
if num_sea_monsters > 0 { | |
let num_waves = ocean | |
.iter() | |
.flat_map(|row| row.iter()) | |
.filter(|&&v| v == '#' as u8) | |
.count(); | |
break num_waves - num_sea_monsters * sea_monster.len(); | |
} | |
// Apply transform | |
transform_iter.next().unwrap()(&mut ocean); | |
}; | |
(part1, part2) | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn examples() { | |
let input = crate::data::_DAY20_EXAMPLE_1.strip_carriage_return(); | |
let (part1, part2) = solve(&input); | |
assert_eq!(part1, 20899048083289); | |
assert_eq!(part2, 273); | |
} | |
#[test] | |
fn verify() { | |
let input = crate::data::DAY20.strip_carriage_return(); | |
let (part1, part2) = solve(&input); | |
assert_eq!(part1, 83775126454273); | |
assert_eq!(part2, 1993); | |
} | |
} | |
} | |
pub mod day21 { | |
use itertools::Itertools; | |
use std::collections::{HashMap, HashSet}; | |
use std::fmt::Write; | |
pub fn run() -> String { | |
let mut result = String::with_capacity(128); | |
let (answer_part1, answer_part2) = solve(crate::data::DAY21); | |
writeln!(&mut result, "Day 21, Problem 1 - [{}]", answer_part1).unwrap(); | |
writeln!(&mut result, "Day 21, Problem 2 - [{}]", answer_part2).unwrap(); | |
result | |
} | |
fn parse(input: &str) -> Vec<(Vec<&str>, Vec<&str>)> { | |
input | |
.lines() | |
.map(|line| { | |
let parts: Vec<_> = line.split(" (contains ").collect(); | |
let ingredients = parts[0].split(" ").collect(); | |
// Trim closing paren | |
let len = parts[1].len(); | |
let allergens = parts[1][..len - 1].split(", ").collect(); | |
(ingredients, allergens) | |
}) | |
.collect() | |
} | |
fn solve(input: &str) -> (usize, String) { | |
let data = parse(input); | |
let all_ingredients: HashSet<&str> = data | |
.iter() | |
.flat_map(|(ingredients, _)| ingredients.iter().copied()) | |
.collect(); | |
let mut maybe_allergen: HashMap<&str, HashSet<&str>> = Default::default(); | |
// First pass | |
for (ingredients, allergens) in &data { | |
let ingredients_set: HashSet<_> = ingredients.iter().copied().collect(); | |
for &allergen in allergens { | |
if !maybe_allergen.contains_key(allergen) { | |
maybe_allergen.insert(allergen, ingredients_set.clone()); | |
} else { | |
let new_set = maybe_allergen | |
.get(allergen) | |
.unwrap() | |
.intersection(&ingredients_set) | |
.copied() | |
.collect(); | |
maybe_allergen.insert(allergen, new_set); | |
} | |
} | |
} | |
// Reduce | |
let mut bad_ingredients: HashSet<&str> = Default::default(); | |
let mut solved_allergens: HashMap<&str, &str> = Default::default(); | |
loop { | |
let solved_allergen = maybe_allergen.iter().find_map(|(allergen, ingredients)| { | |
if ingredients.len() == 1 && !bad_ingredients.contains(allergen) { | |
Some((allergen, *ingredients.iter().next().unwrap())) | |
} else { | |
None | |
} | |
}); | |
if let Some((allergen, ingredient)) = solved_allergen { | |
solved_allergens.insert(allergen, ingredient); | |
for (_, ingredients) in &mut maybe_allergen { | |
ingredients.remove(ingredient); | |
bad_ingredients.insert(ingredient); | |
} | |
} else { | |
break; | |
} | |
} | |
let safe_ingredients: HashSet<_> = all_ingredients.difference(&bad_ingredients).collect(); | |
let part1 = data | |
.iter() | |
.flat_map(|(ingredients, _)| ingredients.iter()) | |
.filter(|ingredient| safe_ingredients.contains(ingredient)) | |
.count(); | |
let part2: String = solved_allergens | |
.iter() | |
.sorted_by_key(|(allergen, _)| *allergen) | |
.map(|(_, ingredient)| ingredient) | |
.join(","); | |
(part1, part2) | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn examples() { | |
let (part1, part2) = solve(crate::data::_DAY21_EXAMPLE_1); | |
assert_eq!(part1, 5); | |
assert_eq!(part2, "mxmxvkd,sqjhc,fvjkl"); | |
} | |
#[test] | |
fn verify() { | |
let (part1, part2) = solve(crate::data::DAY21); | |
assert_eq!(part1, 2280); | |
assert_eq!( | |
part2, | |
"vfvvnm,bvgm,rdksxt,xknb,hxntcz,bktzrz,srzqtccv,gbtmdb" | |
); | |
} | |
} | |
} | |
pub mod day22 { | |
use crate::util::*; | |
use std::collections::hash_map::DefaultHasher; | |
use std::collections::{HashSet, VecDeque}; | |
use std::fmt::Write; | |
use std::hash::{Hash, Hasher}; | |
pub fn run() -> String { | |
let mut result = String::with_capacity(128); | |
let input = crate::data::DAY22.strip_carriage_return(); | |
let answer_part1 = part1(&input); | |
writeln!(&mut result, "Day 22, Problem 1 - [{}]", answer_part1).unwrap(); | |
let answer_part2 = part2(&input); | |
writeln!(&mut result, "Day 22, Problem 2 - [{}]", answer_part2).unwrap(); | |
result | |
} | |
fn parse(input: &str) -> Vec<VecDeque<u8>> { | |
input | |
.split("\n\n") | |
.map(|section| { | |
section | |
.lines() | |
.skip(1) | |
.map(|line| line.parse::<u8>().unwrap()) | |
.collect() | |
}) | |
.collect() | |
} | |
fn part1(input: &str) -> usize { | |
let mut decks = parse(input); | |
let winner_idx = loop { | |
let a = decks[0].pop_front().unwrap(); | |
let b = decks[1].pop_front().unwrap(); | |
let winner_idx = if a > b { 0 } else { 1 }; | |
decks[winner_idx].push_back(a.max(b)); | |
decks[winner_idx].push_back(a.min(b)); | |
let loser_idx = 1 - winner_idx; | |
if decks[loser_idx].is_empty() { | |
break winner_idx; | |
} | |
}; | |
decks[winner_idx] | |
.iter() | |
.rev() | |
.enumerate() | |
.map(|(i, v)| (i + 1) * (*v as usize)) | |
.sum() | |
} | |
fn part2(input: &str) -> usize { | |
let mut decks = parse(input); | |
fn play_recursive_game(decks: &mut Vec<VecDeque<u8>>) -> usize { | |
let mut game_states: HashSet<u64> = Default::default(); | |
let mut duplicate_round = move |decks: &Vec<VecDeque<u8>>| -> bool { | |
let mut hasher = DefaultHasher::new(); | |
decks[0].hash(&mut hasher); | |
decks[1].hash(&mut hasher); | |
let hash = hasher.finish(); | |
let new_state = game_states.insert(hash); | |
!new_state | |
}; | |
loop { | |
if duplicate_round(&decks) { | |
return 0; | |
} | |
let a = decks[0].pop_front().unwrap(); | |
let b = decks[1].pop_front().unwrap(); | |
let winner_idx = if decks[0].len() >= a as usize && decks[1].len() >= b as usize { | |
// Recursive combat! | |
let mut new_decks: Vec<VecDeque<u8>> = vec![ | |
decks[0].iter().take(a as usize).copied().collect(), | |
decks[1].iter().take(b as usize).copied().collect(), | |
]; | |
play_recursive_game(&mut new_decks) | |
} else { | |
if a > b { | |
0 | |
} else { | |
1 | |
} | |
}; | |
let cards = [a, b]; | |
decks[winner_idx].push_back(cards[winner_idx]); | |
decks[winner_idx].push_back(cards[1 - winner_idx]); | |
let loser_idx = 1 - winner_idx; | |
if decks[loser_idx].is_empty() { | |
return winner_idx; | |
} | |
} | |
} | |
let winner_idx = play_recursive_game(&mut decks); | |
decks[winner_idx] | |
.iter() | |
.rev() | |
.enumerate() | |
.map(|(i, v)| (i + 1) * (*v as usize)) | |
.sum() | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn examples() { | |
assert_eq!( | |
part1(&crate::data::_DAY22_EXAMPLE_1.strip_carriage_return()), | |
306 | |
); | |
assert_eq!( | |
part2(&crate::data::_DAY22_EXAMPLE_1.strip_carriage_return()), | |
291 | |
); | |
} | |
#[test] | |
fn verify() { | |
let input = crate::data::DAY22.strip_carriage_return(); | |
assert_eq!(part1(&input), 33559); | |
assert_eq!(part2(&input), 32789); | |
} | |
} | |
} | |
pub mod day23 { | |
use itertools::Itertools; | |
use std::fmt::Write; | |
pub fn run() -> String { | |
let mut result = String::with_capacity(128); | |
let answer_part1 = solve("952316487", 100, true); | |
writeln!(&mut result, "Day 23, Problem 1 - [{}]", answer_part1).unwrap(); | |
let answer_part2 = solve("952316487", 10_000_000, false); | |
writeln!(&mut result, "Day 23, Problem 2 - [{}]", answer_part2).unwrap(); | |
result | |
} | |
fn solve(input: &str, num_steps: usize, part1: bool) -> String { | |
let mut nums: Vec<usize> = input | |
.chars() | |
.map(|c| c.to_string().parse::<usize>().unwrap()) | |
.collect(); | |
// Part2: add n..=1_000_000 | |
if !part1 { | |
nums.reserve(1_000_0001); | |
for i in 10..=1_000_000 { | |
nums.push(i); | |
} | |
assert_eq!(nums.len(), 1_000_000); | |
} | |
let last_num = if part1 { 9 } else { 1_000_000 }; | |
// Initialize linked list | |
let mut linked_list: Vec<usize> = Default::default(); | |
linked_list.resize(nums.len() + 1, 0); | |
for (&num, &next) in nums.iter().tuple_windows() { | |
linked_list[num] = next; | |
} | |
linked_list[0] = 0; // unused | |
linked_list[*nums.last().unwrap()] = nums[0]; // wrap | |
// Run N steps | |
let mut cur_value = nums[0]; | |
for _step in 0..num_steps { | |
// Remove three cups | |
let a = linked_list[cur_value]; | |
let b = linked_list[a]; | |
let c = linked_list[b]; | |
linked_list[cur_value] = linked_list[c]; | |
// Identify destination cup | |
let mut dst = cur_value - 1; | |
while dst == a || dst == b || dst == c || dst == 0 { | |
if dst <= 1 { | |
dst = last_num; | |
} else { | |
dst -= 1; | |
} | |
} | |
// Insert | |
let next = linked_list[dst]; | |
linked_list[dst] = a; | |
linked_list[c] = next; | |
// Advance | |
cur_value = linked_list[cur_value]; | |
} | |
if part1 { | |
let mut answer: Vec<usize> = Default::default(); | |
cur_value = 1; | |
for _ in 0..nums.len() - 1 { | |
cur_value = linked_list[cur_value]; | |
answer.push(cur_value); | |
} | |
answer.iter().map(|v| v.to_string()).join("") | |
} else { | |
let a = linked_list[1]; | |
let b = linked_list[a]; | |
(a * b).to_string() | |
} | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn examples() { | |
let input = "389125467"; | |
assert_eq!(solve(&input, 10, true), "92658374"); | |
assert_eq!(solve(&input, 100, true), "67384529"); | |
assert_eq!(solve(&input, 10_000_000, false), "149245887792"); | |
} | |
#[test] | |
fn verify() { | |
let input = "952316487"; | |
assert_eq!(solve(input, 100, true), "25398647"); | |
assert_eq!(solve(&input, 10_000_000, false), "363807398885"); | |
} | |
} | |
} | |
pub mod day24 { | |
use fts_vecmath::point2::*; | |
use fts_vecmath::vector2::*; | |
use regex::Regex; | |
use std::collections::{HashMap, HashSet}; | |
use std::fmt::Write; | |
type Pos = Point2<i8>; | |
type Offset = Vector2<i8>; | |
#[derive(Copy, Clone, Debug)] | |
enum HexDir { | |
W, | |
E, | |
SW, | |
SE, | |
NW, | |
NE, | |
} | |
pub fn run() -> String { | |
let mut result = String::with_capacity(128); | |
let (answer_part1, answer_part2) = solve(crate::data::DAY24); | |
writeln!(&mut result, "Day 24, Problem 1 - [{}]", answer_part1).unwrap(); | |
writeln!(&mut result, "Day 24, Problem 2 - [{}]", answer_part2).unwrap(); | |
result | |
} | |
fn parse(input: &str) -> Vec<Vec<HexDir>> { | |
let re = Regex::new("(w|e|sw|se|nw|ne)").unwrap(); | |
input | |
.lines() | |
.map(|line| { | |
re.captures_iter(line) | |
.map(|cap| match &cap[1] { | |
"w" => HexDir::W, | |
"e" => HexDir::E, | |
"sw" => HexDir::SW, | |
"se" => HexDir::SE, | |
"nw" => HexDir::NW, | |
"ne" => HexDir::NE, | |
_ => unreachable!(), | |
}) | |
.collect() | |
}) | |
.collect() | |
} | |
fn move_pos(pos: Pos, dir: HexDir) -> Pos { | |
// [EvenRowOffsets, OddRowOffsets] | |
const OFFSETS: [[Offset; 6]; 2] = [ | |
[ | |
Offset { x: -1, y: 0 }, // W | |
Offset { x: 1, y: 0 }, // E | |
Offset { x: -1, y: 1 }, // SW | |
Offset { x: 0, y: 1 }, // SE | |
Offset { x: -1, y: -1 }, // NW | |
Offset { x: 0, y: -1 }, // NE | |
], | |
[ | |
Offset { x: -1, y: 0 }, // W | |
Offset { x: 1, y: 0 }, // E | |
Offset { x: 0, y: 1 }, // SW | |
Offset { x: 1, y: 1 }, // SE | |
Offset { x: 0, y: -1 }, // NW | |
Offset { x: 1, y: -1 }, // NE | |
], | |
]; | |
let idx = if pos.y % 2 == 0 { 0 } else { 1 }; | |
pos + OFFSETS[idx][dir as usize] | |
} | |
fn solve(input: &str) -> (usize, usize) { | |
let data = parse(input); | |
let mut board: HashMap<Pos, bool> = Default::default(); | |
// Part 1 | |
for steps in &data { | |
let pos = steps | |
.iter() | |
.fold(Pos::zero(), |pos, step| move_pos(pos, *step)); | |
let is_black = board.entry(pos).or_default(); | |
*is_black = !*is_black; | |
} | |
let part1 = board.iter().filter(|(_, is_black)| **is_black).count(); | |
// Part 2 | |
const DIRS: [HexDir; 6] = [ | |
HexDir::W, | |
HexDir::E, | |
HexDir::SW, | |
HexDir::SE, | |
HexDir::NW, | |
HexDir::NE, | |
]; | |
for _day in 0..100 { | |
let mut new_board: HashMap<Pos, bool> = Default::default(); | |
// All tiles that might flip (this could be better) | |
let tiles: HashSet<Pos> = board | |
.iter() | |
.filter(|(_, is_black)| **is_black) | |
.flat_map(|(pos, _)| { | |
let pos = *pos; | |
DIRS.iter().map(move |dir| move_pos(pos, *dir)) | |
}) | |
.collect(); | |
// Check all tiles | |
for &pos in &tiles { | |
// Current tile is black | |
let is_black = *board.get(&pos).unwrap_or(&false); | |
// Count neighbors | |
let black_neighbors = DIRS | |
.iter() | |
.map(|dir| move_pos(pos, *dir)) | |
.map(|pos| board.get(&pos).unwrap_or(&false)) | |
.filter(|is_black| **is_black) | |
.count(); | |
// Maybe flip | |
if is_black && (black_neighbors == 0 || black_neighbors > 2) { | |
new_board.insert(pos, false); | |
} else if !is_black && black_neighbors == 2 { | |
new_board.insert(pos, true); | |
} else { | |
new_board.insert(pos, is_black); | |
} | |
} | |
board = new_board; | |
} | |
let part2 = board.iter().filter(|(_, is_black)| **is_black).count(); | |
(part1, part2) | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn examples() { | |
let steps = parse("nwwswee"); | |
assert_eq!( | |
Pos::zero(), | |
steps[0] | |
.iter() | |
.fold(Pos::zero(), |pos, step| move_pos(pos, *step)) | |
); | |
let (part1, part2) = solve(crate::data::_DAY24_EXAMPLE_1); | |
assert_eq!(part1, 10); | |
assert_eq!(part2, 2208); | |
} | |
#[test] | |
fn verify() { | |
let (part1, part2) = solve(crate::data::DAY24); | |
assert_eq!(part1, 346); | |
assert_eq!(part2, 3802); | |
} | |
} | |
} | |
pub mod day25 { | |
use std::fmt::Write; | |
pub fn run() -> String { | |
let mut result = String::with_capacity(128); | |
let answer_part1 = part1(18356117, 5909654); | |
writeln!(&mut result, "Day 25, Problem 1 - [{}]", answer_part1).unwrap(); | |
result | |
} | |
fn transform(subject_number: usize, loop_size: usize) -> usize { | |
let mut value = 1; | |
for _ in 0..loop_size { | |
value = (value * subject_number) % 20201227; | |
} | |
value | |
} | |
fn calc_loop_size(subject_number: usize, desired_result: usize) -> usize { | |
let mut loop_count = 0; | |
let mut value = 1; | |
while value != desired_result { | |
value = (value * subject_number) % 20201227; | |
loop_count += 1; | |
} | |
loop_count | |
} | |
fn part1(card_public_key: usize, door_public_key: usize) -> usize { | |
let card_loop_size = calc_loop_size(7, card_public_key); | |
transform(door_public_key, card_loop_size) | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn examples() { | |
assert_eq!(transform(7, 8), 5764801); | |
assert_eq!(part1(5764801, 17807724), 14897079); | |
} | |
#[test] | |
fn verify() { | |
assert_eq!(part1(18356117, 5909654), 16902792); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment