Day 4: Repose Record - Advent of Code 2018

2018-12-04 00:00:00 +0000

I did the Advent of Code 2018 day 4 challenge in Elixir! Parts one and two are as follows:

defmodule Day4_1 do
  # 1518-11-01 00:00
  @log_info ~r/\[(\d+)-(\d+)-(\d+) (\d+):(\d+)\] (.*)/
  @guard_entry ~r/Guard #(\d+) begins shift/

  def part1(input) do
    {_, _, guards} =
      input
      |> read_input()
      |> extract_logs()

    {guard_id, _time_asleep} = most_asleep(guards)
    {minute, _size} = get_most_asleep_minutes(guards[guard_id])
    minute * String.to_integer(guard_id)
  end

  def part2(input) do
    {_, _, guards} =
      input
      |> read_input()
      |> extract_logs()

    {guard_id, minute, _size} = most_times_asleep(guards)
    minute * String.to_integer(guard_id)
  end

  defp read_input(input) do
    input
    |> File.read!()
    |> String.split("\n", trim: true)
  end

  def extract_logs(lines) do
    lines
    |> Enum.sort()
    |> Enum.map(fn line ->
      [_year, _month, _day, _hour, minute, message] =
        Regex.run(@log_info, line, capture: :all_but_first)

      case message do
        "falls asleep" ->
          {:sleep, String.to_integer(minute)}

        "wakes up" ->
          {:wake, String.to_integer(minute)}

        _ ->
          [guard_id] = Regex.run(@guard_entry, message, capture: :all_but_first)
          {:guard, guard_id}
      end
    end)
    |> Enum.reduce({nil, nil, %{}}, fn log, {current_guard_id, last_minute, guards} ->
      case log do
        {:guard, guard_id} ->
          {guard_id, nil, Map.put_new(guards, guard_id, [])}

        {:sleep, minute} ->
          {current_guard_id, minute, guards}

        {:wake, minute} ->
          new_range = for min <- last_minute..(minute - 1), do: min
          {current_guard_id, nil, Map.update!(guards, current_guard_id, &(&1 ++ new_range))}
      end
    end)
  end

  defp most_asleep(guards) do
    Enum.reduce(guards, {nil, 0}, fn {guard_id, sleep_range}, {_, current_bigger_time} = acc ->
      time_asleep = length(sleep_range)

      if time_asleep > current_bigger_time do
        {guard_id, time_asleep}
      else
        acc
      end
    end)
  end

  defp most_times_asleep(guards) do
    Enum.reduce(guards, {nil, 0, 0}, fn {guard_id, asleep_range},
                                        {_current_guard, _minute, max_size} = acc ->
      {minute, size} = get_most_asleep_minutes(asleep_range)

      if size > max_size do
        {guard_id, minute, size}
      else
        acc
      end
    end)
  end

  defp get_minutes_of(guard_intervals) do
    {asleep_range, []} =
      guard_intervals
      |> Enum.reduce({[], []}, fn
        {datetime, _}, {range, []} ->
          {range, [datetime.minute]}

        {datetime, _}, {range, [asleep_time]} ->
          new_range = for min <- asleep_time..(datetime.minute - 1), do: min
          {[new_range | range], []}
      end)

    {minute, _size} = get_most_asleep_minutes(asleep_range)
    minute
  end

  defp get_most_asleep_minutes(asleep_range) do
    asleep_range
    |> Enum.group_by(& &1)
    |> Enum.reduce({nil, 0}, fn {minute, occurrences}, {_, times} = acc ->
      size = length(occurrences)

      if size > times do
        {minute, size}
      else
        acc
      end
    end)
  end
end

# :aoc2018 |> :code.priv_dir() |> Path.join("day4.txt") |> Day4_1.part1()
# :aoc2018 |> :code.priv_dir() |> Path.join("day4.txt") |> Day4_1.part2()

 

Upcoming conferences

Start booking your calendar. We’ll be releasing more dates soon — here’s what’s planned so far.

Code BEAM Europe 2026 + MQ Summit 2026

Two conferences running at the same time in Haarlem & Online on 21-22 OCTOBER 2026 — choose the one that’s right for you.

Conferences organised by our partners

