> > Advent of Code 2023: Day 1 Part 2 – Fixes Required

# Advent of Code 2023: Day 1 Part 2 – Fixes Required

This post follows on from Advent of Code 2023: Day 1 Part 1 – Calibrating Snow Operations where we were busy solving the Advent of Code 2023 Day 1 exercises using Kotlin and TDD.

Part 1 is solved, but as soon as you provide the answer, Part 2 becomes available. Mean.

The problem is similar, but requires an iteration on the solution to Part 1. Or it did in my case.

## The Problem

OK, so the revised problem is as follows:

Your calculation isn’t quite right. It looks like some of the digits are actually spelled out with letters`one``two``three``four``five``six``seven``eight`, and `nine` also count as valid “digits”.

Equipped with this new information, you now need to find the real first and last digit on each line. For example:

``````two1nine
eightwothree
abcone2threexyz
xtwone3four
4nineeightseven2
zoneight234
7pqrstsixteen``````

In this example, the calibration values are `29``83``13``24``42``14`, and `76`. Adding these together produces `281`.

What is the sum of all of the calibration values?

## Creating Tests

This problem initially provides enough test cases for writing 7 unit tests.

It also makes use of the existing `input.txt` we created in Part 1. Initially that won’t be a concern.

I got started by creating a new package – `day1b` – and adding the following tests:

``````package com.codereviewvideos.aoc23.day1b

import org.junit.jupiter.api.Test
import java.io.File
import kotlin.test.assertEquals

class CalibratorV2Test {
@Test
fun `two1nine returns 29`() {
assertEquals(29, CalibratorV2.calibrate("two1nine"))
}

@Test
fun `eightwothree returns 83`() {
assertEquals(83, CalibratorV2.calibrate("eightwothree"))
}

@Test
fun `abcone2threexyz returns 13`() {
assertEquals(13, CalibratorV2.calibrate("abcone2threexyz"))
}

@Test
fun `xtwone3four returns 24`() {
assertEquals(24, CalibratorV2.calibrate("xtwone3four"))
}

@Test
fun `4nineeightseven2 returns 42`() {
assertEquals(42, CalibratorV2.calibrate("4nineeightseven2"))
}

@Test
fun `zoneight234 returns 14`() {
assertEquals(14, CalibratorV2.calibrate("zoneight234"))
}

@Test
fun `7pqrstsixteen returns 76`() {
assertEquals(76, CalibratorV2.calibrate("7pqrstsixteen"))
}
}```Code language: Kotlin (kotlin)```

At this point `CalibratorV2` didn’t exist, so I let IntelliJ create this for me, which again resulted in a very similar setup to Part 1:

``````package com.codereviewvideos.aoc23.day1b

