A good way to learn a new programming language is to jump in and solve some practice exercises. At the end of each year Advent of Code release a new coding exercise each day until Christmas. It has become fairly popular over the years.

I have had an interest in the functional paradigm for a while, so this year I decided to complete the Advent of Code 2023 exercises in OCaml. So, let’s jump straight into the first day’s exercise.

**Note:** I will be using Jane Street’s core library as a replacement for OCaml’s standard library.

## Part 1

### Problem Statement

There is a long-winded story about the Elves and a weather machine and so on, but let’s just distill it down to a simple problem statement. Basically, we are given an input string that consists of lines of text. For each line of text, we should find the first and last digit in the line, combine them together, and parse them as an integer. We should then sum up all the integers.

Example input:

```
1abc2
pqr3stu8vwx
a1b2c3d4e5f
treb7uchet
```

Correct answer:

```
12 + 38 + 15 + 77 = 142
```

Note that the last line only contains a single digit. So, that single digit is the first and the last digit.

### Solution

To start with we will create a function to parse a digit character into an integer using its ASCII code. The `Char.to_int`

function returns the ASCII code of a character:

```
let char_to_int digit =
let zero = Char.to_int '0' in
Char.to_int digit - zero
```

Let’s now define a function that returns a tuple with the first and last digit character in a line:

```
let digits_from_line line =
let left_char = String.find line ~f:Char.is_digit in
let right_char = String.find (String.rev line) ~f:Char.is_digit in
match (left_char, right_char) with
| Some left_char, Some right_char -> (left_char, right_char)
| _ -> raise (Failure "Couldn't find a digit in the string")
```

The `digits_from_line`

function gets the first and last character digit: `left_char`

and `right_char`

respectively. A pattern match is used to check if either of these values are `None`

. If either is `None`

it raises a `Failure`

.

We can now combine these two functions to calculate the integer value for each line:

```
let num_from_line line =
let left_digit, right_digit = digits_from_line line in
(char_to_int left_digit * 10) + char_to_int right_digit
```

At this point we can write a function that sums all the lines in a list of lines:

```
let sum_lines lines =
let sum_nums acc line = acc + num_from_line line in
List.fold lines ~init:0 ~f:sum_nums
```

The `List.fold`

function takes an initial value which it then uses as an accumulator that’s passed into the `f`

function along with each line. The accumulator’s new value for each line is the return value of the `f`

function.

The last thing that remains is to actually read the input from a file and print the answer to stdout:

```
let () =
let lines = In_channel.read_lines "inputs/day_01.txt" in
let answer = sum_lines lines in
Printf.printf "Answer for part 1: %d\n" answer
```

Putting this all together we end up with a `.ml`

file like this:

```
open! Core
let char_to_int digit =
let zero = Char.to_int '0' in
Char.to_int digit - zero
let digits_from_line line =
let left_char = String.find line ~f:Char.is_digit in
let right_char = String.find (String.rev line) ~f:Char.is_digit in
match (left_char, right_char) with
| Some left_char, Some right_char -> (left_char, right_char)
| _ -> raise (Failure "Couldn't find a digit in the string")
let num_from_line line =
let left_digit, right_digit = digits_from_line line in
(char_to_int left_digit * 10) + char_to_int right_digit
let sum_lines lines =
let sum_nums acc line = acc + num_from_line line in
List.fold lines ~init:0 ~f:sum_nums
let () =
let lines = In_channel.read_lines "inputs/day_01.txt" in
let answer = sum_lines lines in
Printf.printf "Answer for part 1: %d\n" answer
```

When this is compiled and ran we can confirm that we get the correct answer:

```
Answer for part 1: 142
```

Great! Now on to part two.

## Part 2

### Problem Statement

In a similar way to the first part we need to combine the first and last digit in the string on a line to get a number. However, in part 2 it is also valid if the number is spelled out with letters.

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

Given this input, the first line will have a number `29`

because the first spelled out digit is `two`

and the last spelled out digit is `nine`

. The last line has a number `76`

because the first digit is `7`

and the last spelled out digit is `six`

.

The correct answer for the above input would be: `29 + 83 + 13 + 24 + 42 + 14 + 76 = 281`

### Solution

As a first step we define an association list that maps integer values to their spelled out words:

```
let digits_to_words =
[
(0, "zero");
(1, "one");
(2, "two");
(3, "three");
(4, "four");
(5, "five");
(6, "six");
(7, "seven");
(8, "eight");
(9, "nine");
]
```

Next up we want a function that can find each of these spelled out words in the string and return the index and value of the first occurrence:

```
let find_first_word line digits_to_words =
(* Find the first occurrence of a word in the line. Check if the index is
smaller than the smallest index encountered so far and then return it.
Otherwise it's not the smallest index and we return the current smallest
index. *)
let find_word (min_idx, acc_digit) (digit, word) =
let idx = String.substr_index line ~pattern:word in
match idx with
| Some idx -> if idx < min_idx then (idx, digit) else (min_idx, acc_digit)
| None -> (min_idx, acc_digit)
in
let min_idx, acc_digit =
List.fold digits_to_words ~init:(Int.max_value, Int.max_value) ~f:find_word
in
if min_idx = Int.max_value then None else Some (min_idx, acc_digit)
let find_first_word_left line = find_first_word line digits_to_words
(* When we look for the first word from the right, we have to reverse the line
and the letters of the spelled out numbers we are checking for. *)
let find_first_word_right line =
find_first_word (String.rev line)
(List.map digits_to_words ~f:(fun (digit, word) -> (digit, String.rev word)))
```

Take note that `find_first_word_right`

has the same logic as `find_first_word_left`

