# Advent of Code 2023 - Day 18, in Kotlin - Lavaduct Lagoon

Kotlin solutions to parts 1 and 2 of Advent of Code 2023, Day 18: 'Lavaduct Lagoon'

I am not a huge fan of the mostly math-based puzzles. Days like today are not my favorite, but it’s a good learning opportunity for me. I started off on the right track, after remembering how some people solved Day 10 , but ended up getting frustrated with off-by-two errors and ended up seeking help from Reddit to understand why. I’ll try my best to explain the math. :)

If you’d rather just view the code, my GitHub Repository is here.

**Puzzle Input**

Today we’ll take our `input`

as a `List<String>`

and declare it as a property, because we’ll need to parse it twice, one for each part of the puzzle.

```
class Day18(private val input: List<String>) {
}
```

#### ⭐ Day 18, Part 1

The puzzle text can be found here.

Before we get too far into what we’re doing today, we need to make an addition to our `Point2D`

class. It will become handy to be able to multiply a `Point2D`

, usually referencing an offset by some integer. So we’ll define an `operator`

on `Point2D`

to do just that. Its implementation simply multiplies the coordinates by the `amount`

specified.

```
// In Point2D
operator fun times(amount: Int): Point2D =
Point2D(x * amount, y * amount)
```

Next, we’ll write a parser for part 1. It takes a single row of `input`

and returns a `Pair<Point2D, Int>`

where the `Point2D`

is an offset representing the direction to go, and the `Int`

represents the distance.

```
// In Day18
private fun parseRowPart1(input: String): Pair<Point2D, Int> =
when (input[0]) {
'U' -> NORTH
'D' -> SOUTH
'L' -> WEST
'R' -> EAST
else -> throw IllegalStateException("Bad direction $input")
} to input.substringAfter(" ").substringBefore(" ").toInt()
```

We’ll use the `substringAfter`

and `substringBefore`

trick we’ve used a few times already to parse out what we need.

Next, the part that took me a really long time to get right and actually understand - calculating the lava volume. Thankfully, lava is only one unit deep, so we don’t have to do any three-dimensional math here.

```
// In Day18
private fun calculateLava(instructions: List<Pair<Point2D, Int>>): Long {
val area = instructions
.runningFold(ORIGIN) { acc, (direction, distance) ->
acc + (direction * distance)
}
.zipWithNext()
.sumOf { (a, b) ->
(a.x.toLong() * b.y.toLong()) - (a.y.toLong() * b.x.toLong())
} / 2
val perimeter = instructions.sumOf { it.second }
return area + (perimeter / 2) + 1
}
```

The first part of this function uses the Shoelace Formula
to calculate the area of the lava pit. The `runningFold`

is like a normal `fold`

except it keeps all of the transitive values. This lets us create a `List<Point2D>`

which represents all of the actual points on the outside of the lava pit. Each `instruction`

gives us the `direction`

from the last point and the `distance`

to travel and the `fold`

part gives us the previously calculated value (a point). This allows us to string all of the points together beginning and ending with 0,0 (the `ORIGIN`

). This is important for later. The Shoelace Formula asks us to perform some math on each pair of points in the list, so we use `zipWithNext`

in order to pair them together. Taking the `sumOf`

the reciprocal differences gives us the `area`

. Note here we convert these `x`

and `y`

values of the points `toLong`

because we’ll need them that way in part 2.

The `perimeter`

of the lava pit is the `sumOf`

all the distances in the `instructions`

.

This last part is the part that gave me the most trouble. I was trying to follow Pick’s Theorem
without really knowing what I was doing. I was constantly off by 2 implementing it the way wikipedia had it. Pick’s Theorem is `A = i + b/2 - 1`

where `A`

is the area, `i`

is the number of points inside the polygon, `b`

is the number of points on the outside of the polygon. The thing I didn’t realize is that we aren’t trying to calculate `A`

. We have `A`

from the Shoelace Formula. We’re really trying to calculate `b+i`

, and if you rearrange the formula for that, you end up with `i + b = A + b/2 + 1`

. That’s why we add 1 rather than subtract it, and that’s why this works at all. I will fully admit that I got my stars by just saying “well, I’m off by 2 on the example, lets see if that holds true for the actual input”. Not the best way to earn stars, but at least I know why that worked now.

Calling this with the `input`

parsed for part 1 gives us our answer to part 1.

```
// In Day18
fun solvePart1(): Long =
calculateLava(input.map { parseRowPart1(it) })
```

Star earned (with some math theory help from Reddit)! Onward!

#### ⭐ Day 18, Part 2

The puzzle text can be found here.

Part 2 uses the same algorithm as part 1 except the numbers are parsed differently and are much larger. This is why we defined our `calculateLava`

function to use `Long`

rather than `Int`

.

```
// In Day18
private fun parseRowPart2(input: String): Pair<Point2D, Int> =
with(input.substringAfter("#").substringBefore(")")) {
when (last()) {
'0' -> EAST
'1' -> SOUTH
'2' -> WEST
'3' -> NORTH
else -> throw IllegalStateException("Bad direction $input")
} to dropLast(1).toInt(16)
}
```

The call to `calculateLava`

is the same except for the new values.

```
// In Day18
fun solvePart2(): Long =
calculateLava(input.map { parseRowPart2(it) })
```

Star earned! See you tomorrow!

#### Further Reading

- Index of All Solutions - All posts and solutions for 2023, in Kotlin.
- My Github repo - Solutions and tests for each day.
- Solution - Full code for day 18
- Advent of Code - Come join in and do these challenges yourself!