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

  • GiantTree@feddit.org
    link
    fedilink
    arrow-up
    0
    ·
    24 days 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
    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
            }
        }
    }
    
  • VegOwOtenks@lemmy.world
    link
    fedilink
    arrow-up
    0
    ·
    1 month 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
    
  • Pyro@programming.dev
    link
    fedilink
    arrow-up
    0
    ·
    edit-2
    1 month 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
    
  • CameronDev@programming.devOPM
    link
    fedilink
    arrow-up
    0
    ·
    1 month 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

  • janAkali@lemmy.sdf.org
    link
    fedilink
    arrow-up
    0
    ·
    1 month 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

  • Amy@piefed.blahaj.zone
    link
    fedilink
    English
    arrow-up
    0
    ·
    1 month 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  
    
  • mykl@lemmy.world
    link
    fedilink
    arrow-up
    0
    ·
    edit-2
    1 month ago

    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₃)
    

    • CameronDev@programming.devOPM
      link
      fedilink
      arrow-up
      0
      ·
      1 month ago

      Love a good visualisation <3

      I was gonna do the same later when some free time, was wondering if it generated some kind of image.

      • mykl@lemmy.world
        link
        fedilink
        arrow-up
        0
        ·
        1 month ago

        If you click the link on that post, you’ll see that the test data does resolve to a (very low res) elf!

    • Quant@programming.dev
      link
      fedilink
      arrow-up
      0
      ·
      1 month ago

      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.

    • Deebster@programming.dev
      link
      fedilink
      English
      arrow-up
      0
      ·
      1 month ago

      Now you’re just showing off!

      Edit: ooh, this makes it obvious that my puzzle input takes more cycles to reach the done state.

  • Strlcpy@1@lemmy.sdf.org
    link
    fedilink
    arrow-up
    0
    ·
    1 month 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.

  • Deebster@programming.dev
    link
    fedilink
    English
    arrow-up
    0
    ·
    1 month 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).

    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(())
    }
    
  • VegOwOtenks@lemmy.world
    link
    fedilink
    English
    arrow-up
    0
    ·
    1 month 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 opaque 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 part1 (grid: [][]bool) = countIndexed2 (isAccessible grid) grid
    def part2 (grid: [][]bool): i32 = 0
    
    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.

  • Gobbel2000@programming.dev
    link
    fedilink
    arrow-up
    0
    ·
    1 month 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!();
    
  • LeixB@lemmy.world
    link
    fedilink
    arrow-up
    0
    ·
    1 month 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
    
  • ystael@beehaw.org
    link
    fedilink
    arrow-up
    0
    ·
    1 month 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))
    
  • Chais@sh.itjust.works
    link
    fedilink
    arrow-up
    0
    ·
    27 days 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))