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

Day 10: Pipe Maze – Advent of Code 2023

2023/10.livemd

Day 10: Pipe Maze – Advent of Code 2023

Mix.install([
  {:topo, "~> 1.0"}
])

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

test_input = ".....\n.S-7.\n.|.|.\n.L-J.\n....." |> String.split("\n")

test_input2 = "..F7.\n.FJ|.\nSJ.L7\n|F--J\nLJ..." |> String.split("\n")

Parsing

find_neighbors_old = fn tile, x, y ->
  from_south = [{x, y + 1}, :south_north, :north_east, :north_west]
  from_north = [{x, y - 1}, :south_north, :south_east, :south_west]
  to_east = [{x - 1, y}, :east_west, :north_east, :south_east]
  to_west = [{x + 1, y}, :east_west, :north_west, :south_west]

  case tile do
    "|" -> [from_north, from_south]
    "-" -> [to_east, to_west]
    "L" -> [from_south, to_west]
    "J" -> [from_south, to_east]
    "7" -> [from_north, to_east]
    "F" -> [from_north, to_west]
    "." -> []
    "S" -> [to_east, to_west, from_north, from_south]
    _ -> raise "oh no"
  end
end

n = fn {x, y} -> [{x, y - 1}, "|", "7", "F"] end
e = fn {x, y} -> [{x + 1, y}, "-", "7", "J"] end
s = fn {x, y} -> [{x, y + 1}, "|", "J", "L"] end
w = fn {x, y} -> [{x - 1, y}, "-", "F", "L"] end

find_neighbors = fn
  _, "." -> []
  p, "S" -> [n.(p), e.(p), s.(p), w.(p)]
  p, "|" -> [n.(p), s.(p)]
  p, "-" -> [w.(p), e.(p)]
  p, "L" -> [n.(p), e.(p)]
  p, "J" -> [n.(p), w.(p)]
  p, "7" -> [w.(p), s.(p)]
  p, "F" -> [e.(p), s.(p)]
end

add_neighbors = fn map ->
  map
  |> Enum.reduce(Map.new(), fn {{x, y}, tile}, new_map ->
    neighbors =
      find_neighbors.({x, y}, tile)
      |> Enum.map(fn [pos | tiles] ->
        if Enum.member?(["S" | tiles], Map.get(map, pos)) do
          pos
        else
          nil
        end
      end)
      |> Enum.reject(&(&1 == nil))

    Map.put(new_map, {x, y}, {tile, neighbors})
  end)
end

parse = fn input ->
  input
  |> Stream.with_index()
  |> Stream.flat_map(fn {line, y} ->
    String.graphemes(line)
    |> Stream.with_index()
    |> Stream.map(fn {c, x} -> {{x, y}, c} end)
  end)
  |> then(fn lst -> Map.new(lst) end)
  |> add_neighbors.()
  |> then(fn map ->
    {map, map |> Enum.find(fn {_k, {v, _n}} -> v == "S" end) |> elem(0)}
  end)
end

# parse.(test_input2)

Part One

visit_neighbours = fn {map, start_pos} ->
  {_, [next, _]} = Map.get(map, start_pos)

  {start_pos, next}
  |> Stream.iterate(fn {prev, curr} ->
    {_, [n1, n2]} = Map.get(map, curr)
    {curr, if(n1 == prev, do: n2, else: n1)}
  end)
  |> Stream.map(&elem(&1, 1))
  |> Enum.take_while(&(&1 != start_pos))
end

part_one = fn input ->
  input
  |> parse.()
  |> visit_neighbours.()
  |> length
  |> Kernel.+(1)
  |> div(2)
end

part_one.(test_input) == 4 &&
  part_one.(test_input2) == 8 &&
  part_one.(input)

Part Two

# version two : works (but with external dependency)
{map, s} = input |> parse.()

neighbours = {map, s} |> visit_neighbours.()

polygon = %Geo.Polygon{coordinates: [neighbours]}

map
|> Map.keys()
|> Stream.reject(&Enum.member?(neighbours, &1))
|> Stream.map(&Topo.contains?(polygon, &1))
|> Stream.filter(& &1)
|> Enum.count()

New Livebook