# Advent of Code 2022: Day 8

A TypeScript solution for the Advent of Code 2022, Day 8 puzzle: 2D arrays and coordinate gymnastics.

Today’s puzzle involves manipulating a two-dimensional grid of numbers, representing heights of trees in a rectangular forest.

The puzzle provides us with an example input, which looks like this.

30373 25512 65332 33549 35390

To begin, we need to parse this input into a data structure that we can work with.

// The forest of tree heights const FOREST = puzzleInput .split('\n') .map(row => row .split('') .map(n => parseInt(n)))

This code splits the input string into individual lines, and then splits each line into an array of individual numbers. It then uses the `parseInt()`

function to convert each string representation of a number into a numerical (tree) height.

Now, we need to determine the size of the forest.

// Some measurements of the forest size const ROWS = FOREST.length const LAST_ROW = ROWS - 1; const COLS = FOREST[0].length; const LAST_COL = COLS - 1;

This code calculates the number of rows and columns in the forest, as well as the index of the last row and column. These will come in handy later, when we need to check if we’ve reached the edge of the forest.

The next step in solving the puzzle is to determine the line of sight for each tree in the forest. To do this, we need to implement a function that can take a particular point in the forest (i.e. the coordinates of a tree), and a direction, and return a string of coordinates in that direction, to the edge of the forest.

type Direction = 'up' | 'down' | 'left' | 'right'; // Gets a string of coordinates in a given direction // and from a particular point, to the edge const lineOfSight = ( row: number, col: number, direction: Direction ) => { switch (direction) { case 'up': return Array(row) .fill(row) .map((x, y) => [x-y-1, col]); case 'down': return Array(LAST_ROW - row) .fill(row) .map((x, y) => [x+y+1, col]); case 'left': return Array(col) .fill(col) .map((x, y) => [row, x-y-1]); case 'right': return Array(LAST_COL - col) .fill(col) .map((x, y) => [row, x+y+1]); } }

We `switch`

on the `direction`

and calculate the number of steps that need to be taken to reach the edge of the forest. Then we use `Array()`

and `map()`

to generate a range of coordinates along the line of sight, which is returned.

As an example, if the size of the forest is 10 by 10 and we call `lineOfSight()`

at the coordinates `[5,5]`

with the direction “up”, we would get…

[[4, 5], [3, 5], [2, 5], [1, 5], [0, 5]]

…which is a string of coordinates from `[5,5]`

to the top edge of the forest.

Once we are able to get the line of sight coordinates like this, we next need to determine whether the line of sight is blocked by a taller tree, and if so, how many trees are visible along the line of sight.

To do this, we can implement a function called `look()`

, which takes a point in the forest, and a direction to move in as arguments, and returns two properties: `blocked`

and `distance`

.

// Look in a direction from a given point to see // if it's blocked and how many trees you can see const look = ( row: number, col: number, direction: Direction ) => { const sight = lineOfSight(row, col, direction); const tree = FOREST[row][col]; const blockedAt = sight.findIndex(([ii, jj]) => FOREST[ii][jj] >= tree); const blocked = blockedAt !== -1; const distance = blocked ? blockedAt + 1 : sight.length; return { blocked, distance }; }

The key things we do are:

- Use
`lineOfSight()`

to determine the string of tree coordinates in the specified direction to the edge of the forest. - Fetch the height of the tree at the current point from
`FOREST`

, and use`findIndex()`

to determine the index of the first tree along the line of sight that is taller than the current tree. If there is no such tree, the`findIndex()`

method will return`-1`

. - Set the
`blocked`

property to`true`

if`findIndex()`

returns`-1`

. - Set the
`distance`

property to the number of trees up to the blocking tree*including the blocking tree*, if it is`blocked`

, or the total number of trees along the line of sight, if it is not`blocked`

.

The last bit of preparation is to determine the coordinates of every tree in the forest, which will make it easy to iterate over the trees.