Events run by partners across the wider community that we’re proud to support.

ElixirConf Brasil 2026

27-28 NOVEMBER 2026 · Curitiba & Online

Partner event
All conferences

ARTICLES: 9

Day 11: Chronal Charge - Advent of Code 2018

Article by

READ MORE

Day 10: The Stars Align - Advent of Code 2018

Article by

READ MORE

Day 7: The Sum of Its Parts - Advent of Code 2018

Article by

I did the Advent of Code 2018 day 7 challenge in Elixir! Parts one and two are as follows:

defmodule Day7 do
  @instruction_info ~r/Step (\w+) must be finished before step (\w+) can begin/
  def part1(input) do
    input
    |> read_input()
    |> parse_input()
    |> discover_dependencies([])
  end

  defp read_input(input) do
    input
    |> File.read!()
    |> String.split("\n", trim: true)
  end

  defp parse_input(lines) do
    Enum.reduce(lines, %{}, fn line, acc ->
      [step, dependency] = Regex.run(@instruction_info, line, capture: :all_but_first)

      acc
      |> Map.put_new(step, [])
      |> Map.update(dependency, [step], fn dependencies ->
        Enum.sort([step | dependencies])
      end)
    end)
    |> Enum.to_list()
  end

  defp discover_dependencies(deps_tree, acc) when map_size(deps_tree) == 0do
    acc |> Enum.reverse() |> Enum.join()
  end

  defp discover_dependencies(deps_tree, acc) do
    {step, []} = Enum.find(deps_tree, fn {_, deps} -> Enum.empty?(deps) end)

    new_dependencies =
      Enum.reduce(deps_tree, %{}, fn
        {^step, _}, acc -> Map.delete(acc, step)
        {other_step, deps}, acc -> Map.put_new(acc, other_step, List.delete(deps, step))
      end)

    discover_dependencies(new_dependencies, [step | acc])
  end
end

# r Day7; :aoc2018 |> :code.priv_dir() |> Path.join("day7.txt") |> Day7.part1()
# :aoc2018 |> :code.priv_dir() |> Path.join("day7.txt") |> Day7.part2()

input = "Step D must be finished before step E can begin.
Step F must be finished before step E can begin.
Step C must be finished before step A can begin.
Step B must be finished before step E can begin.
Step A must be finished before step D can begin.
Step C must be finished before step F can begin.
Step A must be finished before step B can begin."

# r Day7; Day7.part1(input)
 

 

READ MORE

Day 6: Chronal Coordinates - Advent of Code 2018

Article by

I did the Advent of Code 2018 day 6 challenge in Elixir! Parts one and two are as follows:

defmodule Day6 do
  def part1(input) do
    {_infinites, finites} =
      input
      |> read_input()
      |> create_grid()
      |> group_by_infinites()

    {_coordinates, layout} =
      finites
      |> Enum.group_by(fn {x, y, _dist} -> {x, y} end)
      |> Enum.max_by(fn {_, v} -> length(v) end)

    length(layout)
  end

  def part2(input) do
    {max_x, max_y, coordinates} =
      input
      |> read_input()

    for x <- 0..max_x,
        y <- 0..max_y do
      coordinates
      |> Enum.reduce(0, fn {point_x, point_y}, acc ->
        distance = abs(x - point_x) + abs(y - point_y)
        distance + acc
      end)
    end
    |> Enum.filter(&(&1 < 10_000))
    |> Enum.count()
  end

  defp read_input(input) do
    {max_x, max_y, coordinates} =
      input
      |> File.read!()
      |> String.trim()
      |> String.split("\n", trim: true)
      |> Enum.reduce({-1, -1, []}, fn line, {max_x, max_y, coordinates} ->
        [x, y] = String.split(line, ", ")
        x = String.to_integer(x)
        y = String.to_integer(y)
        coordinate = {x, y}
        max_x = if x > max_x, do: x, else: max_x
        max_y = if y > max_y, do: y, else: max_y
        {max_x, max_y, [coordinate | coordinates]}
      end)

    {max_x, max_y, Enum.reverse(coordinates)}
  end

  defp create_grid({max_x, max_y, coordinates}) do
    for x <- 0..max_x,
        y <- 0..max_y do
      [{px, py, dist1}, {_, _, dist2} | _] =
        Enum.map(coordinates, fn {point_x, point_y} ->
          distance = abs(x - point_x) + abs(y - point_y)
          {point_x, point_y, distance}
        end)
        |> Enum.sort_by(fn {_, _, distance} -> distance end)

      if dist1 == dist2 do
        nil
      else
        cond do
          x == 0 ->
            {:infinite, px, py}

          x == max_x ->
            {:infinite, px, py}

          y == 0 ->
            {:infinite, px, py}

          y == max_y ->
            {:infinite, px, py}

          true ->
            {px, py, dist1}
        end
      end
    end
  end

  defp group_by_infinites(grid) do
    Enum.reduce(grid, {MapSet.new(), []}, fn
      nil, acc ->
        acc

      {:infinite, px, py}, {infinites, finites} ->
        {MapSet.put(infinites, {px, py}), finites}

      {px, py, dist}, {infinites, finites} ->
        if MapSet.member?(infinites, {px, py}) do
          {infinites, finites}
        else
          {infinites, [{px, py, dist} | finites]}
        end
    end)
  end
