seeplusplus
Advent of Code 2024 - Day 10
This one wasn’t too bad
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
Same!
I accidentally solved part 2 first and then fixed it to run part1. lol
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
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
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)
Popular in Challenges
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex









