seeplusplus

seeplusplus

Advent of Code 2024 - Day 10

This one wasn’t too bad :slight_smile: I actually ended up solving part 2 first, and had to work around it in part 1 to get the answer there!

input = ""
|> String.trim()
tiles = input |> String.split("\n")
  |> Stream.with_index() 
  |> Enum.flat_map(fn {line, l_idx} ->
    line 
    |> String.trim() 
    |> String.graphemes()
    |> Stream.with_index()
    |> Enum.map(fn {".", idx} -> {{idx, l_idx}, nil}
      {c, idx} -> {{idx, l_idx}, c |> String.to_integer()} end)
end)
|> Enum.into(Map.new())
grid = Grid2D.new(
  input |> String.split("\n") |> Enum.at(0) |> String.trim() |> String.length(),
  input |> String.trim() |> String.split("\n") |> Enum.count()
)
defmodule TrailFinder do
  def find_trails(input, tiles, grid) do
    tiles
    |> Stream.filter(fn {_, v} -> v == 0 end)
    |> Enum.map(fn {k, _} -> {k, find_trails(input, tiles, grid, k, 0, [])} end)
  end
  def find_trails(_, _, _, p, 9, acc), do: [[{p, 9} | acc]]
  def find_trails(input, tiles, grid, start, height, acc) do
    Grid2D.neighbors(start, grid, :straight)
    |> Enum.filter(fn p -> Map.get(tiles, p) == height + 1 end)
    |> Enum.flat_map(fn p ->
        find_trails(input, tiles, grid, p, height + 1, [{start, height} | acc])
    end)
  end
end
TrailFinder.find_trails(input, tiles, grid)
|> Enum.map(fn {s, l} -> {s, for [peak |_] <- l, into: MapSet.new do peak end } end)
|> Enum.map(fn {_, s} -> MapSet.size(s) end)
|> Enum.sum()
TrailFinder.find_trails(input, tiles, grid)
|> Stream.map(fn {_, l} -> l |> Enum.count end)
|> Enum.sum()

Most Liked

rugyoga

rugyoga

Same!
I accidentally solved part 2 first and then fixed it to run part1. lol

https://github.com/rugyoga/aoc2023/blob/main/lib/2024/10.ex

rySeeR

rySeeR

After having the most inefficient code yesterday that took almost 40 minutes to run hahaha, I’m surprised that today tooks 2ms to run, at the first try.

Also solved both at the same time.

I think it might be time to build a custom grid module…

https://github.com/jbonet/advent_of_code_2024/blob/main/lib/advent_of_code_2024/days/10.ex

Aetherus

Aetherus

Solved Part 2 first, too. Here’s my code using dynamic programming (not very FP, though):

defmodule AoC2024.Day10 do
  @type grid() :: %{coord() => 0..9}
  @type coord() :: {i::non_neg_integer(), j::non_neg_integer()}

  @spec part_1(grid()) :: non_neg_integer()
  def part_1(grid) do
    Task.async(fn ->
      grid
      |> Enum.filter(&elem(&1, 1) == 0)
      |> Enum.map(&elem(&1, 0))
      |> Enum.map(&dp_1(grid, &1))
      |> Enum.map(&length/1)
      |> Enum.sum()
    end)
    |> Task.await(:infinity)
  end

  @spec part_2(grid()) :: non_neg_integer()
  def part_2(grid) do
    Task.async(fn ->
      grid
      |> Enum.filter(&elem(&1, 1) == 0)
      |> Enum.map(&elem(&1, 0))
      |> Enum.map(&dp_2(grid, &1))
      |> Enum.sum()
    end)
    |> Task.await(:infinity)
  end

  defp dp_1(grid, {i, j}) do
    case grid[{i, j}] do
      9 ->
        [{i, j}]

      n ->
        memoized({i, j}, fn ->
          [{i - 1, j}, {i + 1, j}, {i, j - 1}, {i, j + 1}]
          |> Enum.filter(&grid[&1] == n + 1)
          |> Enum.flat_map(&dp_1(grid, &1))
          |> Enum.uniq()
        end)
    end
  end

  defp dp_2(grid, {i, j}) do
    case grid[{i, j}] do
      9 ->
        1

      n ->
        memoized({i, j}, fn ->
          [{i - 1, j}, {i + 1, j}, {i, j - 1}, {i, j + 1}]
          |> Enum.filter(&grid[&1] == n + 1)
          |> Enum.map(&dp_2(grid, &1))
          |> Enum.sum()
        end)
    end
  end

  defp memoized(key, fun) do
    with nil <- Process.get(key) do
      fun.() |> tap(&Process.put(key, &1))
    end
  end
end

Benchmark (without the parsing part)

