An example of what we’ll be discussing in this article
I love Advent of Code, and had some great fun last year completing the first 10 days or so while learning TypeScript.
Now I must admit that due to a lack of skills and motivation, I have never managed to get past the first 10 or 11 exercises.
If you want to see some much more skilled coders complete the whole 30 day long challenge, you'll find a ton of interesting links on this HackerNews recent thread. Yep, some guys are using Prolog or Erlang, hats off to them.
Anyway this year again the fantastic folks behind Advent of Code have come up with a relatively interesting story that glues all the daily exercises together. Without going into too much detail, we're talking this time about the disappearance of a Chief Historian without whom the Christmas sleigh launch might not happen.
Our mission, as hobbyist programmers, is to try and find him!
The exercise for day 1 sees us investigate two lists whose content might give us some clues as to the Chief Historian's whereabouts:
"Maybe the lists are only off by a small amount! To find out, pair up the numbers and measure how far apart they are. Pair up the smallest number in the left list with the smallest number in the right list, then the second-smallest left number with the second-smallest right number, and so on.
Within each pair, figure out how far apart the two numbers are; you'll need to add up all of those distances. For example, if you pair up a 3 from the left list with a 7 from the right list, the distance apart is 4; if you pair up a 9 with a 3, the distance apart is 6."
Here is what the two lists look like:
const input_data: number[][] = [
[3,4,2,1,3,3],
[4,3,5,3,9,3]
];
Alright, that doesn't sound too bad I think. At least depending on the language that we go with for this exercise. The reason why I'm saying this is that this seemingly simple enough problem could easily turn into a much more complex task.
Using a modern scripting language such as Python, Ruby, or in our case TypeScript means that we can rely on a built-in array sorting method to order our two lists. If we had gone for an older or more exotic language, we'd have had to manually write our own sorting function. Which some people find fun, but I really don't.
With this out of the way, let's simply order each array using the .sort()
method and see what happens:
const sorted_array_left : number[] = input_data[0].sort();
const sorted_array_right : number[] = input_data[1].sort();
console.log(sorted_array_left);
console.log(sorted_array_right);
Provided that both arrays have the same length, we can now loop through each element of the said arrays and subtract the value of one from the other.
In other words we'll be subtracting the value of array_right
at index 0 from the value of array_left
at index 0, then the value of array_right
at index 1 from the value of array_left
at index 1, etc..
One thing to keep in mind here is that as we're repeatedly adding all these subtracted nuumbers to a variable named result
, we must use the absolute value of these numbers.
const getDistance = (data: number[][]): number => {
let result: number = 0;
const sorted_array_left : number[] = data[0].sort();
const sorted_array_right : number[] = data[1].sort();
for (let i = 0; i < data[0].length; i++) {
let dist: number = Math.abs(sorted_array_left[i] - sorted_array_right[i]);
result += dist;
}
return result;
};
const distance: number = getDistance(input_data);
console.log(`The result is:\n\n\t${distance}`);
The computed distance we get is 11, which is exactly what the Advent of Code website told us we should see. Yay!
The exercise for day 2 seems slightly more complex, but is actually still pretty straightforward.
We're given this time a series of "reports" that come in the shape of a matrix of numbers. However, these numbers are stored in string format, and separated by a space character. Our task is to determine whether each report (each row) is "safe" or not.
"The engineers are trying to figure out which reports are safe. The Red-Nosed reactor safety systems can only tolerate levels that are either gradually increasing or gradually decreasing. So, a report only counts as safe if both of the following are true:
- The levels are either all increasing or all decreasing. - Any two adjacent levels differ by at least one and at most three."
Ok, let's start by saving these reports as an array of string characters:
const input_data: string[] = [
"7 6 4 2 1",
"1 2 7 8 9",
"9 7 6 2 1",
"1 3 2 4 5",
"8 6 4 4 1",
"1 3 6 7 9"
];
There are multiple ways to solve this problem. Our approach will consist of four distinct steps:
true
values.Ok turning each string into an array of integers is really simple:
const textToMatrix = (data: string[]): number[][] => {
let result: number[][] = [];
for (let d of data) {
let string_to_array: string[] = d.split(" ");
let temp_array: number[] = [];
for (let s of string_to_array) {
temp_array.push(parseInt(s));
};
result.push(temp_array);
};
return result;
};
const text_to_matrix: number[][] = textToMatrix(input_data);
console.log(text_to_matrix);
Now, I think we can combine steps 2 and 3 by writing a single function. We first iterate through the parent array, and then through each nested array. During this nested for loop, we use the indexing position of each element to check for the two conditions that determine whether this array will "pass" or "fail":
You'll have noticed that in our nested loop, we're starting with an iterator value of 1: for (let i = 1; i < d.length; i++)
. This makes sense as we want to start check for "pass" and "fail" conditions from the second element within each array onwards.
If the currently evaluated element meets our requirements, a true
value is added to our matrix of booleans.
const firstFilter = (data: number[][]): boolean[][] => {
let result: boolean[][] = [];
for (let d of data) {
let current_row: boolean[] = [];
for (let i = 1; i < d.length; i++) {
if ((Math.abs(d[i] - d[i-1]) <= 3) && (d[i] != d[i-1])) {
current_row.push(true);
}
else {
current_row.push(false);
};
};
result.push(current_row);
};
return result;
};
const first_filter: boolean[][] = firstFilter(text_to_matrix);
console.log(first_filter);
Where our first two conditions look like this: (Math.abs(d[i] - d[i-1]) <= 2) && (d[i] != d[i-1])
.
What we can now see is that only 3 of the 6 initial "reports" match our first 2 conditions:
const input_data_filtered: number[][] = [
[7,6,4,2,1],
[1,3,2,4,5],
[1,3,6,7,9]
];
To verify that these 3 arrays are always increasing or decreasing, we're going to assign 1
to any increasing value, and 0
to any decreasing one. Logically, this means that only the arrays whose summed elements equal 0 or 4 will pass our test:
const secondFilter = (data: number[][]): number[][] => {
let result: number[][] = [];
for (let d of data) {
let new_row: number[] = [];
for (let i = 1; i < d.length; i++) {
if (d[i] > d[i-1]) {
new_row.push(1);
}
else {
new_row.push(0);
}
};
result.push(new_row);
};
return result;
};
const second_filter: number[][] = secondFilter(input_data_filtered)
console.log(second_filter);
Only the first and last "reports" successfully passed both our checks, which matches what the Advent of Code website tells us is the right answer.
One exercise left!
The task for day 3 sees us work with strings this time.
"It seems like the goal of the program is just to multiply some numbers. It does that with instructions like mul(X,Y), where X and Y are each 1-3 digit numbers. For instance, mul(44,46) multiplies 44 by 46 to get a result of 2024. Similarly, mul(123,4) would multiply 123 by 4.
However, because the program's memory has been corrupted, there are also many invalid characters that should be ignored, even if they look like part of a mul instruction. Sequences like mul(4, mul(6,9!, ?(12,34), or mul ( 2 , 4 ) do nothing.*
Scan the corrupted memory for uncorrupted mul instructions. What do you get if you add up all of the results of the multiplications?"
Here's what the corrupted memory instructions look like:
const input_data: string = "xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5))";
Are we thinking about the same thing? Yep, I'm afraid so. Regular expressions! Without further ado, let's head over to Regex101.com and write something that'll extract the uncorrupted functions for us:
We'll also need a second chunk of regular expressions at a later stage, so let's save it now as a variable named pattern_integers
.
const pattern_functions: RegExp = /mul\(\d+,\d+\)/g;
const pattern_integers: RegExp = /\d+,\d+/g;
Did I know how to extract multiple substrings from a single string in TypeScript before today? I didn't. But StackOverflow did:
const extractFunctions = (data: string): string[] => {
let result: string[] = [];
let match: any;
while ((match = pattern_functions.exec(data)) != null) {
result.push(match[0]);
};
return result;
};
const extracted_functions: string[] = extractFunctions(input_data);
console.log(extracted_functions);
Now that we've extracted the uncorrupted functions, we're going to repeat that process. But this time we'll be using our second set of regular expressions to extract the values that we're then expected to multiply:
const extractIntegers = (data: string[]): number[][] => {
let result: number[][] = [];
let match: any;
for (let d of data) {
let integers_to_multiply: number[] = [];
while ((match = pattern_integers.exec(d)) != null) {
let split_integers: string[] = match[0].split(",");
split_integers.forEach(i => integers_to_multiply.push(parseInt(i)));
result.push(integers_to_multiply);
};
};
return result;
};
const extracted_integers: number[][] = extractIntegers(extracted_functions);
console.log(extracted_integers);
Awesome, that did the trick!
We've one last function left to write. Let's loop through our array of arrays, multiply the values contained within the nested arrays, and add that product to a variable named result
:
const getSum = (data: number[][]): void => {
let result: number = 0;
for (let d of data) {
let multiplied: number = d[0] * d[1];
result += multiplied
};
console.log(`The result is:\n\n\t${result}`)
};
getSum(extracted_integers);
The value we obtain (161
) matches what the Advent of Code website expected us to extract from the uncorrupted functions. We're done for today!
Feel free to reach out to me if you've also been following this year's Advent of Code, and see you next time!