about summary refs log tree commit diff
path: root/day4.exs
blob: afbedb09eb5741aca5a3f56e43c84da813bff1fb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
defmodule Day4 do
  def input() do
    File.stream!("input/day4.in")
    |> Stream.map(&String.trim/1)
    |> Enum.sort()
  end

  def part1() do
    {id, minute, _} =
      input()
      |> Enum.map(&parse_record_pattern/1)
      |> Enum.reduce({%{}, [], []}, &stack_and_map/2)
      |> elem(0)
      |> Enum.max_by(fn {_k, v} ->
        v.asleep
        |> Enum.map(&Enum.sum/1)
        |> Enum.sum()
      end)
      |> sleepiest_minute()

    id * minute
  end

  def part2() do
    {id, minute, _count} =
      input()
      |> Enum.map(&parse_record_pattern/1)
      |> Enum.reduce({%{}, [], []}, &stack_and_map/2)
      |> elem(0)
      |> Enum.map(&sleepiest_minute/1)
      |> Enum.max_by(fn {_id, _min, count} -> count end)

    id * minute
  end

  defp sleepiest_minute({id, %{asleep: list_of_ranges}}) do
    {minute, list} =
      list_of_ranges
      |> Enum.flat_map(& &1)
      |> Enum.group_by(& &1)
      |> Enum.max_by(fn {_k, v} -> length(v) end, fn -> {0, []} end)

    {id, minute, length(list)}
  end

  def parse_record_pattern(string) do
    <<
      "[",
      _date::binary-size(10),
      " ",
      _hour::binary-size(2),
      ":",
      minute::binary-size(2),
      "] ",
      action::binary
    >> = string

    %{
      minute: String.to_integer(minute),
      action: parse_action(action)
    }
  end

  defp parse_action("Guard #" <> string) do
    [id] = Regex.run(~r/\d+/, string)
    {:guard, String.to_integer(id)}
  end

  defp parse_action("wakes up"), do: :wake_up
  defp parse_action("falls asleep"), do: :sleep

  def stack_and_map(%{action: {:guard, id}}, {map, awake_stack, asleep_stack}) do
    {Map.put_new(map, id, %{slept: nil, asleep: []}), [id | awake_stack], asleep_stack}
  end

  def stack_and_map(
        %{action: :sleep, minute: minute} = _record,
        {map, [current_id | awake_stack], asleep_stack}
      ) do
    {put_in(map, [current_id, :slept], minute), awake_stack, [current_id | asleep_stack]}
  end

  def stack_and_map(
        %{action: :wake_up, minute: minute} = _record,
        {map, awake_stack, [current_id | asleep_stack]}
      ) do
    went_to_sleep = get_in(map, [current_id, :slept])

    {update_in(map, [current_id, :asleep], &[went_to_sleep..(minute - 1) | &1]),
     [current_id | awake_stack], asleep_stack}
  end
end

IO.puts(Day4.part1())
IO.puts(Day4.part2())