lud

lud

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.

Showing Posts 1 to 10

Aetherus

Aetherus

For part 2, I expanded every border tile to 1~4 entries depending on which sides the fence is on, like {{i, j}, :up} and {{i, j}, :left}, and then

  1. find and expand all border tiles
  2. pick the smallest (top-left) entry
  3. eliminate the whole border containing that entry
  4. repeat step 2~3 until the set of border entries is empty

Here’s my solution:
https://github.com/Aetherus/advent-of-code/blob/d30deadc820fb0b94e9ce09e86d7353895953bf6/2024/day-12.livemd

sevenseacat

sevenseacat

Author of Ash Framework

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
adamu

adamu

I said I was going to take a break today, but I’m too addicted…

For part 1, I computed the perimeter at the same time as finding the regions, which meant part 2 was a massive troll because I had to go back and walk around the edges to find the sides anyway.

For part 2, I’m not happy with the duplication for the four directions (like day 4), but I’ve spent long enough on this.

The times are 24.371ms for part 1, and 46.861ms for part 2.

https://git.adamu.jp/adam/AdventOfCode/src/branch/main/2024/day12.exs

I am going to take a break for a few days from tomorrow, not sure if I’ll be back so just in case it’s been fun seeing everyone’s approaches so far!

liamcmitchell

liamcmitchell

I appreciate the solutions others post, it’s helped me learn a lot :heart:

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.

https://github.com/liamcmitchell/advent-of-code/blob/3145e87a67328dfed30606d963c8a62fb2fea7f9/2024/12/1.exs#L66-L92

rvnash

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
adamu

adamu

:exploding_head:

lud

lud OP

yeah … I feel dumb now :smiley:

jarlah

jarlah

this task is starting to annoy me. Its simple to get wall count .. it was not so simple to get side count

https://github.com/jarlah/advent_of_code/blob/master/lib/2024/day_12/Part1.ex#L15

anyone who can kick me in the right direction ? i tried to keep the code readable in the domain of the problem. I think ill read the comments here … its time ..

jarlah

jarlah

haha .. no idea what your doing .. the variable names and functions doesnt speak to me :slight_smile: When i do AOC I make code with as good as quality i would do at work, so i will remember after 1 year what the code does … :rofl: But terse can also be good :wink: good work

rvnash

rvnash

I verbosified it if you’re still interested.

Where Next? Top

Trending in Challenges Top

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews