<- /posts

aoc 2023 (scala), part 1

Quick Intro

Here we are, year-and-year again waiting for the magical season that is Christmas. And yet again, we’ve been rewarded with another set of challenges for us to tackle in Advent of Code 2023 edition.

I’ll be addressing some of the challenges that I thought were cool or that I found challenging / interesting. My solutions will be created with Scala since that is the language that I daily-drive nowadays. So, let’s jump straight to it!

Day 1: Trebuchet?!

In this challenge we are presented with a text document that we need to parse, and here are ths instructions for the parsing:

  1. Find all of the numbers on each line of the document.
  2. Create two-digit numbers from each line of the document by combining the first and last digits found. If we only have a single digit, the two-digit number is a repeating number.
  3. Add up all two-digit numbers to get a total.

Now that we know what to do, we are provided with a sample file for us to see if our code fullfils those requirements:

1abc2
pqr3stu8vwx
a1b2c3d4e5f
treb7uchet

Now, we will apply the steps listed before to get the grand total – the value – of the document.

  • First Step: Find all numbers in each line.
12
38
12345
7
  • Second Step: Create two-digit numbers (first and last).
12
38
15
77
  • Third Step: Calculate total by adding up two-digit numbers -> 12 + 38 + 15 + 77 = 142.

Implementation

Amazing! Now we are going to go on and see how we can solve this problem with Scala. First we need to lay out some rules and corner-cases that our solution must consider in order to be robust. We must consider the following scenarios:

  • A line contains no digits -> We consider this a 0.
  • A line contains a single digit -> The two-digit number will be a repeating digit.
  • A line contains more than two digits -> Consider the first and last digits.

This should be enough to start creating some code that will calculate the sample document’s value. Let’s get to it.

Functional Approach

To tackle this I will create functions that will interact with the document in an iterative way. For now, I will take the document and put it into a List[String] to make things easier (no io file reading for now).

Whenever we are iterating over the document, we have to first remove any character that is not a digit.

import scala.util.matching.Regex

def removeNonDigitCharacters(line: String): String = {
  val pattern: Regex = "[^0-9]".r
  pattern.replaceAllIn(line, "")
}

Now we have to convert the “clean” string into an integer. Here we have to take into consideration our test cases.

def convertToValidInt(line: String): Int = {
  if (line.length == 0) return 0

  return s"${line.head}${line.last}".toInt
}

Ok, looking good for now! If we put all things together, then our code should look something like this:

import scala.util.matching.Regex

val document: List[String] = List(
  "1abc2",
  "pqr3stu8vwx",
  "a1b2c3d4e5f",
  "treb7uchet"
)

def removeNonDigitCharacters(line: String): String = {
  val pattern: Regex = "[^0-9]".r
  pattern.replaceAllIn(line, "")
}

def convertToValidInt(line: String): Int = {
  if (line.length == 0) return 0

  return s"${line.head}${line.last}".toInt
}

val documentValue: Int = items
  .map(removeNonDigitCharacters)
  .map(convertToValidInt)
  .reduce(_ + _)

Nice! Our first approach works! It successfully calculates the total being 142.

The only thing missing is parsing the calibration document from AoC’s site. For this we will read the .txt file, each line will be an item from a list of strings. I will assume the file is in the same directory as our code.

val path: String = "calibration.txt"

val document: List[String] = os.read.lines(path).toList

The calibration documents usually are quite long, so I wont be posting it here, but make no mistake I am running this code with the document provided by AoC. And well, to our surprise the code works!

Second Phase

AoC provides an additional challenge with more corner cases, and that is what we’ll be tackling up next.

The calibration document seems to be… odd. We can see sometimes it has numbers written-out (e.g. eight, one, seven, etc…), and these numbers should be considered valid digits. With this in mind, we have to create yet another function that converts written-out numbers into propper integers.

But first, have to consider the following scenarios:

  • We will convert written-out numbers from left-to-right -> whenever numbers share letters (e.g. oneight, sevenine), only the first occurrence will be converted into an integer (e.g. 1ight, 7ine).
  • Written-out numbers can appear in any possition inside the line.

Ok! With these scenarios in mind, let’s create a function.

def parseTextToInt(line: String): String = {
  val pattern: Regex = ".*(one|two|three|four|five|six|seven|eight|nine).*".r
  pattern.findFirstMatchIn(line)
}