11 votes

Day 8: Treetop Tree House

Today's problem description: https://adventofcode.com/2022/day/8

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
Your code here.
```

</details>

13 comments

  1. [4]
    tjf
    Link
    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()
    
    3 votes
    1. [3]
      Gyrfalcon
      Link Parent
      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]
        tjf
        Link Parent
        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.

        2 votes
        1. Gyrfalcon
          Link Parent
          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
    Link
    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 votes
  3. whispersilk
    Link
    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;
    use std::io::Read;
    
    fn main() -> Result<(), Box<dyn Error>> {
        let mut file = File::open("input_8.txt")?;
        let mut contents = String::new();
        file.read_to_string(&mut contents)?;
        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;
    use std::io::Read;
    
    fn main() -> Result<(), Box<dyn Error>> {
        let mut file = File::open("input_8.txt")?;
        let mut contents = String::new();
        file.read_to_string(&mut contents)?;
        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 })
    }
    
    2 votes
  4. jzimbel
    (edited )
    Link
    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}
    
      @adjacency_deltas_by_type %{
        all: @all_adjacent_deltas,
        cardinal: @cardinal_adjacent_deltas,
        intercardinal: @intercardinal_adjacent_deltas
      }
    
      @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
        @adjacency_deltas_by_type[adjacency_type]
        |> 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
        |> lines_of_cells(coords, adjacency_type)
        |> 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
        |> lines_of_cells(coords, adjacency_type)
        |> 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
      alias AdventOfCode.CharGrid
    
      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
    
    2 votes
  5. spit-evil-olive-tips
    Link
    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:
        contents = input_file.read()
    
    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:
         contents = input_file.read()
    @@ -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)
    
    2 votes
  6. wycy
    Link
    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::io::{self, prelude::*, BufReader};
    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<()> {
        let file = File::open(input).expect("Input file not found.");
        let reader = BufReader::new(file);
    
        // 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();
    }
    
    2 votes
  7. Gyrfalcon
    Link
    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]:
    
        unprocessed_grid = load_file.load_cleaned_lines(filepath)
        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)
                )
            ),
        )
    
    2 votes
  8. Crestwave
    Link
    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)
    }
    
    2 votes
  9. bhrgunatha
    Link
    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 (read-forest 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 (read-forest 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))
    
    2 votes
  10. MeckiSpaghetti
    Link
    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
          .read("input.txt")
          .split
          .map{ |x| x.chars.map(&:to_i) }
    
    p (0...$map.size*$map.first.size)
        .count { |i| is_visible?(i%$map.first.size, i/$map.size) }
    
    1 vote