Skip to main content

Day 13: Transparent Origami

by @adpi2

Puzzle description

https://adventofcode.com/2021/day/13

Modeling the objects

The instructions of the manual are composed of dots and folds. We can model those two objects in Scala 3 with:

case class Dot(x: Int, y: Int)

enum Fold:
case Vertical(x: Int)
case Horizontal(y: Int)

A Dot is made of two integers: x and y. A Fold is either Vertical if it has an x coordinate or Horizontal if it has a y coordinate.

Parsing

In order to parse dots and folds, we create two parse functions in the companion objects of Dot and Fold.

object Dot:
def parse(line: String): Dot =
line match
case s"$x,$y" => Dot(x.toInt, y.toInt)
case _ => throw new Exception(s"Cannot parse '$line' to Dot")

object Fold:
def parse(line: String): Fold =
line match
case s"fold along x=$x" => Vertical(x.toInt)
case s"fold along y=$y" => Horizontal(y.toInt)
case _ => throw new Exception(s"Cannot parse '$line' to Fold")

Now, all the instructions can be parsed as follow:

def parseInstructions(input: String): (Set[Dot], List[Fold]) =
val sections = input.split("\n\n")
val dots = sections(0).linesIterator.map(Dot.parse).toSet
val folds = sections(1).linesIterator.map(Fold.parse).toList
(dots, folds)

Notice that we return a set of Dot and a list of Fold.

A set is different from a list because it cannot contain the same element twice. Indeed, inserting an element in a set that already contains it does not alter the set. It returns the same set in which the element is stored only once.

For example:

$ Set(1, 2, 3, 3) == Set(1, 2, 3)
true

$ List(1, 2, 3, 3) == List(1, 2, 3)
false

It is convenient to choose a Set to store the Dots because it will merge all duplicates automatically. This choice will make our program shorter and more efficient.

Folding

We want to compute the folds of all the dots. To do so we can add a method apply in the enum Fold. It takes an instance of Dot as parameter and returns the folded Dot.

def apply(dot: Dot): Dot =
this match
case Vertical(x: Int) => Dot(fold(along = x)(dot.x), dot.y)
case Horizontal(y : Int) => Dot(dot.x, fold(along = y)(dot.y))

In this method we call a function fold that is not yet defined. fold is the mathematical formula that computes the folded value of an integer (value) around another integer (along).

def fold(along: Int)(value: Int): Int =
if value < along then value
else along - (value - along)

We can check this formula with some examples:

  • Folding a dot at 2 along 7 does not move the dot because 2 < 7.
  • Folding a dot at 9 along 7 (......|.#) moves the dot to 7 - 2 = 5 (....#.|..).

Solution of part 1

We are now ready to solve part 1:

def part1(input: String): Int =
val (dots, folds) = parseInstructions(input)
dots.map(folds.head.apply).size

Solution of part 2

To compute the solution of part 2 we apply all folds sequentially, using the foldLeft method.

To format the answer as if it was made of dots on a paper, we create a double array Array[Array[Char]] of size (height, width) initialized with .. Then we iterate over all dots to put a # at their position in the double array. Finally we convert this double array to a String with .map(_.mkString).mkString('\n').

def part2(input: String): String =
val (dots, folds) = parseInstructions(input)
val foldedDots = folds.foldLeft(dots)((dots, fold) => dots.map(fold.apply))

val (width, height) = (foldedDots.map(_.x).max + 1, foldedDots.map(_.y).max + 1)
val paper = Array.fill(height, width)('.')
for dot <- foldedDots do paper(dot.y)(dot.x) = '#'

paper.map(_.mkString).mkString("\n")

Run it locally

You can get this solution locally by cloning the scalacenter/scala-advent-of-code repository.

$ git clone https://github.com/scalacenter/scala-advent-of-code
$ cd scala-advent-of-code

You can run it with scala-cli.

$ scala-cli 2021 -M day13.part1
The answer is: 788

$ scala-cli 2021 -M day10.part2
The answer is:
#..#...##.###..#..#.####.#..#.###...##.
#.#.....#.#..#.#.#..#....#..#.#..#.#..#
##......#.###..##...###..#..#.###..#...
#.#.....#.#..#.#.#..#....#..#.#..#.#.##
#.#..#..#.#..#.#.#..#....#..#.#..#.#..#
#..#..##..###..#..#.####..##..###...###

You can replace the content of the input/day13 file with your own input from adventofcode.com to get your own solution.

Solutions from the community

Share your solution to the Scala community by editing this page.