defmodule DayFour do def decode_all(input) do partial = input |> String.split("\n") |> Enum.filter(&(&1 != "")) |> Enum.map(&DayFour.decode_log/1) |> Enum.sort_by(fn {_, _, year, month, day, hour, minute} -> year * 100_000_000 + month * 100_000 + day * 10000 + hour * 100 + minute end) DayFour.map_to_guard(partial, 0) # |> Enum.map(fn {action, guard, _, _, _, _, minute} -> {action, guard, minute} end) end def decode_log(log) do pattern = ~r/\[(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})\] (wakes|falls|Guard) #?(\d{0,})/ [[_ | match]] = Regex.scan(pattern, log) decode_entry(match) end def decode_entry([year, month, day, hour, minute, "Guard", number]) do {:guard, String.to_integer(number), String.to_integer(year), String.to_integer(month), String.to_integer(day), String.to_integer(hour), String.to_integer(minute)} end def decode_entry([year, month, day, hour, minute, "wakes", _]) do {:wake, nil, String.to_integer(year), String.to_integer(month), String.to_integer(day), String.to_integer(hour), String.to_integer(minute)} end def decode_entry([year, month, day, hour, minute, "falls", _]) do {:sleep, nil, String.to_integer(year), String.to_integer(month), String.to_integer(day), String.to_integer(hour), String.to_integer(minute)} end def map_to_guard([{:guard, id, year, month, day, hour, minute} | tail], _) do now = {:guard, id, year, month, day, hour, minute} [now | map_to_guard(tail, id)] end def map_to_guard([now | tail], current) do {action, _, year, month, day, hour, minute} = now [{action, current, year, month, day, hour, minute} | map_to_guard(tail, current)] end def map_to_guard([], _), do: [] def part_one(input) do minutes = Enum.zip(0..60, Enum.map(0..60, fn _ -> 0 end)) |> Map.new() sleeping_by_guard = decode_all(input) |> Enum.group_by(fn {_, id, _, _, _, _, _} -> id end) |> Enum.map(fn {id, list} -> {id, Enum.sort_by(list, fn {_, _, year, month, day, hour, minute} -> year * 100_000_000 + month * 100_000 + day * 10000 + hour * 100 + minute end)} end) |> Enum.map(fn {id, list} -> {id, calc_asleep_times(list, 60, minutes)} end) time_by_guard = sleeping_by_guard |> Enum.map(fn {id, times} -> {id, Map.to_list(times)} end) |> Enum.sort_by(fn {_, times} -> List.foldl(times, 0, fn {_, c}, acc -> c + acc end) end) |> Enum.reverse() [longest_sleeper | _] = time_by_guard {id, times} = longest_sleeper minutes = times |> Enum.sort_by(fn {_, time} -> time end) |> Enum.reverse() [{longest_minute, _} | _] = minutes "(id: #{id}, minute: #{longest_minute})" end def calc_asleep_times([{:guard, _, _, _, _, _, _} | tail], 60, acc) do calc_asleep_times(tail, 60, acc) end def calc_asleep_times([{:guard, _, _, _, _, _, _} | tail], last, acc) do times = Enum.to_list(last..60) calc_asleep_times(tail, 60, increment_map(times, acc)) end def calc_asleep_times([{:sleep, _, _, _, _, _, minute} | tail], _, acc) do calc_asleep_times(tail, minute, acc) end def calc_asleep_times([{:wake, _, _, _, _, _, minute} | tail], last, acc) do times = Enum.to_list(last..minute) calc_asleep_times(tail, 60, increment_map(times, acc)) end def calc_asleep_times([], 60, acc), do: acc def calc_asleep_times([], last, acc), do: increment_map(Enum.to_list(last..60), acc) def increment_map([head | tail], acc) do increment_map(tail, Map.update!(acc, head, &(&1 + 1))) end def increment_map([], acc), do: acc def part_two(input) do minutes = Enum.zip(0..60, Enum.map(0..60, fn _ -> 0 end)) |> Map.new() sleeping_by_guard = decode_all(input) |> Enum.group_by(fn {_, id, _, _, _, _, _} -> id end) |> Enum.map(fn {id, list} -> {id, Enum.sort_by(list, fn {_, _, year, month, day, hour, minute} -> year * 100_000_000 + month * 100_000 + day * 10000 + hour * 100 + minute end)} end) |> Enum.map(fn {id, list} -> {id, calc_asleep_times(list, 60, minutes)} end) {id, {minute, time}} = sleeping_by_guard |> Enum.map(fn {id, sleeps} -> {id, Enum.max_by(sleeps, fn {minute, time} -> time end)} end) |> Enum.max_by(fn {id, {minute, time}} -> time end) "id: #{id} minute: #{minute} times: #{time}" end end {:ok, content} = File.read("input4.txt") IO.puts("Part 1: #{DayFour.part_one(content)}") IO.puts("Part 2: #{DayFour.part_two(content)}")