end

# r Day6; :aoc2018 |> :code.priv_dir() |> Path.join("day6.txt") |> Day6.part1()
# r Day6; :aoc2018 |> :code.priv_dir() |> Path.join("day6.txt") |> Day6.part2()
 

 

READ MORE

Day 5: Alchemical Reduction - Advent of Code 2018

Article by

I did the Advent of Code 2018 day 5 challenge in Elixir! Parts one and two are as follows:

defmodule Day5 do
  def part1(input) do
    input
    |> read_input()
    |> String.codepoints()
    |> reduce_polymer()
  end

  def part2(input) do
    chars =
      input
      |> read_input()
      |> String.codepoints()

    unit_types =
      chars
      |> Enum.map(&String.downcase/1)
      |> Enum.uniq()

    Enum.reduce(unit_types, 1_000_000, fn unit_type, min_size ->
      without_unit =
        Enum.reject(chars, fn char ->
          String.downcase(char) == unit_type
        end)

      min(min_size, reduce_polymer(without_unit))
    end)
  end

  defp read_input(input) do
    input
    |> File.read!()
    |> String.trim()
  end

  defp reduce_polymer(chars) do
    Enum.reduce(chars, [], fn
      char, [] ->
        [char]

      char, [last_seen | rest] = acc ->
        result =
          if char != last_seen &&
               (char == String.upcase(last_seen) || char == String.downcase(last_seen)) do
            rest
          else
            [char | acc]
          end

        result
    end)
    |> length()
  end
end

# :aoc2018 |> :code.priv_dir() |> Path.join("day5.txt") |> Day5.part1()
# :aoc2018 |> :code.priv_dir() |> Path.join("day5.txt") |> Day5.part2()

# input = "dabAcCaCBAcCcaDA"
# r Day5; Day5.part2(input)

 

READ MORE

Day 4: Repose Record - Advent of Code 2018

Article by

I did the Advent of Code 2018 day 4 challenge in Elixir! Parts one and two are as follows:

