bjorng

bjorng

Erlang Core Team

Advent of Code 2025 - Day 4

defmodule Day04 do
  def part1(input) do
    grid = parse(input)
    Enum.count(removable(grid))
  end

  def part2(input) do
    grid = parse(input)
    remove(grid, 0)
  end

  defp remove(grid, num_removed) do
    case removable(grid) do
      [] ->
        num_removed
      [_|_]=ps ->
        grid = Enum.reduce(ps, grid, &(Map.delete(&2, &1)))
        remove(grid, num_removed + length(ps))
    end
  end

  defp removable(grid) do
    grid
    |> Enum.filter(fn {_position, what} -> what === ?@ end)
    |> Enum.filter(fn {position, _} ->
      adjacent_squares(position)
      |> Enum.count(fn position->
        get_grid(grid, position) === ?@
      end)
      |> then(&(&1 < 4))
    end)
    |> Enum.map(&elem(&1, 0))
  end

  defp get_grid(grid, position) do
    Map.get(grid, position, ?.)
  end

  defp adjacent_squares({row, col}) do
    [{row - 1, col - 1}, {row - 1, col}, {row - 1, col + 1},
     {row, col - 1}, {row, col + 1},
     {row + 1, col - 1}, {row + 1, col}, {row + 1, col + 1}]
  end

  defp parse(input) do
    parse_grid(input)
  end

  defp parse_grid(grid) do
    grid
    |> Enum.with_index
    |> Enum.flat_map(fn {line, row} ->
      String.to_charlist(line)
      |> Enum.with_index
      |> Enum.flat_map(fn {char, col} ->
        position = {row, col}
        [{position, char}]
      end)
    end)
    |> Map.new
  end
end

Most Liked

lud

lud

I love immutability because you can just compare a term against its previous version without managing the copy yourself:

case Map.drop(grid, Map.keys(accessibles(grid))) do
  ^grid -> grid
  new_grid -> loop_remove(new_grid)
end

In context:

defmodule AdventOfCode.Solutions.Y25.Day04 do
  alias AoC.Input

  def parse(input, _part) do
    lines = Input.stream!(input, trim: true)

    for {row, y} <- Enum.with_index(lines),
        {"@", x} <- Enum.with_index(String.graphemes(row)),
        reduce: %{},
        do: (grid -> Map.put(grid, {x, y}, true))
  end

  def part_one(grid) do
    map_size(accessibles(grid))
  end

  def part_two(grid) do
    clear_grid = loop_remove(grid)
    map_size(grid) - map_size(clear_grid)
  end

  defp loop_remove(grid) do
    case Map.drop(grid, Map.keys(accessibles(grid))) do
      ^grid -> grid
      new_grid -> loop_remove(new_grid)
    end
  end

  defp accessibles(grid) do
    Map.filter(grid, &less_than_four_neighbors?(&1, grid))
  end

  defp less_than_four_neighbors?({{x, y}, _}, grid) do
    for(nx <- (x - 1)..(x + 1), ny <- (y - 1)..(y + 1), {nx, ny} != {x, y}, do: {nx, ny})
    |> Enum.count(&Map.has_key?(grid, &1))
    |> Kernel.<(4)
  end
end
dompdv

dompdv

This one was pretty straightforward. Using MapSet (and MapSet operations) and the “for” construct, which is more concise than reduce. No optimization at all


defmodule AdventOfCode.Solution.Year2025.Day04 do
  import Enum, only: [flat_map: 2, with_index: 1, sum: 1, filter: 2, count: 1]

  def parse(input) do
    # Create a MapSet of the {row,col} of the rolls
    with_index(String.split(input, "\n", trim: true))
    |> flat_map(fn {line, row} ->
      for {c, col} <- with_index(to_charlist(line)), c == ?@, do: {row, col}
    end)
    |> MapSet.new()
  end

  def vec_sum({a, b}, {c, d}), do: {a + c, b + d}

  @around for r <- -1..1, c <- -1..1, {r, c} != {0, 0}, do: {r, c}
  def liftable(roll, rolls) do
    sum(for delta <- @around, vec_sum(roll, delta) in rolls, do: 1) < 4
  end

  def part1(input) do
    rolls = parse(input)
    rolls |> filter(&liftable(&1, rolls)) |> count()
  end

  def remove_all(rolls, counter \\ 0) do
    # Identify the removable rolls
    removable = for roll <- rolls, liftable(roll, rolls), into: MapSet.new(), do: roll
    # If nothing to remove, we're done, else remove the removable rolls and increment the counter
    if MapSet.size(removable) == 0,
      do: counter,
      else: remove_all(MapSet.difference(rolls, removable), counter + MapSet.size(removable))
  end

  def part2(input), do: input |> parse() |> remove_all()