// The coordinates of every tree in the forest const EVERY_TREE = Array(ROWS) .fill(0) .flatMap((_, row) => Array(COLS) .fill(0) .map((_, col) => [row, col]));

We use `Array()`

and `map()`

to generate a 2D array of coordinates for every point in the forest, and then use `flatMap()`

to flatten the array into a single dimension.

Now that we have the coordinates of every tree in the forest, we can use them to survey the whole forest and determine the line of sight distances for each tree.

// Survey the whole forest by looking around // at every tree coordinate const SURVEY = EVERY_TREE .map(([row, col]) => ({ up: look(row, col, 'up'), left: look(row, col, 'left'), right: look(row, col, 'right'), down: look(row, col, 'down'), }));

To solve part 1, we need to determine the number of trees that are visible from the edge of the forest. This can be done by iterating over `SURVEY`

, and counting the number of trees that have at least one direction in which the line of sight is not blocked by a taller tree.

// Count the trees that are visible in some direction const part1 = SURVEY .filter(({ up, left, right, down }) => !up.blocked || !left.blocked || !right.blocked || !down.blocked ).length;

To solve part 2, we need to determine the score for each tree in the forest, and return the highest score. This can be done by iterating over `SURVEY`

, and for each tree, calculating the product of its line of sight distances in each direction. We can use `Math.max()`

to determine the highest product, which is the solution.

// Score the trees by the distance in every direction // and find the highest score. const part2 = Math.max( ...SURVEY.map(({ up, left, right, down }) => up.distance * left.distance * right.distance * down.distance ) );

## Final Solution

// The forest of tree heights const FOREST = puzzleInput .split('\n') .map(row => row .split('') .map(n => parseInt(n))) // Some measurements of the forest size const ROWS = FOREST.length const LAST_ROW = ROWS - 1; const COLS = FOREST[0].length; const LAST_COL = COLS - 1; type Direction = 'up' | 'down' | 'left' | 'right'; // Gets a string of coordinates in a given direction // and from a particular point, to the edge const lineOfSight = ( row: number, col: number, direction: Direction ) => { switch (direction) { case 'up': return Array(row) .fill(row) .map((x, y) => [x-y-1, col]); case 'down': return Array(LAST_ROW - row) .fill(row) .map((x, y) => [x+y+1, col]); case 'left': return Array(col) .fill(col) .map((x, y) => [row, x-y-1]); case 'right': return Array(LAST_COL - col) .fill(col) .map((x, y) => [row, x+y+1]); } } // Look in a direction from a given point to see // if it's blocked and how many trees you can see const look = ( row: number, col: number, direction: Direction ) => { const sight = lineOfSight(row, col, direction); const tree = FOREST[row][col]; const blockedAt = sight.findIndex(([ii, jj]) => FOREST[ii][jj] >= tree); const blocked = blockedAt !== -1; const distance = blocked ? blockedAt + 1 : sight.length; return { blocked, distance }; } // The coordinates of every tree in the forest const EVERY_TREE = Array(ROWS) .fill(0) .flatMap((_, row) => Array(COLS) .fill(0) .map((_, col) => [row, col])); // Survey the whole forest by looking around // at every tree coordinate const SURVEY = EVERY_TREE .map(([row, col]) => ({ up: look(row, col, 'up'), left: look(row, col, 'left'), right: look(row, col, 'right'), down: look(row, col, 'down'), })); // Count the trees that are visible in some direction const part1 = SURVEY .filter(({ up, left, right, down }) => !up.blocked || !left.blocked || !right.blocked || !down.blocked ).length; // Score the trees by the distance in every direction // and find the highest score. const part2 = Math.max( ...SURVEY.map(({ up, left, right, down }) => up.distance * left.distance * right.distance * down.distance ) ); console.log("Part 1:", part1); console.log("Part 2:", part2);

Part 1: 1543 Part 2: 595080