Sean McLemon | Advent of Code

Home | Czech | Blog | GitHub | Advent Of Code | Notes


2022-12-08 - Treetop Tree House

(original .ipynb)

Day 8 puzzle input is a grid of numbers representing tree heights (mine is here). Part 1 involves finding whether any tree is "visible" (taller than all trees either above, below, left or right of it). Part 2 involves calculating a "scenic score" based on the product of the number of trees smaller than it in all directions.

puzzle_input_str = open("./puzzle_input/day8.txt").read()

test_input_str = """30373
25512
65332
33549
35390"""


def visible_horizontal(grid, r, c):
    if c == 0 or c == len(grid[r])-1:
        return True
    
    left  = all(grid[r][c] > tree for tree in grid[r][:c])
    right = all(grid[r][c] > tree for tree in grid[r][1+c:])
    
    return right or left


def visible_vertical(grid, r, c):
    if r == 0 or r == len(grid)-1:
        return True    
        
    up   = all(grid[r][c] > grid[xr][c] for xr in range(r))
    down = all(grid[r][c] > grid[xr][c] for xr in range(r+1, len(grid)))
    
    return up or down


def part_one(input_str):
    grid = [
        [int(s) for s in line]
        for line
        in input_str.splitlines()
    ]

    return sum(
        sum(1 for c, col in enumerate(row) if visible_horizontal(grid, r, c) or visible_vertical(grid, r, c))
        for r, row in enumerate(grid)
    )


assert 21 == part_one(test_input_str)

print("part one:", part_one(puzzle_input_str))
part one: 1698
from functools import reduce

def trees_above(grid, r, c):
    count = 0
    tree = grid[r][c]
    r -= 1
    while r >= 0:
        count += 1
        if tree <= grid[r][c]:
            break
        r -= 1
    return count


def trees_below(grid, r, c):
    count = 0
    tree = grid[r][c]
    r += 1
    while r < len(grid):
        count += 1
        if tree <= grid[r][c]:
            break
        r += 1
    return count


def trees_left(grid, r, c):
    count = 0
    tree = grid[r][c]
    c -= 1
    while c >= 0:
        count += 1
        if tree <= grid[r][c]:
            break
        c -= 1
    return count


def trees_right(grid, r, c):
    count = 0
    tree = grid[r][c]
    c += 1
    while c < len(grid[0]):
        count += 1
        if tree <= grid[r][c]:
            break
        c += 1
    return count


def scenic_score(grid, r, c):
    return reduce(lambda acc, s: acc*s, [f(grid, r, c) for f in [trees_above, trees_below, trees_left, trees_right]])


def get_all_scores(grid):
     for r, row in enumerate(grid):
        for c, col in enumerate(row):
            yield scenic_score(grid, r, c)


def part_two(input_str):
    grid = [
        [int(s) for s in line]
        for line
        in input_str.splitlines()
    ]
    
    return max(get_all_scores(grid))


assert 8 == part_two(test_input_str)

print("part two:", part_two(puzzle_input_str))
part two: 672280
# Have I found a CPython bug!? I tightened up my implementation but weirdly
# the next_c/next_r calls never change. Need to come back to this one.

from functools import reduce

def count_trees(grid, r, c, next_r, next_c, not_done):
    count = 0
    tree = grid[r][c]
    
    # note: this always prints the below, even though I use different next_c/next_r funcs
    #    next_r(10) 10
    #    next_c(10) 11
    print("next_r(10)", next_r(10))
    print("next_c(10)", next_c(10))
    
    r = next_r(r)
    c = next_c(c)
    while not_done(grid, r, c):
        count += 1
        if tree <= grid[r][c]:
            break
        r = next_r(r)
        c = next_c(c)
    
    return count   

directions = [
    (lambda c: c,   lambda r: r-1, lambda g,r,c: r >= 0),
    (lambda c: c,   lambda r: r+1, lambda g,r,c: r < len(g)),
    (lambda c: c-1, lambda r: r,   lambda g,r,c: c >= 0),
    (lambda c: c+1, lambda r: r,   lambda g,r,c: c < len(g[0])),    
]

all_tree_ops = [
    lambda grid, r, c: count_trees(grid, r, c, next_c, next_r, not_done)
    for next_c, next_r, not_done
    in directions
]
    
def get_all_scores(grid):
     for r, row in enumerate(grid):
        for c, col in enumerate(row):
            yield reduce(lambda acc,s: acc*s, [f(grid, r, c) for f in all_tree_ops])
    
def part_two(input_str):
    grid = [
        [int(s) for s in line]
        for line
        in input_str.splitlines()
    ]
    
    return max(get_all_scores(grid))
    

assert 8 == part_two(test_input_str)

print("part two:", part_two(puzzle_input_str))
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10
next_r(10) 11
next_c(10) 10