98 lines
3.1 KiB
Elixir
98 lines
3.1 KiB
Elixir
|
defmodule Day4 do
|
||
|
@log_pattern ~r/\[(\d\d\d\d-\d\d-\d\d \d\d:\d\d)\] (.+)/
|
||
|
@guard_pattern ~r/Guard #(\d+) begins shift/
|
||
|
|
||
|
def parse_log_line(str) do
|
||
|
[date_str, entry] = Regex.run(@log_pattern, str, capture: :all_but_first)
|
||
|
{:ok, date} = NaiveDateTime.from_iso8601(date_str <> ":00")
|
||
|
{date, parse_entry(entry)}
|
||
|
end
|
||
|
|
||
|
def parse_entry("wakes up"), do: :wake
|
||
|
def parse_entry("falls asleep"), do: :sleep
|
||
|
|
||
|
def parse_entry(str) do
|
||
|
[id] = Regex.run(@guard_pattern, str, capture: :all_but_first)
|
||
|
{:arrive, String.to_integer(id)}
|
||
|
end
|
||
|
|
||
|
# [
|
||
|
# {~N[1518-02-18 00:08:00], :sleep, 131},
|
||
|
# {~N[1518-02-18 00:29:00], :wake, 131},
|
||
|
# ...
|
||
|
# ]
|
||
|
def parse_log() do
|
||
|
File.stream!("input")
|
||
|
|> Stream.map(&String.trim/1)
|
||
|
|> Stream.map(&parse_log_line/1)
|
||
|
|> Enum.sort(fn {d1, _}, {d2, _} -> NaiveDateTime.compare(d1, d2) === :lt end)
|
||
|
|> Enum.map_reduce(nil, fn
|
||
|
{date, {:arrive, id}}, _id -> {{date, :arrive, id}, id}
|
||
|
{date, entry}, id -> {{date, entry, id}, id}
|
||
|
end)
|
||
|
|> elem(0)
|
||
|
|> Enum.reject(fn {_date, event, _id} -> event === :arrive end)
|
||
|
end
|
||
|
|
||
|
def sleepy do
|
||
|
parse_log()
|
||
|
|> Enum.chunk_every(2)
|
||
|
|> Enum.map(fn [{sleep, _, _}, {wake, _, id}] ->
|
||
|
{id, div(NaiveDateTime.diff(wake, sleep), 60)}
|
||
|
end)
|
||
|
|> Enum.group_by(fn {id, _mins} -> id end, fn {_id, mins} -> mins end)
|
||
|
|> Enum.map(fn {id, mins} -> {id, Enum.sum(mins)} end)
|
||
|
|> Enum.max_by(fn {_id, mins} -> mins end)
|
||
|
|> elem(0)
|
||
|
end
|
||
|
|
||
|
def most_min_slept(guard_id) do
|
||
|
parse_log()
|
||
|
|> Enum.reject(fn {_date, _event, id} -> id !== guard_id end)
|
||
|
|> Enum.chunk_every(2)
|
||
|
|> Enum.map(fn [{sleep, _, _}, {wake, _, _}] -> sleep.minute..(wake.minute - 1) end)
|
||
|
|> Enum.reduce(%{}, fn range, freq_map ->
|
||
|
Enum.reduce(range, freq_map, fn minute, freq_map ->
|
||
|
Map.update(freq_map, minute, 1, &(&1 + 1))
|
||
|
end)
|
||
|
end)
|
||
|
|> Enum.max_by(fn {_minute, freq} -> freq end)
|
||
|
|> elem(0)
|
||
|
end
|
||
|
|
||
|
def part1 do
|
||
|
sleepy = sleepy()
|
||
|
most_min_slept = most_min_slept(sleepy)
|
||
|
IO.puts("Guard most often asleep: #{sleepy}")
|
||
|
IO.puts("Most mintutes that guard slept: #{most_min_slept}")
|
||
|
IO.puts("Part 1 answer: #{sleepy * most_min_slept}")
|
||
|
end
|
||
|
|
||
|
def most_sleepy_guard_in(guard_freq_map) do
|
||
|
Enum.max_by(guard_freq_map, fn {_id, freq} -> freq end)
|
||
|
end
|
||
|
|
||
|
def part2 do
|
||
|
{minute, {id, _freq}} =
|
||
|
parse_log()
|
||
|
|> Enum.chunk_every(2)
|
||
|
|> Enum.map(fn [{sleep, _, _}, {wake, _, id}] -> {id, sleep.minute..(wake.minute - 1)} end)
|
||
|
|> Enum.reduce(%{}, fn {id, range}, minute_map ->
|
||
|
Enum.reduce(range, minute_map, fn minute, minute_map ->
|
||
|
Map.update(minute_map, minute, %{id => 1}, fn guard_map ->
|
||
|
Map.update(guard_map, id, 1, &(&1 + 1))
|
||
|
end)
|
||
|
end)
|
||
|
end)
|
||
|
|> Enum.map(fn {minute, freq_map} -> {minute, most_sleepy_guard_in(freq_map)} end)
|
||
|
|> Enum.max_by(fn {_minute, {_id, freq}} -> freq end)
|
||
|
|
||
|
IO.puts("The guard that was most freequently alseep on the same minute: #{id}")
|
||
|
IO.puts("The minute that guard was most frequently asleep on: #{minute}")
|
||
|
IO.puts("Part 2 answer: #{id * minute}")
|
||
|
end
|
||
|
end
|
||
|
|
||
|
Day4.part1()
|
||
|
Day4.part2()
|