defmodule Day4_1 do
  # 1518-11-01 00:00
  @log_info ~r/\[(\d+)-(\d+)-(\d+) (\d+):(\d+)\] (.*)/
  @guard_entry ~r/Guard #(\d+) begins shift/

  def part1(input) do
    {_, _, guards} =
      input
      |> read_input()
      |> extract_logs()

    {guard_id, _time_asleep} = most_asleep(guards)
    {minute, _size} = get_most_asleep_minutes(guards[guard_id])
    minute * String.to_integer(guard_id)
  end

  def part2(input) do
    {_, _, guards} =
      input
      |> read_input()
      |> extract_logs()

    {guard_id, minute, _size} = most_times_asleep(guards)
    minute * String.to_integer(guard_id)
  end

  defp read_input(input) do
    input
    |> File.read!()
    |> String.split("\n", trim: true)
  end

  def extract_logs(lines) do
    lines
    |> Enum.sort()
    |> Enum.map(fn line ->
      [_year, _month, _day, _hour, minute, message] =
        Regex.run(@log_info, line, capture: :all_but_first)

      case message do
        "falls asleep" ->
          {:sleep, String.to_integer(minute)}

        "wakes up" ->
          {:wake, String.to_integer(minute)}

        _ ->
          [guard_id] = Regex.run(@guard_entry, message, capture: :all_but_first)
          {:guard, guard_id}
      end
    end)
    |> Enum.reduce({nil, nil, %{}}, fn log, {current_guard_id, last_minute, guards} ->
      case log do
        {:guard, guard_id} ->
          {guard_id, nil, Map.put_new(guards, guard_id, [])}

        {:sleep, minute} ->
          {current_guard_id, minute, guards}

        {:wake, minute} ->
          new_range = for min <- last_minute..(minute - 1), do: min
          {current_guard_id, nil, Map.update!(guards, current_guard_id, &(&1 ++ new_range))}
      end
    end)
  end

  defp most_asleep(guards) do
    Enum.reduce(guards, {nil, 0}, fn {guard_id, sleep_range}, {_, current_bigger_time} = acc ->
      time_asleep = length(sleep_range)

      if time_asleep > current_bigger_time do
        {guard_id, time_asleep}
      else
        acc
      end
    end)
  end

  defp most_times_asleep(guards) do
    Enum.reduce(guards, {nil, 0, 0}, fn {guard_id, asleep_range},
                                        {_current_guard, _minute, max_size} = acc ->
      {minute, size} = get_most_asleep_minutes(asleep_range)

      if size > max_size do
        {guard_id, minute, size}
      else
        acc
      end
    end)
  end

  defp get_minutes_of(guard_intervals) do
    {asleep_range, []} =
      guard_intervals
      |> Enum.reduce({[], []}, fn
        {datetime, _}, {range, []} ->
          {range, [datetime.minute]}

        {datetime, _}, {range, [asleep_time]} ->
          new_range = for min <- asleep_time..(datetime.minute - 1), do: min
          {[new_range | range], []}
      end)

    {minute, _size} = get_most_asleep_minutes(asleep_range)
    minute
  end

  defp get_most_asleep_minutes(asleep_range) do
    asleep_range
    |> Enum.group_by(& &1)
    |> Enum.reduce({nil, 0}, fn {minute, occurrences}, {_, times} = acc ->
      size = length(occurrences)

      if size > times do
        {minute, size}
      else
        acc
      end
    end)
  end
end

# :aoc2018 |> :code.priv_dir() |> Path.join("day4.txt") |> Day4_1.part1()
# :aoc2018 |> :code.priv_dir() |> Path.join("day4.txt") |> Day4_1.part2()

 

READ MORE

Day 3: No matter how you slice it - Advent of Code 2018

Article by

I did the Advent of Code 2018 day 3 challenge in Elixir! Parts one and two are as follows:

defmodule Day3 do
  @grid_info ~r/#(\d+) @ (\d+),(\d+): (\d+)x(\d+)/

  def part1(input) do
    {overlaps, _} =
      input
      |> read_input()
      |> extract_grid()
      |> build_map()
      |> Enum.reduce({MapSet.new(), MapSet.new()}, fn {x, y, _id}, {overlap, no_overlap} ->
        if MapSet.member?(no_overlap, {x, y}) do
          {MapSet.put(overlap, {x, y}), no_overlap}
        else
          {overlap, MapSet.put(no_overlap, {x, y})}
        end
      end)

    MapSet.size(overlaps)
  end

  def part2(input) do
    map =
      input
      |> read_input()
      |> extract_grid()
      |> build_keyed_map()

    {id, _} =
      for {id1, coordinates1} <- map,
          {id2, coordinates2} <- map,
          id1 != id2 do
        overlaps = MapSet.intersection(coordinates1, coordinates2)

        if MapSet.size(overlaps) != 0 do
          {id1, id2}
        else
          {id1, nil}
        end
      end
      |> Enum.group_by(fn {key, _} -> key end, fn {_, value} -> value end)
      |> Enum.find(fn {_key, overlaps} ->
        Enum.all?(overlaps, &Kernel.==(&1, nil))
      end)

    id
  end

  defp read_input(input) do
    input
    |> File.read!()
    |> String.split("\n", trim: true)
  end

  defp extract_grid(lines) do
    Enum.map(lines, fn line ->
      [id, x, y, width, height] = Regex.run(@grid_info, line, capture: :all_but_first)

      [
        id,
        String.to_integer(x),
        String.to_integer(y),
        String.to_integer(width),
        String.to_integer(height)
      ]
    end)
  end

  defp build_map(grid) do
    Enum.flat_map(grid, fn [id, x, y, width, height] ->
      for x <- x..(x + width - 1),
          y <- y..(y + height - 1) do
        {x, y, id}
      end
    end)
  end

  defp build_keyed_map(grid) do
    Enum.reduce(grid, %{}, fn [id, x, y, width, height], acc ->
      coordinates =
        for x <- x..(x + width - 1),
            y <- y..(y + height - 1) do
          {x, y}
        end

      Map.put_new(acc, id, MapSet.new(coordinates))
    end)
  end
