Advent of Code 2023 - Day 1

Code on GitHub Back to Advent of Code 2023

The Problem

Day 1 was pretty easy as you would expect though I would say relatively more difficult than most year's day 1 challenge which seems to have set the standard for this year's Advent of Code. The example input looked like below

1abc2
pqr3stu8vwx
a1b2c3d4e5f
treb7uchet

Part 1

The problem for part 1 was too simply find the first and last numerical digit on each line, merge them together (1 and 2 becomes 12), do this for each line and sum the total.

The solution for this was fairly obvious, loop through each line, within each line loop from the start of the string incrementing through each character until you find a number and also do the reverse decrementing from the end of the string until you find a number.

1abc2 => 12
pqr3stu8vwx => 38
a1b2c3d4e5f => 15
treb7uchet => 77

total => 142

First step for the code was to create a really basic utility function to turn the input string from Advent of Code into an array of strings.

1function parseInput(input: string) {
2  return input.split('\n')
3}

That converts something like

1abc2
pqr3stu8vwx
a1b2c3d4e5f
treb7uchet

into

[
  '1abc2',
  'pqr3stu8vwx',
  'a1b2c3d4e5f',
  'treb7uchet'
]

Then we solve that with the below function

1export function solution1(input: string[]) {
2  let total = 0
3
4  for (let i = 0; i < input.length; i++) {
5    const row = input[i]
6    let rowTotal = ''
7    for (let j = 0; j < row.length; j++) {
8      if (Number(row[j])) {
9        rowTotal += row[j]
10        break
11      }
12    }
13    for (let j = row.length - 1; j >= 0; j--) {
14      if (Number(row[j])) {
15        rowTotal += row[j]
16        break
17      }
18    }
19    total += Number(rowTotal)
20  }
21
22  return total
23}

Couple of things to note in the code, rowTotal is a string so when the numerical digit is added (which is actually represented a string) we concatenate the value instead of adding. The other important thing to note is that Number(row[j]) will be false(y) for any string that does not represent a numerical value as Number('not a number') returns NaN which is falsy.

Part 2

For part 2 we are now told that digits spelled out are also now valid numbers so for example

two1nine => 29
eightwothree => 83
abcone2threexyz => 13
xtwone3four => 24
4nineeightseven2 => 42
zoneight234 => 14
7pqrstsixteen => 76

total => 281

I took a simple solution and tackled this problem in the exact same way with a minor variation applied

1export function solution2(input: string[]) {
2  let total = 0
3
4  const digits = {
5    one: '1',
6    two: '2',
7    three: '3',
8    four: '4',
9    five: '5',
10    six: '6',
11    seven: '7',
12    eight: '8',
13    nine: '9',
14  }
15
16  const digitKeys = Object.keys(digits)
17
18  for (let i = 0; i < input.length; i++) {
19    const row = input[i]
20    let rowTotal = ''
21
22    for (let j = 0; j < row.length; j++) {
23      if (Number(row[j])) {
24        rowTotal += row[j]
25        break
26      }
27      let x = false
28      const substring = row.substring(0, j + 1)
29      for (const digit of digitKeys) {
30        if (substring.includes(digit)) {
31          rowTotal += digits[digit as keyof typeof digits]
32          x = true
33          break
34        }
35      }
36      if (x) {
37        break
38      }
39    }
40
41    for (let j = row.length - 1; j >= 0; j--) {
42      if (Number(row[j])) {
43        rowTotal += row[j]
44        break
45      }
46      let x = false
47      const substring = row.substring(j)
48      for (const digit of digitKeys) {
49        if (substring.includes(digit)) {
50          rowTotal += digits[digit as keyof typeof digits]
51          x = true
52          break
53        }
54      }
55      if (x) {
56        break
57      }
58    }
59    total += Number(rowTotal)
60  }
61
62  return total
63}

The changes added in part 2 are keeping an object with a key:value mapping of the digits one to nine and their values 1-9.

Then as well as checking for a number as we iterate through each line (from start to end) and (end to start) we also check if our current substring contains any of the keys in our object mapping.

If the current substring does exist within the substring we use the corresponding value in our rowTotal and break out of the for loop.

There is an opportunity to make this code cleaner and probably more performant with the use of a Trie data structure but I have never used this before, if I have time I will come back to this problem and learn enough to implement the solution using a Trie. Also considering my solution runs in about 10ms on my machine with the real input there is not a need for a more performant solution in this case.