except that both the input `line`

and the words that are matched against are reversed.

The logic for finding the first digit is similar to part 1:

```
(* Find the first digit in the line from the left. Return the index of the digit
as well as its integer value. *)
let find_first_digit_left line =
match String.findi line ~f:(fun _ c -> Char.is_digit c) with
| Some (idx, first_digit) -> Some (idx, char_to_int first_digit)
| None -> None
(* To get the first digit from the right we simply reverse the input line and
then get the first digit from the left. *)
let find_first_digit_right line = find_first_digit_left (String.rev line)
```

Now we can combine these into one function that can check for both spelled out words and digits:

```
(* Try to find the first word and the first digit in the string. Pick the one
with the lowest index and return the value. If none are found, return None. *)
let find_first_num line ~f1 ~f2 =
match (f1 line, f2 line) with
| Some (idx1, digit1), Some (idx2, digit2) ->
if idx1 < idx2 then Some digit1 else Some digit2
| Some (_, digit1), None -> Some digit1
| None, Some (_, digit2) -> Some digit2
| None, None -> None
let find_first_num_left =
find_first_num ~f1:find_first_word_left ~f2:find_first_digit_left
let find_first_num_right =
find_first_num ~f1:find_first_word_right ~f2:find_first_digit_right
```

Putting it all together we can parse the input and come up with an answer:

```
let num_from_line line =
match (find_first_num_left line, find_first_num_right line) with
| Some left, Some right -> (left * 10) + right
| _ -> raise (Failure "Couldn't find a digit in the string")
let sum_lines lines =
let sum_nums acc line = acc + num_from_line line in
List.fold lines ~init:0 ~f:sum_nums
let () =
let lines = In_channel.read_lines "inputs/day_01.txt" in
let answer = sum_lines lines in
Printf.printf "Answer for part 2: %d\n" answer
```

For completeness, here is what the entire `.ml`

file looks like:

```
open! Core
let char_to_int digit =
let zero = Char.to_int '0' in
Char.to_int digit - zero
(* Association list of spelled out digits and their corresponding integer digit
values. *)
let digits_to_words =
[
(0, "zero");
(1, "one");
(2, "two");
(3, "three");
(4, "four");
(5, "five");
(6, "six");
(7, "seven");
(8, "eight");
(9, "nine");
]
let find_first_word line digits_to_words =
(* Find the first occurrence of a word in the line. Check if the index is
smaller than the smallest index encountered so far and then return it.
Otherwise it's not the smallest index and we return the current smallest
index. *)
let find_word (min_idx, acc_digit) (digit, word) =
let idx = String.substr_index line ~pattern:word in
match idx with
| Some idx -> if idx < min_idx then (idx, digit) else (min_idx, acc_digit)
| None -> (min_idx, acc_digit)
in
let min_idx, acc_digit =
List.fold digits_to_words ~init:(Int.max_value, Int.max_value) ~f:find_word
in
if min_idx = Int.max_value then None else Some (min_idx, acc_digit)
let find_first_word_left line = find_first_word line digits_to_words
(* When we look for the first word from the right, we have to reverse the line
and the letters of the spelled out numbers we are checking for. *)
let find_first_word_right line =
find_first_word (String.rev line)
(List.map digits_to_words ~f:(fun (digit, word) -> (digit, String.rev word)))
(* Find the first digit in the line from the left. Return the index of the digit
as well as its integer value. *)
let find_first_digit_left line =
match String.findi line ~f:(fun _ c -> Char.is_digit c) with
| Some (idx, first_digit) -> Some (idx, char_to_int first_digit)
| None -> None
(* To get the first digit from the right we simply reverse the input line and
then get the first digit from the left. *)
let find_first_digit_right line = find_first_digit_left (String.rev line)
(* Try to find the first word and the first digit in the string. Pick the one
with the lowest index and return the value. If none are found, return None. *)
let find_first_num line ~f1 ~f2 =
match (f1 line, f2 line) with
| Some (idx1, digit1), Some (idx2, digit2) ->
if idx1 < idx2 then Some digit1 else Some digit2
| Some (_, digit1), None -> Some digit1
| None, Some (_, digit2) -> Some digit2
| None, None -> None
let find_first_num_left =
find_first_num ~f1:find_first_word_left ~f2:find_first_digit_left
let find_first_num_right =
find_first_num ~f1:find_first_word_right ~f2:find_first_digit_right
let num_from_line line =
match (find_first_num_left line, find_first_num_right line) with
| Some left, Some right -> (left * 10) + right
| _ -> raise (Failure "Couldn't find a digit in the string")
let sum_lines lines =
let sum_nums acc line = acc + num_from_line line in
List.fold lines ~init:0 ~f:sum_nums
let () =
let lines = In_channel.read_lines "inputs/day_01.txt" in
let answer = sum_lines lines in
Printf.printf "Answer for part 2: %d\n" answer
```

Compiling and running this code yields the following result:

```
Answer for part 2: 281
```

### Just a note

Thanks for reading this post on solving the first day of Advent of Code 2023 using OCaml. I am going to experiment this year with writing a lot more blog posts that are shorter-form while timeboxing how much time I spend writing them. I have a habit of spending hours faffing over small details that gets in the way of producing more useful content.

I am working on a lot of cool things in my free time including building a quantitative research platform on my home infrastructure. I am on a journey to learn the functional programming paradigm and absorb that into how I write software. I am also reading some books on quantitative finance and statistics. I have forgotten so much of the mathematics and statistics I learned as a university student and it has been a lot of fun getting back into it.

Bookmark this blog and check back in on occasion if you feel like reading some (hopefully) interesting things about what I’ve been working on.