Skip to content

Commit

Permalink
Day 13
Browse files Browse the repository at this point in the history
  • Loading branch information
Alextopher committed Dec 13, 2023
1 parent 005a468 commit 68515cc
Show file tree
Hide file tree
Showing 8 changed files with 269 additions and 5 deletions.
31 changes: 31 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ members = [
"y23/d10-pipe-maze",
"y23/d11-cosmic-expansion",
"y23/d12-hot-springs",
"y23/d13",
"y23/d13-point-of-incidence",
"y23/d14",
"y23/d15",
"y23/d16",
Expand Down
2 changes: 1 addition & 1 deletion aoc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ aoc-macro = { path = "../aoc-macro" }

binary-heap-plus = "0.5.0"
itertools = "0.12.0"
num-traits = "0.2.17"
num-traits = "0.2.17"
27 changes: 27 additions & 0 deletions aoc/src/iterstuff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,4 +167,31 @@ pub trait IterJunk: Iterator {
{
self.k_largest_by(k, |a, b| f(a).cmp(&f(b)))
}

/// `partial_sums` is a function that returns an iterator over the partial sums of the iterator.
///
/// The first element is the first element of the iterator, the second element is the sum of the
/// first two elements, and so on.
///
/// # Examples
///
/// ```
/// use aoc::iterstuff::IterJunk;
///
/// let v = vec![1, 2, 3, 4, 5];
/// let sums: Vec<i32> = v.into_iter().partial_sums().collect();
///
/// assert_eq!(sums, vec![1, 3, 6, 10, 15]);
/// ```
#[must_use = "iterators are lazy and do nothing unless consumed"]
fn partial_sums(self) -> impl Iterator<Item = <Self as Iterator>::Item>
where
Self: Sized,
Self::Item: std::ops::AddAssign + Copy + Default,
{
self.scan(Default::default(), |sum, x| {
*sum += x;
Some(*sum)
})
}
}
2 changes: 2 additions & 0 deletions y23/d13/Cargo.toml → y23/d13-point-of-incidence/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
aoc = { path = "../../aoc" }
itertools = "0.12.0"
File renamed without changes.
207 changes: 207 additions & 0 deletions y23/d13-point-of-incidence/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
use std::iter::repeat;

use aoc::{input_str, IterJunk};
use itertools::Itertools;

// Read in 2d array of ash and rocks
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Tile {
Ash,
Rock,
}

impl Tile {
fn from_char(c: char) -> Self {
match c {
'.' => Self::Ash,
'#' => Self::Rock,
_ => panic!("Invalid tile"),
}
}
}

#[derive(Debug, Clone)]
struct Map {
tiles: Vec<Tile>,
transposed: Vec<Tile>,
width: usize,
height: usize,
}

impl Map {
fn from_str(s: &str) -> Self {
let mut height = 0;
let tiles = s
.lines()
.map(|s| s.chars())
.flat_map(|line| {
height += 1;
line
})
.map(Tile::from_char)
.collect_vec();

let width = tiles.len() / height;

let transposed = (0..width)
.flat_map(|x| (0..height).zip(repeat(x)))
.map(|(y, x)| tiles[y * width + x])
.collect_vec();

Self {
tiles,
transposed,
width,
height,
}
}

fn get(&self, x: usize, y: usize) -> Tile {
self.tiles[y * self.width + x]
}

fn get_row(&self, y: usize) -> &[Tile] {
&self.tiles[y * self.width..(y + 1) * self.width]
}

fn get_col(&self, x: usize) -> &[Tile] {
&self.transposed[x * self.height..(x + 1) * self.height]
}

fn detect_mirror(&self) -> usize {
fn is_mirror((left, right): (&[Tile], &[Tile])) -> bool {
left.iter().rev().zip(right.iter()).all(|(l, r)| l == r)
}

if let Some(col) = (1..self.width).find(|&col| {
(0..self.height)
.map(|r| self.get_row(r).split_at(col))
.all(is_mirror)
}) {
return col;
}

if let Some(row) = (1..self.height).find(|&row| {
(0..self.width)
.map(|c| self.get_col(c).split_at(row))
.all(is_mirror)
}) {
return 100 * row;
}

panic!("No mirror found");
}

// In part 2, there is a second mirror that is _almost_ correct.
// It is off by exactly one tile
fn detect_with_smudge(&self, smudges: usize) -> usize {
// Helper function that returns how close 2 sets of tiles are to being a mirror
// (returns how many tiles are different)
fn mirror_diff(left: &[Tile], right: &[Tile]) -> usize {
left.iter()
.rev()
.zip(right.iter())
.filter(|(l, r)| l != r)
.count()
}

// Find a column that is almost a mirror with a difference of 1
if let Some(col) = (1..self.width).find(|&col| {
(0..self.height)
.map(|r| self.get_row(r).split_at(col))
.map(|(left, right)| mirror_diff(left, right))
.partial_sums()
.find_or_last(|&x| x > smudges)
.eq(&Some(smudges))
}) {
return col;
}

// Find a row that is almost a mirror with a difference of 1
if let Some(row) = (1..self.height).find(|&row| {
(0..self.width)
.map(|c| self.get_col(c).split_at(row))
.map(|(left, right)| mirror_diff(left, right))
.partial_sums()
.find_or_last(|&x| x > smudges)
.eq(&Some(smudges))
}) {
return 100 * row;
}

panic!("No mirror found");
}
}

impl std::fmt::Display for Map {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for y in 0..self.height {
for x in 0..self.width {
let c = match self.get(x, y) {
Tile::Ash => '.',
Tile::Rock => '#',
};
write!(f, "{}", c)?;
}
writeln!(f)?;
}
Ok(())
}
}

