Day 13: Point of Incidence
by @adpi2
Puzzle description
https://adventofcode.com/2023/day/13
Solution summary
- Convert the input to a sequence of patterns.
- Iterate over each pattern to detect its reflection. If there is no reflection on a pattern we try to find it in the transposition of the pattern.
- Sum up all the reflection numbers.
Parsing of the input
We defines the model of the problem using type aliases:
type Tile = '.' | '#'
type Line = Seq[Tile]
type Pattern = Seq[Line]
To parse the input we split the full input by empty lines, (expressed with the regex \R\R
, which works multiplatform).
This gives a sequence of strings representing the patterns. Then split each pattern by a new line (regex \R
). Then for
each line we assert that all the characters are valid tiles.
def parseInput(input: String): Seq[Pattern] =
val patterns = input.split(raw"\R\R").toSeq
patterns.map: patternStr =>
patternStr.split(raw"\R").toSeq.map: lineStr =>
lineStr.collect[Tile] { case tile: Tile => tile }
In Scala we tend to prefer a declarative style. An alternative imperative option would be to iterate over each line, accumulating lines into a buffer as we encounter them, and then at each empty line transfer all the accumulated lines as a group to a separate pattern buffer.
Part 1: detecting pure reflection
To detect the reflection line in a pattern:
- We iterate over the index of the lines from 1 to the size of the pattern
- We split the patterns in two at the given index
- We invert the first part, zip it with the second part and compare line by line.
The resulting code is:
def findReflection(pattern: Pattern): Option[Int] =
(1 until pattern.size).find: i =>
val (leftPart, rightPart) = pattern.splitAt(i)
leftPart.reverse.zip(rightPart).forall(_ == _)
If we cannot find a line of reflection, then we transpose the pattern and try to find a column of reflection:
findReflection(pattern).map(100 * _).orElse(findReflection(pattern.transpose))
Part 2: detecting the reflection with a unique smudge
The second part is almost identical to the first part. But, instead of comparing the lines based on equality, we count the number of different characters. We keep the index of the line or column which contains only a single smudge.
The inner part of findReflection
becomes:
val (leftPart, rightPart) = pattern.splitAt(i)
val smudges = leftPart.reverse
.zip(rightPart)
.map((l1, l2) => l1.zip(l2).count(_ != _))
.sum
smudges == 1
Final code
type Tile = '.' | '#'
type Line = Seq[Tile]
type Pattern = Seq[Line]
def part1(input: String): Int =
parseInput(input)
.flatMap: pattern =>
findReflection(pattern).map(100 * _).orElse(findReflection(pattern.transpose))
.sum
def part2(input: String) =
parseInput(input)
.flatMap: pattern =>
findReflectionWithSmudge(pattern).map(100 * _)
.orElse(findReflectionWithSmudge(pattern.transpose))
.sum
def parseInput(input: String): Seq[Pattern] =
val patterns = input.split(raw"\R\R").toSeq
patterns.map: patternStr =>
patternStr.split(raw"\R").toSeq.map: lineStr =>
lineStr.collect[Tile] { case tile: Tile => tile }
def findReflection(pattern: Pattern): Option[Int] =
(1 until pattern.size).find: i =>
val (leftPart, rightPart) = pattern.splitAt(i)
leftPart.reverse.zip(rightPart).forall(_ == _)
def findReflectionWithSmudge(pattern: Pattern): Option[Int] =
(1 until pattern.size).find: i =>
val (leftPart, rightPart) = pattern.splitAt(i)
val smudges = leftPart.reverse
.zip(rightPart)
.map((l1, l2) => l1.zip(l2).count(_ != _))
.sum
smudges == 1
Solutions from the community
- Solution by Spamegg
- Solution by Rui Alves
- Solution by jnclt
- Solution by merlin
- Solution by g.berezin
- Solution by Jamie Thompson
- Solution by Paweł Cembaluk
Share your solution to the Scala community by editing this page.