Skip to content

Commit

Permalink
Day 12: Garden Groups
Browse files Browse the repository at this point in the history
  • Loading branch information
ephemient committed Dec 17, 2024
1 parent 5698650 commit f5fd3a3
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 3 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Development occurs in language-specific directories:
|[Day9.hs](hs/src/Day9.hs)|[Day9.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day9.kt)|[day9.py](py/aoc2024/day9.py)|[day9.rs](rs/src/day9.rs)|
|[Day10.hs](hs/src/Day10.hs)|[Day10.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day10.kt)|[day10.py](py/aoc2024/day10.py)|[day10.rs](rs/src/day10.rs)|
|[Day11.hs](hs/src/Day11.hs)|[Day11.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day11.kt)|[day11.py](py/aoc2024/day11.py)|[day11.rs](rs/src/day11.rs)|
|[Day12.hs](hs/src/Day12.hs)|[Day12.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day12.kt)|[day12.py](py/aoc2024/day12.py)||
|[Day12.hs](hs/src/Day12.hs)|[Day12.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day12.kt)|[day12.py](py/aoc2024/day12.py)|[day12.rs](rs/src/day12.rs)|
|[Day13.hs](hs/src/Day13.hs)|[Day13.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day13.kt)|[day13.py](py/aoc2024/day13.py)||
|[Day14.hs](hs/src/Day14.hs)|[Day14.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day14.kt)|||
|[Day15.hs](hs/src/Day15.hs)||||
Expand Down
8 changes: 7 additions & 1 deletion rs/benches/criterion.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use aoc2024::{day1, day10, day11, day2, day3, day4, day5, day6, day7, day8, day9};
use aoc2024::{day1, day10, day11, day12, day2, day3, day4, day5, day6, day7, day8, day9};
use criterion::{black_box, Criterion};
use std::env;
use std::fs;
Expand Down Expand Up @@ -80,6 +80,12 @@ fn aoc2024_bench(c: &mut Criterion) -> io::Result<()> {
g.bench_function("part 2", |b| b.iter(|| day11::part2(black_box(&data))));
g.finish();

let data = get_day_input(12)?;
let mut g = c.benchmark_group("day 12");
g.bench_function("part 1", |b| b.iter(|| day12::part1(black_box(&data))));
g.bench_function("part 2", |b| b.iter(|| day12::part2(black_box(&data))));
g.finish();

Ok(())
}

Expand Down
173 changes: 173 additions & 0 deletions rs/src/day12.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
use std::collections::{BTreeMap, BTreeSet};

use if_chain::if_chain;
use itertools::Itertools;

struct Data<'a> {
lines: Vec<&'a str>,
}

impl Data<'_> {
fn new(data: &str) -> Data {
Data {
lines: data.lines().collect(),
}
}

fn groups(&self) -> impl Iterator<Item = BTreeSet<(usize, usize)>> + use<'_> {
self.lines
.iter()
.enumerate()
.flat_map(|(y, line)| line.bytes().enumerate().map(move |(x, b)| ((y, x), b)))
.scan(BTreeSet::new(), |visited, (pos, b)| {
if !visited.insert(pos) {
return Some(None);
}
let mut group = BTreeSet::new();
let mut stack = vec![pos];
while let Some(pos @ (y, x)) = stack.pop() {
group.insert(pos);
for (dy, dx) in [(-1, 0), (0, -1), (0, 1), (1, 0)] {
if_chain! {
if let Some(y) = y.checked_add_signed(dy);
if let Some(line) = self.lines.get(y);
if let Some(x) = x.checked_add_signed(dx);
if line.as_bytes().get(x).copied() == Some(b);
if visited.insert((y, x));
then {
stack.push((y, x));
}
}
}
}
Some(Some(group))
})
.flatten()
}
}

fn perimeter1(group: &BTreeSet<(usize, usize)>) -> usize {
let mut edges = BTreeMap::<_, u8>::new();
for (y, x) in group.iter() {
for edge in [
(2 * y, 2 * x + 1),
(2 * y + 1, 2 * x),
(2 * y + 1, 2 * x + 2),
(2 * y + 2, 2 * x + 1),
] {
*edges.entry(edge).or_default() += 1;
}
}
edges.into_values().filter(|value| *value == 1).count()
}