end

# :aoc2018 |> :code.priv_dir() |> Path.join("day3.txt") |> Day3.part1()
# :aoc2018 |> :code.priv_dir() |> Path.join("day3.txt") |> Day3.part2()
 

 

READ MORE

Day 2: Inventory Management System - Advent of Code 2018

Article by

I did the Advent of Code 2018 day 2 challenge in Elixir! Parts one and two are as follows:

defmodule Day2 do
  def checksum(input) do
    {two, three} =
      input
      |> read_input()
      |> Enum.reduce({0, 0}, fn line, {appears_two_times, appears_three_times} ->
        {two_times, three_times, _} =
          line
          |> String.graphemes()
          |> Enum.group_by(& &1)
          |> get_checksum_values()

        {appears_two_times + two_times, appears_three_times + three_times}
      end)

    two * three
  end

  def differ(input) do
    lines = input |> read_input()

    # length(diff) == 4 # [eq: "_", del: "_", ins: "_", eq: "_"]
    for line1 <- lines,
        line2 <- lines,
        line1 != line2,
        diff = String.myers_difference(line1, line2),
        length(diff) == 4 do
      diff
      |> Keyword.get_values(:eq)
      |> Enum.join()
    end
    |> Enum.uniq()
  end

  defp read_input(input) do
    input
    |> File.read!()
    |> String.split("\n", trim: true)
  end

  defp get_checksum_values(groups) do
    Enum.reduce(groups, {0, 0, MapSet.new()}, fn {_key, chars},
                                                 {two_times, three_times, seen} = acc ->
      value = length(chars)

      if MapSet.member?(seen, value) do
        acc
      else
        new_seen = MapSet.put(seen, value)

        case value do
          2 -> {two_times + 1, three_times, new_seen}
          3 -> {two_times, three_times + 1, new_seen}
          _ -> {two_times, three_times, new_seen}
        end
      end
    end)
  end
end

:aoc2018 |> :code.priv_dir() |> Path.join("day2.txt") |> Day2.checksum()
:aoc2018 |> :code.priv_dir() |> Path.join("day2.txt") |> Day2.differ()

 

READ MORE

Day 1: Chronal Calibration - Advent of Code 2018

Article by

I did the Advent of Code 2018 day 1 challenge in Elixir! Parts one and two are as follows:

defmodule Day1 do
  def frequency(input) do
    input
    |> read_input()
    |> Enum.reduce(0, fn line, acc ->
      String.to_integer(line) + acc
    end)
  end

  def repeated_frequency(input) do
      input
      |> read_input()
      |> Stream.cycle()
      |> Enum.reduce_while({0, MapSet.new([0])}, fn line, {acc, seen} ->
        value = String.to_integer(line) + acc
        if MapSet.member?(seen, value) do
          {:halt, value}
        else
          {:cont, {value, MapSet.put(seen, value)}}
        end
      end)
  end

  defp read_input(input) do
    input
    |> File.read!()
    |> String.split("\n", trim: true)
  end
end

:aoc2018 |> :code.priv_dir() |> Path.join("day1.txt") |> Day1.frequency()
:aoc2018 |> :code.priv_dir() |> Path.join("day1.txt") |> Day1.repeated_frequency()

 

 

READ MORE