Advent of Code 2022: Day 8

The basic datastructure for this problem is a 2d grid of tree heights: these are read from a file as consecutive rows of strings.

Icon lets us index into a list and strings using the same notation, so forest[1][2] would mean the second character in the first string of the forest list.

Unicon-only: this representation is easily read in using a list comprehension:

  forest := [: !file :]

The items returned by the generator are used to construct the list.

The other Icon point worth mentioning is the to operator:

Output:

> unicon -s test.icn -x
1
2
3
4
5
6
7
8
9
10
10
8
6
4
2

Otherwise, the program to solve the problem is constrained by the logic of following the lines of sight along the four axes. Separate procedures are written to manage this logic along the x or y dimensions, separately.

Final Program

procedure main(inputs)
  local filename

  if *inputs = 1 then {
    filename := inputs[1]

    # Part 1 solution
    write("Part 1: ", part_1(filename))
    # Part 2 solution
    write("Part 2: ", part_2(filename))
  } else {
    write("Provide a filename of data")
  }
end

procedure part_1(filename)
  local forest, num_visible_trees
  local i, j

  forest := read_dataset(filename)
  num_visible_trees := 0

  every i := 1 to *forest do
    every j := 1 to *forest[i] do
      if is_visible(forest, i, j) then
        num_visible_trees +:= 1

  return num_visible_trees
end

procedure part_2(filename)
  local forest, current_scenic_score, most_scenic
  local i, j

  forest := read_dataset(filename)
  most_scenic := 0

  every i := 2 to *forest-1 do {
    every j := 2 to *forest[i]-1 do {
      current_scenic_score := find_scenic_score(forest, i, j)
      if current_scenic_score > most_scenic then
        most_scenic := current_scenic_score
    }
  }
  return most_scenic
end

# Read the given dataset in, return list of strings
procedure read_dataset(filename)
  local file, forest

  file := open(filename) | stop ("Cannot open file")
  forest := list()
  every put(forest, !file)
  close(file)

  return forest
end

# Succeeds if tree at [x, y] is visible, else fails.
# Tree is visible if height larger in one of cardinal directions, or on edge
procedure is_visible(forest, x, y)
  return x = 1 |
    y = 1 |
    x = *forest |
    y = *forest[x] |
    is_visible_x(forest, x, y, 1, x-1) |
    is_visible_x(forest, x, y, x+1, *forest) |
    is_visible_y(forest, x, y, 1, y-1) |
    is_visible_y(forest, x, y, y+1, *forest[x])
end

procedure is_visible_x(forest, x, y, start_x, end_x)
  local highest_tree, x_side

  highest_tree := 0
  every x_side := start_x to end_x do
    if forest[x_side][y] > highest_tree then
      highest_tree := forest[x_side][y]

  return highest_tree < forest[x][y]
end

procedure is_visible_y(forest, x, y, start_y, end_y)
  local highest_tree, y_side

  highest_tree := 0
  every y_side := start_y to end_y do
    if forest[x][y_side] > highest_tree then
      highest_tree := forest[x][y_side]

  return highest_tree < forest[x][y]
end

# Scenic score is product of visible distances in each direction
procedure find_scenic_score(forest, x, y)
  return scenic_score_x(forest, x, y, 1, -1) *
    scenic_score_x(forest, x, y, *forest, 1) *
    scenic_score_y(forest, x, y, 1, -1) *
    scenic_score_y(forest, x, y, *forest[x], 1)
end

procedure scenic_score_x(forest, x, y, end_x, offset)
  local score, try_x

  if x = end_x then return 1
  score := 0
  every try_x := x+offset to end_x by offset do {
    score +:= 1
    if forest[try_x][y] >= forest[x][y] then return score
  }
  return score
end

procedure scenic_score_y(forest, x, y, end_y, offset)
  local score, try_y

  if y = end_y then return 1
  score := 0
  every try_y := y+offset to end_y by offset do {
    score +:= 1
    if forest[x][try_y] >= forest[x][y] then return score
  }
  return score
end

Page from Peter's Scrapbook, output from a VimWiki on 2024-01-29.