Powered by AppSignal & Oban Pro

Day 5

2021/day05.livemd

Day 5

defmodule Day5 do
  defmodule Point do
    defstruct [:x, :y]

    def parse(input) do
      [x, y] = String.split(input, ",")

      %__MODULE__{x: String.to_integer(x), y: String.to_integer(y)}
    end
  end

  defmodule Line do
    defstruct [:start, :finish]

    def new(a, b) do
      {start, finish} =
        cond do
          a.x < b.x -> {a, b}
          a.y < b.y -> {a, b}
          true -> {b, a}
        end

      %__MODULE__{start: start, finish: finish}
    end

    def horizontal?(a), do: a.start.y == a.finish.y
    def vertical?(a), do: a.start.x == a.finish.x

    def points(a) do
      case {sign(a.finish.x - a.start.x), sign(a.finish.y - a.start.y)} do
        {0, dy} -> for y <- a.start.y..a.finish.y//dy, do: {a.start.x, y}
        {dx, 0} -> for x <- a.start.x..a.finish.x//dx, do: {x, a.start.y}
        {dx, dy} -> Enum.zip(a.start.x..a.finish.x//dx, a.start.y..a.finish.y//dy)
      end
    end

    def orientation(a) do
      cond do
        horizontal?(a) -> :horizontal
        vertical?(a) -> :vertical
        true -> :diagonal
      end
    end

    defp sign(0), do: 0
    defp sign(x) when x < 0, do: -1
    defp sign(x) when x > 0, do: 1
  end
end

lines =
  File.stream!("day5.txt")
  |> Stream.map(&String.trim/1)
  |> Stream.map(fn input ->
    [a, b] = String.split(input, " -> ")

    pa = Day5.Point.parse(a)
    pb = Day5.Point.parse(b)

    Day5.Line.new(pa, pb)
  end)
#Stream<[
  enum: %File.Stream{
    line_or_bytes: :line,
    modes: [:raw, :read_ahead, :binary],
    path: "day5.txt",
    raw: true
  },
  funs: [#Function<47.58486609/1 in Stream.map/2>, #Function<47.58486609/1 in Stream.map/2>]
]>

Task 1

lines
|> Stream.filter(&(Day5.Line.orientation(&1) != :diagonal))
|> Stream.flat_map(&Day5.Line.points/1)
|> Enum.frequencies()
|> Enum.count(fn {_k, v} -> v > 1 end)
5197

Task 2

lines
|> Stream.flat_map(&Day5.Line.points/1)
|> Enum.frequencies()
|> Enum.count(fn {_k, v} -> v > 1 end)
18605