summaryrefslogblamecommitdiffstats
path: root/src/day14.exs
blob: 84353f6bf5036d207d7962d2636f77e064d2e9f1 (plain) (tree)














































































































                                                                         
defmodule Day14 do
  defmodule Board do
    @derive Inspect
    defstruct [:w, :h, :robots]

    def from_data(data, w, h) do
      robots = data
      |> String.split("\n", trim: true)
      |> Enum.map(fn line ->
        Regex.scan(~r/-?\d+/, line)
        |> List.flatten()
        |> Enum.map(&String.to_integer/1)
        |> List.to_tuple()
      end)

      %Board{w: w, h: h, robots: robots}
    end

    def move(board, moves) do
      board.robots
      |> Enum.map(fn {x, y, vx, vy} ->
        {
          posrem(x+moves*vx, board.w),
          posrem(y+moves*vy, board.h)
        }
      end)
    end

    defp posrem(n, m) do
      rem(rem(n, m)+m, m)
    end

    def print(board, pos) do
      pos = MapSet.new(pos)
      for i <- 0..board.w-1 do
        for j <- 0..board.h-1 do
          if MapSet.member?(pos, {j, i}) do
            "X"
          else
            "."
          end
        end
      end
      |> Enum.map(&List.to_string/1)
      |> Enum.join("\n")
      |> IO.puts()
    end
  end

  def part1(data) do
    board = Board.from_data(data, 101, 103)

    Board.move(board, 100)
    |> Enum.map(fn {x, y} ->
      cond do
        x < div(board.w, 2) and y < div(board.h, 2) ->
          {1, 0, 0, 0}
        x < div(board.w, 2) and y > div(board.h, 2) ->
          {0, 1, 0, 0}
        x > div(board.w, 2) and y < div(board.h, 2) ->
          {0, 0, 1, 0}
        x > div(board.w, 2) and y > div(board.h, 2) ->
          {0, 0, 0, 1}
        true ->
          {0, 0, 0, 0}
      end
    end)
    |> Enum.reduce({0, 0, 0, 0}, fn {i1, i2, i3, i4}, {q1, q2, q3, q4} ->
      {q1+i1, q2+i2, q3+i3, q4+i4}
    end)
    |> then(fn {q1, q2, q3, q4} -> q1*q2*q3*q4 end)
  end

  def part2(data) do
    board = Board.from_data(data, 101, 103)

    Stream.map(0..10000, fn i ->
      pos = Board.move(board, i)

      most = pos
      |> Enum.group_by(&elem(&1, 0), &(elem(&1, 1)))
      |> Enum.map(&Enum.sort(elem(&1, 1)))
      |> Enum.map(fn ys ->
        Enum.zip(ys, tl(ys))
        |> Enum.map(fn {a, b} -> b-a end)
      end)
      |> List.flatten()
      |> Enum.frequencies()
      |> Enum.max_by(&elem(&1, 1))

      if elem(most, 0) == 1 and elem(most, 1) > 100 do
        dbg({i, most})
        Board.print(board, pos)
        i
      end
    end)
    |> Stream.filter(& &1 != nil)
    |> Enum.take(1)
    |> hd()
  end
end

data = IO.read(:stdio, :eof)

{time1, ans1} = :timer.tc(fn -> Day14.part1(data) end)
IO.puts("Time  : #{time1 / 1000000}")
IO.puts("Answer: #{ans1}")

{time2, ans2} = :timer.tc(fn -> Day14.part2(data) end)
IO.puts("Time  : #{time2 / 1000000}")
IO.puts("Answer: #{ans2}")