Problem description

The puzzle input is a list of problems. For each problem, there is a list of numbers, and an the operation that must be applied to numbers : either an addition (+) or a multiplication (*).

For example, the test case I used is :

123 328  51 64 
 45 64  387 23 
  6 98  215 314
*   +   *   +  

Part 1

In this part, the numbers are arranged vertically, one number per line. The four problems are : 123*45*6,328+64+98,51*387*215,64+23+314123 * 45 * 6, 328 + 64 + 98, 51 * 387 * 215, 64 + 23 + 314

First, I defined an immutable Problem class, with an array of numbers, and the operation to apply. For this class, I defined a result method

case class Problem(op: Char, numbers: Array[Long]):
  def result: Long =
    op match
      case '+' => numbers.sum
      case '*' => numbers.reduce((x, y) => x * y)

Input data parsing is straightforward.

private def parseProblems1(input: List[String]): Array[Problem] =
  val matrix = input.init.map(line => line.split(" ").filter(s => !s.equals("")).map(s => s.toLong))
  val ops = input.last.split(" ").filter(s => !s.equals("")).map(s => s.head)

  (0 until ops.length)
    .map(i => Problem(ops(i), (0 until matrix.length).map(j => matrix(j)(i)).toArray))
    .toArray

The final result is given by summing the results obtained for each problem.

def solvePart1(input: List[String]): Long =
  val problems = parseProblems1(input)
  problems.map(x => x.result).sum

Part 2

Now, things become a bit more complicated. The numbers are now written in columns, one in each column, the most significant digit at the top. The four problems in our test case become : 356*42*1,8+248+369,175*581*32,4+431+623356 * 42 * 1, 8 + 248 + 369, 175 * 581 * 32, 4 + 431 + 623

The first thing I did was to find the empty columns, delimiting the problems, and to build a “datamap”, with start and end columns for each problem.

var problems: ArrayBuffer[Problem] = ArrayBuffer()
  val opLine = input.last
  val numbersLines = input.init
  var opIndexes: ArrayBuffer[Int] = ArrayBuffer()

  for i <- 0 until opLine.length do
    if opLine.charAt(i) != ' ' then opIndexes += i
  opIndexes += opLine.length + 1
  val columns = (0 until opIndexes.length - 1).map(i => (opIndexes(i), opIndexes(i+1) - 2)).toArray

Then, I explored each non-empty column, to build their corresponding numbers :

for (start, end) <- columns do
  var numbers: ArrayBuffer[Long] = ArrayBuffer()
  var i = end
  while i >= start do
    var n = 0
    for line <- numbersLines do
      val c = line.charAt(i)
      if c >= '0' && c <= '9' then n = 10 * n + (c - 48)
    i -= 1
    numbers += n
  problems += Problem(input.last.charAt(start), numbers.toArray)

The final result is obtained by the same method as in Part 1.