pub fn part1(data: &str) -> usize {
Data::new(data)
.groups()
.map(|group| group.len() * perimeter1(&group))
.sum()
}

fn perimeter2(group: &BTreeSet<(usize, usize)>) -> usize {
let mut edges = BTreeMap::<_, u8>::new();
for (y, x) in group.iter() {
*edges.entry((2 * y, 2 * x + 1)).or_default() |= 1;
*edges.entry((2 * y + 1, 2 * x)).or_default() |= 2;
*edges.entry((2 * y + 1, 2 * x + 2)).or_default() |= 8;
*edges.entry((2 * y + 2, 2 * x + 1)).or_default() |= 4;
}
let mut lines = BTreeMap::<_, BTreeSet<_>>::new();
for ((y, x), n) in edges {
if n & (n - 1) == 0 {
lines
.entry(if n.ilog2() % 2 == 0 { y + 1 } else { x })
.or_default()
.insert((x + y, n));
}
}
lines
.into_values()
.map(|line| {
line.into_iter()
.tuple_windows()
.filter(|((p, b), (q, d))| p.abs_diff(*q) > 2 || b != d)
.count()
+ 1
})
.sum()
}

pub fn part2(data: &str) -> usize {
Data::new(data)
.groups()
.map(|group| group.len() * perimeter2(&group))
.sum()
}

#[cfg(test)]
mod tests {
use super::*;
use indoc::indoc;
use pretty_assertions::assert_eq;

static EXAMPLE_1: &str = indoc! {"
AAAA
BBCD
BBCC
EEEC
"};

static EXAMPLE_2: &str = indoc! {"
OOOOO
OXOXO
OOOOO
OXOXO
OOOOO
"};

static EXAMPLE_3: &str = indoc! {"
RRRRIICCFF
RRRRIICCCF
VVRRRCCFFF
VVRCCCJFFF
VVVVCJJCFE
VVIVCCJJEE
VVIIICJJEE
MIIIIIJJEE
MIIISIJEEE
MMMISSJEEE
"};

static EXAMPLE_4: &str = indoc! {"
EEEEE
EXXXX
EEEEE
EXXXX
EEEEE
"};

static EXAMPLE_5: &str = indoc! {"
AAAAAA
AAABBA
AAABBA
ABBAAA
ABBAAA
AAAAAA
"};

#[test]
fn part1_examples() {
assert_eq!(140, part1(EXAMPLE_1));
assert_eq!(772, part1(EXAMPLE_2));
assert_eq!(1930, part1(EXAMPLE_3));
}

#[test]
fn part2_examples() {
assert_eq!(80, part2(EXAMPLE_1));
assert_eq!(436, part2(EXAMPLE_2));
assert_eq!(236, part2(EXAMPLE_4));
assert_eq!(368, part2(EXAMPLE_5));
assert_eq!(1206, part2(EXAMPLE_3));
}
}
1 change: 1 addition & 0 deletions rs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod day1;
pub mod day10;
pub mod day11;
pub mod day12;
pub mod day2;
pub mod day3;
pub mod day4;
Expand Down
10 changes: 9 additions & 1 deletion rs/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use anyhow::anyhow;
use aoc2024::{day1, day10, day11, day2, day3, day4, day5, day6, day7, day8, day9};
use aoc2024::{day1, day10, day11, day12, day2, day3, day4, day5, day6, day7, day8, day9};
use std::collections::HashSet;
use std::env;
use std::fs;
Expand Down Expand Up @@ -105,5 +105,13 @@ fn main() -> anyhow::Result<()> {
println!();
}

if args.is_empty() || args.contains("12") {
println!("Day 12");
let data = get_day_input(12)?;
println!("{:?}", day12::part1(&data));
println!("{:?}", day12::part2(&data));
println!();
}

Ok(())
}

0 comments on commit f5fd3a3

Please sign in to comment.