end
mudasobwa

mudasobwa

Creator of Cure

I did my best to never construct a matrix, binaries rock. Also, Stream.iterate(&calc/1) is a lovely approach to code reuse.

  defmodule Day4 do
    @input "day4_1.input" |> File.read!() |> String.trim()

    defp read_input(input) do
      input
      |> String.split(["\s", "\n"], trim: true)
      |> then(fn [h | t] ->
        stub = "." |> List.duplicate(byte_size(h)) |> Enum.join()
        [stub, h | t] ++ [stub]
      end)
      |> Enum.map(&("." <> &1 <> "."))
    end

    def calc({input, acc} \\ {@input, 0}) do
      input
      |> read_input()
      |> Enum.chunk_every(3, 1, :discard)
      |> Enum.reduce({0, []}, fn [h, l, t], {count, res} ->
        res = ["" | res]

        Enum.reduce(1..(byte_size(l) - 2), {count, res}, fn idx, {count, [hres | tres]} ->
          with <<_::binary-size(idx - 1), r1::binary-size(3), _::binary>> <- h,
               <<_::binary-size(idx - 1), r21::binary-size(1), r22::binary-size(1),
                 r23::binary-size(1), _::binary>> <- l,
               <<_::binary-size(idx - 1), r3::binary-size(3), _::binary>> <- t,
               false <- r22 == "@" and String.count(r1 <> r21 <> r23 <> r3, "@") < 4,
               do: {count, [r22 <> hres | tres]},
               else: (_ -> {count + 1, ["x" <> hres | tres]})
        end)
      end)
      |> then(fn {count, data} -> {Enum.join(data, "\n"), acc + count} end)
    end

    def calc_2(input \\ @input) do
      {input, 0}
      |> Stream.iterate(&calc/1)
      |> Enum.reduce_while({0, -1}, fn
        {_, prev}, {acc, prev} -> {:halt, acc}
        {_, count}, {acc, _} -> {:cont, {count, acc}}
      end)
    end
  end

Where Next?

Popular in Challenges Top

cblavier
Hey there :wave: No magic or algorithmic finesse today, I just finished the challenge and I my code is quite slow (1sec for part1, 3se...
New
bjorng
Note: This topic is to talk about Day 18 of the Advent of Code 2019. There is a private leaderboard for elixirforum members. You can joi...
New
sukhmeetsd
All in all, from what I understand, it is better not to use GenServer.cast when we want some concurrent operations to happen for sure, be...
New
bjorng
Note: This topic is to talk about Day 4 of the Advent of Code 2019. There is a private leaderboard for elixirforum members. You can join...
New
bjorng
Here is my solution for day 4: https://github.com/bjorng/advent-of-code/blob/main/2024/day04/lib/day04.ex
New
adamu
Nobody’s doing Advent of Code this year? :grinning_face_with_smiling_eyes: I might do the first week or so. For Day 1, first I solved i...
New
bjorng
Note: This topic is to talk about Day 6 of the Advent of Code 2019. There is a private leaderboard for elixirforum members. You can join...
New
code-shoily
Just did part 1. Part 2 seems to be demanding too much of my reading time so will get to that after I am done with some chores. Oh here ...
New
christhekeele
Setting this down for the night, as after a quick naive solve for quick part 1 I realize that part 2 is by design computationally expensi...
New
rugyoga
Fairly straightforward Dijkstra’s algorithm import AOC aoc 2023, 17 do def compute(input, candidates) do {{max_row, max_col}, ite...
New

Other popular topics Top

vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 41539 114
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
hariharasudhan94
lets say i have a sample like a = 20; b = 10; if (a &gt; b) do {:ok, "a"} end if (a &lt; b) do {:ok, b} end if (a == b) do {:ok, "equa...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
AstonJ
Please see the new poll here: Which code editor or IDE do you use? (Poll) (2022 Edition) It’s been a while since we first asked this, I...
208 31142 143
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 47930 226
New
dogweather
I wrote this comment on r/haskell, and it’s not popular there. :wink: But I think I’m on to something… Haskell reminds me of Java, and e...
New

We're in Beta

About us Mission Statement