# Day 8: Treetop Tree House

Please post your solutions in your own top-level comment. Here's a template you can copy-paste into your comment to format it nicely, with the code collapsed by default inside an expandable section with syntax highlighting (you can replace `python` with any of the "short names" listed in this page of supported languages):

``````<details>
<summary>Part 1</summary>

```python
```

</details>
``````

1. [4]
deckard
My Python solutions, which are surely not optimal but they do the trick. Between yesterday and today, the problems are starting to get fun! Part 1 #!/usr/bin/env pypy3 import sys up = lambda f, r,...

My Python solutions, which are surely not optimal but they do the
trick. Between yesterday and today, the problems are starting to get fun!

Part 1
``````#!/usr/bin/env pypy3

import sys

up = lambda f, r, c: [f[r_][c] for r_ in range(r)][::-1]
dn = lambda f, r, c: [f[r_][c] for r_ in range(r + 1, len(f))]
lt = lambda f, r, c: [f[r][c_] for c_ in range(c)][::-1]
rt = lambda f, r, c: [f[r][c_] for c_ in range(c + 1, len(f[r]))]

def main():
forest = [[int(tree) for tree in line.strip()] for line in sys.stdin]
total = 0
for r in range(len(forest)):
for c in range(len(forest[0])):
total += any(forest[r][c] > max(d(forest, r, c), default=-1)
for d in (up, dn, lt, rt))

print(total)

if __name__ == '__main__':
main()
``````
Part 2
``````#!/usr/bin/env pypy3

import functools
import operator
import sys

up = lambda f, r, c: [f[r_][c] for r_ in range(r)][::-1]
dn = lambda f, r, c: [f[r_][c] for r_ in range(r + 1, len(f))]
lt = lambda f, r, c: [f[r][c_] for c_ in range(c)][::-1]
rt = lambda f, r, c: [f[r][c_] for c_ in range(c + 1, len(f[r]))]

def view(tree, direction):
distance = 0
for neighbor in direction:
distance += 1
if neighbor >= tree:
break
return distance

def main():
forest = [[int(tree) for tree in line.strip()] for line in sys.stdin]
max_score = 0
for r in range(len(forest)):
for c in range(len(forest[0])):
views = (view(forest[r][c], d(forest, r, c))
for d in (up, dn, lt, rt))
max_score = max(functools.reduce(operator.mul, views, 1),
max_score)

print(max_score)

if __name__ == '__main__':
main()
``````
1. [3]
Gyrfalcon
Could you share more about how you arrived at this solution? You allege that it is not optimal, which I can't say one way or the other, but I do find it elegant and concise - and something I never...

Could you share more about how you arrived at this solution? You allege that it is not optimal, which I can't say one way or the other, but I do find it elegant and concise - and something I never would have come up with even if I had days to work on it.

1 vote
1. [2]
deckard
For every tree in the forest, I make a list of the trees above it, below it, to the left, and to the right. For part 1 I check if the tree of interest is taller than the tallest tree in at least...

For every tree in the forest, I make a list of the trees above it, below it, to the left, and to the right. For part 1 I check if the tree of interest is taller than the tallest tree in at least one of those four lists. If it is, add it to a tally of "visible" trees. For part 2 I count how far into each of those four lists I have to go before the tree of interest is no longer sufficiently tall. Multiply the four directional counts to get a score and check if it is the largest such score we've seen.

I say it's not optimal because it probably does a lot of redundant computation. There is no caching of e.g. how many trees of a certain height exist in a particular direction. The visibility of a tree and and adjacent neighbor are going to be somewhat similar, but I ignore this and calculate each's visibility from scratch.

1. Gyrfalcon
Okay, I think I see the main difference in how we approached the problem. You looked at even the first part from the tree perspective, whereas I looked at that from a row and column wise...

Okay, I think I see the main difference in how we approached the problem. You looked at even the first part from the tree perspective, whereas I looked at that from a row and column wise perspective. Even with that I still think I would have made a bunch of loops rather than mapping things into lambdas, but I think that's more an issue of not actually spending that much time in Python and not really having a good handle on functional approaches than anything else.

