Featured image of post Advent of Code 2021 in Kotlin - Day 13

Advent of Code 2021 in Kotlin - Day 13

Discuss some basic transformations on 2D plane to get our origami puzzles ready for the Christmas 🎄.

Introduction

In Day 13 is focused on implementing the proper way of representing folds of origami transparent cards that finally get some patterns on them. This task is quite easy when we choose some simple (maybe not natural) representation of our card - let’s see that in action.

Solution

The first idea might be to represent the state as 2D array or map from points to value on the plane and try to manipulate them. But it’s worth noticing that it’s enough if we remember only the dots positions on the plane. That’s because having them, we are able to create their equivalents on the other side of fold axe.

So having that, we’re almost done - just find out how to find the new locations of the dots after some fold. We consider only the case of x fold, as the second is analogous. When we want to fold the plane along x, we see how far it is from the folding coord with it.x - coord and put it at that distance from coord by defining the mirrored position as coord - (it.x - coord).

Day13.kt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
object Day13 : AdventDay() {
  override fun solve() {
    val data = reads<String>() ?: return

    val paper = data.toPaper()
    val commands = data.toCommands()

    commands.firstOrNull()?.let { paper.fold(it) }?.dots?.size.printIt()
    commands.fold(paper) { p, cmd -> p.fold(cmd) }.printIt()
  }
}

private fun List<String>.toPaper() = takeWhile { it.isNotBlank() }.map { line ->
  line.split(",").map { it.value<Int>() }.let { (x, y) -> V2(x, y) }
}.let { Paper(it.toSet()) }

private fun String.toFoldCmd() = removePrefix("fold along ").split("=")
  .let { (axe, coord) -> FoldCmd(coord.toInt(), FoldAxe.valueOf(axe)) }

private fun List<String>.toCommands() = dropWhile { it.isNotBlank() }.drop(1).map { it.toFoldCmd() }

private data class V2(val x: Int, val y: Int)

private enum class FoldAxe { x, y }
private data class FoldCmd(val coord: Int, val axe: FoldAxe)

private data class Paper(val dots: Set<V2>) {

  fun fold(cmd: FoldCmd): Paper = with(cmd) {
    val (orig, mod) = when (axe) {
      FoldAxe.x -> dots.partition { it.x <= coord }.let { (left, right) ->
        Pair(left, right.map { it.copy(x = coord - (it.x - coord)) })
      }
      FoldAxe.y -> dots.partition { it.y <= coord }.let { (up, down) ->
        Pair(up, down.map { it.copy(y = coord - (it.y - coord)) })
      }
    }
    Paper((orig + mod).toSet())
  }

  override fun toString() = buildString {
    for (y in 0..dots.maxOf { it.y }) {
      for (x in 0..dots.maxOf { it.x }) {
        append(if (V2(x, y) in dots) '#' else '.')
      }
      appendLine()
    }
  }
}

Extra notes

We used some quite new and pretty Kotlin function when writing this solution which are worth mentioning here. It’s buildString { } builder method that was stabilized not so long time ago. There are a few more builders in Kotlin that can be used also for build the collections in that manner. For example, we can use also the buildList, buildSet and buildMap to create these collections in similar manner, with the usage of loops and some conditions.

comments powered by Disqus