Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Day 14: Parabolic Reflector Dish – Advent of Code 2023

2023/14.livemd

Day 14: Parabolic Reflector Dish – Advent of Code 2023

input =
  File.stream!("/Users/pw/src/weiland/adventofcode/2023/input/14.txt")
  |> Stream.map(&String.trim/1)

test_input =
  "O....#....
O.OO#....#
.....##...
OO.#O....O
.O.....O#.
O.#..O.#.#
..O..#O..O
.......O..
#....###..
#OO..#...." |> String.split("\n")

Parsing

parse = fn input ->
  input
  |> Enum.map(&String.graphemes(&1))
end

parse.(test_input)

Part One

shift = fn lines ->
  lines
  |> Enum.map(fn line ->
    line
    |> Enum.chunk_by(&(&1 == "#"))
    |> Enum.flat_map(&Enum.sort(&1, :desc))
  end)
end

count = fn lines ->
  lines
  |> Enum.map(fn line ->
    line
    |> Enum.reverse()
    |> Enum.with_index(1)
    |> Enum.reverse()
    |> Enum.filter(fn {curr, _i} -> curr == "O" end)
    |> Enum.map(&elem(&1, 1))
    |> Enum.reduce(0, &+/2)
  end)
end

part_one = fn input ->
  input
  |> parse.()
  # default -> north
  # transpose
  |> Enum.zip_with(&Function.identity/1)
  # moves 0 to front
  |> shift.()
  # count
  |> count.()
  |> Enum.sum()
end

part_one.(test_input) == 136 &&
  part_one.(input)

Part Two

cycle = fn lines ->
  lines
  # west (start) -> transpose to north
  |> Enum.zip_with(&Function.identity/1)
  # moves 0 to front
  |> shift.()
  # transpose back to west
  |> Enum.zip_with(&Function.identity/1)
  |> shift.()
  # transpose to north
  |> Enum.zip_with(&Function.identity/1)
  # to south
  |> Enum.map(&Enum.reverse/1)
  |> shift.()
  # back to north
  |> Enum.map(&Enum.reverse/1)
  # transpose to west (begin)
  |> Enum.zip_with(&Function.identity/1)
  # to east
  |> Enum.map(&Enum.reverse/1)
  |> shift.()
  # back to west (begin)
  |> Enum.map(&Enum.reverse/1)
end

n_times = 1_000_000_000

part_two = fn input ->
  lines =
    input
    |> parse.()

  configs =
    1..n_times
    |> Enum.reduce_while([lines, Map.new()], fn index, [lns, map] ->
      res = lns |> cycle.()

      {_, has} = Map.get(map, res, {nil, nil})

      cond do
        has == nil -> {:cont, [res, Map.put(map, res, {index, 1})]}
        has == 1 -> {:cont, [res, Map.put(map, res, {index, 2})]}
        true -> {:halt, map}
      end
    end)

  repeats =
    configs
    |> Enum.filter(fn {_k, {_i, v}} -> v == 2 end)
    |> Enum.sort(fn {_k, {a, _v}}, {_k2, {b, _v2}} -> a < b end)
    |> Enum.map(fn {k, _count} -> k end)

  offset = Enum.count(configs) - Enum.count(repeats)
  index = rem(n_times - offset, Enum.count(repeats)) - 1

  Enum.at(repeats, index) |> Enum.zip_with(&amp;Function.identity/1) |> count.() |> Enum.sum()
end

# &&
part_two.(test_input) == 64
part_two.(input)