1 vote
2. asterisk
I had problem with electricity (almost all day) and work. But I still made it in this day, yahoo! Python grid = [[int(x) for x in y.strip()] for y in open("input.txt").readlines()] onview = [[0...

I had problem with electricity (almost all day) and work. But I still made it in this day, yahoo!

Python
``````grid = [[int(x) for x in y.strip()] for y in open("input.txt").readlines()]
onview = [[0 for _ in range(len(grid[0]))] for _ in range(len(grid))]

def measure(m):
return len(m[0]), len(m)

def rotate(m):
row, col = measure(m)

for i in range(row // 2):
m[i], m[col - i - 1] = m[col - i - 1], m[i]

for i in range(row):
for j in range(i):
m[i][j], m[j][i] = m[j][i], m[i][j]

return m

for side in range(4):
i, j = measure(grid)

for y in range(i):
tallest = grid[y][0]
onview[y][0] = 1

for x in range(j):
if grid[y][x] > tallest:
tallest = grid[y][x]
onview[y][x] = 1
elif grid[y][x] == 9:
break
else:
continue
if side == 4:
break
grid = rotate(grid)
onview = rotate(onview)

print(sum(map(sum, onview)))  # Part One: 1543
``````
``````grid = [[int(x) for x in y.strip()] for y in open("input.txt").readlines()]
hor, ver = len(grid[0]), len(grid)
scenes = [[1 for _ in range(hor)] for _ in range(ver)]

def view(opt: int, length: int, route: int = 1) -> int:
tallest: int = grid[y][x]
trees: int = 0
m = list(zip(*grid) if opt else grid)
i, j = (x, y) if opt else (y, x)

for step in range(1, length):
trees += 1

if tallest <= m[i][j + step * route]:
break

return trees

for y in range(hor):
for x in range(ver):
for opts in [(0, ver - x), (0, x + 1, -1), (1, hor - y), (1, y + 1, -1)]:
scenes[y][x] *= view(*opts)

print(max(map(max, scenes)))  # Part Two: 595080
``````

Only one sad thing: I made it as two tasks, not combined. Maybe later I will try to make it as one.

3. whispersilk
I'm using Rust this year, and trying to keep it std-only throughout the month. Like yesterday I didn't know how to avoid allocation on this one, but at least it's all up front today! Part 1 use...

I'm using Rust this year, and trying to keep it `std`-only throughout the month. Like yesterday I didn't know how to avoid allocation on this one, but at least it's all up front today!

Part 1
``````use std::error::Error;
use std::fs::File;

fn main() -> Result<(), Box<dyn Error>> {
let mut file = File::open("input_8.txt")?;
let mut contents = String::new();
let map: Vec<Vec<char>> = contents.lines().map(|l| l.chars().collect()).collect();
let visible_count = (0..map.len()).fold(0, |acc, h| {
acc + (0..map[h].len()).filter(|w| visible(&map, h, *w)).count()
});
println!("{visible_count}");
Ok(())
}

fn visible(map: &Vec<Vec<char>>, h: usize, w: usize) -> bool {
let (height, tree) = (map.len(), map[h][w]);
let from_left = map[h].iter().take(w).all(|c| c < &tree);
let from_right = map[h].iter().skip(w + 1).all(|c| c < &tree);
let from_top = (0..h).all(|h| map[h][w] < tree);
let from_bottom = ((h + 1)..height).all(|h| map[h][w] < tree);
from_left || from_right || from_top || from_bottom
}
``````
Part 2
``````use std::error::Error;
use std::fs::File;

fn main() -> Result<(), Box<dyn Error>> {
let mut file = File::open("input_8.txt")?;
let mut contents = String::new();
let map: Vec<Vec<char>> = contents.lines().map(|l| l.chars().collect()).collect();
let max_view_score = (0..map.len())
.map(|h| (0..map[h].len()).map(|w| view_score(&map, h, w)).max())
.max()
.unwrap()
.unwrap();
println!("{max_view_score}");
Ok(())
}

fn view_score(map: &Vec<Vec<char>>, h: usize, w: usize) -> usize {
let (height, width, tree) = (map.len(), map[0].len(), map[h][w]);
let lview = (0..w).rev().take_while(|w| map[h][*w] < tree).count();
let rview = ((w + 1)..width).take_while(|w| map[h][*w] < tree).count();
let tview = (0..h).rev().take_while(|h| map[*h][w] < tree).count();
let bview = ((h + 1)..height).take_while(|h| map[*h][w] < tree).count();
(lview + if lview == w { 0 } else { 1 })
* (rview + if rview == width - 1 - w { 0 } else { 1 })
* (tview + if tview == h { 0 } else { 1 })
* (bview + if bview == height - 1 - h { 0 } else { 1 })
}
``````
4. jzimbel
(edited )
Elixir Since these kinds of character-grid based puzzle inputs are so common, I created a CharGrid module in previous years that simplifies parsing and lookups. I only needed to add one new...

Elixir

Since these kinds of character-grid based puzzle inputs are so common, I created a `CharGrid` module in previous years that simplifies parsing and lookups. I only needed to add one new `lines_of_cells/3` function (plus a couple related convenience functions) for this puzzleâ€”you give it a set of target coordinates, and it returns a list of lists of the cells radiating out from that location in each direction.

After that, I just needed to write code that counts the number of cells that satisfy the conditions for each puzzle part.

`CharGrid` module

Only including the relevant parts for this puzzle.

``````defmodule AdventOfCode.CharGrid do
@moduledoc "Data structure representing a finite grid of characters by a map of {x, y} => char"

alias __MODULE__, as: T

@type t :: %T{
grid: grid,
width: non_neg_integer,
height: non_neg_integer
}

@typep grid :: %{coordinates => char}

@type coordinates :: {non_neg_integer, non_neg_integer}

@type cell :: {coordinates, char}

@enforce_keys ~w[grid width height]a
defstruct @enforce_keys

# List of coords that produce the 8 coordinates surrounding a given coord when added to it
@all_adjacent_deltas for x <- -1..1, y <- -1..1, not (x == 0 and y == 0), do: {x, y}

# List of coords that produce the 4 coordinates horizontally/vertically adjacent to a given coord when added to it
@cardinal_adjacent_deltas for x <- -1..1, y <- -1..1, abs(x) + abs(y) == 1, do: {x, y}

# List of coords that produce the 4 coordinates diagonally adjacent to a given coord when added to it
@intercardinal_adjacent_deltas for x <- -1..1, y <- -1..1, abs(x) + abs(y) == 2, do: {x, y}

}

@spec from_input(String.t()) :: t()
def from_input(input) do
charlists =
input
|> String.split()
|> Enum.map(&String.to_charlist/1)

height = length(charlists)
width = length(hd(charlists))

grid =
for {line, y} <- Enum.with_index(charlists),
{char, x} <- Enum.with_index(line),
into: %{} do
{{x, y}, char}
end

%T{
grid: grid,
width: width,
height: height
}
end

@doc "Gets the value at the given coordinates."
@spec at(t(), coordinates) :: char | nil
def at(%T{} = t, coords) do
t.grid[coords]
end

@doc """
Returns a list of lists of cells in lines from the one at `coords`.
Each list starts with the cell nearest `coords`, and radiates outward.

The type of adjacency is determined by the third argument:

- `:all` (default behavior):
```
O.O.O
.OOO.
OO*OO
.OOO.
O.O.O
```
- `:cardinal`:
```
..O..
..O..
OO*OO
..O..
..O..
```
- `:intercardinal`:
```
O...O
.O.O.
..*..
.O.O.
O...O
```
"""
@spec lines_of_cells(t(), coordinates, adjacency_type) :: list(list(cell))
def lines_of_cells(%T{} = t, coords, adjacency_type \\ :all) do
|> Enum.map(&get_line_of_cells(t, &1, sum_coordinates(coords, &1)))
end

@doc """
Convenience function that behaves the same as `lines_of_cells/3`,
but returns only the char value of each cell.
"""
@spec lines_of_values(t(), coordinates, adjacency_type) :: list(list(char))
def lines_of_values(%T{} = t, coords, adjacency_type \\ :all) do
t
|> Enum.map(fn line -> Enum.map(line, &elem(&1, 1)) end)
end

@doc """
Convenience function that behaves the same as `lines_of_cells/3`,
but returns only the coordinates of each cell.
"""
@spec lines_of_coords(t(), coordinates, adjacency_type) :: list(list(coordinates))
def lines_of_coords(%T{} = t, coords, adjacency_type \\ :all) do
t
|> Enum.map(fn line -> Enum.map(line, &elem(&1, 0)) end)
end

defp get_line_of_cells(t, step, coords) do
case at(t, coords) do
nil -> []
val -> [{coords, val} | get_line_of_cells(t, step, sum_coordinates(coords, step))]
end
end

defp sum_coordinates({x1, y1}, {x2, y2}), do: {x1 + x2, y1 + y2}
end
``````
Solution

Edit: Made the solution cleaner (but slightly slower) by removing code that ignores the grid perimeter.

``````defmodule AdventOfCode.Solution.Year2022.Day08 do

def part1(input) do
input
|> parse_sight_lines_by_tree()
|> Enum.count(&visible?/1)
end

def part2(input) do
input
|> parse_sight_lines_by_tree()
|> Enum.map(&scenic_score/1)
|> Enum.max()
end

defp parse_sight_lines_by_tree(input) do
grid = CharGrid.from_input(input)

grid
|> CharGrid.to_list()
|> Enum.map(fn {coords, tree} ->
{tree, CharGrid.lines_of_values(grid, coords, :cardinal)}
end)
end

defp visible?({tree, sight_lines}) do
Enum.any?(sight_lines, fn other_trees ->
Enum.all?(other_trees, &(&1 < tree))
end)
end

defp scenic_score({tree, sight_lines}) do
sight_lines
|> Enum.map(&count_visible(&1, tree))
|> Enum.product()
end

# A little tricky because we want to include the first tree that blocks our view.
# All of the relevant `Enum` functions omit the first element that fails the condition,
# so I needed to define my own recursive function.
defp count_visible([], _), do: 0
defp count_visible([tree | _trees], from_tree) when tree >= from_tree, do: 1
defp count_visible([_tree | trees], from_tree), do: 1 + count_visible(trees, from_tree)
end
``````
5. spit-evil-olive-tips
part 1 I use a dict of (x, y) coordinates as a multi-dimensional array. this is a trick I remember from last year - if I have a grid of something and I'm considering each cell's neighbors, Python...
part 1

I use a dict of `(x, y)` coordinates as a multi-dimensional array. this is a trick I remember from last year - if I have a grid of something and I'm considering each cell's neighbors, Python makes it very easy to implement "wrap-around" behavior, because `index - 1` when index is 0 will give me the other end of the array. when I want "fall off the edge" behavior, such as here, `-1` being a valid index is an annoyance. by using a dict with `(row, col)` tuples as keys, I can get proper fall-off-the-edge behavior from the dict throwing a `KeyError` when I try to access outside the grid.

from there, I iterate each row and each column, backwards and forwards, and keep track of the maximum height in each of the 4 cardinal directions.

and then, the tree is visible from the outside if its height is larger than the view in any direction

``````from dataclasses import dataclass, field

@dataclass
class Tree:
height: int
views: dict = field(default_factory=dict)

class Grid:
def __init__(self, trees, height, width):
self.trees = trees
self.height = height
self.width = width

@staticmethod
def parse(input_str):
trees = dict()

for row, line in enumerate(input_str.split('\n')):
for col, char in enumerate(line):
height = int(char)
trees[(row, col)] = Tree(height=height)

return Grid(trees, row + 1, col + 1)

def calculate_views(self):
# north
delta_row, delta_col = (-1, 0)
for row in range(self.height):
for col in range(self.width):
self.calculate_view(row, col, delta_row, delta_col)

# south
delta_row, delta_col = (1, 0)
for row in reversed(range(self.height)):
for col in range(self.width):
self.calculate_view(row, col, delta_row, delta_col)

# west
delta_row, delta_col = (0, -1)
for col in range(self.width):
for row in range(self.height):
self.calculate_view(row, col, delta_row, delta_col)

# east
delta_row, delta_col = (0, 1)
for col in reversed(range(self.width)):
for row in range(self.height):
self.calculate_view(row, col, delta_row, delta_col)

def calculate_view(self, row, col, delta_row, delta_col):
tree = self.trees[(row, col)]
try:
neighbor = self.trees[(row + delta_row, col + delta_col)]
view = max(neighbor.height, neighbor.views[(delta_row, delta_col)])
except KeyError:
view = -1

tree.views[(delta_row, delta_col)] = view

with open('08.txt') as input_file:

grid = Grid.parse(contents)
grid.calculate_views()

visible_trees = 0
for location, tree in grid.trees.items():
visible = any(tree.height > view for view in tree.views.values())
if visible:
visible_trees += 1

print(visible_trees)
``````
part 2

part 2 was frustrating. what I kept trying to do was keep track of the actual number of trees visible, as I go, but I couldn't make it work quite right.

what I settled on is that my `view` list contains the heights of all trees in that direction. then I feed that through the `count_visible_trees` function to extract only the trees that are visible in order to calculate the scenic score.

eg, looking at the `9` in the bottom row of the example input, looking north its neighbors are `[4, 3, 1, 7]` but the only visible trees are `[4, 7]`.

``````--- aoc08a.py   2022-12-08 14:38:16.232131655 -0800
+++ aoc08b.py   2022-12-08 14:39:17.393847716 -0800
@@ -53,12 +53,23 @@
tree = self.trees[(row, col)]
try:
neighbor = self.trees[(row + delta_row, col + delta_col)]
-            view = max(neighbor.height, neighbor.views[(delta_row, delta_col)])
+            view = [neighbor.height] + neighbor.views[(delta_row, delta_col)]
except KeyError:
-            view = -1
+            view = []

tree.views[(delta_row, delta_col)] = view

+def count_visible_trees(height, view):
+    count = 0
+    current = height
+    for tree in view:
+        count += 1
+        if tree >= current:
+            return count
+
+        current = max(current, tree)
+
+    return count

with open('08.txt') as input_file:
@@ -66,10 +77,19 @@
grid = Grid.parse(contents)
grid.calculate_views()

-visible_trees = 0
+most_scenic_location = None
+most_scenic_score = 0
+
for location, tree in grid.trees.items():
-    visible = any(tree.height > view for view in tree.views.values())
-    if visible:
-        visible_trees += 1
+    scenic_score = 1
+    for view in tree.views.values():
+       scenic_score *= count_visible_trees(tree.height, view)
+
+    if scenic_score > most_scenic_score:
+       most_scenic_score = scenic_score
+       most_scenic_location = location
+

-print(visible_trees)
+print(most_scenic_location)
+print(most_scenic_score)
``````
6. wycy
Rust I'm not very happy with my code today. Feels like I violated DRY a lot. Ah well. Rust use std::env; use std::io::{self, prelude::*, BufReader}; use std::fs::File; const DIM: usize = 99; //...

Rust

I'm not very happy with my code today. Feels like I violated DRY a lot. Ah well.

Rust
``````use std::env;
use std::fs::File;

const DIM: usize = 99; // Sample=5

fn visible_trees(tree_map: &Vec<Vec<u32>>) -> usize {
let mut num_visible = 0;
for y in 0..DIM {
'srch: for x in 0..DIM {
// Handle edges
if x == 0 || y == 0 || x == DIM-1 || y == DIM-1 { num_visible += 1; continue 'srch; }
let v = tree_map[x][y];
// Scan -y
let mut visible = true;
'ym: for y0 in 0..y {
if tree_map[x][y0] >= v { visible = false; break 'ym; }
}
if visible { num_visible += 1; continue 'srch; }
// Scan +y
let mut visible = true;
'yp: for y0 in y+1..DIM {
if tree_map[x][y0] >= v { visible = false; break 'yp; }
}
if visible { num_visible += 1; continue 'srch; }
// Scan -x
let mut visible = true;
'xm: for x0 in 0..x {
if tree_map[x0][y] >= v { visible = false; break 'xm; }
}
if visible { num_visible += 1; continue 'srch; }
// Scan +x
let mut visible = true;
'xp: for x0 in x+1..DIM {
if tree_map[x0][y] >= v { visible = false; break 'xp; }
}
if visible { num_visible += 1; continue 'srch; }
}
}
num_visible
}

fn scenic_score(tree_map: &Vec<Vec<u32>>) -> usize {
let mut max_scenic = 0;
for y in 0..DIM {
'srch: for x in 0..DIM {
// Handle edges
if x == 0 || y == 0 || x == DIM-1 || y == DIM-1 { continue 'srch; } // Zero scenic
let v = tree_map[x][y];
// Scan -y
let mut dist_ym = 0;
'ym: for y0 in (0..y).rev() {
if tree_map[x][y0] <= v { dist_ym += 1; }
if tree_map[x][y0] >= v { break 'ym; }
}
// Scan +y
let mut dist_yp = 0;
'yp: for y0 in y+1..DIM {
if tree_map[x][y0] <= v { dist_yp += 1; }
if tree_map[x][y0] >= v { break 'yp; }
}
// Scan -x
let mut dist_xm = 0;
'xm: for x0 in (0..x).rev() {
if tree_map[x0][y] <= v { dist_xm += 1; }
if tree_map[x0][y] >= v { break 'xm; }
}
// Scan +x
let mut dist_xp = 0;
'xp: for x0 in x+1..DIM {
if tree_map[x0][y] <= v { dist_xp += 1; }
if tree_map[x0][y] >= v { break 'xp; }
}
max_scenic = std::cmp::max(max_scenic, dist_ym * dist_yp * dist_xp * dist_xm);
}
}
max_scenic
}

fn solve(input: &str) -> io::Result<()> {

// Input
let input: Vec<String> = match reader.lines().collect() {
Err(err) => panic!("Unknown error reading input: {}", err),
Ok(result) => result,
};

// Build tree map
let mut tree_map = vec![vec![0; DIM]; DIM];
for (y,line) in input.iter().enumerate() {
for (x,c) in line.chars().enumerate() {
tree_map[x][y] = c.to_digit(10).unwrap();
}
}

// Outputs
println!("Part 1: {}",visible_trees(&tree_map)); // 1693
println!("Part 2: {}",scenic_score(&tree_map)); // 422059

Ok(())
}

fn main() {
let args: Vec<String> = env::args().collect();
let filename = &args[1];
solve(&filename).unwrap();
}
``````
7. Gyrfalcon
Well I certainly repeated myself in this code, and Python's "don't modify while iterating" concept is not one I am fond of, but it works. Part 1 and 2, Python Tree = namedtuple("Tree", ["height",...

Well I certainly repeated myself in this code, and Python's "don't modify while iterating" concept is not one I am fond of, but it works.

Part 1 and 2, Python
``````Tree = namedtuple("Tree", ["height", "visible"])

def process_grid(lines: List[str]) -> List[List[Tree]]:

return [[Tree(int(height), False) for height in line] for line in lines]

def determine_visible(grid: List[List[Tree]]):

# Left to right
for row_idx in range(len(grid)):
max_height: int = -1
for col_idx in range(len(grid[row_idx])):
if grid[row_idx][col_idx].height > max_height:
max_height = grid[row_idx][col_idx].height
grid[row_idx][col_idx] = grid[row_idx][col_idx]._replace(visible=True)

# Right to left
for row_idx in range(len(grid)):
max_height = -1
for col_idx in range(len(grid[row_idx]) - 1, -1, -1):
if grid[row_idx][col_idx].height > max_height:
max_height = grid[row_idx][col_idx].height
grid[row_idx][col_idx] = grid[row_idx][col_idx]._replace(visible=True)

# top to bottom
for row_idx in range(len(grid)):
max_height = -1
for col_idx in range(len(grid[row_idx])):
if grid[col_idx][row_idx].height > max_height:
max_height = grid[col_idx][row_idx].height
grid[col_idx][row_idx] = grid[col_idx][row_idx]._replace(visible=True)

# bottom to top
for row_idx in range(len(grid)):
max_height = -1
for col_idx in range(len(grid[row_idx]) - 1, -1, -1):
if grid[col_idx][row_idx].height > max_height:
max_height = grid[col_idx][row_idx].height
grid[col_idx][row_idx] = grid[col_idx][row_idx]._replace(visible=True)

def viewing_score(grid: List[List[Tree]], row: int, col: int) -> int:

curr_tree = grid[row][col]

up = 0
for row_idx in range(row - 1, -1, -1):
if grid[row_idx][col].height >= curr_tree.height:
up += 1
break
up += 1

down = 0
for row_idx in range(row + 1, len(grid)):
if grid[row_idx][col].height >= curr_tree.height:
down += 1
break
down += 1

left = 0
for col_idx in range(col - 1, -1, -1):
if grid[row][col_idx].height >= curr_tree.height:
left += 1
break
left += 1

right = 0
for col_idx in range(col + 1, len(grid[row])):
if grid[row][col_idx].height >= curr_tree.height:
right += 1
break
right += 1

return up * down * left * right

def main(filepath: str) -> Tuple[int, int]:

grid = process_grid(unprocessed_grid)
determine_visible(grid)

return (
sum((tree.visible for row in grid for tree in row)),
max(
(
viewing_score(grid, row_idx, col_idx)
for row_idx, row in enumerate(grid)
for col_idx, tree in enumerate(row)
)
),
)
``````
8. Crestwave
Not particularly elegant but it works. :P Part 1 #!/usr/bin/awk -f BEGIN { FS = "" } { for (x = 1; x <= NF; ++x) grid[x, NR] = \$x } END { X = length(\$0) Y = NR for (x = 1; x <= X; ++x) { i = -1...

Not particularly elegant but it works. :P

Part 1
``````#!/usr/bin/awk -f
BEGIN { FS = "" }

{
for (x = 1; x <= NF; ++x)
grid[x, NR] = \$x
}

END {
X = length(\$0)
Y = NR

for (x = 1; x <= X; ++x) {
i = -1
for (y = 1; y <= Y; ++y) {
if (grid[x, y] < i)
continue
if (grid[x, y] > i)
visible[x, y] = 1
i = grid[x, y]
}
}

for (x = 1; x <= X; ++x) {
i = -1
for (y = Y; y >= 1; --y) {
if (grid[x, y] < i)
continue
if (grid[x, y] > i)
visible[x, y] = 1
i = grid[x, y]
}
}

for (y = 1; y <= Y; ++y) {
i = -1
for (x = 1; x <= X; ++x) {
if (grid[x, y] < i)
continue
if (grid[x, y] > i)
visible[x, y] = 1
i = grid[x, y]
}
}

for (y = 1; y <= Y; ++y) {
i = -1
for (x = X; x >= 1; --x) {
if (grid[x, y] < i)
continue
if (grid[x, y] > i)
visible[x, y] = 1
i = grid[x, y]
}
}

for (i in visible)
sum += 1

print(sum)
}
``````
Part 2
``````#!/usr/bin/awk -f
function sight(x, y, _x, _y, X, Y,	i, sum) {
i = grid[x, y]
sum = 0

do {
x += _x
do {
y += _y
if (grid[x, y] != "") {
sum += 1
if (grid[x, y] >= i)
return sum
}
} while (y != Y)
} while (x != X)

return sum
}

BEGIN { FS = "" }

{
for (x = 1; x <= NF; ++x)
grid[x, NR] = \$x
}

END {
X = length(\$0)
Y = NR

for (x = 1; x <= X; ++x)
for (y = 1; y <= Y; ++y) {
score = sight(x, y, 0, -1, x, 1-1) * \
sight(x, y, 0, +1, x, Y+1) * \
sight(x, y, -1, 0, 1-1, y) * \
sight(x, y, +1, 0, X+1, y)

if (score > max)
max = score
}

print(max)
}
``````
9. bhrgunatha
Horrendously slow because I was absolutely convinced that I should count visible trees between the treehouse and the final blocking tree. Of course the sample data gives the same answer. when the...

Horrendously slow because I was absolutely convinced that I should count visible trees between the treehouse and the final blocking tree. Of course the sample data gives the same answer.
when the data are too large to deal with by eyeballing. Even harder when you are looking for answers using the wrong question. Also, horrendously verbose code :(

Utilities

My forest is a nested list.

``````(define (read-forest input)
(for/list ([l (in-list input)])
(map char->digit (string->list l))))

(define (char->digit c)
(- (char->integer c) 48))
``````
Part 1
• Visibile trees increase monotonically.
• Iterate through a list of trees, keeping a note of the tallest tree so far.
• Each tree taller than the tallest is marked visible - #t, noting the new tallest tree.
• Horizontal visibility
• Trees visible from the left are monotonically increasing (described above)
• Trees visible from the right are identical after reversing the list.
• Combine those using logical OR
• Vertical visibility is identical using a transposed forest.
• Combine horizontal and vertical visibility again with logical OR

Old lispers know a neat to tranpose nested lists: `(apply map list xs))`
Happy to explain it if anyone asks.

``````(define (part-01 input)
(define d* (transpose d))
(define left/right (map visibility d))
(define up/down (transpose (map visibility d*)))
(for/sum ([row (in-list left/right)]
[col (in-list up/down)])
(count identity (combine-visibile row col))))

(define (transpose xs)
(apply map list xs))

;; #t for each tree visible (from either end)
(define (visibility trees)
(combine-visibile (reverse (monotonic trees))
(monotonic (reverse trees))))

(define (combine-visibile a b)
(for/list ([l (in-list a)]
[r (in-list b)])
(or l r)))

;; #t for each value in the list that is monotonically increasing
;;  1  2  5  3  0  6
;; #t #t #t #f #f #t
(define (monotonic trees)
(for/fold ([visible null]
[tallest -1]
#:result visible)
([t (in-list trees)])
(cond [(> t tallest) (values (cons #t visible) t)]
[else (values (cons #f visible) tallest)])))
``````
Part 2

Part 2 is a similar approach, tranposing the forest, but it was easier to find the viewing distance by converting to a vector.

``````(define (part-02 input)
(define d* (transpose d))
(define left/right (map scenic-score d))
(define up/down (transpose (map scenic-score d*)))
(for/fold ([hut 0])
([row (in-list left/right)]
[col (in-list up/down)])
(apply max hut (map * row col))))

(define (scenic-score trees)
(define v (list->vector trees))
(define left  (viewing-distances v look-left))
(define right (viewing-distances v look-right))
(map * left right))

(define (viewing-distances trees f)
(for/fold ([counts null]
#:result (reverse counts))
([i (in-range (vector-length trees))])
(cons (f trees i) counts)))

(define (look-right trees i) (scan-with trees i add1 (vector-length tv)))
(define (look-left  tress i) (scan-with trees i sub1 -1))

(define (scan-with trees i next stop)
(define (scan hut j total)
(cond [(= j stop) total]
[(>= (vector-ref tv j) hut) (add1 total)]
[else (scan hut (next j) (add1 total))]))
(scan (vector-ref trees i) (next i) 0))
``````
10. MeckiSpaghetti
Ruby Part 1 def is_visible?(x, y) tree = \$map[y][x] up = (0...y).map{ |i| \$map[i][x] } right = (x+1...\$map.first.size).map{ |i| \$map[y][i] } down = (y+1...\$map.size).map{ |i| \$map[i][x] } left =...

Ruby

Part 1
``````def is_visible?(x, y)

tree = \$map[y][x]
up    = (0...y).map{ |i| \$map[i][x] }
right = (x+1...\$map.first.size).map{ |i| \$map[y][i] }
down  = (y+1...\$map.size).map{ |i| \$map[i][x] }
left  = (0...x).map{ |i| \$map[y][i] }

up.all?{ |e| e < tree } ||
right.all?{ |e| e < tree } ||
down.all?{ |e| e < tree } ||
left.all?{ |e| e < tree } ||
x == 0 || y == 0 ||
x == \$map.first.size-1 ||
y == \$map.size-1
end

\$map = File