lud
Advent of Code 2024 - Day 12
At first I was scared but I found is a simple way to compute the sides.
defmodule AdventOfCode.Solutions.Y24.Day12 do
alias AdventOfCode.Grid
alias AoC.Input
def parse(input, _part) do
input |> Input.stream!() |> Grid.parse_lines(fn c -> {:ok, <<c>>} end) |> elem(0)
end
def part_one(full_grid) do
regions = compute_regions(full_grid)
regions
|> Enum.map(&cost_p1/1)
|> Enum.sum()
end
def part_two(full_grid) do
regions = compute_regions(full_grid)
regions
|> Enum.map(&cost_p2/1)
|> Enum.sum()
end
defp compute_regions(grid) do
{regions, rest} =
Enum.reduce(grid, {[], grid}, fn {pos, tag}, {regions, rest_grid} ->
case Map.fetch(rest_grid, pos) do
:error ->
{regions, rest_grid}
{:ok, _} ->
{region, rest_grid} = take_region(rest_grid, tag, [pos])
{[region | regions], rest_grid}
end
end)
0 = map_size(rest)
regions
end
defp take_region(mut_grid, tag, open, closed \\ [])
defp take_region(mut_grid, tag, [pos | open], closed) do
neighs = pos |> Grid.cardinal4() |> Enum.filter(fn xy -> xy not in closed && Map.get(mut_grid, xy) == tag end)
take_region(mut_grid, tag, neighs ++ open, [pos | closed])
end
defp take_region(mut_grid, tag, [], closed) do
region = Map.new(closed, &{&1, tag})
mut_grid = Map.drop(mut_grid, closed)
{region, mut_grid}
end
defp cost_p1(region) do
area(region) * perimeter(region)
end
defp area(region) do
map_size(region)
end
defp perimeter(region) do
keys = Map.keys(region)
Enum.reduce(region, 0, fn {xy, _}, acc ->
borders = xy |> Grid.cardinal4() |> Enum.count(fn neigh -> neigh not in keys end)
acc + borders
end)
end
defp cost_p2(region) do
area(region) * count_sides(region)
end
defp count_sides(region) do
poses = Map.keys(region)
individual_sides =
Enum.flat_map(poses, fn pos ->
[
{:up, Grid.translate(pos, :n)},
{:down, Grid.translate(pos, :s)},
{:left, Grid.translate(pos, :w)},
{:right, Grid.translate(pos, :e)}
]
|> Enum.filter(fn {_, xy} -> xy not in poses end)
end)
sides_by_direction =
Enum.group_by(
individual_sides,
fn
# group sides by their orientation and level
{:up, {_x, y}} -> {:up, y}
{:down, {_x, y}} -> {:down, y}
{:right, {x, _y}} -> {:right, x}
{:left, {x, _y}} -> {:left, x}
end,
fn
# keep side value by orientation and cross direction to know if their
# are touching
{:up, {x, _y}} -> x
{:down, {x, _y}} -> x
{:right, {_x, y}} -> y
{:left, {_x, y}} -> y
end
)
Enum.reduce(sides_by_direction, 0, fn {{_direction, _level}, cross_coords}, acc ->
distinct_sides(cross_coords) + acc
end)
end
defp distinct_sides(cross_coords) do
[h | cross_coords] = Enum.sort(cross_coords)
distinct_sides(cross_coords, h, 0)
end
defp distinct_sides([h | t], prev, acc) when h == prev + 1 do
# No need to accumulate the whole side cross coordinates, we can just keep
# the previous nuber
distinct_sides(t, h, acc)
end
defp distinct_sides([h | t], _prev, acc) do
distinct_sides(t, h, acc + 1)
end
defp distinct_sides([], _prev, acc) do
acc + 1
end
end
BEAM capability of guards like when h == prev + 1 is really neat.
Most Liked
rvnash
My solution solves parts 1 and 2 simultaneously. For part 2 I just count the number of corners, both inside and outside. That’s equivalent to the number of sides.
https://github.com/rvnash/aoc2024/blob/main/lib/d12.ex
AOC 2024 Day 12 Content 0
Part 1: 1549354 in 43.439ms
Part 2: 937032 in 43.241ms
sevenseacat
This was a fun one! Took a bit of thinking outside the box (or maybe I didn’t need to think outside the box, I haven’t looked at anyone else’s solutions yet, I’ll do that now!)
The core of mine is the group_connecting function, which takes a list of coordinates and groups them together if they’re adjacent. This is how I figure out where all the regions are, and also how I split up all the borders into sides.
https://github.com/sevenseacat/advent_of_code/blob/main/lib/y2024/day12.ex
Name ips average deviation median 99th %
day 12, part 1 7.16 139.70 ms ±1.82% 139.22 ms 144.95 ms
day 12, part 2 6.84 146.22 ms ±1.32% 145.82 ms 151.25 ms
liamcmitchell
I appreciate the solutions others post, it’s helped me learn a lot ![]()
I used a recursive function to build a set of region positions, then iterated over individual fences, building up a map of %{fenceStart => fenceEnd, fenceEnd => fenceStart}.
Pattern matching on maps is really clean.
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
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








