DEV Community

Discussion on: AoC Day 4: Repose Record

Collapse
 
yordiverkroost profile image
Yordi Verkroost

Didn't really have much time to do the challenges today, but yeah, whenever you did the first three days it's hard to skip one.

So here are my solutions in Elixir. Some function returns are a bit messy, but it does what it needs to do. The basic idea is that all lines are first parsed into a map with guards, where each guard is the key for another map containing the minutes and the number of times the guard was asleep during that minute.

Having that basic map makes the calculations for both strategies relatively easy.

Common:

defmodule AoC.DayFour.Common do
  @line_regex ~r/\[(.*)\] (.*)/
  @date_format "{YYYY}-{M}-{D} {h24}:{m}"

  def read_input(path) do
    path
    |> File.stream!()
    |> Stream.map(&String.trim_trailing/1)
    |> Enum.to_list()
    |> Enum.sort()
    |> Enum.map(fn x ->
      [datetime, log] = Regex.run(@line_regex, x, capture: :all_but_first)
      {:ok, datetime} = Timex.parse(datetime, @date_format)
      %{datetime: datetime, log: log}
    end)
  end

  def calculate_guard_minutes(input, map \\ %{}, current_guard \\ "", last_minute \\ 0)

  def calculate_guard_minutes([line | rest], map, current_guard, last_minute) do
    case line.log do
      "falls asleep" ->
        calculate_guard_minutes(rest, map, current_guard, line.datetime.minute)

      "wakes up" ->
        map = Map.put_new(map, current_guard, %{})
        guard = Map.get(map, current_guard)

        guard =
          Enum.reduce(last_minute..(line.datetime.minute - 1), guard, fn x, acc ->
            Map.update(acc, x, 1, &(&1 + 1))
          end)

        map = Map.put(map, current_guard, guard)
        calculate_guard_minutes(rest, map, current_guard, line.datetime.minute)

      _ ->
        [new_guard] =
          Regex.run(~r/^Guard #(\d+) begins shift$/, line.log, capture: :all_but_first)

        calculate_guard_minutes(rest, map, new_guard, 0)
    end
  end

  def calculate_guard_minutes([], map, _current_guard, _last_minute) do
    map
  end
end

Part one:

defmodule AoC.DayFour.PartOne do
  alias AoC.DayFour.Common

  def main() do
    "lib/day4/input.txt"
    |> Common.read_input()
    |> Common.calculate_guard_minutes()
    |> get_sleepiest_guard()
    |> get_sleepiest_minute()
    |> calculate_result()
  end

  defp calculate_result({guard, minute}) do
    guard * minute
  end

  defp get_sleepiest_minute(map) do
    {_, {guard, minutes}} = map

    {sleepiest_minute, _} =
      # Format: {guard, minutes_slept}
      Enum.reduce(minutes, {0, 0}, fn {current_minute, count}, {highest_minute, highest_count} ->
        if count > highest_count,
          do: {current_minute, count},
          else: {highest_minute, highest_count}
      end)

    {String.to_integer(guard), sleepiest_minute}
  end

  defp get_sleepiest_guard(map) do
    # Format: {minutes_slept, {guard, minutes}}
    Enum.reduce(map, {0, {0, %{}}}, fn {guard, minutes}, sleepiest_guard ->
      minutes_slept = Enum.reduce(minutes, 0, fn {_, count}, acc -> acc + count end)
      {current_sleep_minutes, _} = sleepiest_guard

      if minutes_slept > current_sleep_minutes,
        do: {minutes_slept, {guard, minutes}},
        else: sleepiest_guard
    end)
  end
end

Part two:

defmodule AoC.DayFour.PartTwo do
  alias AoC.DayFour.Common

  def main() do
    "lib/day4/input.txt"
    |> Common.read_input()
    |> Common.calculate_guard_minutes()
    |> get_sleepiest_guard()
    |> calculate_result()
  end

  defp calculate_result({guard, minute}) do
    guard * minute
  end

  defp get_sleepiest_guard(map) do
    # Format: {guard, minute, count}
    {guard, minute, _} =
      Enum.reduce(map, {0, 0, 0}, fn {guard, minutes}, sleepiest_guard ->
        # Format: {minute, count}
        {minute, count} =
          Enum.reduce(minutes, {0, 0}, fn {minute, count}, {current_minute, highest_count} ->
            if count > highest_count, do: {minute, count}, else: {current_minute, highest_count}
          end)

        {_, _, current_count} = sleepiest_guard

        if count > current_count,
          do: {String.to_integer(guard), minute, count},
          else: sleepiest_guard
      end)

    {guard, minute}
  end
end