-------------| -- Cleanup --| -------------| drop table if exists input; drop table if exists rolls; drop function if exists part_2; -----------| -- Setup --| -----------| create unlogged table input ( y serial primary key, value text ); \copy input (value) from 'input/2025/4.txt'; create unlogged table rolls ( x int not null, y int not null, primary key (x, y) ); -----------------| -- Parse input --| -----------------| insert into rolls (x, y) select col.x, row.y from input as row cross join lateral unnest(string_to_array(row.value, null)) with ordinality as col (char, x) where col.char = '@'; ------------| -- Part 1 --| ------------| select count(*) as part_1 from rolls as pos where ( select count(*) as count from rolls as neighbor where neighbor.x between pos.x - 1 and pos.x + 1 and neighbor.y between pos.y - 1 and pos.y + 1 and not (pos.x = neighbor.x and pos.y = neighbor.y) ) < 4; ------------| -- Part 2 --| ------------| -- Let's get procedural baybey -- I tried for a long time to get this into a recursive CTE, -- but couldn't find a way around the ban on outer joins -- on the recursive table create function part_2() returns int as $$ declare removed_total int = 0; removed_round int = 1; begin while removed_round > 0 loop delete from rolls as pos where ( select count(*) as count from rolls as neighbor where neighbor.x between pos.x - 1 and pos.x + 1 and neighbor.y between pos.y - 1 and pos.y + 1 and not (pos.x = neighbor.x and pos.y = neighbor.y) ) < 4; get diagnostics removed_round = row_count; removed_total = removed_total + removed_round; end loop; return removed_total; end; $$ language plpgsql; select part_2();