2 Commits

Author SHA1 Message Date
44a1bcdc62 Merge owned and non-owned grids
Through the power of generics. Should've been easier if it was possible
to be generic over mutable and non-mutable, but AsRef/AsMut is close
enough
2023-12-14 21:33:09 +01:00
b7baebd050 Very clunky implementation of 2023 day 14 part 2 2023-12-14 20:05:25 +01:00
5 changed files with 183 additions and 19 deletions

View File

@@ -244,20 +244,22 @@ impl IndexMut<usize> for Vec2 {
}
}
pub struct Grid<'a> {
#[derive(PartialEq)]
pub struct Grid<T: AsRef<[u8]>> {
width: usize,
data: &'a [u8],
data: T,
}
impl<'a> Grid<'a> {
pub fn new(data: &'a [u8]) -> anyhow::Result<Self> {
impl<T: AsRef<[u8]>> Grid<T> {
pub fn new(data: T) -> anyhow::Result<Self> {
let width = 1 + data
.as_ref()
.iter()
.position(|&c| c == b'\n')
.context("Failed to find end of line in grid")?;
anyhow::ensure!(
data.len() % width == 0,
data.as_ref().len() % width == 0,
"Grid should divide equally into rows"
);
@@ -265,38 +267,72 @@ impl<'a> Grid<'a> {
}
pub fn height(&self) -> usize {
self.data.len() / self.width
self.data.as_ref().len() / self.width
}
pub fn width(&self) -> usize {
self.width - 1
}
pub fn rows(&self) -> impl Iterator<Item = &'a [u8]> {
pub fn rows(&self) -> impl Iterator<Item = &[u8]> {
let width = self.width();
self.data
.as_ref()
.chunks_exact(self.width)
.map(move |row| &row[..width])
}
pub fn find(&self, c: u8) -> Option<(usize, usize)> {
let pos = self.data.iter().position(|&d| d == c)?;
let pos = self.data.as_ref().iter().position(|&d| d == c)?;
Some((pos % self.width, pos / self.width))
}
}
impl<'a> Index<usize> for Grid<'a> {
impl<T: AsMut<[u8]> + AsRef<[u8]>> Grid<T> {
pub fn rows_mut(&mut self) -> impl Iterator<Item = &mut [u8]> {
let width = self.width();
self.data
.as_mut()
.chunks_exact_mut(self.width)
.map(move |row| &mut row[..width])
}
}
impl<T: AsRef<[u8]>> Index<usize> for Grid<T> {
type Output = [u8];
fn index(&self, y: usize) -> &Self::Output {
let offset = y * self.width;
&self.data[offset..(offset + self.width())]
&self.data.as_ref()[offset..(offset + self.width())]
}
}
impl Display for Grid<'_> {
impl<T: AsRef<[u8]>> Display for Grid<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", String::from_utf8_lossy(self.data))
write!(f, "{}", String::from_utf8_lossy(self.data.as_ref()))
}
}
// Custom Clone impl so we don't allocate in `clone_from`
impl<T: AsRef<[u8]> + Clone> Clone for Grid<T> {
fn clone(&self) -> Self {
Self {
width: self.width,
data: self.data.clone(),
}
}
fn clone_from(&mut self, source: &Self) {
self.width = source.width;
self.data.clone_from(&source.data);
}
}
impl<T: AsMut<[u8]> + AsRef<[u8]>> IndexMut<usize> for Grid<T> {
fn index_mut(&mut self, y: usize) -> &mut Self::Output {
let offset = y * self.width;
let width = self.width;
&mut self.data.as_mut()[offset..(offset + width)]
}
}

View File

@@ -2,7 +2,7 @@ use std::collections::HashMap;
use crate::common::Grid;
fn is_surrounded(grid: &Grid<'_>, y: usize, start: usize, last: usize) -> bool {
fn is_surrounded(grid: &Grid<&[u8]>, y: usize, start: usize, last: usize) -> bool {
fn is_symbol(c: u8) -> bool {
!matches!(c, b'0'..=b'9' | b'.' | b'\n')
}
@@ -53,7 +53,7 @@ pub fn part1(input: &[u8]) -> anyhow::Result<String> {
Ok(sum.to_string())
}
fn find_gear(grid: &Grid<'_>, y: usize, start: usize, end: usize) -> Option<(usize, usize)> {
fn find_gear(grid: &Grid<&[u8]>, y: usize, start: usize, end: usize) -> Option<(usize, usize)> {
let x_min = start.saturating_sub(1);
let x_max = Ord::min(grid.width(), end + 2);

View File

@@ -22,7 +22,7 @@ fn get_connections(c: u8) -> u8 {
}
}
fn find_cycle(map: &Grid<'_>) -> anyhow::Result<(usize, bool, IndexSet)> {
fn find_cycle(map: &Grid<&[u8]>) -> anyhow::Result<(usize, bool, IndexSet)> {
let (start_x, start_y) = map.find(b'S').context("Couldn't find starting point")?;
let mut visited = IndexSet::with_capacity(map.width() * map.height());
let mut todo = VecDeque::new();

View File

@@ -15,7 +15,7 @@ impl Symmetry {
}
}
fn find_symmetry(grid: Grid<'_>, differences: usize) -> Option<Symmetry> {
fn find_symmetry(grid: Grid<&[u8]>, differences: usize) -> Option<Symmetry> {
// Attempt to find a vertical line of reflection first
for c in 1..grid.width() {
if grid
@@ -48,7 +48,7 @@ fn find_symmetry(grid: Grid<'_>, differences: usize) -> Option<Symmetry> {
None
}
fn parse_grids(input: &[u8]) -> anyhow::Result<Vec<Grid<'_>>> {
fn parse_grids(input: &[u8]) -> anyhow::Result<Vec<Grid<&[u8]>>> {
let mut result = Vec::new();
let mut start = 0;
let mut last_newline = 0;

View File

@@ -22,8 +22,131 @@ pub fn part1(input: &[u8]) -> anyhow::Result<String> {
Ok(load.to_string())
}
pub fn part2(_input: &[u8]) -> anyhow::Result<String> {
anyhow::bail!("Not implemented")
fn advance(grid: &mut Grid<Vec<u8>>, stack_heights: &mut [usize]) {
// Tilt north
stack_heights.fill(0);
for y in 0..grid.height() {
for (x, stack_height) in stack_heights.iter_mut().enumerate() {
let c = grid[y][x];
match c {
b'#' => *stack_height = y + 1,
b'O' => {
grid[y][x] = b'.';
grid[*stack_height][x] = b'O';
*stack_height += 1;
}
_ => continue,
}
}
}
// Tilt west
for row in grid.rows_mut() {
let mut stack_height = 0;
for x in 0..row.len() {
let c = row[x];
match c {
b'#' => stack_height = x + 1,
b'O' => {
row[x] = b'.';
row[stack_height] = b'O';
stack_height += 1;
}
_ => continue,
}
}
}
// Tilt south
stack_heights.fill(grid.height() - 1);
for y in (0..grid.height()).rev() {
for (x, stack_height) in stack_heights.iter_mut().enumerate() {
let c = grid[y][x];
match c {
b'#' => *stack_height = y.saturating_sub(1),
b'O' => {
grid[y][x] = b'.';
grid[*stack_height][x] = b'O';
// Saturating because possible underflow
*stack_height = stack_height.saturating_sub(1);
}
_ => continue,
}
}
}
// Tilt east
for row in grid.rows_mut() {
let mut stack_height = row.len() - 1;
for x in (0..row.len()).rev() {
let c = row[x];
match c {
b'#' => stack_height = x.saturating_sub(1),
b'O' => {
row[x] = b'.';
row[stack_height] = b'O';
stack_height = stack_height.saturating_sub(1);
}
_ => continue,
}
}
}
}
fn find_cycle(
it: impl Iterator<Item = usize>,
hare: &mut Grid<Vec<u8>>,
stack_heights: &mut [usize],
) -> Option<usize> {
let mut tortoise = hare.clone();
let mut last_sync = 0;
for cycle in it {
advance(hare, stack_heights);
if tortoise == *hare {
return Some(cycle - last_sync);
} else if cycle.count_ones() == 1 {
// New power of two, sync up the tortoise and the hare
tortoise.clone_from(hare);
last_sync = cycle;
}
}
None
}
pub fn part2(input: &[u8]) -> anyhow::Result<String> {
const GOAL: usize = 1000000000;
let mut hare = Grid::new(input.to_owned())?;
let mut stack_heights = vec![0; hare.width()];
let mut it = 1..=GOAL;
// If there is a cycle found, skip ahead
if let Some(len) = find_cycle(&mut it, &mut hare, &mut stack_heights) {
let remaining = it.size_hint().0;
let steps = remaining % len;
for _ in 0..steps {
advance(&mut hare, &mut stack_heights);
}
}
let height = hare.height();
let load = hare
.rows()
.enumerate()
.flat_map(|(y, row)| {
row.iter()
.filter_map(move |&c| (c == b'O').then_some(height - y))
})
.sum::<usize>();
Ok(load.to_string())
}
#[cfg(test)]
@@ -36,4 +159,9 @@ mod tests {
fn sample_part1() {
assert_eq!("136", part1(SAMPLE).unwrap());
}
#[test]
fn sample_part2() {
assert_eq!("64", part2(SAMPLE).unwrap());
}
}