From b0fce4d80a70961d85d1ec429d3427e3250ff53a Mon Sep 17 00:00:00 2001 From: Tyler Beckman Date: Thu, 12 Dec 2024 23:24:19 -0700 Subject: [PATCH] Day 13 --- Cargo.toml | 4 ++ day12/part2.rs | 140 ++++++++++++++++++++++++++++++++++++++++++++++++ day13/part1.kts | 56 +++++++++++++++++++ day13/part2.kts | 58 ++++++++++++++++++++ 4 files changed, 258 insertions(+) create mode 100644 day12/part2.rs create mode 100755 day13/part1.kts create mode 100755 day13/part2.kts diff --git a/Cargo.toml b/Cargo.toml index 67f5862..0eae11b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,7 @@ path = "day11/part2.rs" [[bin]] name = "d12p1" path = "day12/part1.rs" + +[[bin]] +name = "d12p2" +path = "day12/part2.rs" diff --git a/day12/part2.rs b/day12/part2.rs new file mode 100644 index 0000000..1e81e7d --- /dev/null +++ b/day12/part2.rs @@ -0,0 +1,140 @@ +#![feature(let_chains)] +use std::collections::{HashSet, VecDeque}; + +const GRID_SIZE: usize = include_bytes!("input.txt").len().isqrt(); + +fn dfs(grid: &[[char; GRID_SIZE]; GRID_SIZE], start: (usize, usize)) -> HashSet<(usize, usize)> { + let mut queue = VecDeque::from([start]); + let mut set = HashSet::from([start]); + let plant_type = grid[start.0][start.1]; + + while let Some(next) = queue.pop_front() { + let to_check = [ + (next.0 + 1, next.1), + (next.0.wrapping_sub(1), next.1), + (next.0, next.1 + 1), + (next.0, next.1.wrapping_sub(1)), + ]; + for pos in to_check { + if set.contains(&pos) { + continue; + } + + if let Some(plant) = grid.get(pos.0).and_then(|r| r.get(pos.1)) + && *plant == plant_type + { + queue.push_back(pos); + set.insert(pos); + } + } + } + + set +} + +// fn dfs_perimeter( +// perimeter_set: &HashSet<(isize, isize)>, +// start: (isize, isize), +// ) -> HashSet<(isize, isize)> { +// let mut queue = VecDeque::from([start]); +// let mut set = HashSet::from([start]); + +// while let Some(next) = queue.pop_front() { +// let to_check = [ +// (next.0 + 1, next.1), +// (next.0 - 1, next.1), +// (next.0, next.1 + 1), +// (next.0, next.1 - 1), +// ]; +// for pos in to_check { +// if set.contains(&pos) { +// continue; +// } + +// if perimeter_set.contains(&pos) { +// queue.push_back(pos); +// set.insert(pos); +// } +// } +// } + +// set +// } + +fn main() { + let mut input_iter = include_str!("input.txt").lines().map(|l| { + let mut iter = l.chars(); + std::array::from_fn::<_, GRID_SIZE, _>(|_| iter.next().unwrap()) + }); + let grid: [_; GRID_SIZE] = std::array::from_fn(|_| input_iter.next().unwrap()); + + // Parse all independent regions + let mut regions = Vec::<(char, HashSet<(usize, usize)>)>::new(); + for row in 0..GRID_SIZE { + for col in 0..GRID_SIZE { + if regions.iter().all(|region| !region.1.contains(&(row, col))) { + regions.push((grid[row][col], dfs(&grid, (row, col)))); + } + } + } + + // Sanity check -> are there any duplicated nodes between the regions? + assert_eq!( + regions.iter().map(|r| r.1.len()).sum::(), + regions + .iter() + .map(|r| &r.1) + .flatten() + .collect::>() + .len() + ); + + // Expand grid by two because im going insane + let mut expanded_grid = [[' '; GRID_SIZE * 2 + 1]; GRID_SIZE * 2 + 1]; + for row in 0..GRID_SIZE { + for col in 0..GRID_SIZE { + expanded_grid[row * 2 + 1][col * 2 + 1] = grid[row][col]; + } + } + + for row in 0..(GRID_SIZE*2+1) { + print!("|"); + for col in 0..(GRID_SIZE*2+1) { + print!("{}", expanded_grid[row][col]); + } + println!("|"); + } + + + // Calculate area and sides of each + let mut sum = 0; + for (plant_type, region) in regions.into_iter() { + let area = region.len(); + let sides = 0; + + // Clone the expanded grid + let expanded_grid_clone = expanded_grid.clone(); + for pos in region.iter() { + // None in a position is a special case meaning "overfilled in the negative direction" + // Since we don't actually access the point there, it just gets treated like -1 would + let to_check = [ + (pos.0.checked_add(1), Some(pos.1)), + (pos.0.checked_sub(1), Some(pos.1)), + (Some(pos.0), pos.1.checked_add(1)), + (Some(pos.0), pos.1.checked_sub(1)), + ]; + for outer_pos in to_check { + if (outer_pos.0.is_none() || outer_pos.1.is_none()) + || !region.contains(&(outer_pos.0.unwrap(), outer_pos.1.unwrap())) + { + expanded_grid_clone[outer_pos.0.map(|p| p * 2 + 1)] + } + } + } + + sum += area * sides; + println!("{plant_type}: {area}A {sides}S"); + } + + println!("Result: {sum}"); +} diff --git a/day13/part1.kts b/day13/part1.kts new file mode 100755 index 0000000..ec84c97 --- /dev/null +++ b/day13/part1.kts @@ -0,0 +1,56 @@ +#!/usr/bin/env kotlin + +import java.io.File +import kotlin.math.floor + +// I LOVE MATH +typealias Matrix2 = Pair, Pair> +fun Matrix2.determinant(): Int = (this.first.first * this.second.second) - (this.first.second * this.second.first) + +typealias Point = Pair + +data class Game( + val buttonA: Point, + val buttonB: Point, + val goal: Point +) + +val games = File("input.txt").readLines().chunked(4).map { lines -> + Game( + buttonA = lines[0].substring(12).split(", Y+").let { + it[0].toInt() to it[1].toInt() + }, + buttonB = lines[1].substring(12).split(", Y+").let { + it[0].toInt() to it[1].toInt() + }, + goal = lines[2].substring(9).split(", Y=").let { + it[0].toInt() to it[1].toInt() + } + ) +} + +var tokens = 0 + +games.forEach { game -> + val aPresses = Matrix2( + game.goal.first to game.buttonB.first, + game.goal.second to game.buttonB.second + ).determinant().toDouble() / Matrix2( + game.buttonA.first to game.buttonB.first, + game.buttonA.second to game.buttonB.second + ).determinant() + + val bPresses = Matrix2( + game.buttonA.first to game.goal.first, + game.buttonA.second to game.goal.second + ).determinant().toDouble() / Matrix2( + game.buttonA.first to game.buttonB.first, + game.buttonA.second to game.buttonB.second + ).determinant() + + if (aPresses != floor(aPresses) || bPresses != floor(bPresses)) return@forEach + + tokens += aPresses.toInt() * 3 + bPresses.toInt() +} + +println("Result: $tokens") \ No newline at end of file diff --git a/day13/part2.kts b/day13/part2.kts new file mode 100755 index 0000000..5d556cf --- /dev/null +++ b/day13/part2.kts @@ -0,0 +1,58 @@ +#!/usr/bin/env kotlin + +import java.io.File +import kotlin.math.floor + +// I LOVE MATH +typealias Matrix2 = Pair, Pair> +fun Matrix2.determinant(): Long = (this.first.first * this.second.second) - (this.first.second * this.second.first) + +typealias Point = Pair + +data class Game( + val buttonA: Point, + val buttonB: Point, + val goal: Point +) + +val games = File("input.txt").readLines().chunked(4).map { lines -> + Game( + buttonA = lines[0].substring(12).split(", Y+").let { + it[0].toLong() to it[1].toLong() + }, + buttonB = lines[1].substring(12).split(", Y+").let { + it[0].toLong() to it[1].toLong() + }, + goal = lines[2].substring(9).split(", Y=").let { + it[0].toLong() + 10000000000000 to it[1].toLong() + 10000000000000 + } + ) +} + +var tokens = 0L + +games.forEach { game -> + val aPressesNumer = Matrix2( + game.goal.first to game.buttonB.first, + game.goal.second to game.buttonB.second + ).determinant() + val aPressesDenom = Matrix2( + game.buttonA.first to game.buttonB.first, + game.buttonA.second to game.buttonB.second + ).determinant() + + val bPressesNumer = Matrix2( + game.buttonA.first to game.goal.first, + game.buttonA.second to game.goal.second + ).determinant() + val bPressesDenom = Matrix2( + game.buttonA.first to game.buttonB.first, + game.buttonA.second to game.buttonB.second + ).determinant() + + if (aPressesNumer % aPressesDenom == 0L && bPressesNumer % bPressesDenom == 0L) { + tokens += (aPressesNumer / aPressesDenom) * 3 + (bPressesNumer / bPressesDenom) + } +} + +println("Result: $tokens") \ No newline at end of file