Name             ips        average  deviation         median         99th %
part_2        508.15        1.97 ms     ±3.72%        1.96 ms        2.04 ms
part_1        409.82        2.44 ms     ±1.00%        2.44 ms        2.53 ms

Last Post!

stevensonmt

stevensonmt

I had the same ideas but used comprehensions to add the edges and get the valid paths. First time this year I’ve used :digraph and didn’t have to refactor it away when I realized it wasn’t the right setup.

defmodule Day10 do
  @test """
  89010123
  78121874
  87430965
  96549874
  45678903
  32019012
  01329801
  10456732
  """
  @real File.read!(__DIR__ <> "/input.txt")

  @neighbors [{0, 1}, {0, -1}, {1, 0}, {-1, 0}]

  defp input(:test), do: @test
  defp input(:real), do: @real
  defp input(_), do: raise("Please use :test or :real as the mode to run.")

  defp parse(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.with_index()
    |> Enum.reduce({:digraph.new(), %{}}, fn {line, i}, {map, indexed} ->
      line
      |> String.graphemes()
      |> Enum.map(&String.to_integer/1)
      |> Enum.with_index()
      |> Enum.reduce({map, indexed}, fn {n, j}, {mp, ndxd} ->
        :digraph.add_vertex(mp, {i, j}, n)
        {mp, Map.update(ndxd, n, [{i, j}], fn curr -> [{i, j} | curr] end)}
      end)
    end)
  end

  defp add_edges(graph) do
    for {i, j} <- :digraph.vertices(graph),
        {k, l} <- :digraph.vertices(graph) -- [{i, j}],
        {di, dj} <- @neighbors,
        {i + di, j + dj} == {k, l} do
      {{i, j}, n} = :digraph.vertex(graph, {i, j})
      {{k, l}, m} = :digraph.vertex(graph, {k, l})

      case n - m do
        1 -> :digraph.add_edge(graph, {k, l}, {i, j})
        -1 -> :digraph.add_edge(graph, {i, j}, {k, l})
        _ -> false
      end
    end

    graph
  end

  def run(mode) do
    {map, indexed} =
      mode
      |> input()
      |> parse()

    add_edges(map)

    part_1({map, indexed}) |> IO.inspect(label: :part_1)
    part_2({map, indexed}) |> IO.inspect(label: :part_2)
  end

  defp part_1({map, indexed}) do
    trailheads = indexed[0]
    targets = indexed[9]

    good_trails =
      for th <- trailheads, te <- targets, :digraph.get_path(map, th, te), reduce: %{} do
        acc -> Map.update(acc, th, [te], fn curr -> [te | curr] end)
      end

    score(good_trails)
  end

  defp score(trails) do
    trails
    |> Enum.map(fn {_, nines} -> Enum.count(nines) end)
    |> Enum.sum()
  end

  defp part_2({map, indexed}) do
    for hd <- indexed[0],
        one <- indexed[1],
        :digraph.get_path(map, hd, one),
        two <- indexed[2],
        :digraph.get_path(map, one, two),
        three <- indexed[3],
        :digraph.get_path(map, two, three),
        four <- indexed[4],
        :digraph.get_path(map, three, four),
        five <- indexed[5],
        :digraph.get_path(map, four, five),
        six <- indexed[6],
        :digraph.get_path(map, five, six),
        seven <- indexed[7],
        :digraph.get_path(map, six, seven),
        eight <- indexed[8],
        :digraph.get_path(map, seven, eight),
        nine <- indexed[9],
        :digraph.get_path(map, eight, nine), reduce: 0 do
      acc -> acc + 1
    end
  end
end

Day10.run(:real)

Where Next?

Popular in Challenges Top

Qqwy
Note by the Moderators: This topic is to talk about Day 6 of the Advent of Code. For general discussion about the Advent of Code 2018 an...
New
bjorng
Note: This topic is to talk about Day 9 of the Advent of Code 2019. There is a private leaderboard for elixirforum members. You can join...
New
bjorng
Note: This topic is to talk about Day 13 of the Advent of Code 2019. There is a private leaderboard for elixirforum members. You can joi...
New
bjorng
This topic is about Day 1 of the Advent of Code 2021. We have a private leaderboard (shared with users of Erlang Forums): https://adven...
New
bjorng
This topic is about Day 9 of the Advent of Code 2021 . We have a private leaderboard (shared with users of Erlang Forums): https://adve...
New
jkwchui
Monkeys fitted squarely as GenServers in my head. My initial problem was using cast instead of call; I imagine impolite monkeys slinging...
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

Other popular topics Top

electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" =&gt; #BSON.ObjectId&lt;58eb1a7a9ad169198c3dXXXX&gt;, "email" =&gt; ...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
985 44778 311
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New

We're in Beta

About us Mission Statement