Worst Practice

Advent of Code - Day 8

Posted on December  8, 2022 @ 10:45

Posted under the Backend category

Level: beginner

Posted with the following tags: #Advent, #PHP

It is winter, we have to do some lumber job. But first we need to find the right trees.

Advent of Code - Day 8
Calendar icon by Kevin Sanderson from Pixabay

Today’s puzzle in short: we have a 99 x 99 matrix filled with numbers. We need to check neighbor elements (top, right, bottom, left) and find the right value according to the task’s criteria

The input data

It’s a simple file filled with numbers:

  • 99 rows
  • 99 decimals in a row (0 through 9)

Game rules

The matrix is a forest. Each number represents the height of a tree:

  • 0 is a very short tree
  • 9 is a very tall tree

Our task is to return values defined by the tasks.

Common code

Like yesterday, we have some common code that is the same in both tasks. Namely, to read the input file and create the matrix. So let’s do it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$forest = [];

if ($fn = fopen(__DIR__ . '/input.txt', 'r')) {
    while (($line = fgets($fn, 1024)) !== false) {
        $line = trim($line);

        $forest[] = str_split($line);

    }
    fclose($fn);
}

$rows = count($forest);
$columns = count($forest[0]);

Although we know the input contains 99 rows and columns, we prepare for changes.

Part one

We need to find, how many trees are visible from outside the forest:

  • trees on the edges are visible by default
  • an inner tree is visible only when there are smaller trees in any of the four directions

For this task we have a big help: the trees on the edges are automatically visible, so we can do a fast calculation (2 times the rows and 2 times the columns, minus four for the four corners which we counted twice), and save time and energy on the for loops. Then just iterate through the “inner” matrix and do the math:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
$allVisibleTrees = (2 * $rows) + (2 * $columns) - 4;

for ($i = 1; $i < $rows - 1; $i++) {
    for ($j = 1; $j < $columns - 1; $j++) {
        if (isInnerTreeVisible($i, $j, $forest)) {
            $allVisibleTrees++;
        }
    }
}

function isInnerTreeVisible(int $row, int $column, array $matrix): bool
{
    $rowNumber = count($matrix);
    $columnNumber = count($matrix[0]);
    $actualHeight = (int) $matrix[$row][$column];
    $topHighest = 0;
    $rightHighest = 0;
    $bottomHighest = 0;
    $leftHighest = 0;

    // top
    for ($i = $row - 1; $i >= 0; $i--) {
        $topHighest = max($topHighest, $matrix[$i][$column]);
    }

    // right
    for ($i = $column + 1; $i < $columnNumber; $i++) {
        $rightHighest = max($rightHighest, $matrix[$row][$i]);
    }

    // bottom
    for ($i = $row + 1; $i < $rowNumber; $i++) {
        $bottomHighest = max($bottomHighest, $matrix[$i][$column]);
    }

    // left
    for ($i = $column - 1; $i >= 0; $i--) {
        $leftHighest = max($leftHighest, $matrix[$row][$i]);
    }
    
    return $actualHeight > $topHighest
        || $actualHeight > $rightHighest
        || $actualHeight > $bottomHighest;
        || $actualHeight > $leftHighest
}

echo $allVisibleTrees.PHP_EOL;

Well, it’s not pretty, and deal with four for loops in a function that is called within a for loop which is itself in another for loop is anything but fast. But it’s not a business code, just a local script, so I can bravely focus on the goal.

Part two

Now, we need to invert the logic and check for each tree, how far can you see from the top of the given tree:

  • If the next tree in any direction is the same height or higher than the current tree, we stop counting the distance, that is the furthest tree.
  • Trees on the edges have at least one direction where there are no more trees, so those distances will be zero.
  • We have to calculate the “scenic score” of every tree: multiply the view distance of each direction.

Again, we can save a lot of time and energy by skipping the trees on the edges, because their scenic score will be zero by the rules.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
$highestScenicScore = 0;

for ($i = 1; $i < $rows - 1; $i++) {
    for ($j = 1; $j < $columns - 1; $j++) {
        $highestScenicScore = max($highestScenicScore, getTreeScenicScore($i, $j, $forest));
    }
}

function getTreeScenicScore(int $row, int $column, array $matrix): int
{
    $rowNumber = count($matrix);
    $columnNumber = count($matrix[0]);

    $actualHeight = $matrix[$row][$column];

    $scoreTop = 0;
    $scoreRight = 0;
    $scoreBottom = 0;
    $scoreLeft = 0;

    // top
    for ($i = $row - 1; $i >= 0; $i--) {
        $scoreTop++;

        if ($matrix[$i][$column] >= $actualHeight) {
            break;
        }
    }

    // right
    for ($i = $column + 1; $i < $columnNumber; $i++) {
        $scoreRight++;

        if ($matrix[$row][$i] >= $actualHeight) {
            break;
        }
    }

    // bottom
    for ($i = $row + 1; $i < $rowNumber; $i++) {
        $scoreBottom++;

        if ($matrix[$i][$column] >= $actualHeight) {
            break;
        }
    }

    // left
    for ($i = $column - 1; $i >= 0; $i--) {
        $scoreLeft++;

        if ($matrix[$row][$i] >= $actualHeight) {
            break;
        }
    }

    return $scoreTop * $scoreRight * $scoreBottom * $scoreLeft;
}

echo $highestScenicScore.PHP_EOL;
Gábor Iván