bjorng
Advent of Code 2023 - Day 22
I went for a brute-force solution, implementing the most straight-forward algorithm I could think of. That initial implementation solved both parts in about 10 minutes.
After that, I did some simple improvements that reduced the time to about two and a half minutes.
Now I will optimize my spare time and take the rest of the day off.
https://github.com/bjorng/advent-of-code-2023/blob/main/day22/lib/day22.ex
Most Liked
lud
Ok so finally finished it.. I’m slow ![]()
It takes 28 seconds for each part, mostly to make all the bricks fall. Summing each chain reaction takes 1 second.
I’m all ears for a better falling algorithm if anyone reads this ever ![]()
Edit, ok, got it under 2sec for part 1 and 2.5 for part 2, thanks to this monstruosity:
defp on_top_of?(
{_, {xdeb, ydeb, zdeb}, {xfin, yfin, zfin}},
{_, {xbotdeb, ybotdeb, zbotdeb}, {xbotfin, ybotfin, zbotfin}}
)
when (zdeb == zbotdeb + 1 or
zfin == zbotfin + 1 or
zdeb == zbotfin + 1 or
zfin == zbotdeb + 1) and
(xdeb in xbotdeb..xbotfin or
xfin in xbotdeb..xbotfin or
xbotdeb in xdeb..xfin or
xbotfin in xdeb..xfin) and
(ydeb in ybotdeb..ybotfin or
yfin in ybotdeb..ybotfin or
ybotdeb in ydeb..yfin or
ybotfin in ydeb..yfin) do
true
end
defp on_top_of?(_, _) do
false
end
I LOVE that it is possible to do basic math in guards
midouest
Got stuck on part 1 for a couple days and could not figure out what I was doing wrong. My code was working on all the examples I could find, but no the puzzle input. This morning I decided I would try to visualize the stack using three.js and a custom Kino:

The z-fighting between the bricks revealed that my stacking algorithm was off. I thought that I could sort the bricks and then just use Enum.find to take the first intersecting brick that was settled. I actually needed to take the intersecting brick with the maximum z position.
Part 1 runs in 0.3s and part 2 runs in 0.8s.
Part 1
defmodule Part1 do
def to_alpha(i), do: to_alpha(i, [])
def to_alpha(i, acc) when i < 26, do: to_string([rem(i, 26) + 65 | acc])
def to_alpha(i, acc), do: to_alpha(div(i, 26) - 1, [rem(i, 26) + 65 | acc])
def parse(input) do
for {line, i} <- String.split(input, "\n", trim: true) |> Enum.with_index() do
{x, y, z} =
String.split(line, [",", "~"], trim: true)
|> Enum.map(&String.to_integer/1)
|> Enum.chunk_every(3)
|> Enum.zip_with(fn [a, b] -> Range.new(min(a, b), max(a, b)) end)
|> List.to_tuple()
{to_alpha(i), x, y, z}
end
end
def disjoint?(bricks) do
Enum.all?(bricks, fn {_, ax, ay, az} = brick ->
Enum.filter(bricks, fn other -> other != brick end)
|> Enum.all?(fn {_, bx, by, bz} ->
Range.disjoint?(ax, bx) or
Range.disjoint?(ay, by) or
Range.disjoint?(az, bz)
end)
end)
end
def drop(bricks),
do:
bricks
|> Enum.sort_by(fn {_, _, _, z1.._} -> z1 end)
|> drop([])
def drop([], settled), do: settled
def drop([{_, _, _, 1.._} = brick | bricks], settled),
do: drop(bricks, [brick | settled])
def drop([{id, ax, ay, az} | bricks], settled) do
{_, _, _, _..bz2} =
Enum.filter(settled, fn {_, bx, by, _} ->
not (Range.disjoint?(ax, bx) or
Range.disjoint?(ay, by))
end)
|> Enum.max_by(
fn {_, _, _, _..bz2} -> bz2 end,
fn -> {nil, nil, nil, 0..0} end
)
az1 = bz2 + 1
az2 = bz2 + Range.size(az)
brick = {id, ax, ay, az1..az2}
drop(bricks, [brick | settled])
end
def support(bricks), do: support(bricks, bricks, %{})
def support([], _, supports), do: supports
def support([{_, ax, ay, az1.._} = current | rest], bricks, supports) do
others =
Enum.filter(bricks, fn {_, bx, by, _..bz2} ->
bz2 == az1 - 1 and
not (Range.disjoint?(ax, bx) or
Range.disjoint?(ay, by))
end)
supports =
Map.update(
supports,
current,
%{is_above: others, is_below: []},
fn %{is_above: above} = existing ->
%{existing | is_above: others ++ above}
end
)
supports =
Enum.reduce(others, supports, fn other, supports ->
Map.update(
supports,
other,
%{is_above: [], is_below: [current]},
fn %{is_below: below} = existing ->
%{existing | is_below: [current | below]}
end
)
end)
support(rest, bricks, supports)
end
def free(supports) do
required =
supports
|> Enum.filter(fn {_, %{is_above: is_above}} -> length(is_above) == 1 end)
|> Enum.flat_map(fn {_, %{is_above: is_above}} -> is_above end)
|> MapSet.new()
supports
|> Map.keys()
|> MapSet.new()
|> MapSet.difference(required)
|> Enum.to_list()
end
end
bricks =
input
|> Part1.parse()
|> Part1.drop()
bricks
|> Part1.disjoint?()
|> IO.inspect(label: "disjoint?")
bricks
|> KinoThree.new(z: 15, y: 0, w: 640, h: 480)
|> Kino.render()
bricks
|> Part1.support()
|> Part1.free()
|> length()
Part 2
defmodule Part2 do
def disintegrate(supports) do
supports
|> Map.keys()
|> Enum.map(fn brick ->
brick
|> chain(supports)
|> MapSet.size()
end)
|> Enum.sum()
end
def chain(brick, supports), do: chain([brick], supports, MapSet.new())
def chain([], _, acc), do: acc
def chain([brick | rest], supports, acc) do
destroyed =
supports[brick].is_below
|> Enum.filter(fn above_brick ->
below_bricks = supports[above_brick].is_above
length(below_bricks) == 1 or
Enum.all?(below_bricks, fn below -> MapSet.member?(acc, below) end)
end)
acc =
destroyed
|> MapSet.new()
|> MapSet.union(acc)
chain(rest ++ destroyed, supports, acc)
end
end
input
|> Part1.parse()
|> Part1.drop()
|> Part1.support()
|> Part2.disintegrate()
Kino + three.js
defmodule KinoThree do
use Kino.JS
def new(bricks, opts) do
bricks =
for {id, x, y, z} <- bricks do
%{
id: id,
x: x.first,
y: z.first,
z: y.first,
w: Range.size(x),
h: Range.size(z),
d: Range.size(y)
}
end
%{y: y, h: h} = Enum.max_by(bricks, fn %{y: y, h: h} -> y + h end)
max_y = y + h
z = Keyword.get(opts, :z, 5)
y = Keyword.get(opts, :y, 0)
w = Keyword.get(opts, :w, 640)
h = Keyword.get(opts, :h, 480)
opts = %{bricks: bricks, max_y: max_y, z: z, y: y, w: w, h: h}
Kino.JS.new(__MODULE__, opts)
end
asset "main.js" do
"""
import * as THREE from "https://unpkg.com/three@0.160.0/build/three.module.min.js";
export function init(ctx, opts) {
const renderer = new THREE.WebGLRenderer();
renderer.setSize(opts.w, opts.h);
ctx.root.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, opts.w / opts.h, 0.1, 1000);
for (const {id, x, y, z, w, h, d} of opts.bricks) {
const geometry = new THREE.BoxGeometry(w, h, d);
const color = new THREE.Color(Math.random(), Math.random(), Math.random());
const material = new THREE.MeshBasicMaterial({ color });
const mesh = new THREE.Mesh(geometry, material);
mesh.position.x = x + w / 2;
mesh.position.y = y + h / 2;
mesh.position.z = z + d / 2;
scene.add(mesh);
}
let dy = opts.max_y / 1000;
camera.position.y = opts.y;
camera.position.z = opts.z;
function animate() {
requestAnimationFrame(animate);
scene.rotation.y += 0.01;
camera.position.y += dy;
if (camera.position.y > opts.max_y) {
camera.position.y = opts.max_y;
dy = -dy;
} else if (camera.position.y < 0) {
camera.position.y = 0;
dy = -dy;
}
renderer.render(scene, camera);
}
animate();
}
"""
end
end
code-shoily
Such a beautiful set of LoCs! Thank you for this, helped me quite a bit!
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