class CalibratorV2 {
companion object {
fun calibrate(s: String): Int {}
package com.codereviewvideos.aoc23.day1b

class CalibratorV2 {
companion object {
fun calibrate(s: String): Int {
}
}
}```Code language: Kotlin (kotlin)```

I then copied across my implementation from Part 1, and ran the first test.

It failed, giving:

``````Expected :29
Actual   :11```Code language: CSS (css)```

Which makes sense because Part 1 only concerned itself with finding the numbers in the string, and not the number spelled out as a written word.

## Initial Thoughts On How To Solve

As I know that there is a requirement to parse a full text file full of strings, I’m going to keep the basic setup I had from Part 1:

``````package com.codereviewvideos.aoc23.day1a

class CalibratorV2 {
companion object {
fun calibrate(s: String): Int {
val split = s.split("\n")
return split.sumOf { calibrateLine(it) }
}

private fun calibrateLine(s: String): Int {
// snow magic happens here
}
}
}```Code language: Kotlin (kotlin)```

Doing the absolute least possible to get a pass, we can ‘cheat’ a bit by hard coding the return value to make the first test go green:

``````    private fun calibrateLine(s: String): Int {
return 29
}```Code language: Kotlin (kotlin)```

That falls apart on the second test, of course.

So we need something a little more robust.

My thinking is to use recursion. The idea is that I will start with the initial string, then using a similar idea to Part 1, get the first and last character in the string, then see if that matches a word.

Here’s a more visual example:

Taking the first test:

``````  @Test
fun `two1nine returns 29`() {
assertEquals(29, CalibratorV2.calibrate("two1nine"))
}```Code language: Kotlin (kotlin)```

The idea is the function will do the following:

• check the first letter to see if the letters following it match `one` or `two` or `three` or `four` … or `nine`
• check the last letter to see if the preceding it match `one` or `two` or `three` or `four` … or `nine`
• if they do, we have found a match

Excellent, that actually covers off the first test.

But a more realistic example is one where that doesn’t immediately happen. In that case we have:

This corresponds to the following test:

``````  @Test
fun `abcone2threexyz returns 13`() {
assertEquals(13, CalibratorV2.calibrate("abcone2threexyz"))
}```Code language: Kotlin (kotlin)```

In this case, `a` and `z` do not match.

So we need to remove those letters from the string, giving us a new string of `bcone2threexy` and then call the function again with this new input.

Repeat this process, trimming the string down until we do get a match.

## Implementing This Idea As Code

Like in Part 1 whereby we were able to use `first` and `last`, Kotlin again comes to our rescue with `startsWith` and `endsWith`.

And this works identically for `endsWith`.

Which gave me something like this:

``````       val first =
when {
s.startsWith("one") -> 1
s.startsWith("two") -> 2
s.startsWith("three") -> 3
s.startsWith("four") -> 4
s.startsWith("five") -> 5
s.startsWith("six") -> 6
s.startsWith("seven") -> 7
s.startsWith("eight") -> 8
s.startsWith("nine") -> 9
else -> null
}```Code language: Kotlin (kotlin)```

This got my spidey senses tingling however, as I recognised immediately I would need the same thing for `endsWith`, and that was already going to be over 25 lines of code.

Usually these code puzzles can be solved with a few number of lines… so I was thinking I’d gone awry.

However, the gist of this should work.

There is also the continued need to work with digits, so I had to add one other entry to my `when`:

``````        val first =
when {
s.startsWith("one") -> 1
s.startsWith("two") -> 2
s.startsWith("three") -> 3
s.startsWith("four") -> 4
s.startsWith("five") -> 5
s.startsWith("six") -> 6
s.startsWith("seven") -> 7
s.startsWith("eight") -> 8
s.startsWith("nine") -> 9
s.first().isDigit() -> s.first().digitToInt()
else -> null
}
```Code language: Kotlin (kotlin)```

I should point out that originally I had this as `s.firstOrNull()?.isDigit()`

But after a bit of thought I realised that I didn’t need a `null` check here, as that would be handled by the `else`.

Again, I needed this for both the first and last values, so I ended up with a lot of code here.

### Trimming The String

This actually works well any test case where we begin and end with a valid number.

It needs a bit more code to trim the string when that is not the case.

Here’s the solution I came up with:

``````package com.codereviewvideos.aoc23.day1b

class CalibratorV2 {
companion object {
fun calibrate(s: String): Int {
val split = s.split("\n")
return split.sumOf { calibrateLine(it) }
}

private fun calibrateLine(s: String, firstValue: Int? = null, secondValue: Int? = null): Int {

var first = firstValue
if (first == null) {
first =
when {
s.startsWith("one") -> 1
s.startsWith("two") -> 2
s.startsWith("three") -> 3
s.startsWith("four") -> 4
s.startsWith("five") -> 5
s.startsWith("six") -> 6
s.startsWith("seven") -> 7
s.startsWith("eight") -> 8
s.startsWith("nine") -> 9
s.first().isDigit() -> s.first().digitToInt()
else -> null
}
}

var second = secondValue
if (second == null) {
second =
when {
s.endsWith("one") -> 1
s.endsWith("two") -> 2
s.endsWith("three") -> 3
s.endsWith("four") -> 4
s.endsWith("five") -> 5
s.endsWith("six") -> 6
s.endsWith("seven") -> 7
s.endsWith("eight") -> 8
s.endsWith("nine") -> 9
s.last().isDigit() -> s.last().digitToInt()
else -> null
}
}

var newString = if (first == null) s.subSequence(1, s.length).toString() else s
newString =
if (second == null) newString.subSequence(0, newString.length - 1).toString()
else newString

if (first == null || second == null) {
return calibrateLine(newString, first, second)
}

return (first.toString() + second).toInt()
}
}
}
```Code language: Kotlin (kotlin)```

I had to update the `private fun calibrateLine` to know whether it had already found a match for the two values we care about. Initially both would be `null`, but on any subsequent calls either could be populated.

One thing I am not so keen on with Kotlin is their lack of a ternary operator. Instead we have to use the `if` / `else` expression.

On line 48 we initialise a mutable variable `newString`.

It uses the `if` expression to check whether the variable `first` is `null`.

If `first` is `null`, it takes a substring of `s` starting from index 1 (excluding the character at index 0) to the end of the string (`s.length`).

If `first` is not `null`, it simply assigns the value of `s` to `newString`.

This is then repeated on lines 49 through 51, only taking the end of the string instead.

If either `first` or `second` are still `null` after this process, we go round again.

Otherwise, return the answer, stolen from Part 1.

And this works. All tests pass.

But it’s not pretty.

## Can We Do Better?

TDD gives us the concept of red, green, refactor.

We have green. We have a pass.

For this puzzle we could move on.

But it would be nice to see if we can learn a little more Kotlin in the process, now that we don’t need to worry about finding the answer.

I have a bunch of things to cover on that front, but I will do it in my next post.

This site uses Akismet to reduce spam. Learn how your comment data is processed.