Advent of Code 2023 - Day 22
Mix . install ( [
{ :req , "~> 0.4.0" } ,
{ :libgraph , "~> 0.16.0" }
] )
opts = [ headers: [ { "cookie" , "session=#{ System . fetch_env! ( "LB_AOC_SESSION" ) } " } ] ]
puzzle_input = Req . get! ( "https://adventofcode.com/2023/day/22/input" , opts ) . body
input =
for row <- String . split ( puzzle_input , "\n " , trim: true ) do
String . split ( row , [ "," , "~" ] )
|> Enum . map ( & String . to_integer / 1 )
end
|> Enum . sort_by ( fn [ _ , _ , z1 , _ , _ , z2 ] -> min ( z1 , z2 ) end )
|> Enum . with_index ( )
defmodule SandSlabs do
def fall ( falling , fallen , downs \\ % { } ) do
case tick ( falling , fallen , downs ) do
{ [ ] , all_fallen , downs } -> { Enum . reverse ( all_fallen ) , downs }
{ still_falling , already_fallen , downs } -> fall ( still_falling , already_fallen , downs )
end
end
defp tick ( [ first | rest ] , fallen , downs ) do
if is_fallen? ( first , fallen ) do
{ rest , [ first | fallen ] , downs }
else
{ [ down ( first ) | rest ] , fallen , inc ( downs , first ) }
end
end
defp inc ( downs , { _ , i } ) do
Map . update ( downs , i , 1 , & ( & 1 + 1 ) )
end
defp down ( { [ x1 , y1 , z1 , x2 , y2 , z2 ] , i } ) do
{ [ x1 , y1 , z1 - 1 , x2 , y2 , z2 - 1 ] , i }
end
defp is_fallen? ( { [ _ , _ , z1 , _ , _ , z2 ] , _i } , _fallen ) when z1 == 0 or z2 == 0 do
true
end
defp is_fallen? ( a , fallen ) do
Enum . any? ( fallen , fn b -> touches? ( a , b ) end )
end
def touches? ( { [ xa1 , ya1 , za1 , xa2 , ya2 , za2 ] , _ } , { [ xb1 , yb1 , zb1 , xb2 , yb2 , zb2 ] , _i } ) do
( za1 == zb1 + 1 or za2 == zb2 + 1 or za1 == zb2 + 1 or za2 == zb1 + 1 ) and
( xa1 in xb1 .. xb2 or xa2 in xb1 .. xb2 or xb1 in xa1 .. xa2 or xb2 in xa1 .. xa2 ) and
( ya1 in yb1 .. yb2 or ya2 in yb1 .. yb2 or yb1 in ya1 .. ya2 or yb2 in ya1 .. ya2 )
end
end
{ fallen_bricks , _downs } = SandSlabs . fall ( input , [ ] )
init_graph =
for { v , i } <- fallen_bricks , reduce: Graph . new ( ) do
g -> Graph . add_vertex ( g , { v , i } )
end
graph =
for { a , i } <- Graph . vertices ( init_graph ) ,
{ b , j } <- Graph . vertices ( init_graph ) ,
a != b ,
SandSlabs . touches? ( { b , j } , { a , i } ) ,
reduce: init_graph do
g -> Graph . add_edge ( g , { a , i } , { b , j } )
end
graph
|> Graph . vertices ( )
|> Stream . filter ( fn lower ->
to_upper = Graph . out_edges ( graph , lower )
if length ( to_upper ) == 0 do
true
else
to_upper
|> Stream . map ( fn % { v2: v2 } -> Graph . in_edges ( graph , v2 ) end )
|> Stream . map ( & length / 1 )
|> Enum . all? ( & ( & 1 > 1 ) )
end
end )
|> Enum . count ( )
0 .. length ( fallen_bricks )
|> Stream . map ( fn i ->
fallen_bricks
|> List . pop_at ( i )
|> then ( fn { _ , rest } -> rest end )
|> SandSlabs . fall ( [ ] )
|> then ( fn { _bricks , downs } -> Enum . count ( downs ) end )
end )
|> Enum . sum ( )