refactor for yearly
This commit is contained in:
parent
da00eb3606
commit
dddda24957
57 changed files with 3 additions and 0 deletions
104
2023/src/bin/day01.rs
Normal file
104
2023/src/bin/day01.rs
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
use aoc23::prelude::*;
|
||||
|
||||
fn part1(input: String) -> Result<usize> {
|
||||
let mut sum = 0;
|
||||
for line in input.lines().into_iter() {
|
||||
let chars = line.chars();
|
||||
let first_num: usize = chars
|
||||
.clone()
|
||||
.into_iter()
|
||||
.skip_while(|c| !c.is_digit(10))
|
||||
.take(1)
|
||||
// This would get continuous digits: .take_while(|c| c.is_digit(10))
|
||||
.collect::<String>()
|
||||
.parse()?;
|
||||
let last_num: usize = chars
|
||||
.into_iter()
|
||||
.rev()
|
||||
.skip_while(|c| !c.is_digit(10))
|
||||
.take(1)
|
||||
.collect::<String>()
|
||||
.parse()?;
|
||||
let value = first_num * 10 + last_num;
|
||||
sum += value;
|
||||
}
|
||||
|
||||
// println!("Answer: {}", sum);
|
||||
Ok(sum)
|
||||
}
|
||||
|
||||
fn num_to_word(num: u32) -> Option<&'static str> {
|
||||
match num {
|
||||
1 => Some("one"),
|
||||
2 => Some("two"),
|
||||
3 => Some("three"),
|
||||
4 => Some("four"),
|
||||
5 => Some("five"),
|
||||
6 => Some("six"),
|
||||
7 => Some("seven"),
|
||||
8 => Some("eight"),
|
||||
9 => Some("nine"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn first_occurrence(str: &str, reversed: bool) -> Option<u32> {
|
||||
if reversed {
|
||||
for (i, char) in str.chars().rev().enumerate() {
|
||||
let digit = char.to_digit(10).or_else(|| {
|
||||
for num in 1..=9 {
|
||||
if let Some(num_word) = num_to_word(num) {
|
||||
if i + 1 >= num_word.len()
|
||||
&& &str[str.len() - i - 1..str.len()][..num_word.len()] == num_word
|
||||
{
|
||||
return Some(num);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
});
|
||||
if digit.is_some() {
|
||||
return digit;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (i, char) in str.chars().enumerate() {
|
||||
let digit = char.to_digit(10).or_else(|| {
|
||||
for num in 1..=9 {
|
||||
if let Some(num_word) = num_to_word(num) {
|
||||
if i + 1 >= num_word.len() && &str[i + 1 - num_word.len()..=i] == num_word {
|
||||
return Some(num);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
});
|
||||
if digit.is_some() {
|
||||
return digit;
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn part_2(input: String) -> Result<u32> {
|
||||
let mut sum = 0;
|
||||
for line in input.lines().into_iter() {
|
||||
let first_num = first_occurrence(line, false).ok_or("No number found in line")?;
|
||||
let last_num = first_occurrence(line, true).ok_or("No number found in line reversed")?;
|
||||
let value = first_num * 10 + last_num;
|
||||
sum += value;
|
||||
println!("Line {line}: a:{first_num}|b:{last_num} = {value} ++ {sum}");
|
||||
}
|
||||
Ok(sum)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
println!("Day 1");
|
||||
println!("=====");
|
||||
let input = utils::aoc::get_puzzle_input(1).await?;
|
||||
println!("part 1: {}", part1(input.clone())?);
|
||||
println!("part 2: {}", part_2(input.clone())?);
|
||||
Ok(())
|
||||
}
|
||||
144
2023/src/bin/day01_revised.rs
Normal file
144
2023/src/bin/day01_revised.rs
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
use aoc23::prelude::*;
|
||||
|
||||
fn part_1(input: String) -> Result<usize> {
|
||||
let mut sum = 0;
|
||||
for line in input.lines().into_iter() {
|
||||
let chars = line.chars();
|
||||
let first_num: usize = chars
|
||||
.clone()
|
||||
.into_iter()
|
||||
.skip_while(|c| !c.is_digit(10))
|
||||
.take(1)
|
||||
// This would get continuous digits: .take_while(|c| c.is_digit(10))
|
||||
.collect::<String>()
|
||||
.parse()?;
|
||||
let last_num: usize = chars
|
||||
.into_iter()
|
||||
.rev()
|
||||
.skip_while(|c| !c.is_digit(10))
|
||||
.take(1)
|
||||
.collect::<String>()
|
||||
.parse()?;
|
||||
let value = first_num * 10 + last_num;
|
||||
sum += value;
|
||||
}
|
||||
|
||||
// println!("Answer: {}", sum);
|
||||
Ok(sum)
|
||||
}
|
||||
|
||||
fn part_2(input: String) -> Result<u32> {
|
||||
let numbers: [(&'static str, u32); 9] = [
|
||||
("one", 1),
|
||||
("two", 2),
|
||||
("three", 3),
|
||||
("four", 4),
|
||||
("five", 5),
|
||||
("six", 6),
|
||||
("seven", 7),
|
||||
("eight", 8),
|
||||
("nine", 9),
|
||||
];
|
||||
let mut sum = 0;
|
||||
for line in input.lines().into_iter() {
|
||||
let mut chars = line.chars();
|
||||
let mut peek_index = 0;
|
||||
let first_num = loop {
|
||||
// I wanted this to work but peek advances the iterator anyways despite saying it
|
||||
// doesn't. I don't understand
|
||||
//
|
||||
// ```
|
||||
// if let Some(Some(peek_digit)) = chars.by_ref().peekable().peek().map(|c| c.to_digit(10))
|
||||
// ```
|
||||
if let Some(Some(peek_digit)) = line.chars().nth(peek_index).map(|c| c.to_digit(10)) {
|
||||
break peek_digit;
|
||||
}
|
||||
if let Some((_, digit)) = numbers.iter().find(|num| chars.as_str().starts_with(num.0)) {
|
||||
break *digit;
|
||||
}
|
||||
if chars.next().is_none() {
|
||||
break 0;
|
||||
}
|
||||
peek_index += 1;
|
||||
};
|
||||
let last_num = loop {
|
||||
if let Some((_, digit)) = numbers.iter().find(|num| chars.as_str().ends_with(num.0)) {
|
||||
break *digit;
|
||||
}
|
||||
if let Some(last_char) = chars.next_back() {
|
||||
if let Some(digit) = last_char.to_digit(10) {
|
||||
break digit;
|
||||
}
|
||||
} else {
|
||||
break first_num;
|
||||
}
|
||||
};
|
||||
let value = first_num * 10 + last_num;
|
||||
sum += value;
|
||||
// println!("Line {line}: a:{first_num}|b:{last_num} = {value} ++ {sum}");
|
||||
}
|
||||
Ok(sum)
|
||||
}
|
||||
|
||||
fn part_2_no_peek(input: String) -> Result<u32> {
|
||||
// We can just add the text versions of the number as well and this removes the
|
||||
// need to peek at the digit like in the above method. Idk what I like better...
|
||||
let numbers: [(&'static str, u32); 18] = [
|
||||
("one", 1),
|
||||
("1", 1),
|
||||
("two", 2),
|
||||
("2", 2),
|
||||
("three", 3),
|
||||
("3", 3),
|
||||
("four", 4),
|
||||
("4", 4),
|
||||
("five", 5),
|
||||
("5", 5),
|
||||
("six", 6),
|
||||
("6", 6),
|
||||
("seven", 7),
|
||||
("7", 7),
|
||||
("eight", 8),
|
||||
("8", 8),
|
||||
("nine", 9),
|
||||
("9", 9),
|
||||
];
|
||||
let mut sum = 0;
|
||||
for line in input.lines().into_iter() {
|
||||
let mut chars = line.chars();
|
||||
let first_num = loop {
|
||||
if let Some((_, digit)) = numbers.iter().find(|num| chars.as_str().starts_with(num.0)) {
|
||||
break *digit;
|
||||
}
|
||||
if chars.next().is_none() {
|
||||
break 0;
|
||||
}
|
||||
};
|
||||
let last_num = loop {
|
||||
if let Some((_, digit)) = numbers.iter().find(|num| chars.as_str().ends_with(num.0)) {
|
||||
break *digit;
|
||||
}
|
||||
if chars.next_back().is_none() {
|
||||
break first_num;
|
||||
}
|
||||
};
|
||||
let value = first_num * 10 + last_num;
|
||||
sum += value;
|
||||
// println!("Line {line}: a:{first_num}|b:{last_num} = {value} ++ {sum}");
|
||||
}
|
||||
Ok(sum)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
println!("Day 1");
|
||||
println!("=====");
|
||||
let input = utils::aoc::get_puzzle_input(1).await?;
|
||||
println!("part 1: {}", part_1(input.clone())?);
|
||||
println!("part 2: {}\t[peek method]", part_2(input.clone())?);
|
||||
println!(
|
||||
"part 2: {}\t[no peek method]",
|
||||
part_2_no_peek(input.clone())?
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
134
2023/src/bin/day02.rs
Normal file
134
2023/src/bin/day02.rs
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
use aoc23::prelude::*;
|
||||
use std::{collections::HashMap, fmt::Display, str::FromStr};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||
enum Cube {
|
||||
Blue,
|
||||
Red,
|
||||
Green,
|
||||
}
|
||||
|
||||
impl FromStr for Cube {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
match s {
|
||||
"blue" => Ok(Cube::Blue),
|
||||
"red" => Ok(Cube::Red),
|
||||
"green" => Ok(Cube::Green),
|
||||
_ => Err("asd".into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
struct Game {
|
||||
id: u32,
|
||||
sets: Vec<HashMap<Cube, u32>>,
|
||||
}
|
||||
|
||||
impl FromStr for Game {
|
||||
type Err = String;
|
||||
|
||||
// Game 1: 7 green, 4 blue, 3 red; 4 blue, 10 red, 1 green; 1 blue, 9 red Game 2:
|
||||
// 2 red, 4 blue, 3 green; 5 green, 3 red, 1 blue; 3 green, 5 blue, 3 red Game 3:
|
||||
// 12 red, 1 blue; 6 red, 2 green, 3 blue; 2 blue, 5 red, 3 green
|
||||
fn from_str(line: &str) -> std::result::Result<Self, Self::Err> {
|
||||
let parts: Vec<&str> = line.split(':').collect();
|
||||
let id = parts[0]
|
||||
.trim()
|
||||
.split_whitespace()
|
||||
.last()
|
||||
.ok_or("no id")?
|
||||
.parse::<u32>()
|
||||
.ok()
|
||||
.ok_or("not a number")?;
|
||||
let sets = parts[1]
|
||||
.split(';')
|
||||
.map(|set| {
|
||||
set.split(',')
|
||||
.filter_map(|part| {
|
||||
let mut iter = part.trim().split_whitespace();
|
||||
let count = iter.next().unwrap().parse::<u32>().unwrap();
|
||||
let color = iter.next().unwrap().parse::<Cube>().ok()?;
|
||||
Some((color, count))
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
Ok(Game { id, sets })
|
||||
}
|
||||
}
|
||||
|
||||
fn part1(input: String) -> Result<impl Display> {
|
||||
let games: Vec<Game> = input
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|l| l.parse().expect("failed to parse line"))
|
||||
.collect();
|
||||
let mut possible_game_id_sum = 0;
|
||||
let limit_red = 12;
|
||||
let limit_green = 13;
|
||||
let limit_blue = 14;
|
||||
for game in games {
|
||||
let out_of_bounds = game.sets.iter().any(|set| match set.get(&Cube::Red) {
|
||||
Some(red) => red > &limit_red,
|
||||
None => false,
|
||||
} || match set.get(&Cube::Green) {
|
||||
Some(green) => green > &limit_green,
|
||||
None => false,
|
||||
} || match set.get(&Cube::Blue) {
|
||||
Some(blue) => blue > &limit_blue,
|
||||
None => false,
|
||||
});
|
||||
if !out_of_bounds {
|
||||
// println!("Valid game id, {:?}", game);
|
||||
possible_game_id_sum += game.id;
|
||||
} else {
|
||||
// println!("INVALID game {:?}", game);
|
||||
}
|
||||
}
|
||||
Ok(possible_game_id_sum)
|
||||
}
|
||||
|
||||
fn part2(input: String) -> Result<impl Display> {
|
||||
let games: Vec<Game> = input
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|l| l.parse().expect("failed to parse line"))
|
||||
.collect();
|
||||
let mut power_sum = 0;
|
||||
for game in games {
|
||||
let mut maxes: HashMap<Cube, u32> = [(Cube::Red, 1), (Cube::Green, 1), (Cube::Blue, 1)]
|
||||
.into_iter()
|
||||
.collect();
|
||||
for set in game.sets.iter() {
|
||||
for (cube, &count) in set {
|
||||
let max_count = maxes.get_mut(cube).ok_or("default missing")?;
|
||||
*max_count = u32::max(*max_count, count);
|
||||
}
|
||||
}
|
||||
let power = maxes.values().into_iter().fold(1u32, |acc, v| acc * v);
|
||||
power_sum += power;
|
||||
}
|
||||
Ok(power_sum)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
println!("Day 1");
|
||||
println!("=====");
|
||||
let input = utils::aoc::get_puzzle_input(2).await?;
|
||||
|
||||
// ```
|
||||
// let input = "Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green
|
||||
// Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue
|
||||
// Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red
|
||||
// Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red
|
||||
// Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green"
|
||||
// .to_owned();
|
||||
// ```
|
||||
println!("part 1: {}", part1(input.clone())?);
|
||||
println!("part 2: {}", part2(input.clone())?);
|
||||
Ok(())
|
||||
}
|
||||
189
2023/src/bin/day03.rs
Normal file
189
2023/src/bin/day03.rs
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
use aoc23::prelude::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Item {
|
||||
Digit(u32),
|
||||
Symbol(bool),
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Number {
|
||||
pub value: usize,
|
||||
pub row_index: usize,
|
||||
pub char_start: usize,
|
||||
pub char_end: usize,
|
||||
}
|
||||
|
||||
struct Grid {
|
||||
pub rows: Vec<Vec<Item>>,
|
||||
pub numbers: Vec<Number>,
|
||||
}
|
||||
|
||||
impl Grid {
|
||||
fn get_item(&self, row: usize, col: usize) -> Option<&Item> {
|
||||
match self.rows.get(row) {
|
||||
Some(row) => row.get(col),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_adjacent_items(
|
||||
&self,
|
||||
row: usize,
|
||||
start: usize,
|
||||
end: usize,
|
||||
) -> Vec<(&Item, usize, usize)> {
|
||||
let row_start = if row > 0 { row - 1 } else { 0 };
|
||||
let col_start = if start > 0 { start - 1 } else { 0 };
|
||||
let mut items: Vec<(&Item, usize, usize)> = vec![];
|
||||
for row_i in row_start..=row + 1 {
|
||||
for col_i in col_start..=end + 1 {
|
||||
if row_i != row || col_i < start || col_i > end {
|
||||
let item = self.get_item(row_i, col_i);
|
||||
if let Some(item) = item {
|
||||
items.push((item, row_i, col_i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
fn get_adjacent_numbers(&self, row: usize, col: usize) -> Vec<&Number> {
|
||||
let mut unique_numbers: Vec<&Number> = vec![];
|
||||
for item in self.get_adjacent_items(row, col, col) {
|
||||
match item {
|
||||
(Item::Digit(_), r, c) => {
|
||||
if let Some(number_at) = self
|
||||
.numbers
|
||||
.iter()
|
||||
.find(|n| n.row_index == r && c >= n.char_start && c <= n.char_end)
|
||||
{
|
||||
if unique_numbers.iter().all(|n| n.value != number_at.value) {
|
||||
unique_numbers.push(number_at);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
unique_numbers
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Grid {
|
||||
type Err = BoxE;
|
||||
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
let mut grid = Grid {
|
||||
rows: vec![],
|
||||
numbers: vec![],
|
||||
};
|
||||
for (row_index, line) in s.lines().enumerate() {
|
||||
let mut row: Vec<Item> = vec![];
|
||||
let mut num: Option<usize> = None;
|
||||
let mut char_start = 0;
|
||||
for (char_index, char) in line.trim().chars().enumerate() {
|
||||
if let Some(digit) = char.to_digit(10) {
|
||||
row.push(Item::Digit(digit));
|
||||
num = if let Some(existing_num) = num {
|
||||
Some(existing_num * 10 + digit as usize)
|
||||
} else {
|
||||
char_start = char_index;
|
||||
Some(digit as usize)
|
||||
};
|
||||
} else {
|
||||
if char == '.' {
|
||||
row.push(Item::None);
|
||||
} else {
|
||||
row.push(Item::Symbol(char == '*'))
|
||||
}
|
||||
if let Some(value) = num {
|
||||
grid.numbers.push(Number {
|
||||
value,
|
||||
row_index,
|
||||
char_start,
|
||||
char_end: char_index - 1,
|
||||
});
|
||||
num = None;
|
||||
char_start = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This killed me, I forgot to add a check after the end of the line to see if I
|
||||
// was still "scanning" for a number and adding it. There was 1 single number not
|
||||
// getting parsed that was on the right edge of the grid and I could not figure it
|
||||
// out for a while.
|
||||
if let Some(value) = num {
|
||||
grid.numbers.push(Number {
|
||||
value,
|
||||
row_index,
|
||||
char_start,
|
||||
char_end: line.len() - 1,
|
||||
});
|
||||
}
|
||||
grid.rows.push(row);
|
||||
}
|
||||
Ok(grid)
|
||||
}
|
||||
}
|
||||
|
||||
fn part1(input: String) -> Result<usize> {
|
||||
let grid: Grid = input.parse()?;
|
||||
let mut sum = 0;
|
||||
for number in grid.numbers.iter() {
|
||||
let adjacent =
|
||||
grid.get_adjacent_items(number.row_index, number.char_start, number.char_end);
|
||||
let is_part_no = adjacent
|
||||
.iter()
|
||||
.any(|item| matches!(item, (Item::Symbol(_), _, _)));
|
||||
if is_part_no {
|
||||
sum += number.value;
|
||||
}
|
||||
}
|
||||
Ok(sum)
|
||||
}
|
||||
|
||||
fn part2(input: String) -> Result<usize> {
|
||||
let grid: Grid = input.parse()?;
|
||||
let mut sum = 0;
|
||||
let mut used: Vec<(usize, usize)> = vec![];
|
||||
for number in grid.numbers.iter() {
|
||||
let is_used = used
|
||||
.iter()
|
||||
.any(|(row, char_start)| number.row_index == *row && number.char_start == *char_start);
|
||||
if !is_used {
|
||||
let adjacent =
|
||||
grid.get_adjacent_items(number.row_index, number.char_start, number.char_end);
|
||||
if let Some((_, row, col)) = adjacent
|
||||
.iter()
|
||||
.find(|item| matches!(item.0, Item::Symbol(true)))
|
||||
{
|
||||
let gear_nums = grid.get_adjacent_numbers(*row, *col);
|
||||
if gear_nums.len() >= 2 {
|
||||
// this is lazy.. adds duplicates but w/e
|
||||
let power = gear_nums.iter().fold(1, |pow, gear| pow * gear.value);
|
||||
gear_nums
|
||||
.iter()
|
||||
.for_each(|g| used.push((g.row_index, g.char_start)));
|
||||
sum += power;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(sum)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
println!("Day 3");
|
||||
println!("=====");
|
||||
let input = utils::aoc::get_puzzle_input(3).await?;
|
||||
let part_1 = part1(input.clone())?;
|
||||
println!("part 1: {}", part_1);
|
||||
println!("part 2: {}", part2(input.clone())?);
|
||||
Ok(())
|
||||
}
|
||||
124
2023/src/bin/day04.rs
Normal file
124
2023/src/bin/day04.rs
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
use aoc23::prelude::*;
|
||||
use itertools::Itertools;
|
||||
use std::{collections::VecDeque, str::FromStr, time::Instant};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
|
||||
struct Card {
|
||||
id: usize,
|
||||
part_1_score: usize,
|
||||
part_2_matches: usize,
|
||||
part_2_count: usize,
|
||||
}
|
||||
|
||||
impl FromStr for Card {
|
||||
type Err = BoxE;
|
||||
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
let mut id: Option<usize> = None;
|
||||
let mut winning_numbers: Vec<usize> = vec![];
|
||||
let mut score = 0;
|
||||
let mut matches = 0;
|
||||
for part in s.split(':') {
|
||||
if id.is_some() {
|
||||
for (num_mode, wins_or_nums) in part.split('|').enumerate() {
|
||||
wins_or_nums
|
||||
.trim()
|
||||
.split_whitespace()
|
||||
.into_iter()
|
||||
.map(|num| {
|
||||
num.parse::<usize>()
|
||||
.expect(&format!("could not parse number: {}", num))
|
||||
})
|
||||
.for_each(|num| {
|
||||
if num_mode == 0 {
|
||||
winning_numbers.push(num);
|
||||
} else {
|
||||
if winning_numbers.iter().any(|winner| winner == &num) {
|
||||
matches += 1;
|
||||
score = if score == 0 { 1 } else { score * 2 };
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
id = Some(
|
||||
part.split_whitespace()
|
||||
.last()
|
||||
.ok_or("Failed to get last item")?
|
||||
.parse()?,
|
||||
)
|
||||
}
|
||||
}
|
||||
Ok(Card {
|
||||
id: id.ok_or("no id found")?,
|
||||
part_1_score: score,
|
||||
part_2_matches: matches,
|
||||
part_2_count: 1,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn part1(input: String) -> Result<usize> {
|
||||
Ok(input
|
||||
.lines()
|
||||
.map(|line| line.parse::<Card>())
|
||||
.filter_map(|card| card.ok())
|
||||
.fold(0, |sum, card| sum + card.part_1_score))
|
||||
}
|
||||
|
||||
fn part2(input: String) -> Result<usize> {
|
||||
let mut sum = 0;
|
||||
let cards = input
|
||||
.lines()
|
||||
.map(|line| line.parse::<Card>())
|
||||
.filter_map(|card| card.ok())
|
||||
.collect_vec();
|
||||
let mut queue: VecDeque<&Card> = cards.iter().collect();
|
||||
while let Some(card) = queue.pop_front() {
|
||||
sum += 1;
|
||||
for card_index in card.id..card.id + card.part_2_matches {
|
||||
cards.get(card_index).map(|c| queue.push_back(&c));
|
||||
}
|
||||
}
|
||||
Ok(sum)
|
||||
}
|
||||
|
||||
fn part2_revised(input: String) -> Result<usize> {
|
||||
let mut sum = 0;
|
||||
let mut cards = input
|
||||
.lines()
|
||||
.map(|line| line.parse::<Card>())
|
||||
.filter_map(|card| card.ok())
|
||||
.collect_vec();
|
||||
for i in 0..cards.len() {
|
||||
let (count, matches) = {
|
||||
let card = cards.get(i).ok_or("card not found")?;
|
||||
sum += card.part_2_count;
|
||||
(card.part_2_count, card.part_2_matches)
|
||||
};
|
||||
for card_index in 1..=matches {
|
||||
cards.get_mut(i + card_index).map(|card_below| {
|
||||
card_below.part_2_count += count;
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(sum)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
println!("Day 4");
|
||||
println!("=====");
|
||||
let input = utils::aoc::get_puzzle_input(4).await?;
|
||||
let start = Instant::now();
|
||||
println!("part 1: {}\t[{:?}]", part1(input.clone())?, start.elapsed());
|
||||
let start = Instant::now();
|
||||
println!("part 2: {}\t[{:?}]", part2(input.clone())?, start.elapsed());
|
||||
let start = Instant::now();
|
||||
println!(
|
||||
"part 2: {}\t[{:?}]\t[revised]",
|
||||
part2_revised(input.clone())?,
|
||||
start.elapsed()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
253
2023/src/bin/day05.rs
Normal file
253
2023/src/bin/day05.rs
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
use aoc23::prelude::*;
|
||||
use derive_builder::Builder;
|
||||
use itertools::Itertools;
|
||||
use rayon::prelude::*;
|
||||
use std::{str::FromStr, time::Instant};
|
||||
|
||||
#[derive(Debug, Builder, Clone)]
|
||||
struct Map {
|
||||
destination_start: usize,
|
||||
source_start: usize,
|
||||
length: usize,
|
||||
}
|
||||
|
||||
impl FromStr for Map {
|
||||
type Err = BoxE;
|
||||
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
let mut parts = s
|
||||
.trim()
|
||||
.split_whitespace()
|
||||
.map(|s| s.parse::<usize>().expect("failed to parse"));
|
||||
Ok(MapBuilder::default()
|
||||
.destination_start(parts.next().expect("no dest"))
|
||||
.source_start(parts.next().expect("no start"))
|
||||
.length(parts.next().expect("no length"))
|
||||
.build()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Map {
|
||||
fn includes_source(&self, source: usize) -> bool {
|
||||
source >= self.source_start && source < self.source_start + self.length
|
||||
}
|
||||
|
||||
fn apply(&self, source: usize) -> Option<usize> {
|
||||
if self.includes_source(source) {
|
||||
let diff = source - self.source_start;
|
||||
Some(self.destination_start + diff)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Builder, Clone)]
|
||||
struct Mapper {
|
||||
source: String,
|
||||
destination: String,
|
||||
maps: Vec<Map>,
|
||||
}
|
||||
|
||||
impl FromStr for Mapper {
|
||||
type Err = BoxE;
|
||||
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
let mut mapper = MapperBuilder::default();
|
||||
mapper.maps(vec![]);
|
||||
for line in s.trim().lines() {
|
||||
if mapper.source.is_none() {
|
||||
let mut keys = line
|
||||
.split_whitespace()
|
||||
.next()
|
||||
.expect("no source-dest")
|
||||
.split("-to-");
|
||||
mapper.source(keys.next().expect("no source key").to_owned());
|
||||
mapper.destination(keys.next().expect("no destination key").to_owned());
|
||||
} else if let Some(maps) = &mut mapper.maps {
|
||||
maps.push(line.parse()?);
|
||||
}
|
||||
}
|
||||
Ok(mapper.build()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mapper {
|
||||
fn apply(&self, source: usize) -> usize {
|
||||
self.maps
|
||||
.iter()
|
||||
.find_map(|map| map.apply(source))
|
||||
.unwrap_or(source)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Builder, Clone)]
|
||||
struct Seeds {
|
||||
source: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Builder)]
|
||||
struct Almanac {
|
||||
seeds: Seeds,
|
||||
mappers: Vec<Mapper>,
|
||||
}
|
||||
|
||||
impl Almanac {
|
||||
fn map_source(&self, source: usize, start_key: &str, end_key: &str) -> usize {
|
||||
let mut current_key = start_key;
|
||||
let mut current_value = source;
|
||||
while current_key != end_key {
|
||||
let mapper = self
|
||||
.mappers
|
||||
.iter()
|
||||
.find(|mapper| mapper.source == current_key)
|
||||
.expect("could not find mapper");
|
||||
current_value = mapper.apply(current_value);
|
||||
current_key = &mapper.destination;
|
||||
}
|
||||
current_value
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Almanac {
|
||||
type Err = BoxE;
|
||||
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
let mut almanac = AlmanacBuilder::default();
|
||||
let mut parts = s.trim().split("\n\n");
|
||||
almanac.seeds(Seeds {
|
||||
source: parts.next().expect("seed line missing").to_string(),
|
||||
});
|
||||
almanac.mappers(vec![]);
|
||||
while let Some(mapper_section) = parts.next() {
|
||||
if let Some(mappers) = &mut almanac.mappers {
|
||||
mappers.push(mapper_section.parse()?);
|
||||
}
|
||||
}
|
||||
Ok(almanac.build()?)
|
||||
}
|
||||
}
|
||||
|
||||
fn part1(input: String) -> Result<usize> {
|
||||
// parse
|
||||
let start = Instant::now();
|
||||
let almanac: Almanac = input.parse()?;
|
||||
let parsed_time = start.elapsed();
|
||||
|
||||
// algo
|
||||
let start = Instant::now();
|
||||
let answer = almanac
|
||||
.seeds
|
||||
.source
|
||||
.split(":")
|
||||
.nth(1)
|
||||
.expect("seeds missing")
|
||||
.trim()
|
||||
.split_whitespace()
|
||||
.map(|s| s.parse::<usize>().expect("failed to parse seed as number"))
|
||||
.map(|source| almanac.map_source(source, "seed", "location"))
|
||||
.min()
|
||||
.ok_or("failed to get min location")?;
|
||||
let algo_time = start.elapsed();
|
||||
|
||||
// output
|
||||
println!("Day 5, part 1: {answer}");
|
||||
println!("\tparse: {parsed_time:?}");
|
||||
println!("\talgo: {algo_time:?}");
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
fn part2(input: String) -> Result<usize> {
|
||||
// parse
|
||||
let start = Instant::now();
|
||||
let almanac: Almanac = input.parse()?;
|
||||
let parsed_time = start.elapsed();
|
||||
|
||||
// algo
|
||||
let start = Instant::now();
|
||||
let answer = almanac
|
||||
.seeds
|
||||
.source
|
||||
.split(":")
|
||||
.nth(1)
|
||||
.expect("seeds missing")
|
||||
.trim()
|
||||
.split_whitespace()
|
||||
.map(|s| s.parse::<usize>().expect("failed to parse seed as number"))
|
||||
.tuples()
|
||||
.flat_map(|(start, length)| start..start + length)
|
||||
.take(3000)
|
||||
// Squeeze with rayon for brute force approach
|
||||
.par_bridge()
|
||||
.map(|source| almanac.map_source(source, "seed", "location"))
|
||||
.min()
|
||||
.ok_or("failed to get min location")?;
|
||||
let algo_time = start.elapsed();
|
||||
|
||||
// output
|
||||
println!("Day 5, part 2: {answer}");
|
||||
println!("\tparse: {parsed_time:?}");
|
||||
println!("\talgo: {algo_time:?}");
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
// TODO come back and revise for a faster solution
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let input = utils::aoc::get_puzzle_input(5).await?;
|
||||
part1(input.clone())?;
|
||||
part2(input.clone())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
static DATA: &'static str = "seeds: 79 14 55 13
|
||||
|
||||
seed-to-soil map:
|
||||
50 98 2
|
||||
52 50 48
|
||||
|
||||
soil-to-fertilizer map:
|
||||
0 15 37
|
||||
37 52 2
|
||||
39 0 15
|
||||
|
||||
fertilizer-to-water map:
|
||||
49 53 8
|
||||
0 11 42
|
||||
42 0 7
|
||||
57 7 4
|
||||
|
||||
water-to-light map:
|
||||
88 18 7
|
||||
18 25 70
|
||||
|
||||
light-to-temperature map:
|
||||
45 77 23
|
||||
81 45 19
|
||||
68 64 13
|
||||
|
||||
temperature-to-humidity map:
|
||||
0 69 1
|
||||
1 0 69
|
||||
|
||||
humidity-to-location map:
|
||||
60 56 37
|
||||
56 93 4
|
||||
";
|
||||
|
||||
#[test]
|
||||
fn test_part_1() -> Result<()> {
|
||||
assert_eq!(part1(DATA.to_owned())?, 35);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_part_2() -> Result<()> {
|
||||
assert_eq!(part2(DATA.to_owned())?, 46);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
109
2023/src/bin/day06.rs
Normal file
109
2023/src/bin/day06.rs
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
use aoc23::prelude::*;
|
||||
use derive_builder::Builder;
|
||||
|
||||
static DAY: u8 = 6;
|
||||
|
||||
#[derive(Debug, Builder, Clone)]
|
||||
struct Race {
|
||||
time: usize,
|
||||
record_distance: usize,
|
||||
}
|
||||
|
||||
impl Race {
|
||||
fn get_roots(&self) -> (usize, usize) {
|
||||
let a = -1.0;
|
||||
let b = self.time as f64;
|
||||
let c = -(self.record_distance as f64);
|
||||
let n1 = (-b + f64::sqrt(b * b - (4.0 * a * c))) / (2.0 * a);
|
||||
let n2 = (-b - f64::sqrt(b * b - (4.0 * a * c))) / (2.0 * a);
|
||||
if n1 < n2 {
|
||||
// floor and round up to ensure we win
|
||||
let n1 = f64::floor(n1) as usize + 1;
|
||||
|
||||
// ceil and round down to ensure we don't exceed time cap
|
||||
let n2 = f64::ceil(n2) as usize - 1;
|
||||
(n1, n2)
|
||||
} else {
|
||||
let n1 = f64::ceil(n1) as usize - 1;
|
||||
let n2 = f64::floor(n2) as usize + 1;
|
||||
(n2, n1)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_ways_to_beat_count(&self) -> usize {
|
||||
let (n1, n2) = self.get_roots();
|
||||
if self.time < n1 {
|
||||
0
|
||||
} else {
|
||||
let n2 = usize::min(self.time, n2);
|
||||
n2 - n1 + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_ways_to_beat_in_all_races(races: &[Race]) -> Result<usize> {
|
||||
let mut answer = 1;
|
||||
for race in races {
|
||||
let ways_to_beat = race.get_ways_to_beat_count();
|
||||
answer *= ways_to_beat;
|
||||
}
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
// let input = utils::aoc::get_puzzle_input(DAY).await?;
|
||||
println!("Day {DAY}");
|
||||
println!("=====");
|
||||
let races: [Race; 3] = [
|
||||
Race {
|
||||
time: 7,
|
||||
record_distance: 9,
|
||||
},
|
||||
Race {
|
||||
time: 15,
|
||||
record_distance: 40,
|
||||
},
|
||||
Race {
|
||||
time: 30,
|
||||
record_distance: 200,
|
||||
},
|
||||
];
|
||||
println!(
|
||||
"part 1, example: {}",
|
||||
get_ways_to_beat_in_all_races(&races)?
|
||||
);
|
||||
let races: [Race; 4] = [
|
||||
Race {
|
||||
time: 53,
|
||||
record_distance: 250,
|
||||
},
|
||||
Race {
|
||||
time: 91,
|
||||
record_distance: 1330,
|
||||
},
|
||||
Race {
|
||||
time: 67,
|
||||
record_distance: 1081,
|
||||
},
|
||||
Race {
|
||||
time: 68,
|
||||
record_distance: 1025,
|
||||
},
|
||||
];
|
||||
println!("part 1, real: {}", get_ways_to_beat_in_all_races(&races)?);
|
||||
let races: [Race; 1] = [Race {
|
||||
time: 71530,
|
||||
record_distance: 940200,
|
||||
}];
|
||||
println!(
|
||||
"part 2, example: {}",
|
||||
get_ways_to_beat_in_all_races(&races)?
|
||||
);
|
||||
let races: [Race; 1] = [Race {
|
||||
time: 53916768,
|
||||
record_distance: 250133010811025,
|
||||
}];
|
||||
println!("part 2, real: {}", get_ways_to_beat_in_all_races(&races)?);
|
||||
Ok(())
|
||||
}
|
||||
207
2023/src/bin/day07.rs
Normal file
207
2023/src/bin/day07.rs
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
use aoc23::prelude::*;
|
||||
use derive_builder::Builder;
|
||||
use itertools::Itertools;
|
||||
use std::{cmp::Ordering, str::FromStr, time::Instant};
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)]
|
||||
enum Strength {
|
||||
HighCard,
|
||||
OnePair,
|
||||
TwoPair,
|
||||
ThreeOfAKind,
|
||||
FullHouse,
|
||||
FourOfAKind,
|
||||
FiveOfAKind,
|
||||
}
|
||||
|
||||
impl Strength {
|
||||
#[cfg(feature = "part2")]
|
||||
fn for_hand(hand: &Hand) -> Self {
|
||||
let mut counts = [0; 15];
|
||||
let mut jokers = 0;
|
||||
for card in hand.cards.iter() {
|
||||
if card == &1 {
|
||||
jokers += 1;
|
||||
} else {
|
||||
counts[*card as usize - 2] += 1;
|
||||
}
|
||||
}
|
||||
let mut strength = counts
|
||||
.iter()
|
||||
.fold(Strength::HighCard, |strength, &count| match count {
|
||||
5 => Strength::FiveOfAKind,
|
||||
4 => Strength::FourOfAKind,
|
||||
3 => match strength {
|
||||
Strength::TwoPair => Strength::FullHouse,
|
||||
Strength::OnePair => Strength::FullHouse,
|
||||
_ => Strength::ThreeOfAKind,
|
||||
},
|
||||
2 => match strength {
|
||||
Strength::ThreeOfAKind => Strength::FullHouse,
|
||||
Strength::OnePair => Strength::TwoPair,
|
||||
_ => Strength::OnePair,
|
||||
},
|
||||
_ => strength,
|
||||
});
|
||||
while jokers > 0 {
|
||||
strength = match strength {
|
||||
Strength::HighCard => Strength::OnePair,
|
||||
Strength::OnePair => Strength::ThreeOfAKind,
|
||||
Strength::TwoPair => Strength::FullHouse,
|
||||
Strength::ThreeOfAKind => Strength::FourOfAKind,
|
||||
Strength::FullHouse => Strength::FourOfAKind,
|
||||
Strength::FourOfAKind => Strength::FiveOfAKind,
|
||||
Strength::FiveOfAKind => Strength::FiveOfAKind,
|
||||
};
|
||||
jokers -= 1;
|
||||
}
|
||||
strength
|
||||
}
|
||||
|
||||
#[cfg(feature = "part1")]
|
||||
fn for_hand(hand: &Hand) -> Self {
|
||||
let mut counts = [0; 15];
|
||||
for card in hand.cards.iter() {
|
||||
counts[*card as usize - 2] += 1;
|
||||
}
|
||||
counts
|
||||
.iter()
|
||||
.fold(Strength::HighCard, |strength, &count| match count {
|
||||
5 => Strength::FiveOfAKind,
|
||||
4 => Strength::FourOfAKind,
|
||||
3 => match strength {
|
||||
Strength::TwoPair => Strength::FullHouse,
|
||||
Strength::OnePair => Strength::FullHouse,
|
||||
_ => Strength::ThreeOfAKind,
|
||||
},
|
||||
2 => match strength {
|
||||
Strength::ThreeOfAKind => Strength::FullHouse,
|
||||
Strength::OnePair => Strength::TwoPair,
|
||||
_ => Strength::OnePair,
|
||||
},
|
||||
_ => strength,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Builder, Clone, PartialEq, Eq)]
|
||||
struct Hand {
|
||||
cards: Vec<u32>,
|
||||
bid: usize,
|
||||
}
|
||||
|
||||
impl Ord for Hand {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
let self_strength = Strength::for_hand(self);
|
||||
let other_strength = Strength::for_hand(other);
|
||||
match self_strength.cmp(&other_strength) {
|
||||
Ordering::Equal => self
|
||||
.cards
|
||||
.iter()
|
||||
.zip(other.cards.iter())
|
||||
.map(|(s, o)| s.cmp(o))
|
||||
.skip_while(|ord| matches!(ord, Ordering::Equal))
|
||||
.next()
|
||||
.unwrap_or(Ordering::Equal),
|
||||
Ordering::Less => Ordering::Less,
|
||||
Ordering::Greater => Ordering::Greater,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Hand {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Hand {
|
||||
type Err = BoxE;
|
||||
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
let mut splits = s.trim().split_whitespace();
|
||||
Ok(HandBuilder::default()
|
||||
.cards(
|
||||
splits
|
||||
.next()
|
||||
.unwrap()
|
||||
.chars()
|
||||
.map(|c| {
|
||||
if let Some(digit) = c.to_digit(10) {
|
||||
digit
|
||||
} else {
|
||||
match c {
|
||||
#[cfg(feature = "part1")]
|
||||
'A' => 14,
|
||||
#[cfg(feature = "part2")]
|
||||
'A' => 13,
|
||||
#[cfg(feature = "part1")]
|
||||
'K' => 13,
|
||||
#[cfg(feature = "part2")]
|
||||
'K' => 12,
|
||||
#[cfg(feature = "part1")]
|
||||
'Q' => 12,
|
||||
#[cfg(feature = "part2")]
|
||||
'Q' => 11,
|
||||
#[cfg(feature = "part1")]
|
||||
'J' => 11,
|
||||
#[cfg(feature = "part2")]
|
||||
'J' => 1,
|
||||
'T' => 10,
|
||||
_ => panic!("invalid card: {}", c),
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
.bid(splits.next().unwrap().parse()?)
|
||||
.build()?)
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate(input: String) -> Result<usize> {
|
||||
let start = Instant::now();
|
||||
let answer = input
|
||||
.lines()
|
||||
.map(|line| line.parse::<Hand>().unwrap())
|
||||
.sorted()
|
||||
.enumerate()
|
||||
.map(|(idx, hand)| hand.bid * (idx + 1))
|
||||
.sum();
|
||||
let algo_time = start.elapsed();
|
||||
println!("Day {DAY}: {answer}");
|
||||
println!("\t{algo_time:?}");
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
// TODO come back and revise for a faster solution
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
println!("Day {DAY}");
|
||||
println!("=====");
|
||||
let input = utils::aoc::get_puzzle_input(DAY).await?;
|
||||
calculate(input.clone())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
static DAY: u8 = 7;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
static DATA: &'static str = "32T3K 765
|
||||
T55J5 684
|
||||
KK677 28
|
||||
KTJJT 220
|
||||
QQQJA 483";
|
||||
|
||||
#[test]
|
||||
fn test() -> Result<()> {
|
||||
#[cfg(feature = "part1")]
|
||||
assert_eq!(calculate(DATA.to_owned())?, 6440);
|
||||
#[cfg(feature = "part2")]
|
||||
assert_eq!(calculate(DATA.to_owned())?, 5905);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
212
2023/src/bin/day08.rs
Normal file
212
2023/src/bin/day08.rs
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
use aoc23::prelude::*;
|
||||
use derive_builder::Builder;
|
||||
use itertools::Itertools;
|
||||
use rayon::prelude::*;
|
||||
use std::{collections::HashMap, time::Instant};
|
||||
|
||||
extern crate regex;
|
||||
|
||||
use regex::Regex;
|
||||
use static_init::dynamic;
|
||||
|
||||
// Regex is slow to compile, don't do it in the parse loop, pull it out here and
|
||||
// compile it once and reuse below.
|
||||
#[dynamic]
|
||||
static RE_PARSE_NODE: Regex =
|
||||
Regex::new(r"(?<id>\w{3}).*?(?<left>\w{3}).*?(?<right>\w{3})").expect("re_parse_node invalid");
|
||||
|
||||
#[derive(Debug, Builder, Clone)]
|
||||
struct Node<'a> {
|
||||
id: &'a str,
|
||||
left: &'a str,
|
||||
right: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> Node<'a> {
|
||||
fn new(s: &'a str) -> Result<Self> {
|
||||
let re = RE_PARSE_NODE.captures(s).expect("No match for regex");
|
||||
Ok(NodeBuilder::default()
|
||||
.id(re.name("id").expect("no id").as_str())
|
||||
.left(re.name("left").expect("no left").as_str())
|
||||
.right(re.name("right").expect("no right").as_str())
|
||||
.build()?)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Dir {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
impl Dir {
|
||||
fn from_char(c: &char) -> Self {
|
||||
match c {
|
||||
'L' => Self::Left,
|
||||
'R' => Self::Right,
|
||||
_ => panic!("Not valid dir"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Builder, Clone)]
|
||||
struct Map<'a> {
|
||||
dirs: Vec<Dir>,
|
||||
nodes: HashMap<&'a str, Node<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Map<'a> {
|
||||
fn new(s: &'a str) -> Result<Self> {
|
||||
let mut splits = s.trim().split("\n\n");
|
||||
Ok(MapBuilder::default()
|
||||
.dirs(
|
||||
splits
|
||||
.next()
|
||||
.unwrap()
|
||||
.trim()
|
||||
.chars()
|
||||
.map(|c| Dir::from_char(&c))
|
||||
.collect(),
|
||||
)
|
||||
.nodes(
|
||||
splits
|
||||
.next()
|
||||
.unwrap()
|
||||
.trim()
|
||||
.lines()
|
||||
.map(|a| Node::new(a.trim()).map(|n| (n.id, n)))
|
||||
.try_collect()?,
|
||||
)
|
||||
.build()?)
|
||||
}
|
||||
}
|
||||
|
||||
fn part1(input: String) -> Result<usize> {
|
||||
// parse
|
||||
let start = Instant::now();
|
||||
let map: Map = Map::new(&input)?;
|
||||
let parsed_time = start.elapsed();
|
||||
|
||||
// algo
|
||||
let start = Instant::now();
|
||||
let mut answer = 0;
|
||||
let mut current = map.nodes.get("AAA").expect("no start node");
|
||||
let mut dir = map.dirs.iter().cycle();
|
||||
while !current.id.eq("ZZZ") {
|
||||
match dir.next().unwrap() {
|
||||
Dir::Left => current = map.nodes.get(current.left).expect("no left node"),
|
||||
Dir::Right => current = map.nodes.get(current.right).expect("no right node"),
|
||||
}
|
||||
answer += 1;
|
||||
}
|
||||
let algo_time = start.elapsed();
|
||||
|
||||
// output
|
||||
println!("Day {DAY}, part 1: {answer}");
|
||||
println!("\tparse: {parsed_time:?}");
|
||||
println!("\talgo: {algo_time:?}");
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
fn part2(input: String) -> Result<u64> {
|
||||
// parse
|
||||
let start = Instant::now();
|
||||
let map: Map = Map::new(&input)?;
|
||||
let parsed_time = start.elapsed();
|
||||
|
||||
// algo
|
||||
let start = Instant::now();
|
||||
let currents: Vec<&Node> = map.nodes.values().filter(|n| n.id.ends_with("A")).collect();
|
||||
let steps: Vec<u64> = currents
|
||||
.par_iter()
|
||||
.map(|node| {
|
||||
let mut dir = map.dirs.iter().cycle();
|
||||
let mut steps = 0;
|
||||
let mut current = *node;
|
||||
while !current.id.ends_with("Z") {
|
||||
match dir.next().unwrap() {
|
||||
Dir::Left => current = map.nodes.get(current.left).expect("no left node"),
|
||||
Dir::Right => current = map.nodes.get(current.right).expect("no right node"),
|
||||
}
|
||||
steps += 1;
|
||||
// println!("Step: {answer}"); let _ = std::io::stdout().flush();
|
||||
}
|
||||
steps
|
||||
})
|
||||
.collect();
|
||||
let answer = utils::math::find_lcm(&steps[..]);
|
||||
let algo_time = start.elapsed();
|
||||
|
||||
// output
|
||||
println!("Day {DAY}, part 2: {answer}");
|
||||
println!("\tparse: {parsed_time:?}");
|
||||
println!("\talgo: {algo_time:?}");
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
// TODO come back and revise for a faster solution
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
println!("Day {DAY}");
|
||||
println!("=====");
|
||||
let input = utils::aoc::get_puzzle_input(DAY).await?;
|
||||
part1(input.clone())?;
|
||||
part2(input.clone())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
static DAY: u8 = 8;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_part_1() -> Result<()> {
|
||||
assert_eq!(
|
||||
part1(
|
||||
"RL
|
||||
|
||||
AAA = (BBB, CCC)
|
||||
BBB = (DDD, EEE)
|
||||
CCC = (ZZZ, GGG)
|
||||
DDD = (DDD, DDD)
|
||||
EEE = (EEE, EEE)
|
||||
GGG = (GGG, GGG)
|
||||
ZZZ = (ZZZ, ZZZ)"
|
||||
.to_owned(),
|
||||
)?,
|
||||
2
|
||||
);
|
||||
assert_eq!(
|
||||
part1(
|
||||
"LLR
|
||||
|
||||
AAA = (BBB, BBB)
|
||||
BBB = (AAA, ZZZ)
|
||||
ZZZ = (ZZZ, ZZZ)"
|
||||
.to_owned()
|
||||
)?,
|
||||
6
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_part_2() -> Result<()> {
|
||||
part2(
|
||||
"LR
|
||||
|
||||
11A = (11B, XXX)
|
||||
11B = (XXX, 11Z)
|
||||
11Z = (11B, XXX)
|
||||
22A = (22B, XXX)
|
||||
22B = (22C, 22C)
|
||||
22C = (22Z, 22Z)
|
||||
22Z = (22B, 22B)
|
||||
XXX = (XXX, XXX)"
|
||||
.to_owned(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
131
2023/src/bin/day09.rs
Normal file
131
2023/src/bin/day09.rs
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
use aoc23::prelude::*;
|
||||
use derive_builder::Builder;
|
||||
use itertools::Itertools;
|
||||
use rayon::prelude::*;
|
||||
use std::{arch::x86_64::_MM_FROUND_CUR_DIRECTION, str::FromStr, time::Instant};
|
||||
|
||||
static DAY: u8 = 9;
|
||||
|
||||
fn part1(input: String) -> Result<i64> {
|
||||
let start = Instant::now();
|
||||
let answer: i64 = input
|
||||
.lines()
|
||||
.par_bridge()
|
||||
.map(|line| {
|
||||
let mut sequences: Vec<Vec<i64>> = vec![];
|
||||
line.trim()
|
||||
.split_whitespace()
|
||||
.map(|s| s.parse::<i64>().expect("Failed to parse number in input"))
|
||||
.enumerate()
|
||||
.for_each(|(idx, num)| {
|
||||
// every new number of primary history can result in a new row of depth
|
||||
sequences.push(vec![]);
|
||||
for depth in 0..sequences.len() {
|
||||
if depth == 0 {
|
||||
sequences
|
||||
.get_mut(depth)
|
||||
.expect("top history expected")
|
||||
.push(num);
|
||||
} else {
|
||||
let len = sequences[depth].len();
|
||||
let above = &sequences[depth - 1];
|
||||
let left = *above.get(len).expect("expected value left");
|
||||
let right = *above.get(len + 1).expect("expected value right");
|
||||
sequences
|
||||
.get_mut(depth)
|
||||
.expect("seq current depth expect")
|
||||
.push(right - left);
|
||||
}
|
||||
}
|
||||
});
|
||||
sequences
|
||||
.iter()
|
||||
.rev()
|
||||
.skip_while(|seq| seq.iter().all(|n| n == &0))
|
||||
.fold(0, |acc, seq| seq.last().expect("expected last value") + acc)
|
||||
})
|
||||
.sum();
|
||||
|
||||
// output
|
||||
println!("part 1: {answer}\t[total: {:?}]", start.elapsed());
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
// algo
|
||||
fn part2(input: String) -> Result<i64> {
|
||||
let start = Instant::now();
|
||||
let answer: i64 = input
|
||||
.lines()
|
||||
.par_bridge()
|
||||
.map(|line| {
|
||||
let mut sequences: Vec<Vec<i64>> = vec![];
|
||||
line.trim()
|
||||
.split_whitespace()
|
||||
.map(|s| s.parse::<i64>().expect("Failed to parse number in input"))
|
||||
.enumerate()
|
||||
.for_each(|(idx, num)| {
|
||||
// every new number of primary history can result in a new row of depth
|
||||
sequences.push(vec![]);
|
||||
for depth in 0..sequences.len() {
|
||||
if depth == 0 {
|
||||
sequences
|
||||
.get_mut(depth)
|
||||
.expect("top history expected")
|
||||
.push(num);
|
||||
} else {
|
||||
let len = sequences[depth].len();
|
||||
let above = &sequences[depth - 1];
|
||||
let left = *above.get(len).expect("expected value left");
|
||||
let right = *above.get(len + 1).expect("expected value right");
|
||||
sequences
|
||||
.get_mut(depth)
|
||||
.expect("seq current depth expect")
|
||||
.push(right - left);
|
||||
}
|
||||
}
|
||||
});
|
||||
sequences
|
||||
.iter()
|
||||
.rev()
|
||||
.skip_while(|seq| seq.iter().all(|n| n == &0))
|
||||
.fold(0, |acc, seq| {
|
||||
seq.first().expect("expected last value") - acc
|
||||
})
|
||||
})
|
||||
.sum();
|
||||
|
||||
// output
|
||||
println!("part 2: {answer}\t[total: {:?}]", start.elapsed());
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
println!("Day {DAY}");
|
||||
println!("=====");
|
||||
let input = utils::aoc::get_puzzle_input(DAY).await?;
|
||||
part1(input.clone())?;
|
||||
part2(input.clone())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
static DATA: &'static str = "0 3 6 9 12 15
|
||||
1 3 6 10 15 21
|
||||
10 13 16 21 30 45";
|
||||
|
||||
#[test]
|
||||
fn test_part_1() -> Result<()> {
|
||||
assert_eq!(part1(DATA.to_owned())?, 114);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_part_2() -> Result<()> {
|
||||
assert_eq!(part2(DATA.to_owned())?, 2);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
428
2023/src/bin/day10.rs
Normal file
428
2023/src/bin/day10.rs
Normal file
|
|
@ -0,0 +1,428 @@
|
|||
use aoc23::prelude::*;
|
||||
use derive_builder::Builder;
|
||||
use itertools::Itertools;
|
||||
use rayon::prelude::*;
|
||||
use std::{collections::HashSet, str::FromStr, time::Instant};
|
||||
|
||||
static DAY: u8 = 10;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum PipeDirection {
|
||||
Vertical,
|
||||
Horizontal,
|
||||
NorthEast,
|
||||
NorthWest,
|
||||
SouthWest,
|
||||
SouthEast,
|
||||
Start,
|
||||
Ground,
|
||||
}
|
||||
|
||||
impl PipeDirection {
|
||||
fn is_north(&self) -> bool {
|
||||
matches!(self, PipeDirection::NorthEast | PipeDirection::NorthWest)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Pipe {
|
||||
position: (usize, usize),
|
||||
direction: PipeDirection,
|
||||
}
|
||||
|
||||
impl Eq for Pipe {}
|
||||
|
||||
impl PartialEq for Pipe {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.position == other.position
|
||||
}
|
||||
}
|
||||
|
||||
impl Pipe {
|
||||
fn for_char(c: char, position: (usize, usize)) -> Self {
|
||||
match c {
|
||||
'|' => Self {
|
||||
position,
|
||||
direction: PipeDirection::Vertical,
|
||||
},
|
||||
'-' => Self {
|
||||
position,
|
||||
direction: PipeDirection::Horizontal,
|
||||
},
|
||||
'L' => Self {
|
||||
position,
|
||||
direction: PipeDirection::NorthEast,
|
||||
},
|
||||
'J' => Self {
|
||||
position,
|
||||
direction: PipeDirection::NorthWest,
|
||||
},
|
||||
'7' => Self {
|
||||
position,
|
||||
direction: PipeDirection::SouthWest,
|
||||
},
|
||||
'F' => Self {
|
||||
position,
|
||||
direction: PipeDirection::SouthEast,
|
||||
},
|
||||
'.' => Self {
|
||||
position,
|
||||
direction: PipeDirection::Ground,
|
||||
},
|
||||
'S' => Self {
|
||||
position,
|
||||
direction: PipeDirection::Start,
|
||||
},
|
||||
_ => panic!("invalid pipe char"),
|
||||
}
|
||||
}
|
||||
|
||||
fn connects_to(&self) -> Vec<(usize, usize)> {
|
||||
let deltas = match self.direction {
|
||||
PipeDirection::Vertical => Some([(-1i64, 0i64), (1, 0)]),
|
||||
PipeDirection::Horizontal => Some([(0, -1), (0, 1)]),
|
||||
PipeDirection::NorthEast => Some([(-1, 0), (0, 1)]),
|
||||
PipeDirection::NorthWest => Some([(-1, 0), (0, -1)]),
|
||||
PipeDirection::SouthWest => Some([(1, 0), (0, -1)]),
|
||||
PipeDirection::SouthEast => Some([(1, 0), (0, 1)]),
|
||||
_ => None,
|
||||
};
|
||||
match deltas {
|
||||
Some(deltas) => {
|
||||
let mut to = vec![];
|
||||
for delta in deltas {
|
||||
let (row_delta, col_delta) = delta;
|
||||
let (row, col) = self.position;
|
||||
if row == 0 && row_delta < 0 {
|
||||
continue;
|
||||
}
|
||||
if col == 0 && col_delta < 0 {
|
||||
continue;
|
||||
}
|
||||
to.push((
|
||||
(row as i64 + row_delta).try_into().unwrap(),
|
||||
(col as i64 + col_delta).try_into().unwrap(),
|
||||
));
|
||||
}
|
||||
to
|
||||
}
|
||||
None => vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Pipes {
|
||||
grid: Vec<Vec<Pipe>>,
|
||||
start: Pipe,
|
||||
}
|
||||
|
||||
impl Pipes {
|
||||
fn new(s: &str) -> Result<Self> {
|
||||
let mut start = None;
|
||||
let mut grid = vec![];
|
||||
for (row_index, line) in s.trim().lines().enumerate() {
|
||||
let mut row = vec![];
|
||||
for (col_index, char) in line.trim().chars().enumerate() {
|
||||
row.push(Pipe::for_char(char, (row_index, col_index)));
|
||||
if char == 'S' {
|
||||
start = Some((row_index, col_index))
|
||||
}
|
||||
}
|
||||
grid.push(row);
|
||||
}
|
||||
|
||||
// Get actual start pipe variant for easier code on the rest of the walking code
|
||||
// later
|
||||
let start = start
|
||||
.map(|position| {
|
||||
let mut up = true;
|
||||
let mut left = true;
|
||||
|
||||
// We assume start always has two connections by definition of the problem. Will
|
||||
// assume it is up and left and swap to the opposite after checking those.
|
||||
let (row, col) = position;
|
||||
if let Some(down) = grid.get(row + 1) {
|
||||
match down[col].direction {
|
||||
PipeDirection::Vertical
|
||||
| PipeDirection::NorthEast
|
||||
| PipeDirection::NorthWest => {
|
||||
up = false;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if let Some(right) = grid[row].get(col + 1) {
|
||||
match right.direction {
|
||||
PipeDirection::Horizontal
|
||||
| PipeDirection::NorthWest
|
||||
| PipeDirection::SouthWest => {
|
||||
left = false;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if up && left {
|
||||
Pipe {
|
||||
position,
|
||||
direction: PipeDirection::NorthWest,
|
||||
}
|
||||
} else if up && !left {
|
||||
Pipe {
|
||||
position,
|
||||
direction: PipeDirection::NorthEast,
|
||||
}
|
||||
} else if !up && left {
|
||||
Pipe {
|
||||
position,
|
||||
direction: PipeDirection::SouthWest,
|
||||
}
|
||||
} else {
|
||||
Pipe {
|
||||
position,
|
||||
direction: PipeDirection::SouthEast,
|
||||
}
|
||||
}
|
||||
})
|
||||
.expect("start");
|
||||
Ok(Self { grid, start })
|
||||
}
|
||||
|
||||
fn get(&self, position: &(usize, usize)) -> &Pipe {
|
||||
&self.grid[position.0][position.1]
|
||||
}
|
||||
}
|
||||
|
||||
fn part1(input: String) -> Result<usize> {
|
||||
// parse
|
||||
let start = Instant::now();
|
||||
let pipes = Pipes::new(&input)?;
|
||||
let parsed_time = start.elapsed();
|
||||
|
||||
// algo
|
||||
let a_start = Instant::now();
|
||||
let mut answer = 1;
|
||||
let mut starters = pipes.start.connects_to().into_iter();
|
||||
let mut paths: [(&Pipe, &Pipe); 2] = [
|
||||
(&pipes.start, pipes.get(&starters.next().unwrap())),
|
||||
(&pipes.start, pipes.get(&starters.next().unwrap())),
|
||||
];
|
||||
loop {
|
||||
// reached end
|
||||
if paths[0].1.position.0 == paths[1].1.position.0
|
||||
&& paths[0].1.position.1 == paths[1].1.position.1
|
||||
{
|
||||
break;
|
||||
}
|
||||
answer += 1;
|
||||
for path in paths.iter_mut() {
|
||||
let next = path
|
||||
.1
|
||||
.connects_to()
|
||||
.into_iter()
|
||||
.filter(|pipe| pipe.0 != path.0.position.0 || pipe.1 != path.0.position.1)
|
||||
.next()
|
||||
.map(|pos| pipes.get(&pos))
|
||||
.unwrap();
|
||||
*path = (path.1, next);
|
||||
}
|
||||
}
|
||||
let algo_time = a_start.elapsed();
|
||||
|
||||
// output
|
||||
println!("part 1: {answer}\t[total: {:?}]", start.elapsed());
|
||||
println!("\tparse: {parsed_time:?}");
|
||||
println!("\talgo: {algo_time:?}");
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
fn part2(input: String) -> Result<usize> {
|
||||
// parse
|
||||
let start = Instant::now();
|
||||
let pipes = Pipes::new(&input)?;
|
||||
let parsed_time = start.elapsed();
|
||||
|
||||
// algo
|
||||
let a_start = Instant::now();
|
||||
let mut path = vec![
|
||||
&pipes.start,
|
||||
pipes
|
||||
.start
|
||||
.connects_to()
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|pos| pipes.get(&pos))
|
||||
.unwrap(),
|
||||
];
|
||||
loop {
|
||||
let previous = path.get(path.len() - 2).unwrap();
|
||||
let next = path
|
||||
.last()
|
||||
.unwrap()
|
||||
.connects_to()
|
||||
.into_iter()
|
||||
.filter(|pipe| pipe.0 != previous.position.0 || pipe.1 != previous.position.1)
|
||||
.next()
|
||||
.map(|pos| pipes.get(&pos))
|
||||
.unwrap();
|
||||
match next.direction {
|
||||
PipeDirection::Start => {
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
path.push(next);
|
||||
}
|
||||
};
|
||||
}
|
||||
let mut answer = 0;
|
||||
for (ri, row) in pipes.grid.iter().enumerate() {
|
||||
for (idx, pipe) in row.iter().enumerate() {
|
||||
if !path.contains(&pipe) {
|
||||
// println!("Pipe check, {}, {}", ri, idx);
|
||||
let (intersections, _) = row
|
||||
.iter()
|
||||
// Start at current non path pipe we are checking
|
||||
.skip(idx + 1)
|
||||
// Add fake pipe at the end so we can match on pipes on the right edge of the Grid
|
||||
// .chain(std::iter::once(&Pipe { position: (0, 0), direction:
|
||||
// PipeDirection::Ground, }))
|
||||
.fold(
|
||||
(0, None),
|
||||
|(i_count, intersecting_dir): (usize, Option<&PipeDirection>), pipe| {
|
||||
if path.contains(&pipe) {
|
||||
return match pipe.direction {
|
||||
PipeDirection::Vertical => (i_count + 1, None),
|
||||
PipeDirection::NorthWest
|
||||
| PipeDirection::NorthEast
|
||||
| PipeDirection::SouthEast
|
||||
| PipeDirection::SouthWest => match intersecting_dir {
|
||||
Some(last_direction) => {
|
||||
match (
|
||||
last_direction.is_north(),
|
||||
pipe.direction.is_north(),
|
||||
) {
|
||||
(true, true) | (false, false) => (i_count, None),
|
||||
(true, false) | (false, true) => {
|
||||
(i_count + 1, None)
|
||||
}
|
||||
}
|
||||
}
|
||||
None => (i_count, Some(&pipe.direction)),
|
||||
},
|
||||
PipeDirection::Horizontal => (i_count, intersecting_dir),
|
||||
_ => (i_count, None),
|
||||
};
|
||||
}
|
||||
return (i_count, None);
|
||||
},
|
||||
);
|
||||
|
||||
// println!( "\t\tPipe check, {}, {} intersections: {} | {}", ri, idx,
|
||||
// intersections, (intersections > 0 && intersections % 2 == 1) );
|
||||
if intersections > 0 && intersections % 2 == 1 {
|
||||
answer += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let algo_time = a_start.elapsed();
|
||||
|
||||
// output
|
||||
println!("part 2: {answer}\t[total: {:?}]", start.elapsed());
|
||||
println!("\tparse: {parsed_time:?}");
|
||||
println!("\talgo: {algo_time:?}");
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
println!("Day {DAY}");
|
||||
println!("=====");
|
||||
let input = utils::aoc::get_puzzle_input(DAY).await?;
|
||||
part1(input.clone())?;
|
||||
part2(input.clone())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_part_1() -> Result<()> {
|
||||
assert_eq!(
|
||||
part1(
|
||||
"..F7.
|
||||
.FJ|.
|
||||
SJ.L7
|
||||
|F--J
|
||||
LJ..."
|
||||
.to_owned()
|
||||
)?,
|
||||
8
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_part_2_small() -> Result<()> {
|
||||
assert_eq!(
|
||||
part2(
|
||||
"...........
|
||||
.S-------7.
|
||||
.|F-----7|.
|
||||
.||.....||.
|
||||
.||.....||.
|
||||
.|L-7.F-J|.
|
||||
.|..|.|..|.
|
||||
.L--J.L--J.
|
||||
..........."
|
||||
.to_owned(),
|
||||
)?,
|
||||
4
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_part_2_med() -> Result<()> {
|
||||
assert_eq!(
|
||||
part2(
|
||||
".F----7F7F7F7F-7....
|
||||
.|F--7||||||||FJ....
|
||||
.||.FJ||||||||L7....
|
||||
FJL7L7LJLJ||LJ.L-7..
|
||||
L--J.L7...LJS7F-7L7.
|
||||
....F-J..F7FJ|L7L7L7
|
||||
....L7.F7||L7|.L7L7|
|
||||
.....|FJLJ|FJ|F7|.LJ
|
||||
....FJL-7.||.||||...
|
||||
....L---J.LJ.LJLJ..."
|
||||
.to_owned(),
|
||||
)?,
|
||||
8
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_part_2_hard() -> Result<()> {
|
||||
assert_eq!(
|
||||
part2(
|
||||
"FF7FSF7F7F7F7F7F---7
|
||||
L|LJ||||||||||||F--J
|
||||
FL-7LJLJ||||||LJL-77
|
||||
F--JF--7||LJLJ7F7FJ-
|
||||
L---JF-JLJ.||-FJLJJ7
|
||||
|F|F-JF---7F7-L7L|7|
|
||||
|FFJF7L7F-JF7|JL---7
|
||||
7-L-JL7||F7|L7F-7F7|
|
||||
L.L7LFJ|||||FJL7||LJ
|
||||
L7JLJL-JLJLJL--JLJ.L"
|
||||
.to_owned(),
|
||||
)?,
|
||||
10
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
173
2023/src/bin/day11.rs
Normal file
173
2023/src/bin/day11.rs
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
use aoc23::prelude::*;
|
||||
use derive_builder::Builder;
|
||||
use itertools::Itertools;
|
||||
use rayon::prelude::*;
|
||||
use std::{str::FromStr, time::Instant};
|
||||
|
||||
static DAY: u8 = 11;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
struct Vec2D {
|
||||
x: usize,
|
||||
y: usize,
|
||||
}
|
||||
|
||||
impl From<(usize, usize)> for Vec2D {
|
||||
fn from(value: (usize, usize)) -> Self {
|
||||
Self {
|
||||
x: value.0,
|
||||
y: value.1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Space {
|
||||
galaxies: Vec<Vec2D>,
|
||||
x_max: usize,
|
||||
y_max: usize,
|
||||
}
|
||||
|
||||
impl Space {
|
||||
fn new(s: &str) -> Result<Self> {
|
||||
let mut x_max = 0;
|
||||
let mut y_max = 0;
|
||||
let mut galaxies = vec![];
|
||||
for (y, row) in s.trim().lines().enumerate() {
|
||||
for (x, col) in row.chars().enumerate() {
|
||||
if col == '#' {
|
||||
galaxies.push((x, y).into());
|
||||
}
|
||||
x_max = x;
|
||||
}
|
||||
y_max = y;
|
||||
}
|
||||
Ok(Space {
|
||||
galaxies,
|
||||
x_max,
|
||||
y_max,
|
||||
})
|
||||
}
|
||||
|
||||
fn expand(&mut self, by: usize) {
|
||||
for y in (0..=self.y_max).rev() {
|
||||
if self.galaxies.iter().filter(|g| g.y == y).count() == 0 {
|
||||
self.galaxies
|
||||
.iter_mut()
|
||||
.filter(|g| g.y > y)
|
||||
.for_each(|g| g.y += by);
|
||||
}
|
||||
}
|
||||
for x in (0..=self.x_max).rev() {
|
||||
if self.galaxies.iter().filter(|g| g.x == x).count() == 0 {
|
||||
self.galaxies
|
||||
.iter_mut()
|
||||
.filter(|g| g.x > x)
|
||||
.for_each(|g| g.x += by);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn galaxy_pairs(&self) -> Vec<(&Vec2D, &Vec2D)> {
|
||||
let mut galaxy_pairs = vec![];
|
||||
for i in 0..self.galaxies.len() {
|
||||
for i2 in i + 1..self.galaxies.len() {
|
||||
galaxy_pairs.push((&self.galaxies[i], &self.galaxies[i2]));
|
||||
}
|
||||
}
|
||||
galaxy_pairs
|
||||
}
|
||||
}
|
||||
|
||||
fn part1(input: String) -> Result<usize> {
|
||||
// parse
|
||||
let start = Instant::now();
|
||||
let mut space = Space::new(&input)?;
|
||||
let parsed_time = start.elapsed();
|
||||
let step_start = Instant::now();
|
||||
space.expand(1);
|
||||
let expansion_time = step_start.elapsed();
|
||||
|
||||
// algo
|
||||
let step_start = Instant::now();
|
||||
let mut answer = 0;
|
||||
for pair in space.galaxy_pairs() {
|
||||
let distance = pair.0.y.abs_diff(pair.1.y) + pair.0.x.abs_diff(pair.1.x);
|
||||
answer += distance;
|
||||
}
|
||||
let algo_time = step_start.elapsed();
|
||||
|
||||
// output
|
||||
println!("part 1: {answer}\t[total: {:?}]", start.elapsed());
|
||||
println!("\tparse: {parsed_time:?}");
|
||||
println!("\texpansion: {expansion_time:?}");
|
||||
println!("\talgo: {algo_time:?}");
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
fn part2(input: String) -> Result<usize> {
|
||||
// parse
|
||||
let start = Instant::now();
|
||||
let mut space = Space::new(&input)?;
|
||||
let parsed_time = start.elapsed();
|
||||
let step_start = Instant::now();
|
||||
space.expand(1000000 - 1);
|
||||
let expansion_time = step_start.elapsed();
|
||||
|
||||
// algo
|
||||
let step_start = Instant::now();
|
||||
let mut answer = 0;
|
||||
for pair in space.galaxy_pairs() {
|
||||
let distance = pair.0.y.abs_diff(pair.1.y) + pair.0.x.abs_diff(pair.1.x);
|
||||
answer += distance;
|
||||
}
|
||||
let algo_time = step_start.elapsed();
|
||||
|
||||
// output
|
||||
println!("part 2: {answer}\t[total: {:?}]", start.elapsed());
|
||||
println!("\tparse: {parsed_time:?}");
|
||||
println!("\texpansion: {expansion_time:?}");
|
||||
println!("\talgo: {algo_time:?}");
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
println!("Day {DAY}");
|
||||
println!("=====");
|
||||
let input = utils::aoc::get_puzzle_input(DAY).await?;
|
||||
part1(input.clone())?;
|
||||
part2(input.clone())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
static DATA: &'static str = "...#......
|
||||
.......#..
|
||||
#.........
|
||||
..........
|
||||
......#...
|
||||
.#........
|
||||
.........#
|
||||
..........
|
||||
.......#..
|
||||
#...#.....
|
||||
";
|
||||
|
||||
#[test]
|
||||
fn test_part_1() -> Result<()> {
|
||||
assert_eq!(part1(DATA.to_owned())?, 374);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Must set the space.expand(1000000 - 1); to (100 - 1) instead for this test
|
||||
#[ignore]
|
||||
fn test_part_2() -> Result<()> {
|
||||
assert_eq!(part2(DATA.to_owned())?, 8410);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
164
2023/src/bin/day12.rs
Normal file
164
2023/src/bin/day12.rs
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
use aoc23::prelude::*;
|
||||
use derive_builder::Builder;
|
||||
use itertools::Itertools;
|
||||
use rayon::prelude::*;
|
||||
use std::{collections::HashMap, str::FromStr, time::Instant};
|
||||
|
||||
static DAY: u8 = 12;
|
||||
|
||||
fn count_possibilities(
|
||||
layout: &str,
|
||||
contiguous: &[usize],
|
||||
cache: &mut HashMap<(String, Vec<usize>), usize>,
|
||||
) -> usize {
|
||||
if layout.chars().count() == 0 {
|
||||
return if contiguous.len() == 0 {
|
||||
// println!("\tLine is variant: {layout}");
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
}
|
||||
let cache_key = (layout.to_owned(), contiguous.to_vec());
|
||||
if let Some(&result) = cache.get(&cache_key) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Remove leading dots
|
||||
let result = if layout.starts_with('.') {
|
||||
count_possibilities(layout.to_owned().trim_matches('.'), contiguous, cache)
|
||||
// Try both options for ?
|
||||
} else if layout.starts_with('?') {
|
||||
count_possibilities(&layout.to_owned().replacen('?', &".", 1), contiguous, cache)
|
||||
+ count_possibilities(&layout.to_owned().replacen('?', &"#", 1), contiguous, cache)
|
||||
// If group, check if it matches
|
||||
} else if layout.starts_with("#") {
|
||||
// no groups left to match || not enough # to make the group || not all of this
|
||||
// group are #
|
||||
if contiguous.len() == 0
|
||||
|| layout.chars().count() < contiguous[0]
|
||||
|| layout.chars().take(contiguous[0]).any(|c| c == '.')
|
||||
{
|
||||
0
|
||||
} else if contiguous.len() > 1 {
|
||||
if layout.len() < contiguous[0] + 1
|
||||
|| layout.chars().skip(contiguous[0]).next().unwrap() == '#'
|
||||
{
|
||||
0
|
||||
} else {
|
||||
count_possibilities(
|
||||
&layout.chars().skip(contiguous[0] + 1).collect::<String>(),
|
||||
&contiguous[1..],
|
||||
cache,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
count_possibilities(
|
||||
&layout.chars().skip(contiguous[0]).collect::<String>(),
|
||||
&contiguous[1..],
|
||||
cache,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
panic!("Should not get here.")
|
||||
};
|
||||
cache.insert(cache_key, result);
|
||||
result
|
||||
}
|
||||
|
||||
fn part1(input: String) -> Result<usize> {
|
||||
// parse
|
||||
let start = Instant::now();
|
||||
let answer = input
|
||||
.trim()
|
||||
.lines()
|
||||
.enumerate()
|
||||
.par_bridge()
|
||||
.map(|(idx, line)| {
|
||||
let mut split = line.trim().split_whitespace();
|
||||
let layout = split.next().unwrap();
|
||||
let contiguous = split
|
||||
.next()
|
||||
.unwrap()
|
||||
.split(',')
|
||||
.map(|s| s.parse::<usize>().unwrap())
|
||||
.collect_vec();
|
||||
let count = count_possibilities(&layout, &contiguous, &mut HashMap::new());
|
||||
|
||||
// println!("Line {idx}: {line} = {count}");
|
||||
count
|
||||
})
|
||||
.sum();
|
||||
|
||||
// output
|
||||
println!("part 1: {answer}\t[total: {:?}]", start.elapsed());
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
fn part2(input: String) -> Result<usize> {
|
||||
// parse
|
||||
let start = Instant::now();
|
||||
let answer = input
|
||||
.trim()
|
||||
.lines()
|
||||
.enumerate()
|
||||
.par_bridge()
|
||||
.map(|(idx, line)| {
|
||||
let mut split = line.trim().split_whitespace();
|
||||
let layout = split.next().unwrap();
|
||||
let contiguous = split
|
||||
.next()
|
||||
.unwrap()
|
||||
.split(',')
|
||||
.map(|s| s.parse::<usize>().unwrap())
|
||||
.collect_vec();
|
||||
let layout = std::iter::repeat(layout).take(5).join("?");
|
||||
let contiguous = std::iter::repeat(contiguous)
|
||||
.take(5)
|
||||
.flatten()
|
||||
.collect_vec();
|
||||
let count = count_possibilities(&layout, &contiguous, &mut HashMap::new());
|
||||
|
||||
// println!("Line {idx}: {line} = {count}");
|
||||
count
|
||||
})
|
||||
.sum();
|
||||
|
||||
// output
|
||||
println!("part 2: {answer}\t[total: {:?}]", start.elapsed());
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
println!("Day {DAY}");
|
||||
println!("=====");
|
||||
let input = utils::aoc::get_puzzle_input(DAY).await?;
|
||||
part1(input.clone())?;
|
||||
part2(input.clone())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
static DATA: &'static str = "???.### 1,1,3
|
||||
.??..??...?##. 1,1,3
|
||||
?#?#?#?#?#?#?#? 1,3,1,6
|
||||
????.#...#... 4,1,1
|
||||
????.######..#####. 1,6,5
|
||||
?###???????? 3,2,1";
|
||||
|
||||
#[test]
|
||||
fn test_part_1() -> Result<()> {
|
||||
assert_eq!(part1(DATA.to_owned())?, 21);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_part_2() -> Result<()> {
|
||||
assert_eq!(part2(DATA.to_owned())?, 525152);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
249
2023/src/bin/day13.rs
Normal file
249
2023/src/bin/day13.rs
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
use aoc23::prelude::*;
|
||||
use derive_builder::Builder;
|
||||
use itertools::Itertools;
|
||||
use rayon::prelude::*;
|
||||
use std::{fmt::Debug, str::FromStr, time::Instant};
|
||||
|
||||
static DAY: u8 = 13;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Grid {
|
||||
cells: Vec<Vec<bool>>,
|
||||
}
|
||||
|
||||
impl Debug for Grid {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
for row in &self.cells {
|
||||
f.write_str("\t");
|
||||
for b in row {
|
||||
f.write_str(if *b { "#" } else { "." });
|
||||
}
|
||||
f.write_str("\n");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
type Split<'a> = (&'a [Vec<bool>], &'a [Vec<bool>]);
|
||||
|
||||
impl Grid {
|
||||
fn new(s: &str) -> Result<Self> {
|
||||
Ok(Self {
|
||||
cells: s
|
||||
.trim()
|
||||
.lines()
|
||||
.map(|l| l.chars().map(|c| c == '#').collect_vec())
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
fn all_vertical_splits(&self) -> Vec<Split> {
|
||||
let mut splits = vec![];
|
||||
for i in 1..self.cells.len() {
|
||||
splits.push(self.vertical_split(i));
|
||||
}
|
||||
splits
|
||||
}
|
||||
|
||||
fn vertical_split(&self, index: usize) -> Split {
|
||||
(&self.cells[0..index], &self.cells[index..])
|
||||
}
|
||||
|
||||
fn is_vertical_mirror(top: &[Vec<bool>], bot: &[Vec<bool>]) -> bool {
|
||||
let tl = top.len();
|
||||
let bl = bot.len();
|
||||
for i in 0..usize::min(tl, bl) {
|
||||
let t = &top[tl - 1 - i];
|
||||
let b = &bot[i];
|
||||
|
||||
// println!("\tChecking {i}: {t:?} <=> {b:?} || {}", t == b);
|
||||
if t != b {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn is_vertical_mirror_with_smudge(top: &[Vec<bool>], bot: &[Vec<bool>]) -> bool {
|
||||
let tl = top.len();
|
||||
let bl = bot.len();
|
||||
let mut claimed_smudge = false;
|
||||
for i in 0..usize::min(tl, bl) {
|
||||
let t = &top[tl - 1 - i];
|
||||
let b = &bot[i];
|
||||
|
||||
// println!( "\tChecking {i}: {t:?} <=> {b:?} || {} || {}", t == b, claimed_smudge
|
||||
// );
|
||||
if t != b {
|
||||
if !claimed_smudge && t.iter().zip(b.iter()).filter(|(z, x)| z != x).count() == 1 {
|
||||
claimed_smudge = true
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FFS I had `true` here but all mirrors have EXACTLY ONE smudge, not at most
|
||||
// one...
|
||||
claimed_smudge
|
||||
}
|
||||
|
||||
fn clone_and_rotate(&self) -> Self {
|
||||
Self {
|
||||
cells: (0..self.cells[0].len())
|
||||
.map(|i| self.cells.iter().map(|row| row[i]).collect())
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn part1(input: String) -> Result<usize> {
|
||||
// parse
|
||||
let start = Instant::now();
|
||||
let grids = input
|
||||
.split("\n\n")
|
||||
.map(|g| Grid::new(g).unwrap())
|
||||
.collect_vec();
|
||||
let parsed_time = start.elapsed();
|
||||
|
||||
// algo
|
||||
let a_start = Instant::now();
|
||||
let answer: usize = grids
|
||||
.iter()
|
||||
.map(|grid| {
|
||||
let mut vertical_mirror_at = None;
|
||||
for (idx, (top, bot)) in grid.all_vertical_splits().iter().enumerate() {
|
||||
if Grid::is_vertical_mirror(top, bot) {
|
||||
vertical_mirror_at = Some(idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
let mut horizontal_mirror_at = None;
|
||||
if vertical_mirror_at.is_none() {
|
||||
let rotated = grid.clone_and_rotate();
|
||||
for (idx, (top, bot)) in rotated.all_vertical_splits().iter().enumerate() {
|
||||
if Grid::is_vertical_mirror(top, bot) {
|
||||
horizontal_mirror_at = Some(idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// index + 1 = number of rows to the left or above the mirror index
|
||||
let value = horizontal_mirror_at
|
||||
.map(|h| h + 1)
|
||||
.or(vertical_mirror_at.map(|v| (v + 1) * 100))
|
||||
.unwrap_or(0);
|
||||
|
||||
// println!( "Grid: vertical mirror at: {vertical_mirror_at:?}\thorizontal at:
|
||||
// {horizontal_mirror_at:?} == {value}" );
|
||||
value
|
||||
})
|
||||
.sum();
|
||||
let algo_time = a_start.elapsed();
|
||||
|
||||
// output
|
||||
println!("part 1: {answer}\t[total: {:?}]", start.elapsed());
|
||||
println!("\tparse: {parsed_time:?}");
|
||||
println!("\talgo: {algo_time:?}");
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
fn part2(input: String) -> Result<usize> {
|
||||
// parse
|
||||
let start = Instant::now();
|
||||
let grids = input
|
||||
.split("\n\n")
|
||||
.map(|g| Grid::new(g).unwrap())
|
||||
.collect_vec();
|
||||
let parsed_time = start.elapsed();
|
||||
|
||||
// algo
|
||||
let a_start = Instant::now();
|
||||
let answer: usize = grids
|
||||
.iter()
|
||||
.map(|grid| {
|
||||
// println!("GRID: {grid:?}");
|
||||
let mut vertical_mirror_at = None;
|
||||
for (idx, (top, bot)) in grid.all_vertical_splits().iter().enumerate() {
|
||||
if Grid::is_vertical_mirror_with_smudge(top, bot) {
|
||||
// println!("GOT VERT: {idx}");
|
||||
vertical_mirror_at = Some(idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
let mut horizontal_mirror_at = None;
|
||||
if vertical_mirror_at.is_none() {
|
||||
let rotated = grid.clone_and_rotate();
|
||||
|
||||
// println!("ROTATED: {grid:?}");
|
||||
for (idx, (top, bot)) in rotated.all_vertical_splits().iter().enumerate() {
|
||||
if Grid::is_vertical_mirror_with_smudge(top, bot) {
|
||||
horizontal_mirror_at = Some(idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// index + 1 = number of rows to the left or above the mirror index
|
||||
let value = horizontal_mirror_at
|
||||
.map(|h| h + 1)
|
||||
.or(vertical_mirror_at.map(|v| (v + 1) * 100))
|
||||
.unwrap_or(0);
|
||||
|
||||
// println!( "Grid: vertical mirror at: {vertical_mirror_at:?}\thorizontal at:
|
||||
// {horizontal_mirror_at:?} == {value}" );
|
||||
value
|
||||
})
|
||||
.sum();
|
||||
let algo_time = a_start.elapsed();
|
||||
|
||||
// output
|
||||
println!("part 2: {answer} == 31836\t[total: {:?}]", start.elapsed());
|
||||
println!("\tparse: {parsed_time:?}");
|
||||
println!("\talgo: {algo_time:?}");
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
println!("Day {DAY}");
|
||||
println!("=====");
|
||||
let input = utils::aoc::get_puzzle_input(DAY).await?;
|
||||
part1(input.clone())?;
|
||||
part2(input.clone())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
static DATA: &'static str = "#.##..##.
|
||||
..#.##.#.
|
||||
##......#
|
||||
##......#
|
||||
..#.##.#.
|
||||
..##..##.
|
||||
#.#.##.#.
|
||||
|
||||
#...##..#
|
||||
#....#..#
|
||||
..##..###
|
||||
#####.##.
|
||||
#####.##.
|
||||
..##..###
|
||||
#....#..#";
|
||||
|
||||
#[test]
|
||||
fn test_part_1() -> Result<()> {
|
||||
assert_eq!(part1(DATA.to_owned())?, 405);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_part_2() -> Result<()> {
|
||||
assert_eq!(part2(DATA.to_owned())?, 400);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
234
2023/src/bin/day14.rs
Normal file
234
2023/src/bin/day14.rs
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
use aoc23::prelude::*;
|
||||
use grid::Grid;
|
||||
use itertools::Itertools;
|
||||
use std::{collections::HashMap, fmt::Display, time::Instant};
|
||||
|
||||
static DAY: u8 = 14;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
enum Rock {
|
||||
Round,
|
||||
Square,
|
||||
None,
|
||||
}
|
||||
|
||||
impl Rock {
|
||||
fn from_char(char: char) -> Self {
|
||||
match char {
|
||||
'O' => Rock::Round,
|
||||
'#' => Rock::Square,
|
||||
'.' => Rock::None,
|
||||
_ => panic!("unknown rock type"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
struct Dish {
|
||||
grid: Grid<Rock>,
|
||||
}
|
||||
|
||||
impl std::hash::Hash for Dish {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
for r in self.grid.iter() {
|
||||
r.hash(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dish {
|
||||
fn new(s: &str) -> Result<Self> {
|
||||
let mut grid: Option<Grid<Rock>> = None;
|
||||
for line in s.trim().lines() {
|
||||
let rocks = line.chars().map(Rock::from_char).collect_vec();
|
||||
let len = rocks.len();
|
||||
match grid {
|
||||
Some(ref mut grid) => {
|
||||
grid.push_row(rocks);
|
||||
}
|
||||
None => {
|
||||
grid = Some(Grid::from_vec(rocks, len));
|
||||
}
|
||||
}
|
||||
}
|
||||
let grid = grid.unwrap();
|
||||
Ok(Dish { grid })
|
||||
}
|
||||
|
||||
fn tilt_north(mut self) -> Self {
|
||||
for col_idx in 0..self.grid.cols() {
|
||||
let column = self.grid.iter_col(col_idx);
|
||||
let column_length = column.len();
|
||||
|
||||
// start,end,round_count ranges for rolling zones
|
||||
let mut rolling_ranges: Vec<(usize, usize, usize)> = vec![];
|
||||
let mut in_range: Option<(usize, usize)> = None;
|
||||
for (row_idx, rock) in column
|
||||
.enumerate()
|
||||
// Add one at the end so we close out our range, it should be square so it is
|
||||
// treated as a wall
|
||||
.chain(std::iter::once((column_length, &Rock::Square)))
|
||||
{
|
||||
if let Rock::Square = rock {
|
||||
if let Some((start, round_count)) = in_range {
|
||||
// rolling range it only including round or none, not this wall.
|
||||
rolling_ranges.push((start, row_idx - 1, round_count));
|
||||
in_range = None;
|
||||
}
|
||||
} else {
|
||||
let is_round = matches!(rock, Rock::Round);
|
||||
if let Some((_, ref mut round_count)) = in_range {
|
||||
if is_round {
|
||||
*round_count += 1;
|
||||
}
|
||||
} else {
|
||||
in_range = Some((row_idx, if is_round { 1 } else { 0 }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Go through ranges and set them to tilt north.
|
||||
rolling_ranges.iter().for_each(|(start, end, round_count)| {
|
||||
let mut round_remaining = *round_count;
|
||||
for i in *start..=*end {
|
||||
if let Some(rock) = self.grid.get_mut(i, col_idx) {
|
||||
if round_remaining > 0 {
|
||||
*rock = Rock::Round;
|
||||
round_remaining -= 1;
|
||||
} else {
|
||||
*rock = Rock::None;
|
||||
}
|
||||
} else {
|
||||
panic!("no rock");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
fn calculate_north_load(&self) -> usize {
|
||||
let len = self.grid.rows();
|
||||
self.grid
|
||||
.iter_rows()
|
||||
.enumerate()
|
||||
.map(|(row_idx, row)| {
|
||||
let multiplier = len - row_idx;
|
||||
multiplier * row.filter(|rock| matches!(rock, Rock::Round)).count()
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Dish {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
for row in self.grid.iter_rows() {
|
||||
for rock in row {
|
||||
match rock {
|
||||
Rock::Round => f.write_str("O"),
|
||||
Rock::Square => f.write_str("#"),
|
||||
Rock::None => f.write_str("."),
|
||||
}?;
|
||||
}
|
||||
f.write_str("\n")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn part1(input: String) -> Result<usize> {
|
||||
// parse
|
||||
let start = Instant::now();
|
||||
let mut dish = Dish::new(&input)?;
|
||||
dish = dish.tilt_north();
|
||||
let parsed_time = start.elapsed();
|
||||
|
||||
// algo
|
||||
let a_start = Instant::now();
|
||||
let answer = dish.calculate_north_load();
|
||||
let algo_time = a_start.elapsed();
|
||||
|
||||
// output
|
||||
println!("part 1: {answer}\t[total: {:?}]", start.elapsed());
|
||||
println!("\tparse: {parsed_time:?}");
|
||||
println!("\talgo: {algo_time:?}");
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
fn part2(input: String) -> Result<usize> {
|
||||
// parse
|
||||
let start = Instant::now();
|
||||
let mut dish = Dish::new(&input)?;
|
||||
let parsed_time = start.elapsed();
|
||||
|
||||
// algo
|
||||
let a_start = Instant::now();
|
||||
let cycles = 1000000000;
|
||||
let mut seen = HashMap::new();
|
||||
let mut scores = vec![];
|
||||
let mut answer = 0;
|
||||
for i in 0..cycles {
|
||||
for _ in 0..4 {
|
||||
dish = dish.tilt_north();
|
||||
dish.grid.rotate_right();
|
||||
}
|
||||
scores.push(dish.calculate_north_load());
|
||||
if let Some(repeated_index) = seen.get(&dish) {
|
||||
// Figure out final score based on sliding window of known scores within the
|
||||
// repeated pattern:
|
||||
//
|
||||
// (cycles - repeated index) gives us the remaining times we need to run through.
|
||||
// We then get the remainder of that divided by the difference of the current
|
||||
// index minus all seen (total repeating count).
|
||||
answer = scores
|
||||
[repeated_index - 1 + (cycles - repeated_index) % (seen.len() - repeated_index)];
|
||||
break;
|
||||
}
|
||||
seen.insert(dish.clone(), i);
|
||||
}
|
||||
let algo_time = a_start.elapsed();
|
||||
|
||||
// output
|
||||
println!("part 2: {answer}\t[total: {:?}]", start.elapsed());
|
||||
println!("\tparse: {parsed_time:?}");
|
||||
println!("\talgo: {algo_time:?}");
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
println!("Day {DAY}");
|
||||
println!("=====");
|
||||
let input = utils::aoc::get_puzzle_input(DAY).await?;
|
||||
part1(input.clone())?;
|
||||
part2(input.clone())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
static DATA: &'static str = "O....#....
|
||||
O.OO#....#
|
||||
.....##...
|
||||
OO.#O....O
|
||||
.O.....O#.
|
||||
O.#..O.#.#
|
||||
..O..#O..O
|
||||
.......O..
|
||||
#....###..
|
||||
#OO..#....";
|
||||
|
||||
#[test]
|
||||
fn test_part_1() -> Result<()> {
|
||||
assert_eq!(part1(DATA.to_owned())?, 136);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_part_2() -> Result<()> {
|
||||
assert_eq!(part2(DATA.to_owned())?, 64);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
190
2023/src/bin/day15.rs
Normal file
190
2023/src/bin/day15.rs
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
use aoc23::prelude::*;
|
||||
use derive_builder::Builder;
|
||||
use itertools::Itertools;
|
||||
use rayon::prelude::*;
|
||||
use std::{str::FromStr, time::Instant};
|
||||
|
||||
static DAY: u8 = 15;
|
||||
|
||||
#[derive(Debug, Clone, Default, Eq, PartialEq)]
|
||||
struct Seq {
|
||||
chars: Vec<char>,
|
||||
}
|
||||
|
||||
impl Seq {
|
||||
fn new(s: &str) -> Result<Self> {
|
||||
Ok(Self {
|
||||
chars: s.trim().chars().filter(|c| !c.is_whitespace()).collect(),
|
||||
})
|
||||
}
|
||||
|
||||
fn hash(&self) -> usize {
|
||||
self.chars.iter().fold(0, |acc, char| {
|
||||
let mut value = acc;
|
||||
value += *char as usize;
|
||||
value *= 17;
|
||||
value = value % 256;
|
||||
value
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Operation {
|
||||
Remove,
|
||||
// focal length
|
||||
Insert(usize),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct LensOp {
|
||||
label: Seq,
|
||||
box_index: usize,
|
||||
operation: Operation,
|
||||
}
|
||||
|
||||
impl LensOp {
|
||||
fn new(value: &Seq) -> Self {
|
||||
let label = Seq {
|
||||
chars: value
|
||||
.chars
|
||||
.iter()
|
||||
.take_while(|c| c != &&'=' && c != &&'-')
|
||||
.map(|c| *c)
|
||||
.collect(),
|
||||
};
|
||||
let box_index = label.hash();
|
||||
let mut op = value.chars.iter().skip(label.chars.len());
|
||||
Self {
|
||||
label,
|
||||
box_index,
|
||||
operation: match op.next().unwrap() {
|
||||
'-' => Operation::Remove,
|
||||
'=' => Operation::Insert(op.next().unwrap().to_digit(10).unwrap() as usize),
|
||||
o => panic!("Unknown op {o}"),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct LensBox {
|
||||
lenses: Vec<LensOp>,
|
||||
}
|
||||
|
||||
fn part1(input: String) -> Result<usize> {
|
||||
// parse
|
||||
let start = Instant::now();
|
||||
let sequences: Vec<Seq> = input.split(',').map(|seq| Seq::new(seq)).try_collect()?;
|
||||
let parsed_time = start.elapsed();
|
||||
|
||||
// algo
|
||||
let a_start = Instant::now();
|
||||
let answer = sequences.par_iter().map(Seq::hash).sum();
|
||||
let algo_time = a_start.elapsed();
|
||||
|
||||
// output
|
||||
println!("part 1: {answer}\t[total: {:?}]", start.elapsed());
|
||||
println!("\tparse: {parsed_time:?}");
|
||||
println!("\talgo: {algo_time:?}");
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
fn part2(input: String) -> Result<usize> {
|
||||
// parse
|
||||
let start = Instant::now();
|
||||
let lens_ops: Vec<LensOp> = input
|
||||
.split(',')
|
||||
.map(|seq| Seq::new(seq).unwrap())
|
||||
.map(|s| LensOp::new(&s))
|
||||
.collect();
|
||||
let mut lens_boxes: Vec<LensBox> = std::iter::repeat_with(|| LensBox::default())
|
||||
.take(256)
|
||||
.collect();
|
||||
let parsed_time = start.elapsed();
|
||||
|
||||
// algo
|
||||
let a_start = Instant::now();
|
||||
for lens_op in lens_ops {
|
||||
if let Some(lens_box) = lens_boxes.get_mut(lens_op.box_index) {
|
||||
match lens_op.operation {
|
||||
Operation::Remove => lens_box.lenses.retain(|lop| lop.label != lens_op.label),
|
||||
Operation::Insert(_) => {
|
||||
if let Some(existing) = lens_box
|
||||
.lenses
|
||||
.iter_mut()
|
||||
.find(|lop| lop.label == lens_op.label)
|
||||
{
|
||||
*existing = lens_op;
|
||||
} else {
|
||||
lens_box.lenses.push(lens_op);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let answer = lens_boxes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(box_idx, lens_box)| {
|
||||
lens_box
|
||||
.lenses
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(lens_idx, lens)| {
|
||||
if let Operation::Insert(focal_length) = lens.operation {
|
||||
(box_idx + 1) * (lens_idx + 1) * focal_length
|
||||
} else {
|
||||
panic!("How did a removal lens get in there?");
|
||||
}
|
||||
})
|
||||
.sum::<usize>()
|
||||
})
|
||||
.sum();
|
||||
let algo_time = a_start.elapsed();
|
||||
|
||||
// output
|
||||
println!("part 2: {answer}\t[total: {:?}]", start.elapsed());
|
||||
println!("\tparse: {parsed_time:?}");
|
||||
println!("\talgo: {algo_time:?}");
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
println!("Day {DAY}");
|
||||
println!("=====");
|
||||
let input = utils::aoc::get_puzzle_input(DAY).await?;
|
||||
part1(input.clone())?;
|
||||
part2(input.clone())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_part_1_a() -> Result<()> {
|
||||
assert_eq!(part1("HASH".to_owned())?, 52);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_part_1_b() -> Result<()> {
|
||||
assert_eq!(
|
||||
part1("rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7".to_owned())?,
|
||||
1320
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_part_2() -> Result<()> {
|
||||
assert_eq!(
|
||||
part2("rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7".to_owned())?,
|
||||
145
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
256
2023/src/bin/day16.rs
Normal file
256
2023/src/bin/day16.rs
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
use aoc23::prelude::*;
|
||||
use derive_builder::Builder;
|
||||
use grid::Grid;
|
||||
use itertools::Itertools;
|
||||
use rayon::prelude::*;
|
||||
use std::{collections::HashSet, ops::Add, str::FromStr, time::Instant};
|
||||
|
||||
static DAY: u8 = 16;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
enum Tile {
|
||||
#[default]
|
||||
Empty,
|
||||
VerticalSplit,
|
||||
HorizontalSplit,
|
||||
RMirror,
|
||||
LMirror,
|
||||
}
|
||||
|
||||
impl Tile {
|
||||
fn new_tile_grid(input: &str) -> Grid<Self> {
|
||||
let mut grid = Grid::new(0, 0);
|
||||
for line in input.lines() {
|
||||
let tiles = line
|
||||
.trim()
|
||||
.chars()
|
||||
.map(|c| match c {
|
||||
'.' => Self::Empty,
|
||||
'|' => Self::VerticalSplit,
|
||||
'-' => Self::HorizontalSplit,
|
||||
'/' => Self::RMirror,
|
||||
'\\' => Self::LMirror,
|
||||
unknown => panic!("unknown tile: {unknown}"),
|
||||
})
|
||||
.collect_vec();
|
||||
grid.push_row(tiles);
|
||||
}
|
||||
grid
|
||||
}
|
||||
|
||||
fn direction_to(&self, direction: &Dir) -> Vec<Dir> {
|
||||
match self {
|
||||
Tile::Empty => vec![direction.clone()],
|
||||
Tile::VerticalSplit => match direction {
|
||||
Dir::Up | Dir::Down => vec![direction.clone()],
|
||||
Dir::Right | Dir::Left => vec![Dir::Up, Dir::Down],
|
||||
},
|
||||
Tile::HorizontalSplit => match direction {
|
||||
Dir::Right | Dir::Left => vec![direction.clone()],
|
||||
Dir::Up | Dir::Down => vec![Dir::Right, Dir::Left],
|
||||
},
|
||||
Tile::RMirror => vec![match direction {
|
||||
Dir::Up => Dir::Right,
|
||||
Dir::Right => Dir::Up,
|
||||
Dir::Down => Dir::Left,
|
||||
Dir::Left => Dir::Down,
|
||||
}],
|
||||
Tile::LMirror => vec![match direction {
|
||||
Dir::Up => Dir::Left,
|
||||
Dir::Right => Dir::Down,
|
||||
Dir::Down => Dir::Right,
|
||||
Dir::Left => Dir::Up,
|
||||
}],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
enum Dir {
|
||||
Up,
|
||||
Right,
|
||||
Down,
|
||||
Left,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
struct Beam(usize, usize, Dir);
|
||||
|
||||
impl Beam {
|
||||
fn add_dir(&self, dir: &Dir) -> Self {
|
||||
Self(
|
||||
match dir {
|
||||
Dir::Up => self.0.saturating_sub(1),
|
||||
Dir::Down => self.0.saturating_add(1),
|
||||
_ => self.0,
|
||||
},
|
||||
match dir {
|
||||
Dir::Left => self.1.saturating_sub(1),
|
||||
Dir::Right => self.1.saturating_add(1),
|
||||
_ => self.1,
|
||||
},
|
||||
dir.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
fn next_path(&self, tiles: &Grid<Tile>) -> Vec<Beam> {
|
||||
tiles
|
||||
.get(self.0, self.1)
|
||||
.map(|tile| tile.direction_to(&self.2))
|
||||
.map(|dirs| {
|
||||
dirs.iter()
|
||||
.map(|dir| self.add_dir(dir))
|
||||
.filter(|beam| tiles.get(beam.0, beam.1).is_some())
|
||||
.collect_vec()
|
||||
})
|
||||
.unwrap_or(vec![])
|
||||
}
|
||||
|
||||
fn part2_starts(grid: &Grid<Tile>) -> Vec<Self> {
|
||||
let mut starts = vec![];
|
||||
let rows = grid.rows();
|
||||
let cols = grid.cols();
|
||||
starts.push(Beam(0, 0, Dir::Right));
|
||||
starts.push(Beam(0, 0, Dir::Down));
|
||||
starts.push(Beam(0, cols - 1, Dir::Down));
|
||||
starts.push(Beam(0, cols - 1, Dir::Left));
|
||||
starts.push(Beam(rows - 1, cols - 1, Dir::Left));
|
||||
starts.push(Beam(rows - 1, cols - 1, Dir::Up));
|
||||
starts.push(Beam(rows - 1, 0, Dir::Up));
|
||||
starts.push(Beam(rows - 1, 0, Dir::Right));
|
||||
for row in 1..rows - 2 {
|
||||
starts.push(Beam(row, 0, Dir::Right));
|
||||
starts.push(Beam(row, cols - 1, Dir::Left));
|
||||
}
|
||||
for col in 1..cols - 2 {
|
||||
starts.push(Beam(0, col, Dir::Down));
|
||||
starts.push(Beam(rows - 1, col, Dir::Up));
|
||||
}
|
||||
starts
|
||||
}
|
||||
}
|
||||
|
||||
fn part1(input: String) -> Result<usize> {
|
||||
// parse
|
||||
let start = Instant::now();
|
||||
let grid = Tile::new_tile_grid(&input);
|
||||
let parsed_time = start.elapsed();
|
||||
|
||||
// algo
|
||||
let a_start = Instant::now();
|
||||
let mut energized = HashSet::new();
|
||||
let mut full_beam = HashSet::new();
|
||||
let mut beam_path = vec![Beam(0, 0, Dir::Right)];
|
||||
while let Some(beam) = beam_path.pop() {
|
||||
energized.insert((beam.0, beam.1));
|
||||
full_beam.insert(beam.clone());
|
||||
beam.next_path(&grid)
|
||||
.into_iter()
|
||||
.filter(|b| !full_beam.contains(b))
|
||||
.for_each(|new_beam| {
|
||||
beam_path.push(new_beam);
|
||||
});
|
||||
}
|
||||
let answer = energized.len();
|
||||
let algo_time = a_start.elapsed();
|
||||
|
||||
// output
|
||||
println!("part 1: {answer}\t[total: {:?}]", start.elapsed());
|
||||
println!("\tparse: {parsed_time:?}");
|
||||
println!("\talgo: {algo_time:?}");
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
fn part2(input: String) -> Result<usize> {
|
||||
// parse
|
||||
let start = Instant::now();
|
||||
let grid = Tile::new_tile_grid(&input);
|
||||
let parsed_time = start.elapsed();
|
||||
|
||||
// algo
|
||||
let a_start = Instant::now();
|
||||
let answer = Beam::part2_starts(&grid)
|
||||
.into_iter()
|
||||
.map(|start| {
|
||||
let mut energized = HashSet::new();
|
||||
let mut full_beam = HashSet::new();
|
||||
let mut beam_path = vec![start];
|
||||
while let Some(beam) = beam_path.pop() {
|
||||
energized.insert((beam.0, beam.1));
|
||||
full_beam.insert(beam.clone());
|
||||
beam.next_path(&grid)
|
||||
.into_iter()
|
||||
.filter(|b| !full_beam.contains(b))
|
||||
.for_each(|new_beam| {
|
||||
beam_path.push(new_beam);
|
||||
});
|
||||
}
|
||||
energized.len()
|
||||
})
|
||||
.max()
|
||||
.unwrap();
|
||||
let algo_time = a_start.elapsed();
|
||||
|
||||
// output
|
||||
println!("part 2: {answer}\t[total: {:?}]", start.elapsed());
|
||||
println!("\tparse: {parsed_time:?}");
|
||||
println!("\talgo: {algo_time:?}");
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
println!("Day {DAY}");
|
||||
println!("=====");
|
||||
let input = utils::aoc::get_puzzle_input(DAY).await?;
|
||||
part1(input.clone())?;
|
||||
part2(input.clone())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_part_1() -> Result<()> {
|
||||
assert_eq!(
|
||||
part1(
|
||||
r#".|...\....
|
||||
|.-.\.....
|
||||
.....|-...
|
||||
........|.
|
||||
..........
|
||||
.........\
|
||||
..../.\\..
|
||||
.-.-/..|..
|
||||
.|....-|.\
|
||||
..//.|...."#
|
||||
.to_owned(),
|
||||
)?,
|
||||
46
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_part_2() -> Result<()> {
|
||||
assert_eq!(
|
||||
part2(
|
||||
r#".|...\....
|
||||
|.-.\.....
|
||||
.....|-...
|
||||
........|.
|
||||
..........
|
||||
.........\
|
||||
..../.\\..
|
||||
.-.-/..|..
|
||||
.|....-|.\
|
||||
..//.|...."#
|
||||
.to_owned(),
|
||||
)?,
|
||||
51
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
259
2023/src/bin/day17.rs
Normal file
259
2023/src/bin/day17.rs
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
use aoc23::prelude::*;
|
||||
use derive_builder::Builder;
|
||||
use grid::Grid;
|
||||
use itertools::Itertools;
|
||||
use pathfinding::prelude::dijkstra;
|
||||
use rayon::prelude::*;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
str::FromStr,
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
static DAY: u8 = 17;
|
||||
type Pos = (usize, usize);
|
||||
|
||||
fn delta_pos(from: &Pos, to: &Pos) -> (isize, isize) {
|
||||
return (
|
||||
to.0 as isize - from.0 as isize,
|
||||
to.1 as isize - from.1 as isize,
|
||||
);
|
||||
}
|
||||
|
||||
fn grid(input: &str) -> Grid<usize> {
|
||||
let mut grid = Grid::new(0, 0);
|
||||
for line in input.lines() {
|
||||
let weights = line
|
||||
.trim()
|
||||
.chars()
|
||||
.map(|c| c.to_digit(10).expect("invalid weight") as usize)
|
||||
.collect_vec();
|
||||
grid.push_row(weights);
|
||||
}
|
||||
grid
|
||||
}
|
||||
|
||||
struct Graph {
|
||||
edges: HashMap<Pos, HashMap<Pos, usize>>,
|
||||
}
|
||||
|
||||
impl Graph {
|
||||
fn new_from_weighted_grid(weights: &Grid<usize>) -> Self {
|
||||
let mut edges = HashMap::new();
|
||||
for r in 0..weights.rows() {
|
||||
for c in 0..weights.cols() {
|
||||
let pos = (r, c);
|
||||
for (n_pos, n_weight) in weights.neighbors_orthogonal(pos) {
|
||||
edges
|
||||
.entry(pos)
|
||||
.or_insert_with(|| HashMap::new())
|
||||
.insert(n_pos, *n_weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
Self { edges }
|
||||
}
|
||||
|
||||
fn shortest_path_with_movement_limit(
|
||||
&self,
|
||||
start: Pos,
|
||||
end: Pos,
|
||||
move_limit_min: usize,
|
||||
move_limit_max: usize,
|
||||
) -> usize {
|
||||
// TODO min move limit?
|
||||
let valid_move_in_limit =
|
||||
|delta: (isize, isize), since: &Pos, predecessors: &HashMap<Pos, Option<Pos>>| {
|
||||
let mut current = since;
|
||||
let mut previous = vec![];
|
||||
for i in 0..move_limit_max {
|
||||
if let Some(Some(prev)) = predecessors.get(current) {
|
||||
let previous_movement = delta_pos(prev, current);
|
||||
if previous_movement != delta {
|
||||
// movement is different we are okay
|
||||
break;
|
||||
} else if i == (move_limit_max - 1) {
|
||||
// movement is the same and we are not at the movement limit, break out of this
|
||||
// path, not allowed
|
||||
previous.push(*current);
|
||||
return (false, previous);
|
||||
} else {
|
||||
// check the prior element
|
||||
previous.push(*current);
|
||||
current = prev;
|
||||
}
|
||||
} else {
|
||||
// no path we are good
|
||||
break;
|
||||
}
|
||||
}
|
||||
(true, previous)
|
||||
};
|
||||
|
||||
// based on https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
|
||||
let mut queue: HashMap<Pos, usize> = HashMap::new();
|
||||
let mut distances: HashMap<Pos, usize> = HashMap::new();
|
||||
let mut predecessors: HashMap<Pos, Option<Pos>> = HashMap::new();
|
||||
self.edges.iter().for_each(|(pos, _)| {
|
||||
distances.insert(*pos, usize::MAX);
|
||||
predecessors.insert(*pos, None);
|
||||
queue.insert(*pos, 0);
|
||||
});
|
||||
distances.insert(start, 0);
|
||||
|
||||
// go from start node and check all other nodes
|
||||
while queue.len() > 0 {
|
||||
let ((current, backtrack), distance) = queue
|
||||
.iter()
|
||||
.map(|item| (item, distances.get(item.0).unwrap()))
|
||||
.sorted_by(|(_, distance_a), (_, distance_b)| distance_a.cmp(distance_b))
|
||||
.next()
|
||||
.unwrap();
|
||||
let current = *current;
|
||||
let distance = *distance;
|
||||
queue.remove(¤t);
|
||||
|
||||
// Can terminate dijkstra early if we hit our end value.
|
||||
if current == end {
|
||||
break;
|
||||
}
|
||||
self.edges
|
||||
.get(¤t)
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|(neighbor, n_dist)| {
|
||||
let movement = delta_pos(¤t, neighbor);
|
||||
let in_queue = queue.contains_key(neighbor);
|
||||
let is_not_backwards = {
|
||||
let pred = predecessors.get(¤t).unwrap().unwrap();
|
||||
let pred_movement = delta_pos(&pred, ¤t);
|
||||
|
||||
// not 180 degree turn
|
||||
!(pred_movement.0 * -1 == movement.0 && pred_movement.1 * -1 == movement.1)
|
||||
};
|
||||
let (valid_movement, _) =
|
||||
valid_move_in_limit(movement, ¤t, &predecessors);
|
||||
if in_queue && is_not_backwards && !valid_movement {
|
||||
// if not valid movement in limits then backtrack and try those again ... todo
|
||||
// TODO Do this thing?
|
||||
}
|
||||
return in_queue && valid_movement;
|
||||
})
|
||||
.for_each(|(neighbor, n_dist)| {
|
||||
let current_n_dist = *distances.get(neighbor).unwrap();
|
||||
let alt = distance + n_dist;
|
||||
if alt < current_n_dist {
|
||||
distances.insert(*neighbor, alt);
|
||||
predecessors.insert(*neighbor, Some(current.clone()));
|
||||
}
|
||||
});
|
||||
}
|
||||
*distances.get(&end).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn part1(input: String) -> Result<usize> {
|
||||
// parse
|
||||
let start = Instant::now();
|
||||
let weights = grid(&input);
|
||||
let graph = Graph::new_from_weighted_grid(&weights);
|
||||
let root = (0, 0);
|
||||
let goal = (weights.rows() - 1, weights.cols() - 1);
|
||||
let parsed_time = start.elapsed();
|
||||
|
||||
// algo
|
||||
let a_start = Instant::now();
|
||||
let answer = graph.shortest_path_with_movement_limit(root, goal, 1, 3);
|
||||
let algo_time = a_start.elapsed();
|
||||
|
||||
// output
|
||||
println!("part 1: {answer}\t[total: {:?}]", start.elapsed());
|
||||
println!("\tparse: {parsed_time:?}");
|
||||
println!("\talgo: {algo_time:?}");
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
fn part2(input: String) -> Result<usize> {
|
||||
// parse
|
||||
let start = Instant::now();
|
||||
let weights = grid(&input);
|
||||
let graph = Graph::new_from_weighted_grid(&weights);
|
||||
let root = (0, 0);
|
||||
let goal = (weights.rows() - 1, weights.cols() - 1);
|
||||
let parsed_time = start.elapsed();
|
||||
|
||||
// algo
|
||||
let a_start = Instant::now();
|
||||
let mut answer = 0;
|
||||
let algo_time = a_start.elapsed();
|
||||
|
||||
// output
|
||||
println!("part 2: {answer}\t[total: {:?}]", start.elapsed());
|
||||
println!("\tparse: {parsed_time:?}");
|
||||
println!("\talgo: {algo_time:?}");
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
println!("Day {DAY}");
|
||||
println!("=====");
|
||||
let input = utils::aoc::get_puzzle_input(DAY).await?;
|
||||
part1(input.clone())?;
|
||||
part2(input.clone())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_part_1() -> Result<()> {
|
||||
assert_eq!(
|
||||
part1(
|
||||
"2413432311323
|
||||
3215453535623
|
||||
3255245654254
|
||||
3446585845452
|
||||
4546657867536
|
||||
1438598798454
|
||||
4457876987766
|
||||
3637877979653
|
||||
4654967986887
|
||||
4564679986453
|
||||
1224686865563
|
||||
2546548887735
|
||||
4322674655533"
|
||||
.to_owned(),
|
||||
)?,
|
||||
102
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_part_2() -> Result<()> {
|
||||
assert_eq!(
|
||||
part2(
|
||||
"2413432311323
|
||||
3215453535623
|
||||
3255245654254
|
||||
3446585845452
|
||||
4546657867536
|
||||
1438598798454
|
||||
4457876987766
|
||||
3637877979653
|
||||
4654967986887
|
||||
4564679986453
|
||||
1224686865563
|
||||
2546548887735
|
||||
4322674655533"
|
||||
.to_owned(),
|
||||
)?,
|
||||
0
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
180
2023/src/bin/day18.rs
Normal file
180
2023/src/bin/day18.rs
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
use aoc23::prelude::*;
|
||||
use derive_builder::Builder;
|
||||
use geo::{Area, Coord, Coordinate, LineString, Polygon};
|
||||
use itertools::Itertools;
|
||||
use pathfinding::num_traits::{Float, Signed};
|
||||
use rayon::prelude::*;
|
||||
use std::{collections::HashSet, str::FromStr, time::Instant};
|
||||
|
||||
static DAY: u8 = 18;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct DigCommand {
|
||||
direction: char,
|
||||
distance: i32,
|
||||
color: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct DigPlan {
|
||||
commands: Vec<DigCommand>,
|
||||
}
|
||||
|
||||
impl DigPlan {
|
||||
fn new(s: &str) -> Result<Self> {
|
||||
let commands = s
|
||||
.lines()
|
||||
.map(|line| {
|
||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||
if parts.len() != 3 {
|
||||
return Err("Invalid command format".into());
|
||||
}
|
||||
Ok(DigCommand {
|
||||
direction: parts[0].chars().next().ok_or("Invalid direction")?,
|
||||
distance: parts[1].parse()?,
|
||||
color: parts[2].to_string(),
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<DigCommand>>>()?;
|
||||
Ok(Self { commands })
|
||||
}
|
||||
|
||||
fn calculate_poly(&self) -> geo::Polygon {
|
||||
let mut position = (0, 0);
|
||||
let mut visited: Vec<(i32, i32)> = vec![position];
|
||||
for command in &self.commands {
|
||||
let (dx, dy) = match command.direction {
|
||||
'U' => (0, -1),
|
||||
'D' => (0, 1),
|
||||
'L' => (-1, 0),
|
||||
'R' => (1, 0),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
for _ in 0..command.distance {
|
||||
position.0 += dx;
|
||||
position.1 += dy;
|
||||
visited.push(position);
|
||||
}
|
||||
}
|
||||
if *visited.last().unwrap() != *visited.first().unwrap() {
|
||||
panic!("expected a closed loop")
|
||||
}
|
||||
if visited.iter().sorted_unstable().dedup().count() != visited.len() - 1 {
|
||||
panic!("Loop intersected with itself more than the 1 allowed time for the start node");
|
||||
}
|
||||
println!("visited {}: {visited:?}", visited.len());
|
||||
let coordinates: Vec<Coordinate<f64>> = visited
|
||||
.into_iter()
|
||||
.map(|(x, y)| Coordinate {
|
||||
x: x as f64,
|
||||
y: y as f64,
|
||||
})
|
||||
.collect_vec();
|
||||
Polygon::new(LineString::from(coordinates), vec![])
|
||||
// visited.len()
|
||||
}
|
||||
}
|
||||
|
||||
fn part1(input: String) -> Result<usize> {
|
||||
println!("Input\n====\n{input}\n\n");
|
||||
|
||||
// parse
|
||||
let start = Instant::now();
|
||||
let dig_plan = DigPlan::new(&input)?;
|
||||
let parsed_time = start.elapsed();
|
||||
|
||||
// algo
|
||||
let a_start = Instant::now();
|
||||
let polygon = dig_plan.calculate_poly();
|
||||
let answer = polygon.signed_area().abs();
|
||||
let algo_time = a_start.elapsed();
|
||||
|
||||
// output
|
||||
println!("part 1: {answer}\t[total: {:?}]", start.elapsed());
|
||||
println!("\tparse: {parsed_time:?}");
|
||||
println!("\talgo: {algo_time:?}");
|
||||
Ok(answer as usize)
|
||||
}
|
||||
|
||||
fn part2(input: String) -> Result<usize> {
|
||||
// parse
|
||||
let start = Instant::now();
|
||||
let dig_plan = DigPlan::new(&input)?;
|
||||
let parsed_time = start.elapsed();
|
||||
|
||||
// algo
|
||||
let a_start = Instant::now();
|
||||
let mut answer = 0;
|
||||
let algo_time = a_start.elapsed();
|
||||
|
||||
// output
|
||||
println!("part 2: {answer}\t[total: {:?}]", start.elapsed());
|
||||
println!("\tparse: {parsed_time:?}");
|
||||
println!("\talgo: {algo_time:?}");
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
println!("Day {DAY}");
|
||||
println!("=====");
|
||||
let input = utils::aoc::get_puzzle_input(DAY).await?;
|
||||
part1(input.clone())?;
|
||||
part2(input.clone())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_part_1() -> Result<()> {
|
||||
assert_eq!(
|
||||
part1(
|
||||
"R 6 (#70c710)
|
||||
D 5 (#0dc571)
|
||||
L 2 (#5713f0)
|
||||
D 2 (#d2c081)
|
||||
R 2 (#59c680)
|
||||
D 2 (#411b91)
|
||||
L 5 (#8ceee2)
|
||||
U 2 (#caa173)
|
||||
L 1 (#1b58a2)
|
||||
U 2 (#caa171)
|
||||
R 2 (#7807d2)
|
||||
U 3 (#a77fa3)
|
||||
L 2 (#015232)
|
||||
U 2 (#7a21e3)"
|
||||
.to_owned(),
|
||||
)?,
|
||||
62
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_part_2() -> Result<()> {
|
||||
assert_eq!(
|
||||
part2(
|
||||
"R 6 (#70c710)
|
||||
D 5 (#0dc571)
|
||||
L 2 (#5713f0)
|
||||
D 2 (#d2c081)
|
||||
R 2 (#59c680)
|
||||
D 2 (#411b91)
|
||||
L 5 (#8ceee2)
|
||||
U 2 (#caa173)
|
||||
L 1 (#1b58a2)
|
||||
U 2 (#caa171)
|
||||
R 2 (#7807d2)
|
||||
U 3 (#a77fa3)
|
||||
L 2 (#015232)
|
||||
U 2 (#7a21e3)"
|
||||
.to_owned(),
|
||||
)?,
|
||||
0
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
425
2023/src/bin/day19.rs
Normal file
425
2023/src/bin/day19.rs
Normal file
|
|
@ -0,0 +1,425 @@
|
|||
use aoc23::prelude::*;
|
||||
use itertools::Itertools;
|
||||
use regex::Regex;
|
||||
use static_init::dynamic;
|
||||
use std::{collections::HashMap, time::Instant};
|
||||
|
||||
static DAY: u8 = 19;
|
||||
#[dynamic]
|
||||
static RE_PARSE_PART: Regex = Regex::new(r"x=(?<x>\d+).*m=(?<m>\w+).*a=(?<a>\d+).*s=(?<s>\d+)")
|
||||
.expect("re_parse_part invalid");
|
||||
#[dynamic]
|
||||
static RE_PARSE_WORKFLOW: Regex =
|
||||
Regex::new(r"^(?<name>\w+)\{(?<ops>.*)}").expect("re_parse_workflow invalid");
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Part {
|
||||
x: usize,
|
||||
m: usize,
|
||||
a: usize,
|
||||
s: usize,
|
||||
}
|
||||
|
||||
impl Part {
|
||||
fn new_from_line(line: &str) -> Self {
|
||||
let re = RE_PARSE_PART.captures(line).expect("part does not match");
|
||||
Self {
|
||||
x: re.name("x").unwrap().as_str().parse().unwrap(),
|
||||
m: re.name("m").unwrap().as_str().parse().unwrap(),
|
||||
a: re.name("a").unwrap().as_str().parse().unwrap(),
|
||||
s: re.name("s").unwrap().as_str().parse().unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum PartCategory {
|
||||
X,
|
||||
M,
|
||||
A,
|
||||
S,
|
||||
}
|
||||
|
||||
impl PartCategory {
|
||||
fn new_from_char(c: &char) -> Self {
|
||||
match c {
|
||||
'x' => Self::X,
|
||||
'm' => Self::M,
|
||||
'a' => Self::A,
|
||||
's' => Self::S,
|
||||
_ => panic!("wee woo"),
|
||||
}
|
||||
}
|
||||
|
||||
fn value_of(&self, part: &Part) -> usize {
|
||||
match self {
|
||||
PartCategory::X => part.x,
|
||||
PartCategory::M => part.m,
|
||||
PartCategory::A => part.a,
|
||||
PartCategory::S => part.s,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum BasicFlowOp {
|
||||
Accept,
|
||||
Reject,
|
||||
Route(String),
|
||||
}
|
||||
|
||||
impl BasicFlowOp {
|
||||
fn new_from_line(line: &str) -> Self {
|
||||
match line {
|
||||
"A" => Self::Accept,
|
||||
"R" => Self::Reject,
|
||||
route => Self::Route(route.to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
fn as_flow_op(&self) -> FlowOp {
|
||||
match self {
|
||||
BasicFlowOp::Accept => FlowOp::Accept,
|
||||
BasicFlowOp::Reject => FlowOp::Reject,
|
||||
BasicFlowOp::Route(to) => FlowOp::Route(to.to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
fn part2_to(&self) -> String {
|
||||
match self {
|
||||
BasicFlowOp::Accept => "A".to_owned(),
|
||||
BasicFlowOp::Reject => "R".to_owned(),
|
||||
BasicFlowOp::Route(to) => to.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum FlowOp {
|
||||
LessThan(PartCategory, usize, BasicFlowOp),
|
||||
GreaterThan(PartCategory, usize, BasicFlowOp),
|
||||
Accept,
|
||||
Reject,
|
||||
Route(String),
|
||||
}
|
||||
|
||||
impl FlowOp {
|
||||
fn new_from_line(line: &str) -> Self {
|
||||
let split = line.split([':', ',', '>', '<']).collect_vec();
|
||||
if split.len() == 1 {
|
||||
BasicFlowOp::new_from_line(split.get(0).unwrap()).as_flow_op()
|
||||
} else {
|
||||
if line.contains('>') {
|
||||
Self::GreaterThan(
|
||||
PartCategory::new_from_char(&split.get(0).unwrap().chars().next().unwrap()),
|
||||
split.get(1).unwrap().parse().unwrap(),
|
||||
BasicFlowOp::new_from_line(split.get(2).unwrap()),
|
||||
)
|
||||
} else {
|
||||
Self::LessThan(
|
||||
PartCategory::new_from_char(&split.get(0).unwrap().chars().next().unwrap()),
|
||||
split.get(1).unwrap().parse().unwrap(),
|
||||
BasicFlowOp::new_from_line(split.get(2).unwrap()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Workflow {
|
||||
name: String,
|
||||
flow: Vec<FlowOp>,
|
||||
}
|
||||
|
||||
impl Workflow {
|
||||
fn new_from_line(line: &str) -> Self {
|
||||
let re = RE_PARSE_WORKFLOW
|
||||
.captures(line)
|
||||
.expect("workflow does not match");
|
||||
Self {
|
||||
name: re.name("name").unwrap().as_str().to_owned(),
|
||||
flow: re
|
||||
.name("ops")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.split(',')
|
||||
.map(FlowOp::new_from_line)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_system(input: &str) -> (HashMap<String, Workflow>, Vec<Part>) {
|
||||
let split = input.split("\n\n").collect_vec();
|
||||
(
|
||||
split
|
||||
.get(0)
|
||||
.unwrap()
|
||||
.lines()
|
||||
.map(Workflow::new_from_line)
|
||||
.map(|w| (w.name.clone(), w))
|
||||
.collect(),
|
||||
split
|
||||
.get(1)
|
||||
.unwrap()
|
||||
.lines()
|
||||
.map(Part::new_from_line)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn check_part_accepted(part: &Part, workflows: &HashMap<String, Workflow>) -> bool {
|
||||
let mut current_workflow = workflows.get("in");
|
||||
'flows: while let Some(workflow) = current_workflow {
|
||||
current_workflow = None;
|
||||
for flow_op in workflow.flow.iter() {
|
||||
let basic_op = match flow_op {
|
||||
FlowOp::LessThan(cat, num, bop) => {
|
||||
if cat.value_of(part) < *num {
|
||||
bop.as_flow_op()
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
FlowOp::GreaterThan(cat, num, bop) => {
|
||||
if cat.value_of(part) > *num {
|
||||
bop.as_flow_op()
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
op => op.clone(),
|
||||
};
|
||||
match basic_op {
|
||||
FlowOp::Accept => return true,
|
||||
FlowOp::Reject => return false,
|
||||
FlowOp::Route(to) => {
|
||||
current_workflow = workflows.get(&to);
|
||||
continue 'flows;
|
||||
}
|
||||
_ => panic!("not basic"),
|
||||
}
|
||||
}
|
||||
}
|
||||
panic!("why are we here?")
|
||||
}
|
||||
|
||||
fn part1(input: String) -> Result<usize> {
|
||||
// parse
|
||||
let start = Instant::now();
|
||||
let (workflows, parts) = parse_system(&input);
|
||||
let parsed_time = start.elapsed();
|
||||
|
||||
// algo
|
||||
let a_start = Instant::now();
|
||||
let answer = parts
|
||||
.iter()
|
||||
.filter_map(|p| check_part_accepted(p, &workflows).then(|| p.x + p.m + p.a + p.s))
|
||||
.sum();
|
||||
let algo_time = a_start.elapsed();
|
||||
|
||||
// output
|
||||
println!("part 1: {answer}\t[total: {:?}]", start.elapsed());
|
||||
println!("\tparse: {parsed_time:?}");
|
||||
println!("\talgo: {algo_time:?}");
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
fn part2(input: String) -> Result<usize> {
|
||||
// parse
|
||||
let start = Instant::now();
|
||||
let (workflows, _) = parse_system(&input);
|
||||
let parsed_time = start.elapsed();
|
||||
|
||||
// algo
|
||||
let a_start = Instant::now();
|
||||
|
||||
// LOL yeah right... no no no to brute force on this one
|
||||
//
|
||||
// ```
|
||||
// let bar = ProgressBar::new(4000 * 4000 * 4000 * 4000);
|
||||
// let answer = iproduct!(1..=4000usize, 1..=4000usize, 1..=4000usize, 1..=4000usize)
|
||||
// .par_bridge()
|
||||
// .map(|(x, m, a, s)| {
|
||||
// bar.inc(1);
|
||||
// Part { x, m, a, s }
|
||||
// })
|
||||
// .filter(|part| check_part_accepted(part, &workflows))
|
||||
// .count();
|
||||
// bar.finish();
|
||||
// ```
|
||||
//
|
||||
// Honestly copied someone else's approach for this one...
|
||||
// https://github.com/klimesf/advent-of-code/blob/master/src/y2023/day19.rs
|
||||
let mut stack: Vec<(
|
||||
(usize, usize),
|
||||
(usize, usize),
|
||||
(usize, usize),
|
||||
(usize, usize),
|
||||
String,
|
||||
usize,
|
||||
)> = vec![(
|
||||
(1, 4000),
|
||||
(1, 4000),
|
||||
(1, 4000),
|
||||
(1, 4000),
|
||||
"in".to_owned(),
|
||||
0,
|
||||
)];
|
||||
let mut accepted: Vec<(
|
||||
(usize, usize),
|
||||
(usize, usize),
|
||||
(usize, usize),
|
||||
(usize, usize),
|
||||
)> = vec![];
|
||||
while let Some(range) = stack.pop() {
|
||||
let (x, m, a, s, wf_key, rule_key) = range;
|
||||
if wf_key == "A" {
|
||||
accepted.push((x, m, a, s));
|
||||
continue;
|
||||
} else if wf_key == "R" {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Invalid bounds check
|
||||
if x.0 > x.1 || m.0 > m.1 || a.0 > a.1 || s.0 > s.1 {
|
||||
continue;
|
||||
}
|
||||
let workflow = workflows.get(&wf_key).unwrap();
|
||||
let flow_op = &workflow.flow[rule_key];
|
||||
match flow_op {
|
||||
FlowOp::Accept => {
|
||||
accepted.push((x, m, a, s));
|
||||
continue;
|
||||
}
|
||||
FlowOp::Reject => {
|
||||
continue;
|
||||
}
|
||||
FlowOp::Route(new_wf_key) => {
|
||||
stack.push((x, m, a, s, new_wf_key.to_owned(), 0));
|
||||
continue;
|
||||
}
|
||||
FlowOp::GreaterThan(category, number, flow_op) => match category {
|
||||
PartCategory::X => {
|
||||
stack.push(((number + 1, x.1), m, a, s, flow_op.part2_to(), 0));
|
||||
stack.push(((x.0, *number), m, a, s, wf_key, rule_key + 1));
|
||||
}
|
||||
PartCategory::M => {
|
||||
stack.push((x, (number + 1, m.1), a, s, flow_op.part2_to(), 0));
|
||||
stack.push((x, (m.0, *number), a, s, wf_key, rule_key + 1));
|
||||
}
|
||||
PartCategory::A => {
|
||||
stack.push((x, m, (number + 1, a.1), s, flow_op.part2_to(), 0));
|
||||
stack.push((x, m, (a.0, *number), s, wf_key, rule_key + 1));
|
||||
}
|
||||
PartCategory::S => {
|
||||
stack.push((x, m, a, (number + 1, s.1), flow_op.part2_to(), 0));
|
||||
stack.push((x, m, a, (s.0, *number), wf_key, rule_key + 1));
|
||||
}
|
||||
},
|
||||
FlowOp::LessThan(category, number, flow_op) => match category {
|
||||
PartCategory::X => {
|
||||
stack.push(((x.0, number - 1), m, a, s, flow_op.part2_to(), 0));
|
||||
stack.push(((*number, x.1), m, a, s, wf_key, rule_key + 1));
|
||||
}
|
||||
PartCategory::M => {
|
||||
stack.push((x, (m.0, number - 1), a, s, flow_op.part2_to(), 0));
|
||||
stack.push((x, (*number, m.1), a, s, wf_key, rule_key + 1));
|
||||
}
|
||||
PartCategory::A => {
|
||||
stack.push((x, m, (a.0, number - 1), s, flow_op.part2_to(), 0));
|
||||
stack.push((x, m, (*number, a.1), s, wf_key, rule_key + 1));
|
||||
}
|
||||
PartCategory::S => {
|
||||
stack.push((x, m, a, (s.0, number - 1), flow_op.part2_to(), 0));
|
||||
stack.push((x, m, a, (*number, s.1), wf_key, rule_key + 1));
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
let answer = accepted
|
||||
.iter()
|
||||
.map(|(x, m, a, s)| (x.1 - x.0 + 1) * (m.1 - m.0 + 1) * (a.1 - a.0 + 1) * (s.1 - s.0 + 1))
|
||||
.sum();
|
||||
let algo_time = a_start.elapsed();
|
||||
|
||||
// output
|
||||
println!("part 2: {answer}\t[total: {:?}]", start.elapsed());
|
||||
println!("\tparse: {parsed_time:?}");
|
||||
println!("\talgo: {algo_time:?}");
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
println!("Day {DAY}");
|
||||
println!("=====");
|
||||
let input = utils::aoc::get_puzzle_input(DAY).await?;
|
||||
part1(input.clone())?;
|
||||
part2(input.clone())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_part_1() -> Result<()> {
|
||||
assert_eq!(
|
||||
part1(
|
||||
"px{a<2006:qkq,m>2090:A,rfg}
|
||||
pv{a>1716:R,A}
|
||||
lnx{m>1548:A,A}
|
||||
rfg{s<537:gd,x>2440:R,A}
|
||||
qs{s>3448:A,lnx}
|
||||
qkq{x<1416:A,crn}
|
||||
crn{x>2662:A,R}
|
||||
in{s<1351:px,qqz}
|
||||
qqz{s>2770:qs,m<1801:hdj,R}
|
||||
gd{a>3333:R,R}
|
||||
hdj{m>838:A,pv}
|
||||
|
||||
{x=787,m=2655,a=1222,s=2876}
|
||||
{x=1679,m=44,a=2067,s=496}
|
||||
{x=2036,m=264,a=79,s=2244}
|
||||
{x=2461,m=1339,a=466,s=291}
|
||||
{x=2127,m=1623,a=2188,s=1013}
|
||||
"
|
||||
.to_owned(),
|
||||
)?,
|
||||
19114
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_part_2() -> Result<()> {
|
||||
assert_eq!(
|
||||
part2(
|
||||
"px{a<2006:qkq,m>2090:A,rfg}
|
||||
pv{a>1716:R,A}
|
||||
lnx{m>1548:A,A}
|
||||
rfg{s<537:gd,x>2440:R,A}
|
||||
qs{s>3448:A,lnx}
|
||||
qkq{x<1416:A,crn}
|
||||
crn{x>2662:A,R}
|
||||
in{s<1351:px,qqz}
|
||||
qqz{s>2770:qs,m<1801:hdj,R}
|
||||
gd{a>3333:R,R}
|
||||
hdj{m>838:A,pv}
|
||||
|
||||
{x=787,m=2655,a=1222,s=2876}
|
||||
{x=1679,m=44,a=2067,s=496}
|
||||
{x=2036,m=264,a=79,s=2244}
|
||||
{x=2461,m=1339,a=466,s=291}
|
||||
{x=2127,m=1623,a=2188,s=1013}
|
||||
"
|
||||
.to_owned(),
|
||||
)?,
|
||||
167409079868000
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
81
2023/src/bin/template.rs
Normal file
81
2023/src/bin/template.rs
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
use aoc23::prelude::*;
|
||||
use derive_builder::Builder;
|
||||
use itertools::Itertools;
|
||||
use rayon::prelude::*;
|
||||
use std::{str::FromStr, time::Instant};
|
||||
|
||||
static DAY: u8 = TODO;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Todo {}
|
||||
|
||||
impl Todo {
|
||||
fn new(s: &str) -> Result<Self> {
|
||||
Ok(Self {})
|
||||
}
|
||||
}
|
||||
|
||||
fn part1(input: String) -> Result<usize> {
|
||||
println!("Input\n====\n{input}\n\n");
|
||||
|
||||
// parse
|
||||
let start = Instant::now();
|
||||
let todo = Todo::new(&input)?;
|
||||
let parsed_time = start.elapsed();
|
||||
|
||||
// algo
|
||||
let a_start = Instant::now();
|
||||
let mut answer = 0;
|
||||
let algo_time = a_start.elapsed();
|
||||
|
||||
// output
|
||||
println!("part 1: {answer}\t[total: {:?}]", start.elapsed());
|
||||
println!("\tparse: {parsed_time:?}");
|
||||
println!("\talgo: {algo_time:?}");
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
fn part2(input: String) -> Result<usize> {
|
||||
// parse
|
||||
let start = Instant::now();
|
||||
let todo = Todo::new(&input)?;
|
||||
let parsed_time = start.elapsed();
|
||||
|
||||
// algo
|
||||
let a_start = Instant::now();
|
||||
let mut answer = 0;
|
||||
let algo_time = a_start.elapsed();
|
||||
|
||||
// output
|
||||
println!("part 2: {answer}\t[total: {:?}]", start.elapsed());
|
||||
println!("\tparse: {parsed_time:?}");
|
||||
println!("\talgo: {algo_time:?}");
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
println!("Day {DAY}");
|
||||
println!("=====");
|
||||
let input = utils::aoc::get_puzzle_input(DAY).await?;
|
||||
part1(input.clone())?;
|
||||
part2(input.clone())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_part_1() -> Result<()> {
|
||||
assert_eq!(part1("REPLACE".to_owned())?, 0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_part_2() -> Result<()> {
|
||||
assert_eq!(part2("REPLACE".to_owned())?, 0);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
66
2023/src/bin/test.rs
Normal file
66
2023/src/bin/test.rs
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
use std::io::{self, Write};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
let mut food = (0, 0);
|
||||
let mut snake = vec![(1, 2), (1, 3)];
|
||||
let mut direction = (0, 1);
|
||||
print_board(&snake, &food);
|
||||
loop {
|
||||
let command: String = io::stdin().lock().read_line().unwrap();
|
||||
match command.as_str() {
|
||||
"\n" | "" => {}
|
||||
"w" | "W" => direction = (0, -1),
|
||||
"a" | "A" => direction = (-1, 0),
|
||||
"s" | "S" => direction = (0, 1),
|
||||
"d" | "D" => direction = (1, 0),
|
||||
_ => println!("Invalid command!"),
|
||||
}
|
||||
let new_head = get_new_position(&snake.last().unwrap(), &direction);
|
||||
if snake.contains(&new_head)
|
||||
|| new_head.0 < 0
|
||||
|| new_head.0 >= 20
|
||||
|| new_head.1 < 0
|
||||
|| new_head.1 >= 20
|
||||
{
|
||||
println!("Game over!");
|
||||
break;
|
||||
}
|
||||
snake.push(new_head);
|
||||
if new_head == food {
|
||||
food = get_new_food();
|
||||
} else {
|
||||
snake.remove(0);
|
||||
}
|
||||
print_board(&snake, &food);
|
||||
thread::sleep(Duration::from_millis(50));
|
||||
}
|
||||
}
|
||||
|
||||
fn get_new_position(head: &(i32, i32), direction: &(i32, i32)) -> (i32, i32) {
|
||||
let x = head.0 + direction.0;
|
||||
let y = head.1 + direction.1;
|
||||
(x, y)
|
||||
}
|
||||
|
||||
fn get_new_food() -> (i32, i32) {
|
||||
(rand::random::<i32>() % 20, rand::random::<i32>() % 20)
|
||||
}
|
||||
|
||||
fn print_board(snake: &Vec<(i32, i32)>, food: &(i32, i32)) {
|
||||
// Clear screen
|
||||
print!("\x1b[H\x1b[J");
|
||||
for y in 0..20 {
|
||||
for x in 0..20 {
|
||||
if snake.contains(&(x, y)) {
|
||||
print!("█");
|
||||
} else if (x, y) == *food {
|
||||
print!("*");
|
||||
} else {
|
||||
print!(".");
|
||||
}
|
||||
}
|
||||
println!();
|
||||
}
|
||||
}
|
||||
12
2023/src/lib.rs
Normal file
12
2023/src/lib.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#[cfg(all(feature = "part1", feature = "part2"))]
|
||||
compile_error!("Part 1 and Part 2 are mutually exclusive and cannot be enabled together");
|
||||
pub mod utils;
|
||||
|
||||
pub mod prelude {
|
||||
pub use super::utils::{
|
||||
self,
|
||||
common::{BoxE, Result, SError, SResult},
|
||||
config::get_config,
|
||||
grid::GridExtras,
|
||||
};
|
||||
}
|
||||
61
2023/src/utils/aoc.rs
Normal file
61
2023/src/utils/aoc.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
use crate::prelude::*;
|
||||
use reqwest::{
|
||||
header::{self, COOKIE},
|
||||
Client,
|
||||
};
|
||||
use reqwest_middleware::{ClientBuilder as MiddlewareClientBuilder, ClientWithMiddleware};
|
||||
use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware};
|
||||
use std::fs::{self, File};
|
||||
use std::io::{Read, Write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
static AOC_PUZZLE_INPUT_CACHE: &str = "aoc_puzzle_cache";
|
||||
|
||||
pub async fn get_puzzle_input(day: u8) -> Result<String> {
|
||||
let file_name = format!("day_{:02}", day);
|
||||
let cache_path = PathBuf::from(AOC_PUZZLE_INPUT_CACHE).join(file_name);
|
||||
if cache_path.exists() {
|
||||
// Read from the cache file
|
||||
let mut cache_file = File::open(cache_path)?;
|
||||
let mut contents = String::new();
|
||||
cache_file.read_to_string(&mut contents)?;
|
||||
Ok(contents)
|
||||
} else {
|
||||
// Fetch the content from the URL
|
||||
let response = aoc_client()
|
||||
.await?
|
||||
.get(format!("https://adventofcode.com/2023/day/{}/input", day))
|
||||
.send()
|
||||
.await?
|
||||
.text()
|
||||
.await?;
|
||||
|
||||
// Cache the content to a file Ensure the cache directory exists
|
||||
fs::create_dir_all(AOC_PUZZLE_INPUT_CACHE)?;
|
||||
let mut cache_file = File::create(cache_path)?;
|
||||
cache_file.write_all(response.as_bytes())?;
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn aoc_client() -> Result<ClientWithMiddleware> {
|
||||
let session = &get_config().aoc_session;
|
||||
let mut session_cookie = header::HeaderValue::from_str(&format!("session={}", session))
|
||||
.expect("failed to create header with api_key.");
|
||||
session_cookie.set_sensitive(true);
|
||||
let mut headers = header::HeaderMap::new();
|
||||
headers.insert(COOKIE, session_cookie);
|
||||
let client = Client::builder()
|
||||
.default_headers(headers)
|
||||
.gzip(true)
|
||||
.brotli(true)
|
||||
.deflate(true)
|
||||
.https_only(false)
|
||||
.build()?;
|
||||
let client = MiddlewareClientBuilder::new(client)
|
||||
.with(RetryTransientMiddleware::new_with_policy(
|
||||
ExponentialBackoff::builder().build_with_max_retries(2),
|
||||
))
|
||||
.build();
|
||||
Ok(client)
|
||||
}
|
||||
4
2023/src/utils/common.rs
Normal file
4
2023/src/utils/common.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
pub type SResult<T, R> = std::result::Result<T, R>;
|
||||
pub type SError = dyn std::error::Error;
|
||||
pub type BoxE = Box<SError>;
|
||||
pub type Result<T> = SResult<T, BoxE>;
|
||||
20
2023/src/utils/config.rs
Normal file
20
2023/src/utils/config.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
use std::{env::var, sync::OnceLock};
|
||||
|
||||
static CONFIG: OnceLock<Config> = OnceLock::new();
|
||||
|
||||
pub struct Config {
|
||||
pub aoc_session: String,
|
||||
}
|
||||
|
||||
impl Config {}
|
||||
|
||||
fn get_var(var_name: &str) -> String {
|
||||
var(var_name).unwrap_or("".to_owned())
|
||||
}
|
||||
|
||||
pub fn get_config() -> &'static Config {
|
||||
let config = CONFIG.get_or_init(|| Config {
|
||||
aoc_session: get_var("AOC_SESSION"),
|
||||
});
|
||||
config
|
||||
}
|
||||
36
2023/src/utils/grid.rs
Normal file
36
2023/src/utils/grid.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
use super::math::orthogonal_u_bounded;
|
||||
use grid::Grid;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub trait GridExtras<T> {
|
||||
fn neighbors_all(&self, pos: (usize, usize)) -> HashMap<(usize, usize), &T>;
|
||||
fn neighbors_orthogonal(&self, pos: (usize, usize)) -> HashMap<(usize, usize), &T>;
|
||||
}
|
||||
|
||||
impl<T> GridExtras<T> for Grid<T> {
|
||||
fn neighbors_all(&self, pos: (usize, usize)) -> HashMap<(usize, usize), &T> {
|
||||
let mut n = HashMap::new();
|
||||
for r in -1..=1 {
|
||||
for c in -1..=1 {
|
||||
// skip self
|
||||
if r == 0 && c == 0 {
|
||||
continue;
|
||||
}
|
||||
if let Some(neighbor) = pos.0.checked_add_signed(r).zip(pos.1.checked_add_signed(c))
|
||||
{
|
||||
if let Some(t) = self.get(neighbor.0, neighbor.1) {
|
||||
n.insert(neighbor, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
n
|
||||
}
|
||||
|
||||
fn neighbors_orthogonal(&self, pos: (usize, usize)) -> HashMap<(usize, usize), &T> {
|
||||
orthogonal_u_bounded(pos)
|
||||
.into_iter()
|
||||
.filter_map(|(pos, _delta)| self.get(pos.0, pos.1).map(|t| (pos, t)))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
27
2023/src/utils/math.rs
Normal file
27
2023/src/utils/math.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
fn gcd(a: u64, b: u64) -> u64 {
|
||||
if b == 0 {
|
||||
a
|
||||
} else {
|
||||
gcd(b, a % b)
|
||||
}
|
||||
}
|
||||
|
||||
fn lcm(a: u64, b: u64) -> u64 {
|
||||
a / gcd(a, b) * b
|
||||
}
|
||||
|
||||
pub fn find_lcm(numbers: &[u64]) -> u64 {
|
||||
numbers.iter().cloned().fold(1, |acc, num| lcm(acc, num))
|
||||
}
|
||||
|
||||
pub fn orthogonal_u_bounded(root: (usize, usize)) -> Vec<((usize, usize), (isize, isize))> {
|
||||
[(0, 1), (0, -1), (1, 0), (-1, 0)]
|
||||
.into_iter()
|
||||
.filter_map(|delta| {
|
||||
root.0
|
||||
.checked_add_signed(delta.0)
|
||||
.zip(root.1.checked_add_signed(delta.1))
|
||||
.map(|new_pos| (new_pos, delta))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
5
2023/src/utils/mod.rs
Normal file
5
2023/src/utils/mod.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
pub mod aoc;
|
||||
pub mod common;
|
||||
pub mod config;
|
||||
pub mod grid;
|
||||
pub mod math;
|
||||
Loading…
Add table
Add a link
Reference in a new issue