Day 4: Printing Department

Megathread guidelines

  • Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
  • You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL

FAQ

all 28 comments

sorted by: hot top controversial new old
[–] 5 points 10 months ago* (last edited 10 months ago) (5 children)

Uiua

Suspiciously easy. I even included a free animation generator for your entertainment.

"..@@.@@@@.\n@@@.@.@.@@\n@@@@@.@.@@\n@.@@@@..@.\n@@.@@@@.@@\n.@@@@@@@.@\n.@.@.@.@@@\n@.@@@.@@@@\n.@@@@@@@@.\n@.@.@@@.@."
# You can run against your own input by dragging your file
# onto this pane and uncommenting the line below.
# &fras"day4.txt" # edit to match the filename.
⊜(=@@)βŠΈβ‰ @\n
Nβ‚ˆ ← βŠ‚Aβ‚‚Cβ‚‚
R  ← Γ—<4⊸(/+⬚0↻)Nβ‚ˆ
P₁ ← /+β™­R
Pβ‚‚ ← /+β™­-⊸β₯(-⊸R)∞
P₃ ← β₯(▽₃10)<1e6/Γ—βŠΈβ–³β₯⊸(-⊸R)∞
βŠƒ(P₁|Pβ‚‚|P₃)

  • source
  • hideshow 5 child comments
  • [–] 2 points 10 months ago (1 child)

    That's a great addition :D

    Running my own input I also noticed that your solution is a lot faster than mine (processing each roll individually). I'll keep that 2D-rotation in mind for the future.

  • source
  • parent
  • hideshow 1 child comment
  • [–] 1 point 10 months ago

    Yeah, that's one thing the gurus keep hammering home: anything you can move out of loop constructs (inc rows, partition, etc as well as the obvious do, repeat) and handle pervasively is a big win.

  • source
  • parent
  • [–] 4 points 10 months ago

    Haskell

    I tried rewriting part 2 to use a MutableArray, but it only made everything slower. So I left it at this. I saw somebody do a 1-second-challenge last year and I feel like that will be very hard unless I up my performance game.

    Solution, Both Parts

    {-# LANGUAGE OverloadedStrings #-}
    {-# OPTIONS_GHC -Wall #-}
    module Main (main) where
    import qualified Data.Text as Text
    import Data.Array.Unboxed (UArray)
    import qualified Data.Array.IArray as Array
    import qualified Data.List as List
    import Control.Monad ((<$!>), guard)
    import qualified Data.Text.IO as TextIO
    import Data.Maybe (fromMaybe)
    import Control.Arrow ((&&&))
    
    parse :: Text.Text -> UArray (Int, Int) Bool
    parse t = let
        gridLines = init $ Text.lines t
        lineSize = maybe 0 pred $ Text.findIndex (== '\n') t
        lineCount = Text.count "\n" t - 2
      in Array.listArray ((0, 0), (lineCount, lineSize)) $ List.concatMap (fmap (== '@') . Text.unpack) gridLines
    
    neighbors8 :: (Int, Int) -> [(Int, Int)]
    neighbors8 p@(x, y) = do
      x' <- [pred x .. succ x]
      y' <- [pred y .. succ y]
      let p' = (x', y')
      guard (p /= p')
      pure p'
    
    main :: IO ()
    main = do
      grid <- parse <$!> TextIO.getContents
      print $ part1 grid
      print $ part2 grid
    
    part2 :: UArray (Int, Int) Bool -> Int
    part2 grid = case accessiblePositions grid of
      [] -> 0
      xs -> List.length xs + part2 (grid Array.// fmap (id &&& const False) xs)
    
    part1 :: UArray (Int, Int) Bool -> Int
    part1 = List.length . accessiblePositions
    
    accessiblePositions :: UArray (Int, Int) Bool -> [(Int, Int)]
    accessiblePositions grid = let
         lookupPosition = fromMaybe False . (grid Array.!?)
         positions = Array.indices grid
         paperRollPositions = List.filter lookupPosition positions
         isPositionAccessible = (< 4) . List.length . List.filter lookupPosition . neighbors8
       in List.filter isPositionAccessible paperRollPositions
    

  • source
  • [–] 3 points 10 months ago* (last edited 10 months ago)

    Python

    Simple brute-force is enough.

    DIRECTIONS = [
        (0, 1), (1, 0), (0, -1), (-1, 0),   # cardinal
        (1, 1), (1, -1), (-1, 1), (-1, -1)  # diagonal
    ]
    # yield all valid neighbor coordinates
    def yield_neighbors(i, j, m, n):
        for di, dj in DIRECTIONS:
            ni, nj = i+di, j+dj
            if 0 <= ni < m and 0 <= nj < n:
                yield ni, nj
    
    # build char grid from input data
    def build_grid(data: str):
        return [list(row) for row in data.splitlines()]
    
    def part1(data: str):
        grid = build_grid(data)
        m, n = len(grid), len(grid[0])
    
        # count accessible rolls
        accessible = 0
        for i in range(m):
            for j in range(n):
                if grid[i][j] != '@':
                    continue
    
                neighbor_rolls = sum(grid[ni][nj] == '@' for ni, nj in yield_neighbors(i, j, m, n))
                if neighbor_rolls < 4:
                    accessible += 1
        return accessible
    
    def part2(data: str):
        grid = build_grid(data)
        m, n = len(grid), len(grid[0])
    
        total_accessible = 0    # total accessible rolls over all cycles
        accessible = None       # rolls accessible this cycle
    
        # repeat until no more rolls can be accessed
        while accessible != 0:
            accessible = 0
            for i in range(m):
                for j in range(n):
                    if grid[i][j] != '@':
                        continue
    
                    neighbor_rolls = sum(grid[ni][nj] == '@' for ni, nj in yield_neighbors(i, j, m, n))
                    if neighbor_rolls < 4:
                        accessible += 1
                        # we can immediately remove this roll, no need to wait for next cycle
                        grid[i][j] = '.'
            
            # accumulate accessible rolls this cycle
            total_accessible += accessible
        return total_accessible
    
    sample = """<paste sample here>"""
    assert part1(sample) == 13
    assert part2(sample) == 43
    
  • source
  • [–] 3 points 10 months ago

    Nim

    type
      AOCSolution[T,U] = tuple[part1: T, part2: U]
      Vec2 = tuple[x,y: int]
    
    proc removePaper(rolls: var seq[string]): int =
      var toRemove: seq[Vec2]
      for y, line in rolls:
        for x, c in line:
          if c != '@': continue
          var adjacent = 0
          for (dx, dy) in [(-1,-1),(0,-1),(1,-1),
                           (-1, 0),       (1, 0),
                           (-1, 1),(0, 1),(1, 1)]:
            let pos: Vec2 = (x+dx, y+dy)
            if pos.x < 0 or pos.x >= rolls[0].len or
               pos.y < 0 or pos.y >= rolls.len: continue
            if rolls[pos.y][pos.x] == '@': inc adjacent
    
          if adjacent < 4:
            inc result
            toRemove.add (x, y)
    
      for (x, y) in toRemove: rolls[y][x] = '.'
    
    proc solve(input: string): AOCSolution[int, int] =
      var rolls = input.splitLines()
      result.part1 = rolls.removePaper()
      result.part2 = result.part1
      while (let cnt = rolls.removePaper(); result.part2 += cnt; cnt) > 0:
        discard
    

    Today was so easy, that I decided to solve it twice, just for fun. First is a 2D traversal (see above). And then I did a node graph solution in a few minutes (in repo below). Both run in ~27 ms.

    It's a bit concerning, because a simple puzzle can only mean that tomorrow will be a nightmare. Good Luck everyone, we will need it.

    Full solution is at Codeberg: solution.nim

  • source
  • [–] 3 points 10 months ago

    Haskell

    Very simple, this one.

    import Data.List  
    import Data.Set qualified as Set  
    
    readInput s =  
      Set.fromDistinctAscList  
        [ (i, j) :: (Int, Int)  
          | (i, l) <- zip [0 ..] (lines s),  
            (j, c) <- zip [0 ..] l,  
            c == '@'  
        ]  
    
    accessible ps = Set.filter ((< 4) . adjacent) ps  
      where  
        adjacent (i, j) =  
          length . filter (`Set.member` ps) $  
            [ (i + di, j + dj)  
              | di <- [-1 .. 1],  
                dj <- [-1 .. 1],  
                (di, dj) /= (0, 0)  
            ]  
    
    main = do  
      input <- readInput <$> readFile "input04"  
      let removed =  
            (`unfoldr` input) $  
              \ps ->  
                case accessible ps of  
                  d  
                    | Set.null d -> Nothing  
                    | otherwise -> Just (Set.size d, ps Set.\\ d)  
      print $ head removed  
      print $ sum removed  
    
  • source
  • [–] 3 points 10 months ago

    C

    For loops!

    Code

    #include <stdio.h>
    
    #define GZ 144
    
    static char g[GZ][GZ];
    
    int
    main()
    {
    	int p1=0,p2=0, nc=0, x,y;
    
    	for (y=1; fgets(g[y]+1, GZ-2, stdin); y++)
    		;
    
    	for (y=1; y<GZ-1; y++)
    	for (x=1; x<GZ-1; x++)
    		p1 += g[y][x] == '@' &&
    		      (g[y-1][x-1] == '@') +
    		      (g[y-1][x  ] == '@') +
    		      (g[y-1][x+1] == '@') +
    		      (g[y  ][x-1] == '@') +
    		      (g[y  ][x+1] == '@') +
    		      (g[y+1][x-1] == '@') +
    		      (g[y+1][x  ] == '@') +
    		      (g[y+1][x+1] == '@') < 4;
    
    	do {
    		nc = 0;
    
    		for (y=1; y<GZ-1; y++)
    		for (x=1; x<GZ-1; x++)
    			if (g[y][x] == '@' &&
    			    (g[y-1][x-1] == '@') +
    			    (g[y-1][x  ] == '@') +
    			    (g[y-1][x+1] == '@') +
    			    (g[y  ][x-1] == '@') +
    			    (g[y  ][x+1] == '@') +
    			    (g[y+1][x-1] == '@') +
    			    (g[y+1][x  ] == '@') +
    			    (g[y+1][x+1] == '@') < 4) {
    				nc++;
    				p2++;
    				g[y][x] = '.';
    			}
    	} while (nc);
    
    	printf("04: %d %d\n", p1, p2);
    	return 0;
    }
    

    Repo

    For my x86-16 version, the 20K input is pushing it over the 64K .COM limit, so I'll need to implement some better compression first.

  • source
  • [–] 3 points 10 months ago* (last edited 10 months ago)

    Rust

    I pulled out some code from last year to make representing 2D grids as a vector easier, so this was quite straightforward. 2.5ms runtime (including reading/parsing the input twice cos of TDD).

    impl Grid {
        fn neighbour(&self, i: usize, dir: Direction) -> Option<bool> {
            let width = self.width;
            let length = self.grid.len();
    
            use Direction::*;
            match dir {
                N if i >= width => Some(i - width),
                NE if i >= width && i % width != width - 1 => Some(i - width + 1),
                E if i % width != width - 1 => Some(i + 1),
                SE if i + width + 1 < length && i % width != width - 1 => Some(i + width + 1),
                S if i + width < length => Some(i + width),
                SW if i + width - 1 < length && !i.is_multiple_of(width) => Some(i + width - 1),
                W if !i.is_multiple_of(width) => Some(i - 1),
                NW if i >= width && !i.is_multiple_of(width) => Some(i - width - 1),
                _ => None,
            };
            .map(|i| self.grid[i])
        }
    
        #[rustfmt::skip]
        fn cell_accessible(&self, i: usize) -> bool {
            Direction::ALL
                .iter()
                .filter(|&&dir| self.neighbour(i, dir).unwrap_or(false))
                .count() < 4
        }
    
        fn num_accessible(&self) -> usize {
            self.grid
                .iter()
                .enumerate()
                .filter(|&(i, &is_occupied)| is_occupied && self.cell_accessible(i))
                .count()
        }
    
        fn remove_accessible(&mut self) -> Option<usize> {
            let removables = self
                .grid
                .iter()
                .enumerate()
                .filter_map(|(i, &is_occupied)| (is_occupied && self.cell_accessible(i)).then_some(i))
                .collect::<Vec<_>>();
    
            let count = removables.len();
            if count > 0 {
                for idx in removables {
                    self.grid[idx] = false;
                }
                Some(count)
            } else {
                None
            }
        }
    
        fn remove_recursive(&mut self) -> usize {
            let mut total_removed = 0;
            while let Some(removed) = self.remove_accessible() {
                total_removed += removed;
            }
            total_removed
        }
    }
    

    ::: spoiler Full code


    use std::{fs, str::FromStr};
    
    use color_eyre::eyre::{Report, Result};
    
    #[derive(Debug, Copy, Clone)]
    enum Direction {
        N, NE, E, SE, S, SW, W, NW,
    }
    
    impl Direction {
        const ALL: [Direction; 8] = [
            Direction::N,
            Direction::NE,
            Direction::E,
            Direction::SE,
            Direction::S,
            Direction::SW,
            Direction::W,
            Direction::NW,
        ];
    }
    
    #[derive(Debug, PartialEq, Eq, Clone)]
    struct Grid {
        grid: Vec<bool>,
        width: usize,
        height: usize,
    }
    
    impl FromStr for Grid {
        type Err = Report;
    
        fn from_str(s: &str) -> Result<Self, Self::Err> {
            let grid: Vec<_> = s
                .chars()
                .filter_map(|ch| match ch {
                    '.' => Some(false),
                    '@' => Some(true),
                    '\n' => None,
                    _ => panic!("Invalid input"),
                })
                .collect();
            let width = s
                .chars()
                .position(|ch| ch == '\n')
                .ok_or_else(|| Report::msg("grid width cannot be zero, or one line"))?;
            let height = grid.len() / width;
            Ok(Self {
                grid,
                width,
                height,
            })
        }
    }
    
    impl Grid {
        fn neighbour(&self, i: usize, dir: Direction) -> Option<bool> {
            let width = self.width;
            let length = self.grid.len();
    
            use Direction::*;
            match dir {
                N if i >= width => Some(i - width),
                NE if i >= width && i % width != width - 1 => Some(i - width + 1),
                E if i % width != width - 1 => Some(i + 1),
                SE if i + width + 1 < length && i % width != width - 1 => Some(i + width + 1),
                S if i + width < length => Some(i + width),
                SW if i + width - 1 < length && !i.is_multiple_of(width) => Some(i + width - 1),
                W if !i.is_multiple_of(width) => Some(i - 1),
                NW if i >= width && !i.is_multiple_of(width) => Some(i - width - 1),
                _ => None,
            };
            .map(|i| self.grid[i])
        }
    
        #[rustfmt::skip]
        fn cell_accessible(&self, i: usize) -> bool {
            Direction::ALL
                .iter()
                .filter(|&&dir| self.neighbour(i, dir).unwrap_or(false))
                .count() < 4
        }
    
        fn num_accessible(&self) -> usize {
            self.grid
                .iter()
                .enumerate()
                .filter(|&(i, &is_occupied)| is_occupied && self.cell_accessible(i))
                .count()
        }
    
        fn remove_accessible(&mut self) -> Option<usize> {
            let removables = self
                .grid
                .iter()
                .enumerate()
                .filter_map(|(i, &is_occupied)| (is_occupied && self.cell_accessible(i)).then_some(i))
                .collect::<Vec<_>>();
    
            let count = removables.len();
            if count > 0 {
                for idx in removables {
                    self.grid[idx] = false;
                }
                Some(count)
            } else {
                None
            }
        }
    
        fn remove_recursive(&mut self) -> usize {
            let mut total_removed = 0;
            while let Some(removed) = self.remove_accessible() {
                total_removed += removed;
            }
            total_removed
        }
    }
    
    fn part1(filepath: &str) -> Result<usize> {
        let input = fs::read_to_string(filepath)?;
        let grid = Grid::from_str(&input)?;
        Ok(grid.num_accessible())
    }
    
    fn part2(filepath: &str) -> Result<usize> {
        let input = fs::read_to_string(filepath)?;
        let mut grid = Grid::from_str(&input)?;
        Ok(grid.remove_recursive())
    }
    
    fn main() -> Result<()> {
        color_eyre::install()?;
    
        println!("Part 1: {}", part1("d04/input.txt")?);
        println!("Part 2: {}", part2("d04/input.txt")?);
        Ok(())
    }
    
  • source
  • [–] 3 points 10 months ago

    Turns out on part 2 you can remove on access rather than after a full sweep of the grid, which cuts down the number of iterations you need to do about 1/2 sometimes 1/3 (depending on input).

    import itertools as it
    from pathlib import Path
    
    import numpy as np
    
    cwd = Path(__file__).parent.resolve()
    
    
    def parse_input(file_path):
      with file_path.open("r") as fp:
        grid = np.array(list(map(list, list(map(str.strip, fp.readlines())))),
                        dtype=str)
      return grid
    
    
    def solve_problem(file_name, single_attempt=True):
    
      grid = parse_input(Path(cwd, file_name))
      nr, nc = grid.shape
      nacc_total = 0
      stop = False
    
      while not stop:
    
        nacc = 0
    
        for i,j in it.product(range(nr), range(nc)):
    
          if grid[i,j] != '@':
            continue
    
          if np.count_nonzero(grid[max(i-1, 0):min(i+2, nr),
                                   max(j-1, 0):min(j+2, nc)] == '@')<5:
            nacc += 1
    
            if not single_attempt:
              grid[i,j] = '.'
    
        nacc_total += nacc
    
        if nacc==0 or single_attempt:
          stop = True
    
      return nacc_total
    
  • source
  • [–] 3 points 10 months ago (2 children)

    Kotlin

    Pretty simple solution, just plain count / remove the rolls until none can be removed anymore. I would've liked to try using imaginary numbers this year (due to this article), but sadly Kotlin doesn't natively support them and I was too lazy to use a library.

    Solution

    class Day04 : Puzzle {
    
        val grid = mutableListOf<MutableList<Char>>()
    
        override fun readFile() {
            val input = readInputFromFile("src/main/resources/a2025/day04.txt")
            for ((r, line) in input.lines().filter(String::isNotBlank).withIndex()) {
                grid.add(mutableListOf())
                for (char in line) {
                    grid[r].add(char)
                }
            }
        }
    
        override fun solvePartOne(): String {
            return countRolls(grid).toString()
        }
    
        override fun solvePartTwo(): String {
            var sum = 0
            var removed = -1
            while (removed != 0) {
                // main grid is modified here, not the best but doesn't really matter
                // also no idea how to clone 2D list in Kotlin
                removed = countRolls(grid, true)
                sum += removed
            }
            return sum.toString()
        }
    
        private fun countRolls(grid: MutableList<MutableList<Char>>, removeRolls: Boolean = false): Int {
            val dr = listOf(-1, -1, -1, 0, 0, 1, 1, 1)
            val dc = listOf(-1, 0, 1, -1, 1, -1, 0, 1)
    
            var sum = 0
            for (r in grid.indices) {
                for (c in grid[r].indices) {
                    if (grid[r][c] != '@') continue
    
                    var neighborCount = 0
                    for (i in dr.indices) {
                        if (gridGet(grid, r + dr[i], c + dc[i]) == '@') neighborCount++
                    }
                    if (neighborCount < 4) {
                        sum++
                        if (removeRolls) grid[r][c] = '.'
                    }
                }
            }
            return sum
        }
    
        private fun gridGet(grid: List<List<Char>>, r: Int, c: Int, default: Char = '.'): Char {
            return if (r in grid.indices && c in grid[r].indices) {
                grid[r][c]
            } else {
                default
            }
        }
    }
    

    full code on Codeberg

  • source
  • hideshow 2 child comments
  • [–] 2 points 10 months ago*

    Edit: looking at your code, I had forgotten about .indices. That would have made this a little easier to write.

    I completely forgot to do the puzzle yesterday somehow. I struggled a bit on this one for a while because I'd used a <= 4 where I should have used a < 4. Just a complete brainfart of thinking, "It needs to be 4 or less". I wasted more time on that than I'd like to admit.

    My first stab at this set all of the adjacency counts to 0, and that lead to a few rolls that had no rolls adjacent to them staying on the map by accident.

    const val toiletPaperRoll = '@'
    const val adjacentLimit = 4
    var height = 0
    var width = 0
    
    fun main() {
        val input = getInput(4)
        val map = parseInput1(input)
        height = map.size
        width = map[0].size
        val adjacencyMap = createAdjacencyMap(map)
        var anyRemoved = true
        var count = 0
        while (anyRemoved) {
            anyRemoved = false
            for (y in 0..<height) {
                for (x in 0..<width) {
                    if (adjacencyMap[y][x] in 0..<adjacentLimit) {
                        count++
                        anyRemoved = true
                        removeToiletPaperRoll(adjacencyMap, x, y)
                    }
                }
            }
        }
        println(count)
    }
    
    fun parseInput1(input: String): List<List<Char>> = input
        .lines()
        .filter { it.isNotBlank() }
        .map { it.toCharArray().toList() }
    
    fun createAdjacencyMap(map: List<List<Char>>): List<MutableList<Int>> {
        val adjacencyMap = List(height) { MutableList(width) { -1 } }
        for (y in 0..<height) {
            for (x in 0..<width) {
                if (map[y][x] != toiletPaperRoll) {
                    continue
                }
                adjacencyMap[y][x]++
                for (y2 in y - 1..y + 1) {
                    for (x2 in x - 1..x + 1) {
                        if (y == y2 && x == x2 || (y2 < 0 || x2 < 0 || y2 >= height || x2 >= width)) {
                            continue
                        }
                        if (map[y2][x2] == toiletPaperRoll) {
                            adjacencyMap[y2][x2]++
                        }
                    }
                }
            }
        }
        return adjacencyMap
    }
    
    fun removeToiletPaperRoll(adjacencyMap: List<MutableList<Int>>, x: Int, y: Int) {
        adjacencyMap[y][x] = -1
        for (y2 in y - 1..y + 1) {
            for (x2 in x - 1..x + 1) {
                if (y == y2 && x == x2 || (y2 < 0 || x2 < 0 || y2 >= height || x2 >= width)) {
                    continue
                }
                if (adjacencyMap[y2][x2] >= adjacentLimit) {
                    adjacencyMap[y2][x2]--
                }
            }
        }
    }
    
  • source
  • parent
  • [–] 3 points 10 months ago

    This is the first day I've wished I were working in J, like last year, instead of Lisp. Common Lisp arrays show the age of the language design. They're basically C arrays with less convenient syntax; if you want higher-order array iteration you have to write it yourself or use a package that provides it. This solution is also less efficient than it could be for part 2, because I recompute the full neighbors array on each iteration rather than updating it incrementally.

    (defun read-inputs (filename)
      (let* ((input-lines (uiop:read-file-lines filename))
             (rows (length input-lines))
             (cols (length (car input-lines)))
             (result (make-array (list rows cols) :initial-element 0)))
        (loop for line in input-lines
              for y from 0 to (1- rows)
              do (loop for x from 0 to (1- cols)
                       if (eql (char line x) #\@)
                         do (setf (aref result y x) 1)))
        result))
    
    (defun neighbors (grid)
      (let* ((dimensions (array-dimensions grid))
             (rows (car dimensions))
             (cols (cadr dimensions))
             (result (make-array dimensions :initial-element 0)))
        (flet ((neighbors-at (y x)
                 (loop for dy from -1 to 1
                       sum (loop for dx from -1 to 1
                                 sum (let ((yy (+ y dy))
                                           (xx (+ x dx)))
                                       (if (and (>= yy 0)
                                                (< yy rows)
                                                (>= xx 0)
                                                (< xx cols)
                                                (not (and (= dx 0) (= dy 0)))
                                                (> (aref grid yy xx) 0))
                                           1
                                           0))))))
          (loop for y from 0 to (1- rows)
                do (loop for x from 0 to (1- cols)
                         do (setf (aref result y x) (neighbors-at y x))))
          result)))
    
    (defun main-1 (filename)
      (let* ((grid (read-inputs filename))
             (dimensions (array-dimensions grid))
             (neighbors-grid (neighbors grid)))
        (loop for y from 0 to (1- (car dimensions))
              sum (loop for x from 0 to (1- (cadr dimensions))
                        sum (if (and (> (aref grid y x) 0)
                                     (< (aref neighbors-grid y x) 4))
                                1
                                0)))))
    
    (defun remove-accessible (grid)
      (let* ((dimensions (array-dimensions grid))
             (neighbors-grid (neighbors grid))
             (removed 0))
        (loop for y from 0 to (1- (car dimensions))
              do (loop for x from 0 to (1- (cadr dimensions))
                       do (if (and (> (aref grid y x) 0)
                                   (< (aref neighbors-grid y x) 4))
                              (progn
                                (setf (aref grid y x) 0)
                                (incf removed)))))
        removed))
    
    (defun main-2 (filename)
      (let ((grid (read-inputs filename))
            (removed 0))
        (loop for this-removed = (remove-accessible grid)
              until (zerop this-removed)
              do (incf removed this-removed))
        removed))
    
  • source
  • [–] 2 points 10 months ago

    Python

    Send in the object orientation!
    Honestly though, it was just a convenient way to keep things contained.

    from pathlib import Path
    
    
    class Grid:
        def __init__(self, input: str) -> None:
            self.grid = list(map(lambda l: list(map(lambda c: 1 if c == "@" else 0, l)), input.splitlines()))
            self.dims = (len(self.grid[0]), len(self.grid))
    
        def get(self, x: int, y: int) -> int:
            if x < 0 or x >= self.dims[0] or y < 0 or y >= self.dims[1]:
                return 0
            return self.grid[x][y]
    
        def set(self, x: int, y: int, value: int):
            self.grid[x][y] = value
    
        def is_accessible(self, x: int, y: int) -> bool:
            if self.get(x, y):
                return sum(map(lambda t: self.get(*t), [(ix, iy) for ix in range(x - 1, x + 2) for iy in range(y - 1, y + 2)])) - 1 < 4
            return False
    
    
    def part_one(input: str) -> int:
        grid = Grid(input)
        return len(list(filter(None, map(lambda t: grid.is_accessible(*t), [(x, y) for x in range(grid.dims[0]) for y in range(grid.dims[1])]))))
    
    
    def part_two(input: str) -> int:
        grid = Grid(input)
        total = 0
        while True:
            remove = list(filter(None, map(lambda t: t if grid.is_accessible(*t) else None, [(x, y) for x in range(grid.dims[0]) for y in range(grid.dims[1])])))
            total += len(remove)
            if remove:
                [grid.set(*t, 0) for t in remove]
            else:
                break
        return total
    
    
    if __name__ == "__main__":
        input = Path("_2025/_4/input").read_text("utf-8")
        print(part_one(input))
        print(part_two(input))
    
  • source
  • [–] 2 points 10 months ago* (last edited 10 months ago)

    Futhark

    Only part 1 so far, I want to do part 2 later too.

    This is my first ever futhark program. I have not yet figured out whether string parsing is possible or intended with this language. I used a combination of sed and vim to bring the input into a form futhark can read.

    def neighbors (x: i32, y: i32): [8](i32, i32) = [(x+1, y+1), (x+1, y), (x+1, y-1), (x, y+1), (x, y-1), (x-1, y+1), (x-1, y), (x-1, y-1)]
    
    def count 't (p: t -> bool) (xs: []t) : i32 = reduce (+) 0 (map (\ x -> i32.bool (p x)) xs)
    def count2 't (p: t -> bool) (xs: [][]t) : i32 = reduce (+) 0 (map (count p) xs)
    
    def zipIndices [n] 't (xs: [n]t): [n](i32, t) = zip (map i32.i64 (indices xs)) xs
    def zipIndices2 [n][m] 't (xs: [m][n]t): [m][n]((i32, i32), t) = 
      let innerIndices = map zipIndices xs in
      let innerAndOuterIndices = zipIndices innerIndices in
      map (\ (r, a) -> map (\ (c, x) -> ((r, c), x)) a) innerAndOuterIndices
    
    def countIndexed2 't (p: (i32, i32) -> t -> bool) (xs: [][]t): i32 = 
      let withIndices = zipIndices2 xs in
      count2 (\ (i, x) -> p i x) withIndices
    
    type option 't
      = #single t
      | #empty
    
    def safeIndex 't (xs: []t) (i: i32): option t = if i32.i64 (length xs) > i && i >= 0
      then #single xs[i]
      else #empty
    
    def safeIndex2 't (xs: [][]t) ((r, c): (i32, i32)): option t = 
      match safeIndex xs r
        case #single a -> safeIndex a c
        case #empty -> #empty
    
    def orElse 't (o: option t) (d: t): t =
      match o
        case #single x -> x
        case #empty    -> d
    
    def isAccessible (grid: [][]bool) (p: (i32, i32)) (x:bool): bool =
      let neighborsOptions = map (safeIndex2 grid) (neighbors p) in
      let neighborsFilled = map (`orElse` false) neighborsOptions in
      x && count id neighborsFilled < 4
    
    def mapIndexed2 'a 'b (f: (i32, i32) -> a -> b) (xs: [][]a): [][]b =
      let withIndices = zipIndices2 xs in
      map (map (\ (i, x) -> f i x)) withIndices
    
    def removeAccessibles (grid: [][]bool): [][]bool = mapIndexed2 (\ p x -> x && not (isAccessible grid p x)) grid
    
    def part1 (grid: [][]bool): i32 = countIndexed2 (isAccessible grid) grid
    def part2 (grid: [][]bool): i32 =
      let (reducedGrid, _) = 
        loop (current, last) = (removeAccessibles grid, grid)
        while current != last
        do 
          let current' = removeAccessibles current in
          let last'    = copy current in
          (current', last')
      in count2 id grid - count2 id reducedGrid
    
    def main (grid: [][]bool) = (part1 grid, part2 grid)
    
    

    The highlighting is a bit off because I used ocaml as the language. There is no futhark highlighter (at least in Web UI) yet.
    Edit: Part2

    Also, it runs blazingly fast πŸš€ :O, even in sequential C mode

  • source
  • [–] 2 points 9 months ago* (last edited 9 months ago)

    Kotlin

    I'm catching up on this year's AOC.
    This one was rather easy. I already have a pretty versatile grid class that I have just iterated as often as needed.

    Doing this one also lead me into the rabbit hole that is source code generation in Gradle. I used this to generate all the implementations for the primitive types of the grid class as primitive arrays are not generic in the JVM.
    An Array<Int> is an array of integer references, but an IntArray is an array of primitive integers.

    Code on GitHub

    Code

    class Day04 : AOCSolution {
        override val year = 2025
        override val day = 4
    
        override fun part1(inputFile: String): String {
            val grid = readResourceLines(inputFile).map(CharSequence::toList).toCharGrid()
    
            var accessiblePaperRolls = 0
    
            // Quickly iterate the grid in top-left to bottom-right order
            for (y in 0 until grid.height) {
                for (x in 0 until grid.width) {
                    // Count the neighbours of each paper roll.
                    if (grid[x, y] == PAPER_ROLL &&
                        grid.countNeighbours(x, y, 1) { it == PAPER_ROLL } < 4
                    ) {
                        accessiblePaperRolls++
                    }
                }
            }
            return accessiblePaperRolls.toString()
        }
    
        override fun part2(inputFile: String): String {
            val grid = readResourceLines(inputFile).map(CharSequence::toList).toCharGrid()
    
            var count = 0
            while (true) {
                var iterationCount = 0
    
                // Quickly iterate the grid in top-left to bottom-right order
                for (y in 0 until grid.height) {
                    for (x in 0 until grid.width) {
                        if (grid[x, y] == PAPER_ROLL &&
                            grid.countNeighbours(x, y, 1) { it == PAPER_ROLL } < 4
                        ) {
                            // Remove the paper roll for the next iteration
                            grid[x, y] = REMOVED_PAPER_ROLL
                            iterationCount++
                        }
                    }
                }
                count += iterationCount
    
                // Repeat the count until no paper rolls are accessible.
                if (iterationCount == 0) {
                    break
                }
            }
    
            return count.toString()
        }
    
        private companion object {
            const val PAPER_ROLL = '@'
            const val REMOVED_PAPER_ROLL = 'x'
    
            /**
             * Count the neighbours of the given cell in the given [radius] of cells that satisfy the given predicate.
             *
             * @param startX the horizontal position of the center of the count in the grid
             * @param startY the vertical position of the center of the count in the grid
             * @param radius the radius counted in Manhattan distance to the center
             * @param predicate the test that needs to pass for a neighbour to count.
             */
            private fun CharGrid.countNeighbours(
                startX: Int,
                startY: Int,
                radius: Int,
                predicate: Predicate<Char>,
            ): Int {
                var count = 0
                for (y in maxOf(startY - radius, 0)..minOf(startY + radius, height - 1)) {
                    for (x in maxOf(startX - radius, 0)..minOf(startX + radius, width - 1)) {
                        if (y == startY && x == startX) {
                            continue
                        }
                        if (predicate.test(this[x, y])) {
                            count++
                        }
                    }
                }
                return count
            }
        }
    }
    

  • source
  • [–] 2 points 10 months ago
    [–] 2 points 10 months ago

    Javascript

    After smashing out a functional version in 20 minutes, I converted it into a OOP approach for a more appealing solution.

    Solution

    const input = require('fs').readFileSync('input-day4.txt', 'utf-8');
    
    class PrintingDepartmentMap {
        /** @param {string} input */
        constructor(input) {
            /** @type {number[][]} */
            this.map = input.split("\n").map(r => r.split('').map(v => v === '@' ? 1 : 0));
        }
    
        /**
         * @param {number} x
         * @param {number} y
         * @returns {number} the value of that tile, or -1 if invalid
         */
        getCoord(x, y) {
            if (x < 0 || y < 0 || x >= this.map[0].length || y >= this.map.length) {
                return -1;
            }
            return this.map[y][x];
        }
    
        /**
         * @param {number} x
         * @param {number} y
         * @returns {number} the number of adjacent tiles that are >= 1
         */
        countAdjacent(x, y) {
            return [
                // top-left
                this.getCoord(x - 1, y - 1) >= 1,
                // top
                this.getCoord(x, y - 1) >= 1,
                // top-right
                this.getCoord(x + 1, y - 1) >= 1,
                // right
                this.getCoord(x + 1, y) >= 1,
                // bottom-right
                this.getCoord(x + 1, y + 1) >= 1,
                // bottom
                this.getCoord(x, y + 1) >= 1,
                // bottom-left
                this.getCoord(x - 1, y + 1) >= 1,
                // left
                this.getCoord(x - 1, y) >= 1
            ].reduce((acc, v) => !!v ? acc + 1 : acc, 0);
        }
    
        /**
         * transform in place the map replacing any rolls (1) with (2) if they are accessible
         * @returns {PrintingDepartmentMap}
         */
        markAccessable() {
            for (let y = 0; y < this.map.length; y += 1) {
                for (let x = 0; x < this.map[0].length; x += 1) {
                    if (this.map[y][x] < 1) continue;
                    if (this.countAdjacent(x, y) < 4) {
                        this.map[y][x] = 2;
                    }
    
                }
            }
            return this;
        }
    
        /** @returns {number} the number of removed rolls */
        removeAccessable() {
            let removed = 0;
            for (let y = 0; y < this.map.length; y += 1) {
                for (let x = 0; x < this.map[0].length; x += 1) {
                    if (this.map[y][x] === 2) {
                        removed += 1;
                        this.map[y][x] = 0;
                    }
                }
            }
            return removed;
        }
    }
    
    const map = new PrintingDepartmentMap(input);
    
    let removed = 0;
    while (true) {
        const justRemoved = map.markAccessable().removeAccessable();
        if (removed === 0) {
            console.log(`Part 1 Answer: ${justRemoved}`);
        }
        if (justRemoved === 0) break;
        removed += justRemoved;
    }
    
    console.log(`Part 2 Answer: ${removed}`);
    

  • source
  • [–] 2 points 10 months ago

    Haskell

    import Data.Array.Unboxed
    import Control.Arrow
    import Data.Foldable
    
    type Coord = (Int, Int)
    type Diagram = UArray Coord Char
    
    moves :: Coord -> [Coord]
    moves pos = (.+. pos) <$> deltas
      where
        deltas = [(x, y) | x <- [-1, 0, 1], y <- [-1, 0, 1], not (x == 0 && y == 0)]
        (ax, ay) .+. (bx, by) = (ax + bx, ay + by)
    
    parse :: String -> Diagram
    parse s = listArray ((1, 1), (n, m)) $ concat l
      where
        l = lines s
        n = length l
        m = length $ head l
    
    isRoll = (== '@')
    numRolls = length . filter isRoll
    
    neighbors d p = (d !) <$> filter (inRange (bounds d)) (moves p)
    
    removable d = filter ((<4) . numRolls . neighbors d . fst) . filter (isRoll . snd) $ assocs d
    
    part1 :: Diagram -> Int
    part1 = length . removable
    
    part2 d = fmap ((initial -) . fst) . find (uncurry (==)) $ zip stages (tail stages)
      where
        initial = numRolls $ elems d
        stages = numRolls . elems <$> iterate (\x -> x // toX (removable x)) d
        toX = fmap (second (const 'x'))
    
    main = getContents >>= print . (part1 &&& part2) . parse
    
  • source
  • [–] 1 point 9 months ago

    (Browser-based) Javascript

    This was a good opportunity to refresh my grasp on the math involved in losslessly stuffing a tuple into a single number. JS-in-the-browser has Sets and Maps but no Tuples, and Arrays are indexed on their id / memory handle instead of their value contents, so if you want to put coordinates into a set or map and have the collection behave as expected you need to serialize the coordinates into a primitive type. Stuff it into a string if you don't want to think too hard. For this specific problem we don't even need to be able to compute the original coordinates (just count the unique removed points) but implementing that computation was a handy way to verify the "serializer" was working correctly.

    Seeing as the record tuple proposal was withdrawn in February of this year this is still a technique worth knowing when working with coords in JS.

    Code

    function part1(inputText) {
      const gridWidth = inputText.indexOf('\n');
      const lines = inputText.trim().split('\n');
      const gridHeight = lines.length;
      let accessibleRolls = 0;
      for (let y = 0; y < gridHeight; y++) {
        for (let x = 0; x < gridWidth; x++) {
          if (lines[y][x] === '@') {
            let occupiedNeighbors = 0;
            for (const [neighborX, neighborY] of [
                [x - 1, y],
                [x + 1, y],
                [x, y - 1],
                [x, y + 1],
                [x - 1, y - 1],
                [x - 1, y + 1],
                [x + 1, y - 1],
                [x + 1, y + 1],
              ]) {
              if (neighborX < 0 || neighborX >= gridWidth || neighborY < 0 || neighborY >= gridHeight) {
                continue;
              }
              if (lines[neighborY][neighborX] === '@') {
                occupiedNeighbors++;
              }
            }
            if (occupiedNeighbors < 4) {
              accessibleRolls++;
            }
          }
        }
      }
      return accessibleRolls;
    }
    {
      const start = performance.now()
      const result = part1(document.body.textContent)
      const end = performance.now()
      console.info({day: 4, part: 1, time: end - start, result})
    }
    
    function serializeCoords(x, y, gridWidth) {
      const leftShiftAmount = Math.ceil(Math.log10(gridWidth));
      return x * (10 ** leftShiftAmount) + y;
    }
    /*
    {
      const x = 3;
      const y = 4;
      const gridWidth = 13;
      const serialized = serializeCoords(x, y, gridWidth);
      console.debug({ x, y, gridWidth, serialized });
    }
    */
    
    function deserializeCoords(serialized, gridWidth) {
      const leftShiftAmount = Math.ceil(Math.log10(gridWidth));
      const x = Math.floor(serialized / (10 ** leftShiftAmount));
      const y = serialized - x * 10 ** leftShiftAmount;
      return [x, y];
    }
    /*
    {
      const serialized = 304;
      const gridWidth = 13;
      const [x, y] = deserializeCoords(serialized, gridWidth);
      console.debug({ serialized, gridWidth, x, y });
    }
    */
    function part2(inputText) {
      const gridWidth = inputText.indexOf('\n');
      const lines = inputText.trim().split('\n');
      const gridHeight = lines.length;
      let removed = new Set();
      while (true) {
        const toRemove = new Set();
        for (let y = 0; y < gridHeight; y++) {
          for (let x = 0; x < gridWidth; x++) {
            const serialized = serializeCoords(x, y, gridWidth);
            if (lines[y][x] === '@' && !removed.has(serialized)) {
              let occupiedNeighbors = 0;
              for (const [neighborX, neighborY] of [
                  [x - 1, y],
                  [x + 1, y],
                  [x, y - 1],
                  [x, y + 1],
                  [x - 1, y - 1],
                  [x - 1, y + 1],
                  [x + 1, y - 1],
                  [x + 1, y + 1],
                ]) {
                if (neighborX < 0 || neighborX >= gridWidth || neighborY < 0 || neighborY >= gridHeight) {
                  continue;
                }
                const serializedNeighbor = serializeCoords(neighborX, neighborY, gridWidth);
                if (lines[neighborY][neighborX] === '@' && !removed.has(serializedNeighbor)) {
                  occupiedNeighbors++;
                }
              }
              if (occupiedNeighbors < 4) {
                toRemove.add(serialized);
              }
            }
          }
        }
        if (toRemove.size === 0) {
          break;
        }
        removed = removed.union(toRemove);
      }
      return removed.size;
    }
    /*
    {
      const exampleText = `..@@.@@@@.
    @@@.@.@.@@
    @@@@@.@.@@
    @.@@@@..@.
    @@.@@@@.@@
    .@@@@@@@.@
    .@.@.@.@@@
    @.@@@.@@@@
    .@@@@@@@@.
    @.@.@@@.@.
    `;
      const start = performance.now();
      const result = part2(exampleText);
      const end = performance.now();
      console.info({ day: 4, part: 2, time: end - start, result });
    }
    */
    
    {
      const start = performance.now()
      const result = part2(document.body.textContent)
      const end = performance.now()
      console.info({ day: 4, part: 2, time: end - start, result });
    }
    

  • source
  • [–] [S] 1 point 10 months ago

    Rust

       fn count_sides(grid: &[Vec<char>], x: usize, y: usize) -> usize {
            let mut count = 0;
            for i in y.saturating_sub(1)..=y + 1 {
                for j in x.saturating_sub(1)..=x + 1 {
                    if i == y && j == x {
                        continue;
                    }
    
                    if let Some(row) = grid.get(i) {
                        if let Some(col) = row.get(j) {
                            if *col == '@' {
                                count += 1;
                            }
                        }
                    }
                }
            }
            count
        }
    
        #[test]
        fn test_y2025_day4_part1() {
            let input = std::fs::read_to_string("input/2025/day_4.txt").unwrap();
            let grid = input
                .lines()
                .map(|l| l.chars().collect::<Vec<_>>())
                .collect::<Vec<_>>();
            let mut total = 0;
            let width = grid[0].len();
            let height = grid.len();
            for y in 0..height {
                for x in 0..width {
                    if grid[y][x] != '@' {
                        continue;
                    }
    
                    if count_sides(&grid, x, y) < 4 {
                        total += 1
                    }
                }
            }
            println!("Total = {total}")
        }
    
        #[test]
        fn test_y2025_day4_part2() {
            let input = std::fs::read_to_string("input/2025/day_4.txt").unwrap();
            let grid = input
                .lines()
                .map(|l| l.chars().collect::<Vec<_>>())
                .collect::<Vec<_>>();
            let mut grid = input
                .lines()
                .map(|l| l.chars().collect::<Vec<_>>())
                .collect::<Vec<_>>();
            let mut total = 0;
            let width = grid[0].len();
            let height = grid.len();
            loop {
                let mut iter_total = 0;
                for y in 0..height {
                    for x in 0..width {
                        if grid[y][x] != '@' {
                            continue;
                        }
    
                        if count_sides(&grid, x, y) < 4 {
                            grid[y][x] = '*';
                            iter_total += 1
                        }
                    }
                }
                println!("Moved = {iter_total}");
                if iter_total == 0 {
                    break;
                }
                total += iter_total;
            }
            println!("Total = {total}");
        }
    

    Nothing really exciting here, was pretty straightforward

  • source
  • [–] 1 point 10 months ago

    Go

    package main
    
    import (
    	"aoc/utils"
    	"fmt"
    )
    
    type rollMap [][]bool
    
    func (rm *rollMap) addRow(line string) {
    	runes := []rune(line)
    	row := make([]bool, len(runes))
    
    	for idx, r := range runes {
    		row[idx] = r == '@'
    	}
    
    	*rm = append(*rm, row)
    }
    
    func (rm rollMap) getAdjacentContents(row, col int) []bool {
    	contents := []bool{}
    	lenrow := len(rm[0])
    	lenrm := len(rm)
    
    	for _, i := range []int{-1, 0, 1} {
    		for _, j := range []int{-1, 0, 1} {
    			if i == 0 && j == 0 {
    				continue
    			}
    
    			r := row + i
    			c := col + j
    			if r >= 0 && r < lenrm && c >= 0 && c < lenrow {
    				contents = append(contents, rm[r][c])
    			}
    		}
    	}
    
    	return contents
    }
    
    func countTrue(array []bool) int {
    	count := 0
    	for _, val := range array {
    		if val {
    			count++
    		}
    	}
    	return count
    }
    
    func createMap(input chan string) rollMap {
    	rm := rollMap{}
    
    	for line := range input {
    		rm.addRow(line)
    	}
    
    	return rm
    }
    
    func stepOne(input chan string) (int, error) {
    	rm := createMap(input)
    	lenrow := len(rm[0])
    	accessibleRolls := 0
    
    	for row := range rm {
    		for col := range lenrow {
    			if rm[row][col] {
    				adj := rm.getAdjacentContents(row, col)
    				numRolls := countTrue(adj)
    				if numRolls < 4 {
    					accessibleRolls++
    				}
    			}
    		}
    	}
    
    	return accessibleRolls, nil
    }
    
    type position struct {
    	row, col int
    }
    
    func stepTwo(input chan string) (int, error) {
    	rm := createMap(input)
    	lenrow := len(rm[0])
    	accessibleRolls := 0
    
    	for true {
    		accessedRolls := []position{}
    		for row := range rm {
    			for col := range lenrow {
    				if rm[row][col] {
    					adj := rm.getAdjacentContents(row, col)
    					numRolls := countTrue(adj)
    					if numRolls < 4 {
    						accessibleRolls++
    						accessedRolls = append(accessedRolls, position{
    							row: row,
    							col: col,
    						})
    					}
    				}
    			}
    		}
    		if len(accessedRolls) == 0 {
    			break
    		} else {
    			for _, pos := range accessedRolls {
    				rm[pos.row][pos.col] = false
    			}
    		}
    	}
    
    	return accessibleRolls, nil
    }
    
    func main() {
    	input := utils.FilePath("day04.txt")
    	ch1, err1 := input.GetLineChannel()
    	if err1 != nil {
    		fmt.Errorf("step one error: %s\n", err1)
    		return
    	}
    
    	val1, errStep1 := stepOne(ch1)
    	if errStep1 != nil {
    		fmt.Errorf("step one error: %s\n", errStep1)
    		return
    	}
    
    	fmt.Printf("Step one result: %d\n", val1)
    
    	ch2, err2 := input.GetLineChannel()
    	if err2 != nil {
    		fmt.Errorf("step two error: %s\n", err2)
    		return
    	}
    
    	val2, errStep2 := stepTwo(ch2)
    	if errStep2 != nil {
    		fmt.Errorf("step two error: %s\n", errStep2)
    		return
    	}
    
    	fmt.Printf("Step two result: %d\n", val2)
    }
    
  • source
  • [–] 1 point 10 months ago

    Rust

    View on github

    fn parse_input(input: &str) -> Vec<Vec<bool>> {
        input
            .lines()
            .map(|l| l.chars().map(|c| c == '@').collect())
            .collect()
    }
    
    fn count_adj(grid: &[Vec<bool>], (x, y): (usize, usize)) -> usize {
        let width = grid[0].len();
        let height = grid.len();
        grid.iter()
            .take((y + 2).min(height))
            .skip(y.saturating_sub(1))
            .map(|r| {
                r.iter()
                    .take((x + 2).min(width))
                    .skip(x.saturating_sub(1))
                    .take(3)
                    .filter(|e| **e)
                    .count()
            })
            .sum::<usize>()
    }
    
    fn part1(input: String) {
        let grid = parse_input(&input);
        let mut count = 0u32;
        for (y, row) in grid.iter().enumerate() {
            for (x, _) in row.iter().enumerate().filter(|(_, r)| **r) {
                let n_adj = count_adj(&grid, (x, y));
                // Center roll is counted too
                if n_adj < 5 {
                    count += 1;
                }
            }
        }
        println!("{count}");
    }
    
    fn part2(input: String) {
        let mut grid = parse_input(&input);
        let mut removed = 0u32;
        loop {
            let mut next_grid = grid.clone();
            let prev_removed = removed;
            for (y, row) in grid.iter().enumerate() {
                for (x, _) in row.iter().enumerate().filter(|(_, r)| **r) {
                    let n_adj = count_adj(&grid, (x, y));
                    // Center roll is counted too
                    if n_adj < 5 {
                        next_grid[y][x] = false;
                        removed += 1;
                    }
                }
            }
            if removed == prev_removed {
                break;
            }
            grid = next_grid;
        }
        println!("{}", removed);
    }
    
    util::aoc_main!();
    
  • source
  • [–] 1 point 10 months ago

    Uiua

    Quite simple this one. Part 2 still takes a few seconds because I'm essentially checking off each roll individually.

    Run with example input

    Code

    $ ..@@.@@@@.
    $ @@@.@.@.@@
    $ @@@@@.@.@@
    $ @.@@@@..@.
    $ @@.@@@@.@@
    $ .@@@@@@@.@
    $ .@.@.@.@@@
    $ @.@@@.@@@@
    $ .@@@@@@@@.
    $ @.@.@@@.@.
    βŠœβˆ˜βŠΈβ‰ @\n
    =@@
    
    Rolls ← ⍣(β§»βŠšΛ™β€)∞⊸⊑1_1
    Removable ← (
      ⬚0⧈Rolls[3_3 1_1 1_1]
      βŠšβ‰€4
    )
    
    Remove ← ⍜⊑(Λœβ†―0β§»)
    
    PartOne ← β§»Removable
    
    PartTwo ← (
      βŠ™0
      β₯(
        βŠ™(βŠ™+⟜⧻)⟜Removable
        ˜Remove
      )∞
      β—Œ
    )
    &pf "Part One: "
    &p ⊸PartOne
    
    &pf "Part Two: "
    &p PartTwo
    

    Old Part 2Before seeing mykl's solution this was my solution for part 2

    PartTwoOld ← (
      0βŠ™0
      ⍒(βŠ™βŠ™β—Œ
        βŠ™βŠΈRemovable
        +βŠ™βŠΈβ§»
        βŠ™βŠΈRemove
      | Β¬β‰β—Œ)
      βŠ™β‹…β—Œ
    )
    

    It's basically the same, just that I used a while-do-loop, making the check for the ending condition myself (which took me a bit to get right because I still find loops in Uiua a bit confusing).
    Using the repeat-loop as above also gets rid of the dip's (βŠ™). I could've removed them here as well but I was already deep in the trouble of getting the loop to work correctly and I liked the little face at the beginning 0βŠ™0

  • source