Skip to content

Instantly share code, notes, and snippets.

@jmikkola
Created December 13, 2024 00:44
Show Gist options
  • Save jmikkola/141a50426424de6b6c49fa2fe525abb0 to your computer and use it in GitHub Desktop.
Save jmikkola/141a50426424de6b6c49fa2fe525abb0 to your computer and use it in GitHub Desktop.
use std::env;
use std::fs;
use std::collections::HashSet;
fn main() {
let grid = split_lines(get_content(get_arg()));
println!("Part 1: {}", part1(&grid));
println!("Part 2: {}", part2(&grid));
}
fn part2(grid: &[Vec<char>]) -> i64 {
let mut total = 0;
let mut seen = HashSet::new();
for (i, row) in grid.iter().enumerate() {
for (j, plant) in row.iter().enumerate() {
let key = (i as i32, j as i32);
if !seen.contains(&key) {
let area = search2(&mut seen, grid, i as i32, j as i32, *plant);
let edges = find_edges(grid, i, j, *plant);
total += (area * edges) as i64;
}
}
}
total
}
fn find_edges(grid: &[Vec<char>], i: usize, j: usize, plant: char) -> i32 {
let mut seen = HashSet::new();
count_corners(&mut seen, grid, i as i32, j as i32, plant)
}
fn count_corners(seen: &mut HashSet<(i32, i32)>, grid: &[Vec<char>], i: i32, j: i32, plant: char) -> i32 {
if !is_plant(grid, i, j, plant) {
return 0;
}
let key = (i, j);
if seen.contains(&key) {
return 0;
}
seen.insert(key);
let mut corners = num_corners(grid, i, j, plant);
for (di, dj) in [(0, 1), (0, -1), (1, 0), (-1, 0)] {
corners += count_corners(seen, grid, i + di, j + dj, plant);
}
corners
}
fn num_corners(grid: &[Vec<char>], i: i32, j: i32, plant: char) -> i32 {
let mut neighbors: [[bool; 3]; 3] = [[false; 3]; 3];
for di in 0..3 {
for dj in 0..3 {
neighbors[di][dj] = is_plant(
grid,
i + (di as i32) - 1,
j + (dj as i32) - 1,
plant);
}
}
assert!(neighbors[1][1]);
let mut corners = 0;
// top right corner
if !neighbors[0][1] && !neighbors[1][2] {
corners += 1;
} else if neighbors[0][1] && neighbors[1][2] && !neighbors[0][2] {
corners += 1;
}
// top left corner
if !neighbors[0][1] && !neighbors[1][0] {
corners += 1;
} else if neighbors[0][1] && neighbors[1][0] && !neighbors[0][0] {
corners += 1;
}
// bottom left corner
if !neighbors[1][0] && !neighbors[2][1] {
corners += 1;
} else if neighbors[1][0] && neighbors[2][1] && !neighbors[2][0] {
corners += 1;
}
// bottom right corner
if !neighbors[2][1] && !neighbors[1][2] {
corners += 1;
} else if neighbors[2][1] && neighbors[1][2] && !neighbors[2][2] {
corners += 1;
}
corners
}
fn in_bounds(grid: &[Vec<char>], i: i32, j: i32) -> bool {
i >= 0 && j >= 0 && i < (grid.len() as i32) && j < (grid[0].len() as i32)
}
fn is_plant(grid: &[Vec<char>], i: i32, j: i32, plant: char) -> bool {
in_bounds(grid, i, j) && grid[i as usize][j as usize] == plant
}
fn search2(seen: &mut HashSet<(i32, i32)>, grid: &[Vec<char>], i: i32, j: i32, plant: char) -> i32 {
if i < 0 || j < 0 || i >= (grid.len() as i32) || j >= (grid[0].len() as i32) {
return 0;
}
if grid[i as usize][j as usize] != plant {
return 0;
}
let key = (i, j);
if seen.contains(&key) {
return 0;
}
seen.insert(key);
let mut area = 1;
for (di, dj) in [(0, 1), (0, -1), (1, 0), (-1, 0)] {
area += search2(seen, grid, i + di, j + dj, plant);
}
area
}
fn part1(grid: &[Vec<char>]) -> i64 {
let mut total = 0;
let mut seen = HashSet::new();
for (i, row) in grid.iter().enumerate() {
for (j, plant) in row.iter().enumerate() {
let key = (i as i32, j as i32);
if !seen.contains(&key) {
let (area, perim) = search1(&mut seen, grid, i as i32, j as i32, *plant);
// println!("a: {}, p: {}, plant: {}", area, perim, *plant);
total += (area * perim) as i64;
}
}
}
total
}
fn search1(seen: &mut HashSet<(i32, i32)>, grid: &[Vec<char>], i: i32, j: i32, plant: char) -> (i32, i32) {
if i < 0 || j < 0 || i >= (grid.len() as i32) || j >= (grid[0].len() as i32) {
// Finding a border means the perimeter increases
return (0, 1);
}
if grid[i as usize][j as usize] != plant {
// Finding a border means the perimeter increases
return (0, 1);
}
// Check for seen after looking for perimeters because perimeters are counted
// for the plants on both sides of the fence
let key = (i, j);
if seen.contains(&key) {
return (0, 0);
}
seen.insert(key);
let mut area = 1;
let mut perim = 0;
for (di, dj) in [(0, 1), (0, -1), (1, 0), (-1, 0)] {
let (a, p) = search1(seen, grid, i + di, j + dj, plant);
area += a;
perim += p;
}
(area, perim)
}
fn split_lines(s: String) -> Vec<Vec<char>> {
s.split('\n')
.map(|line| line.chars().collect())
.collect()
}
fn get_arg() -> String {
match env::args().nth(1) {
Some(f) => f,
None => "example".into(),
}
}
fn get_content(filename: String) -> String {
fs::read_to_string(filename).unwrap().trim().to_string()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment