From 0fbb57f14ca3c51a0856a086fd4a4110d9fcc639 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Wed, 30 Dec 2020 22:11:19 +0100 Subject: [PATCH] 2020: day20: refacto before part 2 --- aoc2020/src/day20.rs | 222 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 179 insertions(+), 43 deletions(-) diff --git a/aoc2020/src/day20.rs b/aoc2020/src/day20.rs index 6fca696..53ee851 100644 --- a/aoc2020/src/day20.rs +++ b/aoc2020/src/day20.rs @@ -18,24 +18,7 @@ fn part1(input: &str) -> Result { Ok(tiles .iter() .filter_map(|tile| { - let mut count = 0; - for other in &tiles { - if tile == other { - continue; - } - - count += tile - .edges - .iter() - .filter(|e| other.edges.contains(e)) - .count(); - - count += tile - .reversed_edges - .iter() - .filter(|e| other.edges.contains(e)) - .count(); - } + let count = tile.neighbours(&tiles).len(); // corners have 2 edges in common if count == 2 { @@ -47,11 +30,182 @@ fn part1(input: &str) -> Result { .product()) } -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] +enum Rotation { + R90, + R180, + R270, +} + +/// Represents a transformation of a tile or image. +/// +/// Note: we don't need a horizontal and a vertical flip, these result in the same output as a 180 +/// degree rotation when combined, so only one is necessary +#[derive(Debug, Clone, Copy)] +struct Transform { + flip: bool, + rotation: Option, +} + +impl Transform { + fn new(flip: bool, rotation: Option) -> Self { + Self { flip, rotation } + } + + fn all() -> Vec { + vec![ + Transform::new(false, None), + Transform::new(false, Some(Rotation::R90)), + Transform::new(false, Some(Rotation::R180)), + Transform::new(false, Some(Rotation::R270)), + Transform::new(true, None), + Transform::new(true, Some(Rotation::R90)), + Transform::new(true, Some(Rotation::R180)), + Transform::new(true, Some(Rotation::R270)), + ] + } +} + +impl Default for Transform { + fn default() -> Self { + Self { + flip: false, + rotation: None, + } + } +} + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +enum Position { + Down, + Left, + Right, + Up, +} + +impl Position { + fn opposite(&self) -> Self { + match self { + Self::Down => Self::Up, + Self::Left => Self::Right, + Self::Right => Self::Left, + Self::Up => Self::Down, + } + } + + fn ordered() -> [Self; 4] { + [Self::Down, Self::Left, Self::Right, Self::Up] + } +} + +const TILE_WIDTH: usize = 10; +const TILE_HEIGHT: usize = 10; + +#[derive(Debug, Clone)] struct Tile { id: u64, - edges: [Vec; 4], - reversed_edges: [Vec; 4], + cells: [[bool; TILE_WIDTH]; TILE_HEIGHT], + transform: Transform, +} + +type Borders = [Vec; 4]; + +impl Tile { + fn with_transform(&self, transform: Transform) -> Self { + let mut res = self.clone(); + res.transform = transform; + res + } + + /// Returns the tile's 4 borders, according to its current transformation. + /// + /// See [`Self::borders_with_transform()`] for more details + fn borders(&self) -> Borders { + self.borders_with_transform(self.transform) + } + + /// Returns the tile's 4 borders, according to the provided transformation. + /// + /// Each border is associated with its position on the tile + fn borders_with_transform(&self, transform: Transform) -> Borders { + let mut up = Vec::new(); + let mut down = Vec::new(); + for k in 0..TILE_WIDTH { + up.push(self.get_with_transform(0, k, transform)); + down.push(self.get_with_transform(TILE_HEIGHT - 1, k, transform)); + } + + let mut left = Vec::new(); + let mut right = Vec::new(); + for k in 0..TILE_HEIGHT { + left.push(self.get_with_transform(k, 0, transform)); + right.push(self.get_with_transform(k, TILE_WIDTH - 1, transform)); + } + + // NOTE: the ordering is important, must use the enum's integer representations as indices + [down, left, right, up] + } + + /// Returns the pixel at indices (i, j) in the tile. + /// + /// Uses the tile's current `self.transform`. + fn get(&self, i: usize, j: usize) -> bool { + self.get_with_transform(i, j, self.transform) + } + + /// Returns the pixel at indices (i, j) in the tile, using the provided transform. + fn get_with_transform(&self, mut i: usize, mut j: usize, transform: Transform) -> bool { + if let Some(rotation) = transform.rotation { + match rotation { + Rotation::R90 => { + let prev_i = i; + i = j; + j = (TILE_WIDTH - 1) - prev_i; + } + Rotation::R180 => { + i = (TILE_HEIGHT - 1) - i; + j = (TILE_WIDTH - 1) - j; + } + Rotation::R270 => { + let prev_j = j; + j = i; + i = (TILE_HEIGHT - 1) - prev_j; + } + } + } + + if transform.flip { + i = (TILE_HEIGHT - 1) - i; + } + + self.cells[i][j] + } + + /// Returns a list of neighbour tiles, along with the offset where they'd fit compared to the + /// current tile (depending on which borders match) + fn neighbours(&self, tiles: &[Tile]) -> Vec<(Position, Tile)> { + let borders = &self.borders(); + + tiles + .iter() + .filter(|other| *other != self) + .flat_map(|other| { + Transform::all().into_iter().filter_map(move |transform| { + let other_borders = other.borders_with_transform(transform); + + for (bord, pos) in other_borders.iter().zip(Position::ordered().iter()) { + let opposite = pos.opposite(); + + if bord == &borders[opposite as usize] { + return Some((opposite, other.with_transform(transform))); + } + } + + None + }) + }) + .collect() + } } impl std::cmp::PartialEq for Tile { @@ -64,8 +218,6 @@ impl std::str::FromStr for Tile { type Err = anyhow::Error; fn from_str(s: &str) -> Result { - const LINE_LENGTH: usize = 10; - let mut lines = s.lines(); let title = lines.next().context("couldn't find line with tile ID")?; @@ -73,7 +225,7 @@ impl std::str::FromStr for Tile { let colon = title.find(':').unwrap(); let id = title[(space + 1)..colon].parse()?; - let mut edges = [vec![], vec![], vec![], vec![]]; + let mut cells = [[false; TILE_WIDTH]; TILE_HEIGHT]; lines .enumerate() @@ -85,18 +237,7 @@ impl std::str::FromStr for Tile { _ => return Err(anyhow!("unknown char `{}` while parsing tile", c)), }; - if i == 0 { - edges[0].push(c); - } - if j == 0 { - edges[1].push(c); - } - if i == (LINE_LENGTH - 1) { - edges[2].push(c); - } - if j == (LINE_LENGTH - 1) { - edges[3].push(c); - } + cells[i][j] = c; Ok(()) })?; @@ -104,15 +245,10 @@ impl std::str::FromStr for Tile { Ok(()) })?; - let mut reversed_edges = [vec![], vec![], vec![], vec![]]; - for (i, edge) in edges.iter().enumerate() { - reversed_edges[i] = edge.iter().copied().rev().collect(); - } - Ok(Tile { id, - edges, - reversed_edges, + cells, + transform: Transform::default(), }) } }