Day 8

The input looks like this:

30373
25512
65332
33549
35390

The problem is (succintely) seeing if numbers up, down, left and right of a given tree are smaller than the tree at a given position, and then marking that position and counting a total of marks.

My solution for part one was approaching this as boolean mask, where I just needed to transform the orientation of the input array to adjust to the orientation (from up, from down, from left and from right), then apply a single function that checks for each element if the elements that precede it in the row are smaller.

The input array and function to check it from a certain orientation looked like this:

import numpy as np
with open("input.txt") as file:
            input = file.readlines()
input = [line.strip('\n') for line in input]

digits_input = [list(x) for x in input]
input_array = np.array(digits_input, dtype=int)

def check_array(input_array):
    mask = []
    for row in input_array:
        row_mask = []
        for i in range(len(row)):
            if i == 0:
                row_mask.append(True)
            else:
                posterior = row[:i]
                if all(row[i] > x for x in posterior):
                    row_mask.append(True)
                else:
                    row_mask.append(False*(len(row)-i-1))
        mask.append(row_mask)
    
    mask = np.array(mask,dtype=bool)
    return mask

And the actual transformations for the array to be checked like this:

# set true condition for all elements in edges
edge_mask =  np.zeros_like(input_array, dtype=bool)
edge_mask[0] = True
edge_mask[-1] = True
edge_mask[:,0] = True
edge_mask[:,-1] = True

# left to right
lr_mask = check_array(input_array)

# right to left
rl_array = np.flip(input_array, axis = 1)
rl_mask = check_array(rl_array) 
rl_mask =  np.flip(rl_mask, axis = 1)

# up to down
ud_array = input_array.T
ud_mask = check_array(ud_array)
ud_mask = ud_mask.T

# down to up
du_array = np.flip(input_array.T, axis = 1)
du_mask = check_array(du_array)
du_mask = np.flip(du_mask.T, axis = 0)

# merge arrays:
mask = np.logical_or(lr_mask, edge_mask)
mask = np.logical_or(mask, rl_mask)
mask = np.logical_or(mask, lr_mask)
mask = np.logical_or(mask, ud_mask)
mask = np.logical_or(mask, du_mask)

print("solution 1:", mask.sum())

Not too hard, actually - took me an embarrasing amount of time to come up with this solution, as I was making a function for each orientation and it took a bit of rethinking to make it less complicated.

Part 2 requires that you calculate a scenic score by multiplying the number of numbers that are set to True in the mask: you have to get the highest score. After some fiddling, it ended up like this:

def check_conseqs(tree, list_input):
    """
    checks how many of the trees preceding, following, up or down 
    are bigger than a given tree
    """
    smaller_than_input = []
    # control for edge
    if len(list_input) == 0:
        return 1
    for adj_tree in list_input:
        if tree > adj_tree:
            smaller_than_input.append(tree)
        else:
            return len(smaller_than_input)+1
    return len(smaller_than_input)

def check_score(arr):
    """
    iterates over all trees and calculates the score
    """
    grid_score = []
    for row in range(len(arr)):
        row_score = []
        for tree in range(len(arr[row])):
            following = arr[row][tree:][1:]
            score = check_conseqs(arr[row][tree], following)
            row_score.append(score)

        grid_score.append(row_score)
    scores = np.array(grid_score)
    return scores

#looking right        
lr_scenery = check_score(input_array)

# looking left
rl_scenery = np.flip(check_score(rl_array), axis = 1)

# looking down
ud_scenery = check_score(ud_array)
ud_scenery = ud_scenery.T

# looking up 
du_scenery = check_score(du_array)
du_scenery = np.flip(du_scenery.T, axis = 0)

# sets edges at 0...
edge_mask =  np.ones(input_array.shape, dtype = np.int8)
edge_mask[0] = 0
edge_mask[-1] = 0
edge_mask[:,0] = 0
edge_mask[:,-1] = 0

final_scenery_score = du_scenery*rl_scenery*ud_scenery*lr_scenery*edge_mask
print("solution 2:", np.max(final_scenery_score))

Very similar approach (one function for all calculations, just rotating the input, iterating over rows to create a new array with given scores). I’m sure there was a more efficient way to do this, but well, I’m ok with the “naive” solution.