fn main() {
let input = input_str!(2023, 13);

// Each map uses multiple lines, maps are separated by a blank line
let start = std::time::Instant::now();
let maps = input.split("\n\n").map(Map::from_str).collect_vec();
println!("Parsed: {:?}", start.elapsed());

// Part 1 - sum the mirror values
let start = std::time::Instant::now();
let sum: usize = maps.iter().map(|m| m.detect_mirror()).sum();
println!("Part 1: {}", sum);
println!("Part 1: {:?}", start.elapsed());

// Part 2 - sum the mirror values with the smudge
let start = std::time::Instant::now();
let sum: usize = maps.iter().map(|m| m.detect_with_smudge(1)).sum();
println!("Part 2: {}", sum);
println!("Part 2: {:?}", start.elapsed());
}

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

#[test]
fn test_map() {
let input = "#.##..##.\n..#.##.#.\n##......#\n##......#\n..#.##.#.\n..##..##.\n#.#.##.#.";
let map = Map::from_str(input);

assert_eq!(map.width, 9);
assert_eq!(map.height, 7);

// Detect the mirror
assert_eq!(map.detect_mirror(), 5);

let input = "#...##..#\n#....#..#\n..##..###\n#####.##.\n#####.##.\n..##..###\n#....#..#";
let map = Map::from_str(input);

assert_eq!(map.width, 9);
assert_eq!(map.height, 7);

// Detect the mirror
assert_eq!(map.detect_mirror(), 400);
}

#[test]
fn test_regressions() {
let input = "##.#.##...####.\n#####..#...##..\n##.##.#.#####.#\n.##.#..##..##..\n###..#.#####.#.\n###..#.#####.#.\n.##.#..##..##..\n##.##.#.#####.#\n#####..#...##..\n##.#.##..#####.\n...#...#....###\n##.#.###.###..#\n..#.###.#.#....\n####...#.#.#...\n.#..#....##.#.#\n....####.###.##\n....####.###.##";
let map = Map::from_str(input);

// Detect the mirror (row 4)
println!("{} {}", map.width, map.height);
assert_eq!(map.detect_mirror(), 1600);
}
}
3 changes: 0 additions & 3 deletions y23/d13/src/main.rs

This file was deleted.

0 comments on commit 68515cc

Please sign in to comment.