this post was submitted on 04 Dec 2024
18 points (95.0% liked)

Advent Of Code

981 readers
28 users here now

An unofficial home for the advent of code community on programming.dev!

Advent of Code is an annual Advent calendar of small programming puzzles for a variety of skill sets and skill levels that can be solved in any programming language you like.

AoC 2024

Solution Threads

M T W T F S S
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25

Rules/Guidelines

Relevant Communities

Relevant Links

Credits

Icon base by Lorc under CC BY 3.0 with modifications to add a gradient

console.log('Hello World')

founded 1 year ago
MODERATORS
 

Day 4: Ceres Search

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

top 35 comments
sorted by: hot top controversial new old
[–] [email protected] 1 points 1 week ago* (last edited 5 days ago)

Part 1:

with open('input') as data:
    lines = [l.strip() for l in data.readlines()]
# Remove empty line
class Result():
    def __init__(self):
        self.count = 0


def analyze_lines(lines: list[str]):
    ans.count += get_rights(lines)
    ans.count += get_ups(lines)
    ans.count += get_downs(lines)
    ans.count += get_down_rights(lines)
    ans.count += get_down_lefts(lines)
    ans.count += get_up_lefts(lines)
    ans.count += get_up_rights(lines)
    for line in lines:
        ans.count += get_lefts(line)




def get_ups(lines: list[str]) -> int:
    up_count = 0
    for i_l, line in enumerate(lines):
        result = ""
        if i_l < 3:
            continue
        for i_c, char in enumerate(line):
            if char == "X":
                result = char
                result += "".join([lines[i_l - n][i_c] for n in range(1, 4)])
                if result == "XMAS":
                    up_count += 1
                else:
                    result = ""
    return up_count


def get_downs(lines: list[str]) -> int:
    down_count = 0
    for i_l, l in enumerate(lines):
        result = ""
        for i_c, c in enumerate(l):
            if c == "X":
                result += c
                try:
                    result += "".join([lines[i_l + n][i_c] for n in range(1, 4)])
                except IndexError:
                    result = ""
                    continue
                finally:
                    if result == "XMAS":
                        down_count += 1
                    result = ""
    return down_count


        
def get_lefts(line: str) -> int:
    left_count = 0
    for i, char in enumerate(line):
        if i < 3:
            continue
        elif char == "X" and line[i-1] == "M" and line[i-2] == "A" and line[i-3] == "S":
            left_count += 1
    return left_count


def get_rights(lines: list[str]) -> int:
    right_counts = 0
    for l in lines:
        right_counts += l.count("XMAS")
    return right_counts

def get_down_rights(lines: list[str]) -> int:
    down_right_count = 0
    for i_l, l in enumerate(lines):
        result = ""
        for i_c, c in enumerate(l):
            if c == "X":
                result += c
                try:
                    result += "".join(
                            [lines[i_l + n][i_c + n] for n in range(1,4)]
                            )
                except IndexError:
                    result = ""
                    continue
                finally:
                    if result == "XMAS":
                        down_right_count += 1
                    result = ""
    return down_right_count

def get_down_lefts(lines: list[str]) -> int:
    down_left_count = 0
    for i_l, l in enumerate(lines):
        result = ""
        for i_c, c in enumerate(l):
            if i_c < 3:
                continue
            if c == "X":
                result += c
                try:
                    result += "".join(
                            [lines[i_l + n][i_c - n] for n in range(1,4)]
                            )
                except IndexError:
                    result = ""
                    continue
                finally:
                    if result == "XMAS":
                        down_left_count += 1
                    result = ""
    return down_left_count

def get_up_rights(lines: list[str]) -> int:
    up_right_count = 0
    for i_l, l in enumerate(lines):
        result = ""
        if i_l < 3:
            continue
        for i_c, c in enumerate(l):
            if c == "X":
                result += c
                try:
                    result += "".join(
                            [lines[i_l - n][i_c + n] for n in range(1,4)]
                            )
                except IndexError:
                    result = ""
                    continue
                finally:
                    if result == "XMAS":
                        up_right_count += 1
                    result = ""
    return up_right_count


def get_up_lefts(lines: list[str]) -> int:
    up_left_count = 0
    for i_l, l in enumerate(lines):
        result = ""
        if i_l < 3:
            continue
        for i_c, c in enumerate(l):
            if i_c < 3:
                continue
            if c == "X":
                result = c
                try:
                    result += "".join(
                            [lines[i_l - n][i_c - n] for n in range(1,4)]
                            )
                except IndexError as e:
                    result = ""
                    continue
                finally:
                    if result == "XMAS":
                        up_left_count += 1
                    result = ""
    return up_left_count

ans = Result()
analyze_lines(lines)
print(ans.count)

Part 2:

with open('input') as data:
    lines = list(filter(lambda x: x != '', [l.strip() for l in data.readlines()]))
    
xmases = 0
for i in range(1, len(lines)):
    for j in range(1, len(lines[i])):
        if lines[i][j] == "A":
            try:
                up_back = lines[i-1][j-1]
                down_over = lines[i+1][j+1]
                up_over = lines[i-1][j+1]
                down_back = lines[i+1][j-1]
            except IndexError:
                continue
            else:
                if {up_back, down_over} == set("MS") and {up_over, down_back} == set("MS"):
                    xmases += 1

print(xmases)

I actually found part two A LOT easier than part 1.

[–] [email protected] 5 points 3 weeks ago

Haskell

Popular language this year :)

I got embarrassingly stuck on this one trying to be clever with list operations. Then I realized I should just use an array...

import Data.Array.Unboxed (UArray)
import Data.Array.Unboxed qualified as A
import Data.Bifunctor

readInput :: String -> UArray (Int, Int) Char
readInput s =
  let rows = lines s
      n = length rows
   in A.listArray ((1, 1), (n, n)) $ concat rows

s1 `eq` s2 = s1 == s2 || s1 == reverse s2

part1 arr = length $ filter isXmas $ concatMap lines $ A.indices arr
  where
    isXmas ps = all (A.inRange $ A.bounds arr) ps && map (arr A.!) ps `eq` "XMAS"
    lines p = [take 4 $ iterate (bimap (+ di) (+ dj)) p | (di, dj) <- [(1, 0), (0, 1), (1, 1), (1, -1)]]

part2 arr = length $ filter isXmas innerPoints
  where
    innerPoints =
      let ((i1, j1), (i2, j2)) = A.bounds arr
       in [(i, j) | i <- [i1 + 1 .. i2 - 1], j <- [j1 + 1 .. j2 - 1]]
    isXmas p = up p `eq` "MAS" && down p `eq` "MAS"
    up (i, j) = map (arr A.!) [(i + 1, j - 1), (i, j), (i - 1, j + 1)]
    down (i, j) = map (arr A.!) [(i - 1, j - 1), (i, j), (i + 1, j + 1)]

main = do
  input <- readInput <$> readFile "input04"
  print $ part1 input
  print $ part2 input
[–] [email protected] 4 points 3 weeks ago* (last edited 3 weeks ago) (1 children)

Uiua

Just part1 for now as I need to walk the dog :-)

[edit] Part 2 now added, and a nicer approach than Part 1 in my opinion, if you're able to keep that many dimensions straight in your head :-)

[edit 2] Tightened it up a bit more.

Grid ← ⊜∘⊸≠@\n "MMMSXXMASM\nMSAMXMSMSA\nAMXSXMAAMM\nMSAMASMSMX\nXMASAMXAMM\nXXAMMXXAMA\nSMSMSASXSS\nSAXAMASAAA\nMAMMMXMMMM\nMXMXAXMASX"

≡⍉⍉×⇡4¤[1_0 0_1 1_1 1_¯1]         # Use core dirs to build sets of 4-offsets.
↯∞_2⇡△ Grid                       # Get all possible starting points.
&p/+♭⊞(+∩(≍"XMAS")⇌.⬚@.⊡:Grid≡+¤) # Part 1. Join the two into a table, use to pick 4-elements, check, count.

Diags   ← [[¯. 1_1] [¯. 1_¯1]]
BothMas ← /×≡(+∩(≍"MS")⇌.)⬚@.⊡≡+Diags¤¤ # True if both diags here are MAS.
&p/+≡BothMas⊚="A"⟜¤Grid                 # Part 2. For all "A"s in grid, check diags, count where good.
[–] [email protected] 3 points 3 weeks ago (1 children)

I'm not even sure how to write most of these characters

[–] [email protected] 2 points 3 weeks ago (1 children)

The operators have all got ascii names you can type, and the formatter converts them to the symbols. It's a bit odd but really worthwhile, as you get access to the powerful array handling functionality that made solving today's challenges so much more straightforward than in other languages.

[–] [email protected] 2 points 3 weeks ago

It looks quite functional indeed

[–] [email protected] 4 points 3 weeks ago

I struggled a lot more when doing list slices that I would've liked to

Haskell


import Data.List qualified as List

collectDiagonal :: [String] -> Int -> Int -> String
collectDiagonal c y x
        | length c > y && length (c !! y) > x = c !! y !! x : collectDiagonal c (y+1) (x+1)
        | otherwise = []

part1 c = do
        let forwardXMAS  = map (length . filter (List.isPrefixOf "XMAS") . List.tails) $ c
        let backwardXMAS = map (length . filter (List.isPrefixOf "XMAS") . List.tails . reverse) $ c
        let downwardXMAS  = map (length . filter (List.isPrefixOf "XMAS") . List.tails ) . List.transpose $ c
        let upwardXMAS = map (length . filter (List.isPrefixOf "XMAS") . List.tails . reverse ) . List.transpose $ c
        let leftSideDiagonals = map (\ y -> collectDiagonal c y 0) [0..length c]
        let leftTopDiagonals = map (\ x -> collectDiagonal c 0 x) [1..(length . List.head $ c)]
        let leftDiagonals = leftSideDiagonals ++ leftTopDiagonals
        let rightSideDiagonals = map (\ y -> collectDiagonal (map List.reverse c) y 0) [0..length c]
        let rightTopDiagonals = map (\ x -> collectDiagonal (map List.reverse c) 0 x) [1..(length . List.head $ c)]
        let rightDiagonals = rightSideDiagonals ++ rightTopDiagonals
        let diagonals = leftDiagonals ++ rightDiagonals

        let diagonalXMAS = map (length . filter (List.isPrefixOf "XMAS") . List.tails) $ diagonals
        let reverseDiagonalXMAS = map (length . filter (List.isPrefixOf "XMAS") . List.tails . reverse) $ diagonals

        print . sum $ [sum forwardXMAS, sum backwardXMAS, sum downwardXMAS, sum upwardXMAS, sum diagonalXMAS, sum reverseDiagonalXMAS]
        return ()

getBlock h w c y x = map (take w . drop x) . take h . drop y $ c

isXBlock b = do
        let diagonal1 = collectDiagonal b 0 0
        let diagonal2 = collectDiagonal (map List.reverse b) 0 0

        diagonal1 `elem` ["SAM", "MAS"] && diagonal2 `elem` ["SAM", "MAS"]

part2 c = do
        
        let lineBlocks = List.map (getBlock 3 3 c) [0..length c - 1]
        let groupedBlocks = List.map (flip List.map [0..(length . head $ c) - 1]) lineBlocks

        print . sum . map (length . filter isXBlock) $ groupedBlocks

        return ()

main = do
        c <- lines <$> getContents

        part1 c
        part2 c

        return ()
[–] [email protected] 4 points 3 weeks ago* (last edited 3 weeks ago)

J

Unsurprisingly this is the kind of problem that J is really good at. The dyadic case (table) of the adverb / is doing all the heavy lifting here: it makes a higher rank tensor by traversing items of the specified rank on each side and combining them according to the remaining frame of each side's shape. The hard part is arranging the arguments so that your resulting matrix has its axes in the correct order.

data_file_name =: '4.data'

NB. cutopen yields boxed lines, so unbox them and ravel items to make a letter matrix
grid =: ,. > cutopen fread data_file_name
NB. pad the grid on every side with #'XMAS' - 1 spaces
hpadded_grid =: (('   ' &amp; ,) @: (, &amp; '   '))"1 grid
padded_grid =: (3 1 $ ' ') , hpadded_grid , (3 1 $ ' ')
NB. traversal vectors
directions =: 8 2 $ 1 0 1 1 0 1 _1 1 _1 0 _1 _1 0 _1 1 _1
NB. rpos cpos matches rdir cdir if the string starting at rpos cpos in
NB. direction rdir cdir is the string we want
matches =: 4 : 0
*/ ,'XMAS' -: padded_grid {~ &lt;"1 x +"1 y *"1 0 i. 4
)"1
positions =: (3 + i. 0 { $ grid) ,"0/ (3 + i. 1 { $ grid)
result1 =: +/, positions matches/ directions

NB. pairs of traversal vectors
x_directions =: 4 2 2 $ 1 1 _1 1 1 1 1 _1 _1 _1 _1 1 _1 _1 1 _1
NB. rpos cpos x_matches 2 2 $ rdir1 cdir1 rdir2 cdir2 if there is an 'A' at
NB. rpos cpos and the string in each of dir1 and dir2 centered at rpos cpos
NB. is the string we want
x_matches =: 4 : 0
NB. (2 2 $ rdir1 cdir1 rdir2 cdir2) *"1 0/ (_1 + i.3) yields a matrix
NB. 2 3 $ (_1 * dir1) , (0 * dir1) , (1 * dir1) followed by the same for dir2
*/ ,'MAS' -:"1 padded_grid {~ &lt;"1 x +"1 y *"1 0/ _1 + i. 3
)"1 2
result2 =: +/, positions x_matches/ x_directions
[–] [email protected] 4 points 3 weeks ago* (last edited 3 weeks ago)

Nim

Could be done more elegantly, but I haven’t bothered yet.

proc solve(input: string): AOCSolution[int, int] =
  var lines = input.splitLines()

  block p1:
    # horiz
    for line in lines:
      for i in 0..line.high-3:
        if line[i..i+3] in ["XMAS", "SAMX"]:
          inc result.part1

    for y in 0..lines.high-3:
      #vert
      for x in 0..lines[0].high:
        let word = collect(for y in y..y+3: lines[y][x])
        if word in [@"XMAS", @"SAMX"]:
          inc result.part1

      #diag \
      for x in 0..lines[0].high-3:
        let word = collect(for d in 0..3: lines[y+d][x+d])
        if word in [@"XMAS", @"SAMX"]:
          inc result.part1

      #diag /
      for x in 3..lines[0].high:
        let word = collect(for d in 0..3: lines[y+d][x-d])
        if word in [@"XMAS", @"SAMX"]:
          inc result.part1

  block p2:
    for y in 0..lines.high-2:
      for x in 0..lines[0].high-2:
        let diagNW = collect(for d in 0..2: lines[y+d][x+d])
        let diagNE = collect(for d in 0..2: lines[y+d][x+2-d])
        if diagNW in [@"MAS", @"SAM"] and diagNE in [@"MAS", @"SAM"]:
          inc result.part2

Codeberg repo

[–] [email protected] 3 points 3 weeks ago* (last edited 2 weeks ago)

Factor

spoiler

: get-input ( -- rows )
  "vocab:aoc-2024/04/input.txt" utf8 file-lines ;

: verticals ( rows -- lines )
  [ dimension last [0..b) ] keep cols ;

: slash-origins ( rows -- coords )
  dimension
  [ first [0..b) [ 0 2array ] map ] [
    first2 [ 1 - ] [ 1 (a..b] ] bi*
    [ 2array ] with map
  ] bi append ;

: backslash-origins ( rows -- coords )
  dimension first2
  [ [0..b) [ 0 2array ] map ]
  [ 1 (a..b] [ 0 swap 2array ] map ] bi* append ;

: slash ( rows origin -- line )
  first2
  [ 0 [a..b] ]
  [ pick dimension last [a..b) ] bi* zip
  swap matrix-nths ;

: backslash ( rows origin -- line )
  [ dup dimension ] dip first2
  [ over first [a..b) ]
  [ pick last [a..b) ] bi* zip nip
  swap matrix-nths ;

: slashes ( rows -- lines )
  dup slash-origins
  [ slash ] with map ;

: backslashes ( rows -- lines )
  dup backslash-origins
  [ backslash ] with map ;

: word-count ( line word -- n )
  dupd [ reverse ] dip
  '[ _ subseq-indices length ] bi@ + ;

: part1 ( -- n )
  get-input
  { [ ] [ verticals ] [ slashes ] [ backslashes ] } cleave-array concat
  [ "XMAS" word-count ] map-sum ;

: origin-adistances ( rows origins line-quot: ( rows origin -- line ) -- origin-adistances-assoc )
  with zip-with
  "MAS" "SAM" [ '[ [ _ subseq-indices ] map-values ] ] bi@ bi append
  harvest-values
  [ [ 1 + ] map ] map-values ; inline

: a-coords ( origin-adistances coord-quot: ( adistance -- row-delta col-delta ) -- coords )
  '[ first2 [ @ 2array v+ ] with map ] map-concat ; inline

: slash-a-coords ( rows -- coords )
  dup slash-origins [ slash ] origin-adistances
  [ [ 0 swap - ] keep ] a-coords ;

: backslash-a-coords ( rows -- coords )
  dup backslash-origins [ backslash ] origin-adistances
  [ dup ] a-coords ;

: part2 ( -- n )
  get-input [ slash-a-coords ] [ backslash-a-coords ] bi
  intersect length ;

Better viewed on GitHub.

[–] [email protected] 3 points 3 weeks ago* (last edited 3 weeks ago)

Haskell

import Control.Arrow
import Data.Array.Unboxed
import Data.List

type Pos = (Int, Int)
type Board = Array Pos Char
data Dir = N | NE | E | SE | S | SW | W | NW

target = "XMAS"

parse s = listArray ((1, 1), (n, m)) [l !! i !! j | i <- [0 .. n - 1], j <- [0 .. m - 1]]
  where
    l = lines s
    (n, m) = (length $ head l, length l)

move N = first pred
move S = first succ
move E = second pred
move W = second succ
move NW = move N . move W
move SW = move S . move W
move NE = move N . move E
move SE = move S . move E

check :: Board -> Pos -> Int -> Dir -> Bool
check b p i d =
    i >= length target
        || ( inRange (bounds b) p
                && (b ! p) == (target !! i)
                && check b (move d p) (succ i) d
           )

checkAllDirs :: Board -> Pos -> Int
checkAllDirs b p = length . filter (check b p 0) $ [N, NE, E, SE, S, SW, W, NW]

check2 :: Board -> Pos -> Bool
check2 b p =
    all (inRange (bounds b)) moves && ((b ! p) == 'A') && ("SSMM" `elem` rotations)
  where
    rotations = rots $ (b !) <$> moves
    moves = flip move p <$> [NE, SE, SW, NW]

    rots xs = init $ zipWith (++) (tails xs) (inits xs)

part1 b = sum $ checkAllDirs b <$> indices b
part2 b = length . filter (check2 b) $ indices b

main = getContents >>= print . (part1 &&& part2) . parse
[–] [email protected] 2 points 3 weeks ago

Nim

import ../aoc, strutils

type
  Cell* = tuple[x,y:int]

#the 8 grid direction
const directions : array[8, Cell] = [
  (1, 0), (-1, 0),
  (0, 1), ( 0,-1),
  (1, 1), (-1,-1),
  (1,-1), (-1, 1)
]

const xmas = "XMAS"

#part 1
proc searchXMAS*(grid:seq[string], x,y:int):int =
  #search in all 8 directions (provided we can find a full match in that direction)
  let w = grid[0].len
  let h = grid.len
  
  for dir in directions:
    # check if XMAS can even fit
    let xEnd = x + dir.x * 3
    let yEnd = y + dir.y * 3
    if xEnd < 0 or xEnd >= w or
       yEnd < 0 or yEnd >= h:
      continue;
    
    #step along direction
    var matches = 0
    for s in 0..3:
      if grid[y + dir.y * s][x + dir.x * s] == xmas[s]:
        inc matches
        
    if matches == xmas.len:
      inc result

#part 2
proc isMAS(grid:seq[string], c, o:Cell):bool=
  let ca : Cell = (c.x+o.x, c.y+o.y)
  let cb : Cell = (c.x-o.x, c.y-o.y)
  let a = grid[ca.y][ca.x]
  let b = grid[cb.y][cb.x]
  (a == 'M' and b == 'S') or (a == 'S' and b == 'M')

proc searchCrossMAS*(grid:seq[string], x,y:int):bool =
  grid[y][x] == 'A' and
  grid.isMAS((x,y), (1,1)) and
  grid.isMAS((x,y), (1,-1))

proc solve*(input:string): array[2,int] =
  let grid = input.splitLines
  let w = grid[0].len
  let h = grid.len
  
  #part 1
  for y in 0..<h:
    for x in 0..<w:
      result[0] += grid.searchXMAS(x, y)
  
  #part 2, skipping borders
  for y in 1..<h-1:
    for x in 1..<w-1:
      result[1] += (int)grid.searchCrossMAS(x, y)

Part 1 was done really quickly. Part 2 as well, but the result was not accepted...

Turns out +MAS isn't actually a thing :P

[–] [email protected] 2 points 2 weeks ago* (last edited 2 weeks ago)

Rust

I had a hunch about part two that didn't pay off, so I over-coded this instead of just using an array of arrays.

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 {
    fn all() -> &'static [Direction] {
        &[
            Direction::N,
            Direction::NE,
            Direction::E,
            Direction::SE,
            Direction::S,
            Direction::SW,
            Direction::W,
            Direction::NW,
        ]
    }
}

#[derive(Debug, PartialEq, Eq)]
struct WordSearch {
    grid: Vec<char>,
    width: usize,
    height: usize,
}

impl FromStr for WordSearch {
    type Err = Report;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let grid: Vec<_> = s.chars().filter(|&ch| ch != '\n').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 WordSearch {
    fn neighbour(&self, i: usize, dir: Direction) -> Option<usize> {
        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 % width != 0 => Some(i + width - 1),
            W if i % width != 0 => Some(i - 1),
            NW if i >= width && i % width != 0 => Some(i - width - 1),
            _ => None,
        }
    }

    fn word_count(&self, word: &str) -> Result<usize> {
        let mut found = 0;
        for i in 0..self.grid.len() {
            for dir in Direction::all() {
                if self.word_present(word, i, *dir) {
                    found += 1;
                }
            }
        }
        Ok(found)
    }

    fn x_count(&self) -> Result<usize> {
        let mut found = 0;
        for i in 0..self.grid.len() {
            if self.x_present(i) {
                found += 1;
            }
        }
        Ok(found)
    }

    fn word_present(&self, word: &str, location: usize, dir: Direction) -> bool {
        let mut next = Some(location);
        for ch in word.chars() {
            let i = if let Some(i) = next {
                i
            } else {
                // Off the edge
                return false;
            };

            if self.grid[i] != ch {
                return false;
            }
            next = self.neighbour(i, dir);
        }
        true
    }

    fn x_present(&self, location: usize) -> bool {
        if self.grid.get(location) != Some(&'A') {
            return false;
        }
        let diags = [
            (Direction::NE, Direction::SW),
            (Direction::NW, Direction::SE),
        ];
        diags.iter().all(|(dir_a, dir_b)| {
            let Some(a_idx) = self.neighbour(location, *dir_a) else {
                return false;
            };
            let Some(b_idx) = self.neighbour(location, *dir_b) else {
                return false;
            };
            let a = self.grid[a_idx];
            let b = self.grid[b_idx];
            (a == 'M' && b == 'S') || (b == 'M' && a == 'S')
        })
    }
}

fn part1(filepath: &str) -> Result<usize> {
    let input = fs::read_to_string(filepath)?;
    let grid = WordSearch::from_str(&input)?;
    grid.word_count("XMAS")
}

fn part2(filepath: &str) -> Result<usize> {
    let input = fs::read_to_string(filepath)?;
    let grid = WordSearch::from_str(&input)?;
    grid.x_count()
}

fn main() -> Result<()> {
    color_eyre::install()?;

    println!("Part 1: {}", part1("d04/input.txt")?);
    println!("Part 2: {}", part2("d04/input.txt")?);
    Ok(())
}
[–] [email protected] 2 points 3 weeks ago* (last edited 3 weeks ago)

python

solution

import aoc

def setup():
    return (aoc.get_lines(4, padded=(True, '.', 3)), 0)

def one():
    lines, acc = setup()
    for row, l in enumerate(lines):
        for col, c in enumerate(l):
            if c == 'X':
                w = l[col - 3:col + 1]
                e = l[col:col + 4]
                n = c + lines[row - 1][col] + \
                    lines[row - 2][col] + lines[row - 3][col]
                s = c + lines[row + 1][col] + \
                    lines[row + 2][col] + lines[row + 3][col]
                nw = c + lines[row - 1][col - 1] + \
                    lines[row - 2][col - 2] + lines[row - 3][col - 3]
                ne = c + lines[row - 1][col + 1] + \
                    lines[row - 2][col + 2] + lines[row - 3][col + 3]
                sw = c + lines[row + 1][col - 1] + \
                    lines[row + 2][col - 2] + lines[row + 3][col - 3]
                se = c + lines[row + 1][col + 1] + \
                    lines[row + 2][col + 2] + lines[row + 3][col + 3]
                for word in [w, e, n, s, nw, ne, sw, se]:
                    if word in ['XMAS', 'SAMX']:
                        acc += 1
    print(acc)

def two():
    lines, acc = setup()
    for row, l in enumerate(lines):
        for col, c in enumerate(l):
            if c == 'A':
                l = lines[row - 1][col - 1] + c + lines[row + 1][col + 1]
                r = lines[row + 1][col - 1] + c + lines[row - 1][col + 1]
                if l in ['MAS', 'SAM'] and r in ['MAS', 'SAM']:
                    acc += 1
    print(acc)

one()
two()

[–] [email protected] 2 points 3 weeks ago

Rust

Ugh. Spent way too long on today's. Should have just used my own grid structure from last year. I will likely refactor to use that. Even though it's likely a super slow implementation, the convenience of dealing with it is better than shoehorning in the grid::Grid<T> from that crate.

solution (no supporting code)

use grid::Grid;

use crate::shared::{
    grid2d::{iter_diag_nesw, iter_diag_nwse, Point},
    util::read_lines,
};

fn parse_grid(input: &[String]) -> Grid<u8> {
    let cols = input.first().unwrap().len();
    Grid::from_vec(
        input
            .iter()
            .flat_map(|row| row.chars().map(|c| c as u8).collect::<Vec<u8>>())
            .collect(),
        cols,
    )
}

fn part1(grid: &Grid<u8>) -> usize {
    let mut xmas_count = 0;
    let rows = grid
        .iter_rows()
        .map(|d| String::from_utf8(d.copied().collect()).unwrap());
    let cols = grid
        .iter_cols()
        .map(|d| String::from_utf8(d.copied().collect()).unwrap());
    for diag in iter_diag_nesw(grid)
        .chain(iter_diag_nwse(grid))
        .filter_map(|d| {
            if d.len() >= 4 {
                Some(String::from_utf8(d.clone()).unwrap())
            } else {
                None
            }
        })
        .chain(rows)
        .chain(cols)
    {
        xmas_count += diag.matches("XMAS").count() + diag.matches("SAMX").count()
    }
    xmas_count
}

fn part2(grid: &Grid<u8>) -> usize {
    let mut xmas_count = 0;
    let valid = [
        [b'M', b'M', b'S', b'S'],
        [b'M', b'S', b'S', b'M'],
        [b'S', b'M', b'M', b'S'],
        [b'S', b'S', b'M', b'M'],
    ];
    for x in 1..grid.cols() - 1 {
        for y in 1..grid.rows() - 1 {
            if grid.get(y, x) == Some(&b'A')
                && valid.contains(
                    &(Point::new(x as isize, y as isize)
                        .diagonal_neighbors(grid)
                        .map(|i| i.unwrap_or(0))),
                )
            {
                xmas_count += 1;
            }
        }
    }
    xmas_count
}

pub fn solve() {
    let input = read_lines("inputs/day04.txt");
    let grid = parse_grid(&input);
    println!("Part 1: {}", part1(&grid));
    println!("Part 2: {}", part2(&grid));
}

And here's a link to the Github if you care to see the gross supporting code :D

[–] [email protected] 2 points 3 weeks ago

C#

public class Day04 : Solver
{
  private int width, height;
  private char[,] data;

  public void Presolve(string input) {
    var lines = input.Trim().Split("\n").ToList();
    height = lines.Count;
    width = lines[0].Length;
    data = new char[height, width];
    for (int i = 0; i < height; i++) {
      for (int j = 0; j < width; j++) {
        data[i, j] = lines[i][j];
      }
    }
  }

  private static readonly string word = "XMAS";

  public string SolveFirst()
  {
    int counter = 0;
    for (int start_i = 0; start_i < height; start_i++) {
      for (int start_j = 0; start_j < width; start_j++) {
        if (data[start_i, start_j] != word[0]) continue;
        for (int di = -1; di <= 1; di++) {
          for (int dj = -1; dj <= 1; dj++) {
            if (di == 0 && dj == 0) continue;
            int end_i = start_i + di * (word.Length - 1);
            int end_j = start_j + dj * (word.Length - 1);
            if (end_i < 0 || end_j < 0 || end_i >= height || end_j >= width) continue;
            for (int k = 1; k < word.Length; k++) {
              if (data[start_i + di * k, start_j + dj * k] != word[k]) break;
              if (k == word.Length - 1) counter++;
            }
          }
        }
      }
    }
    return counter.ToString();
  }

  public string SolveSecond()
  {
    int counter = 0;
    for (int start_i = 1; start_i < height - 1; start_i++) {
      for (int start_j = 1; start_j < width - 1; start_j++) {
        if (data[start_i, start_j] != 'A') continue;
        int even_mas_starts = 0;
        for (int di = -1; di <= 1; di++) {
          for (int dj = -1; dj <= 1; dj++) {
            if (di == 0 && dj == 0) continue;
            if ((di + dj) % 2 != 0) continue;
            if (data[start_i + di, start_j + dj] != 'M') continue;
            if (data[start_i - di, start_j - dj] != 'S') continue;
            even_mas_starts++;
          }
        }
        if (even_mas_starts == 2) counter++;
      }
    }
    return counter.ToString();
  }
}
[–] [email protected] 2 points 3 weeks ago

C

What can I say, bunch of for loops! I add a 3 cell border to avoid having to do bounds checking in the inner loops.

Code

#include "common.h"
#define GZ 146

int main(int argc, char **argv) {
	static char g[GZ][GZ];
	static const char w[] = "XMAS";
	int p1=0,p2=0, x,y, m,i;

	if (argc > 1) DISCARD(freopen(argv[1], "r", stdin));
	for (y=3; y<GZ && fgets(g[y]+3, GZ-3, stdin); y++) ;

	for (y=3; y<GZ-3; y++)
	for (x=3; x<GZ-3; x++) {
		for (m=1,i=0; i<4; i++) {m &= g[y+i][x]==w[i];} p1+=m;
		for (m=1,i=0; i<4; i++) {m &= g[y-i][x]==w[i];} p1+=m;
		for (m=1,i=0; i<4; i++) {m &= g[y][x+i]==w[i];} p1+=m;
		for (m=1,i=0; i<4; i++) {m &= g[y][x-i]==w[i];} p1+=m;
		for (m=1,i=0; i<4; i++) {m &= g[y+i][x+i]==w[i];} p1+=m;
		for (m=1,i=0; i<4; i++) {m &= g[y-i][x-i]==w[i];} p1+=m;
		for (m=1,i=0; i<4; i++) {m &= g[y+i][x-i]==w[i];} p1+=m;
		for (m=1,i=0; i<4; i++) {m &= g[y-i][x+i]==w[i];} p1+=m;

		p2 += g[y+1][x+1]=='A' &&
		      ((g[y][x]  =='M' && g[y+2][x+2]=='S')  ||
		       (g[y][x]  =='S' && g[y+2][x+2]=='M')) &&
		      ((g[y+2][x]=='M' && g[y][x+2]  =='S')  ||
		       (g[y+2][x]=='S' && g[y][x+2]  =='M'));
	}

	printf("04: %d %d\n", p1, p2);
}

https://github.com/sjmulder/aoc/blob/master/2024/c/day04.c

[–] [email protected] 2 points 2 weeks ago

Uiua

This one was nice. The second part seemed quite daunting at first but wasn't actually that hard in the end.

Run with example input here

Row    ← ⌕ "XMAS"
RevRow ← ⌕"SAMX"
Sum    ← /+/+
Count  ← +∩Sum⊃Row RevRow

PartOne ← (
  &rs ∞ &fo "input-4.txt"
  ⊜∘≠@\n.
  ⊙+⟜∩Count⟜⍉ # horizontal and vertical search
  ⟜(/+⧈(Count⍉≡⬚@ ↻⇡⧻.)4)
  /+⧈(Count⍉≡⬚@ ↻¯⇡⧻.)4
  ++
)

Mask ← °⊚×2⇡5
# Create variations of X-MAS
Vars ← (
  ["M S"
   " A "
   "M S"]
  ≡♭[∩⟜⍉]≡⇌.
  Mask
  ⊏0⊞▽¤
)

PartTwo ← (
  &rs ∞ &fo "input-4.txt"
  ⊜∘≠@\n.
  ⧈(/+♭⊞≍⊙¤Vars▽Mask♭)3_3
  Sum
)

&p "Day 4:"
&pf "Part 1: "
&p PartOne
&pf "Part 2: "
&p PartTwo
[–] [email protected] 1 points 3 weeks ago

Kotlin

fun part1(input: String): Int {
    return countWordOccurrences(input.lines())
}

fun part2(input: String): Int {
    val grid = input.lines().map(String::toList)
    var count = 0
    for (row in 1..grid.size - 2) {
        for (col in 1..grid[row].size - 2) {
            if (grid[row][col] == 'A') {
                count += countCrossMatch(grid, row, col)
            }
        }
    }
    return count
}

private fun countCrossMatch(grid: List<List<Char>>, row: Int, col: Int): Int {
    val surroundingCorners = listOf(
        grid[row - 1][col - 1], // upper left
        grid[row - 1][col + 1], // upper right
        grid[row + 1][col - 1], // lower left
        grid[row + 1][col + 1], // lower right
    )
    // no matches:
    //   M S   S M
    //    A     A
    //   S M   M S
    return if (surroundingCorners.count { it == 'M' } == 2
        && surroundingCorners.count { it == 'S' } == 2
        && surroundingCorners[0] != surroundingCorners[3]
    ) 1 else 0
}

private fun countWordOccurrences(matrix: List<String>): Int {
    val rows = matrix.size
    val cols = if (rows > 0) matrix[0].length else 0
    val directions = listOf(
        Pair(0, 1),   // Horizontal right
        Pair(1, 0),   // Vertical down
        Pair(1, 1),   // Diagonal down-right
        Pair(1, -1),  // Diagonal down-left
        Pair(0, -1),  // Horizontal left
        Pair(-1, 0),  // Vertical up
        Pair(-1, -1), // Diagonal up-left
        Pair(-1, 1)   // Diagonal up-right
    )

    fun isWordAt(row: Int, col: Int, word: String, direction: Pair<Int, Int>): Boolean {
        val (dx, dy) = direction
        for (i in word.indices) {
            val x = row + i * dx
            val y = col + i * dy
            if (x !in 0 until rows || y !in 0 until cols || matrix[x][y] != word[i]) {
                return false
            }
        }
        return true
    }

    var count = 0

    for (row in 0 until rows) {
        for (col in 0 until cols) {
            for (direction in directions) {
                if (isWordAt(row, col, "XMAS", direction)) {
                    count++
                }
            }
        }
    }

    return count
}
[–] [email protected] 1 points 2 weeks ago

Lisp

Not super happy with the code, but it got the job done.

Part 1 and 2

(defun p1-process-line (line)
  (to-symbols line))

(defun found-word-h (word data i j)
  "checks for a word existing from the point horizontally to the right"
  (loop for j2 from j 
        for w in word
        when (not (eql w (aref data i j2)))
          return nil
        finally (return t)))

(defun found-word-v (word data i j)
  "checks for a word existing from the point vertically down"
  (loop for i2 from i 
        for w in word
        when (not (eql w (aref data i2 j)))
          return nil
        finally (return t)))

(defun found-word-d-l (word data i j)
  "checks for a word existsing from the point diagonally to the left and down"
  (destructuring-bind (n m) (array-dimensions data)
    (declare (ignorable n))
    
    (and (>= (- i (length word)) -1)
         (>= m (+ j  (length word)))
         (loop for i2 from i downto 0
               for j2 from j
               for w in word
               when (not (eql w (aref data i2 j2)))
                 return nil
               finally  (return t)))))

(defun found-word-d-r (word data i j)
  "checks for a word existing from the point diagonally to the right and down"
  (destructuring-bind (n m) (array-dimensions data)
    (and (>= n (+ i (length word)))
         (>= m (+ j  (length word)))
         (loop for i2 from i
               for j2 from j
               for w in word
               when (not (eql w (aref data i2 j2)))
                 return nil
               finally  (return t)))
    ))

(defun count-word-h (data word)
  "Counts horizontal matches of the word"
  (let ((word-r (reverse word))
        (word-l (length word)))
    (destructuring-bind (n m) (array-dimensions data)
      (loop for i from 0 below n 
            sum (loop for j from 0 upto (- m word-l)
                      count (found-word-h word data i j)
                      count (found-word-h word-r data i j))))))

(defun count-word-v (data word)
  "Counts vertical matches of the word"
  (let ((word-r (reverse word))
        (word-l (length word)))
    (destructuring-bind (n m) (array-dimensions data)
      (loop for j from 0 below m 
            sum (loop for i from 0 upto (- n word-l)
                      count (found-word-v word data i j)
                      count (found-word-v word-r data i j))))))

(defun count-word-d (data word)
  "Counts diagonal matches of the word"
  (let ((word-r (reverse word)))
    (destructuring-bind (n m) (array-dimensions data)
      (loop for i from 0 below n
            sum (loop for j from 0 below m
                      count (found-word-d-l word data i j)
                      count (found-word-d-l word-r data i j)
                      count (found-word-d-r word data i j)
                      count (found-word-d-r word-r data i j)
                      )))))


(defun run-p1 (file)
  "cares about the word xmas in any direction"
  (let ((word '(X M A S))
        (data (list-to-2d-array (read-file file #'p1-process-line))))
    (+
     (count-word-v data word)
     (count-word-h data word)
     (count-word-d data word))))


(defun run-p2 (file) 
  "cares about an x of mas crossed with mas"
  (let ((word '(M A S))
        (word-r '(S A M))
        (data (list-to-2d-array (read-file file #'p1-process-line))))
    (destructuring-bind (n m) (array-dimensions data)
      (loop for i from 0 below (- n 2)
            sum (loop for j from 0 below (- m 2)
                      count (and (found-word-d-r word data i j)
                                 (found-word-d-l word data (+ i 2) j))
                      count (and (found-word-d-r word-r data i j)
                                 (found-word-d-l word data (+ i 2) j))
                      count (and (found-word-d-r word data i j)
                                 (found-word-d-l word-r data (+ i 2) j))
                      count (and (found-word-d-r word-r data i j)
                                 (found-word-d-l word-r data (+ i 2) j))
                        )))))

[–] [email protected] 1 points 3 weeks ago

C#

namespace Day04;

static class Program
{
    public record struct Point(int Row, int Col);

    static void Main(string[] args)
    {
        var sample = File.ReadAllLines("sample.txt");
        var data = File.ReadAllLines("data.txt");

        Console.WriteLine($"Part 1 (sample): {SolvePart1(sample)}");
        Console.WriteLine($"Part 1 (data): {SolvePart1(data)}");

        Console.WriteLine($"Part 2 (sample): {SolvePart2(sample)}");
        Console.WriteLine($"Part 2 (data): {SolvePart2(data)}");
    }

    private static readonly string Search = "XMAS";

    private static readonly Func<Point, Point>[] DirectionalMoves =
    {
        p => new Point(p.Row + 1, p.Col),
        p => new Point(p.Row + 1, p.Col + 1),
        p => new Point(p.Row, p.Col + 1),
        p => new Point(p.Row - 1, p.Col + 1),
        p => new Point(p.Row - 1, p.Col),
        p => new Point(p.Row - 1, p.Col - 1),
        p => new Point(p.Row, p.Col - 1),
        p => new Point(p.Row + 1, p.Col - 1),
    };

    private static readonly Func<Point, Point>[] ForwardSlashMoves =
    {
        p => new Point(p.Row - 1, p.Col - 1),
        p => new Point(p.Row + 1, p.Col + 1),
    };
    
    private static readonly Func<Point, Point>[] BackSlashMoves =
    {
        p => new Point(p.Row + 1, p.Col - 1),
        p => new Point(p.Row - 1, p.Col + 1),
    };

    static long SolvePart1(string[] data)
    {
        return Enumerable
            .Range(0, data.Length)
            .SelectMany(row => Enumerable.Range(0, data[row].Length)
                .Select(col => new Point(row, col)))
            .Where(p => IsMatch(data, p, Search[0]))
            .Sum(p => DirectionalMoves
                .Count(move => DeepMatch(data, move(p), move, Search, 1)));
    }

    static long SolvePart2(string[] data)
    {
        return Enumerable
            .Range(0, data.Length)
            .SelectMany(row => Enumerable.Range(0, data[row].Length)
                .Select(col => new Point(row, col)))
            .Where(p => IsMatch(data, p, 'A'))
            .Count(p => CheckDiagonalMoves(data, p, ForwardSlashMoves)
                        && CheckDiagonalMoves(data, p, BackSlashMoves));
    }

    static bool CheckDiagonalMoves(string[] data, Point p, Func<Point, Point>[] moves)
        => (IsMatch(data, moves[0](p), 'S') && IsMatch(data, moves[1](p), 'M'))
           || (IsMatch(data, moves[0](p), 'M') && IsMatch(data, moves[1](p), 'S'));

    static bool DeepMatch(string[] data, Point p, Func<Point, Point> move, string search, int searchIndex) =>
        (searchIndex >= search.Length) ? true :
        (!IsMatch(data, p, search[searchIndex])) ? false :
        DeepMatch(data, move(p), move, search, searchIndex + 1);

    static bool IsMatch(string[] data, Point p, char searchChar)
        => IsInBounds(data, p) && (data[p.Row][p.Col] == searchChar);

    static bool IsInBounds(string[] data, Point p) =>
        (p.Row >= 0) && (p.Col >= 0) && (p.Row < data.Length) && (p.Col < data[0].Length);
}
[–] [email protected] 1 points 2 weeks ago* (last edited 2 weeks ago)

Python

Essentially I'm extracting strings from the word search and compare them to the desired value. For part one that means extracting from an X in eight directions. Because I'm reading from the central X outwards, I don't need to reverse any of them.
Part two reads two strings in an X-shape around the coordinates of each X. The resulting strings are filtered down to include only "MAS" and "SAM". If there are exactly two strings we found an X-MAS.

from pathlib import Path


def parse_input(input: str) -> list[str]:
    return input.strip().splitlines()


def extract_strings_one(m: int, n: int, haystack: list[str], l: int = 4) -> list[str]:
    result = []
    # Right
    if m + l <= len(haystack[n]):
        result.append(haystack[n][m : m + l])
    # Up-Right
    if m + l <= len(haystack[n]) and n > l - 2:
        result.append("".join([haystack[n - i][m + i] for i in range(l)]))
    # Up
    if n > l - 2:
        result.append("".join([haystack[n - i][m] for i in range(l)]))
    # Up-Left
    if m > l - 2 and n > l - 2:
        result.append("".join([haystack[n - i][m - i] for i in range(l)]))
    # Left
    if m > l - 2:
        result.append("".join([haystack[n][m - i] for i in range(l)]))
    # Down-Left
    if m > l - 2 and n + l <= len(haystack):
        result.append("".join([haystack[n + i][m - i] for i in range(l)]))
    # Down
    if n + l <= len(haystack):
        result.append("".join([haystack[n + i][m] for i in range(l)]))
    # Down-Right
    if m + l <= len(haystack[n]) and n + l <= len(haystack):
        result.append("".join([haystack[n + i][m + i] for i in range(l)]))
    return result


def extract_strings_two(m: int, n: int, haystack: list[str], d: int = 1) -> list[str]:
    result = []
    if 0 <= m - d and m + d < len(haystack[n]) and 0 <= n - d and n + d < len(haystack):
        result.append("".join([haystack[n + i][m + i] for i in range(-d, d + 1)]))
        result.append("".join([haystack[n - i][m + i] for i in range(-d, d + 1)]))
    return result


def part_one(input: str) -> int:
    lines = parse_input(input)
    xmas_count = 0
    for i, line in enumerate(lines):
        x = line.find("X", 0)
        while x != -1:
            xmas_count += len(
                list(filter(lambda s: s == "XMAS", extract_strings_one(x, i, lines)))
            )
            x = line.find("X", x + 1)
    return xmas_count


def part_two(input: str) -> int:
    lines = parse_input(input)
    x_mas_count = 0
    for i, line in enumerate(lines[1:-1], 1):
        a = line.find("A", 0)
        while a != -1:
            if (
                len(
                    list(
                        filter(
                            lambda s: s in ("MAS", "SAM"),
                            extract_strings_two(a, i, lines),
                        )
                    )
                )
                == 2
            ):
                x_mas_count += 1
            a = line.find("A", a + 1)
    return x_mas_count


if __name__ == "__main__":
    input = Path("input").read_text("utf-8")
    print(part_one(input))
    print(part_two(input))
[–] [email protected] 1 points 3 weeks ago

Go

Just a bunch of ifs and bounds checking. Part 2 was actually simpler.

Code

func part1(W [][]rune) {
	m := len(W)
	n := len(W[0])
	xmasCount := 0

	for i := 0; i < m; i++ {
		for j := 0; j < n; j++ {
			if W[i][j] != 'X' {
				continue
			}
			if j < n-3 && W[i][j+1] == 'M' && W[i][j+2] == 'A' && W[i][j+3] == 'S' {
				// Horizontal left to right
				xmasCount++
			}
			if j >= 3 && W[i][j-1] == 'M' && W[i][j-2] == 'A' && W[i][j-3] == 'S' {
				// Horizontal right to left
				xmasCount++
			}
			if i < m-3 && W[i+1][j] == 'M' && W[i+2][j] == 'A' && W[i+3][j] == 'S' {
				// Vertical up to down
				xmasCount++
			}
			if i >= 3 && W[i-1][j] == 'M' && W[i-2][j] == 'A' && W[i-3][j] == 'S' {
				// Vertical down to up
				xmasCount++
			}
			if j < n-3 && i < m-3 && W[i+1][j+1] == 'M' && W[i+2][j+2] == 'A' && W[i+3][j+3] == 'S' {
				// Diagonal left to right and up to down
				xmasCount++
			}
			if j >= 3 && i < m-3 && W[i+1][j-1] == 'M' && W[i+2][j-2] == 'A' && W[i+3][j-3] == 'S' {
				// Diagonal right to left and up to down
				xmasCount++
			}
			if j < n-3 && i >= 3 && W[i-1][j+1] == 'M' && W[i-2][j+2] == 'A' && W[i-3][j+3] == 'S' {
				// Diagonal left to right and down to up
				xmasCount++
			}
			if j >= 3 && i >= 3 && W[i-1][j-1] == 'M' && W[i-2][j-2] == 'A' && W[i-3][j-3] == 'S' {
				// Diagonal right to left and down to up
				xmasCount++
			}
		}
	}

	fmt.Println(xmasCount)
}

func part2(W [][]rune) {
	m := len(W)
	n := len(W[0])
	xmasCount := 0

	for i := 0; i <= m-3; i++ {
		for j := 0; j <= n-3; j++ {
			if W[i+1][j+1] != 'A' {
				continue
			}
			if W[i][j] == 'M' && W[i][j+2] == 'M' && W[i+2][j] == 'S' && W[i+2][j+2] == 'S' {
				xmasCount++
			} else if W[i][j] == 'M' && W[i][j+2] == 'S' && W[i+2][j] == 'M' && W[i+2][j+2] == 'S' {
				xmasCount++
			} else if W[i][j] == 'S' && W[i][j+2] == 'S' && W[i+2][j] == 'M' && W[i+2][j+2] == 'M' {
				xmasCount++
			} else if W[i][j] == 'S' && W[i][j+2] == 'M' && W[i+2][j] == 'S' && W[i+2][j+2] == 'M' {
				xmasCount++
			}
		}
	}

	fmt.Println(xmasCount)
}

func main() {
	file, _ := os.Open("input.txt")
	defer file.Close()
	scanner := bufio.NewScanner(file)

	var W [][]rune
	for scanner.Scan() {
		line := scanner.Text()
		W = append(W, []rune(line))
	}

	part1(W)
	part2(W)
}

[–] [email protected] 1 points 3 weeks ago* (last edited 3 weeks ago)

Julia

Had some time to clean up the code today since the solution was quite straight forward after making a plan on how to approach it.

spoiler

function readWordSearch(inputFile::String)::Matrix{Char}
	f = open(inputFile,"r")
	lines::Vector{String} = readlines(f)
	close(f)
	wordSearch = Matrix{Char}(undef,length(lines),length(lines))
	for (i,line) in enumerate(lines)
		wordSearch[i,:] = collect(line)
	end
	return wordSearch
end

function countXMASAppearances(wS::Matrix{Char})::Int
	appearanceCount::Int = 0
	for i=1 : size(wS)[1] #lines
		for j=1 : size(wS)[2] #columns
			wS[i,j]!='X' ? continue : nothing #continue if char is not X
			#if char is X, check surrounding area
			# check horizontals
			#left
			j>=4 ? (wS[i,j-1]*wS[i,j-2]*wS[i,j-3]=="MAS" ? appearanceCount+=1 : nothing) : nothing
			#right
			j<=size(wS)[2]-3 ? (wS[i,j+1]*wS[i,j+2]*wS[i,j+3]=="MAS" ? appearanceCount+=1 : nothing) : nothing
			# check verticals
			#up
			i>=4 ? (wS[i-1,j]*wS[i-2,j]*wS[i-3,j]=="MAS" ? appearanceCount+=1 : nothing) : nothing
			#down
			i<=size(wS)[1]-3 ? (wS[i+1,j]*wS[i+2,j]*wS[i+3,j]=="MAS" ? appearanceCount+=1 : nothing) : nothing
			# check diagonals
			#left up
			i>=4 && j>=4 ? (wS[i-1,j-1]*wS[i-2,j-2]*wS[i-3,j-3]=="MAS" ? appearanceCount+=1 : nothing) : nothing
			#right up
			i>=4 && j<=size(wS)[2]-3 ? (wS[i-1,j+1]*wS[i-2,j+2]*wS[i-3,j+3]=="MAS" ? appearanceCount+=1 : nothing) : nothing
			#left down
			i<=size(wS)[1]-3 && j>=4 ? (wS[i+1,j-1]*wS[i+2,j-2]*wS[i+3,j-3]=="MAS" ? appearanceCount+=1 : nothing) : nothing
			#right down
			i<=size(wS)[1]-3 && j<=size(wS)[2]-3 ? (wS[i+1,j+1]*wS[i+2,j+2]*wS[i+3,j+3]=="MAS" ? appearanceCount+=1 : nothing) : nothing
		end
	end
	return appearanceCount
end

function countX_MASAppearances(wordSearch::Matrix{Char})::Int
	appearances::Int = 0
	for l=2 : size(wordSearch)[1]-1
		for c=2 : size(wordSearch)[2]-1
			wordSearch[l,c]!='A' ? continue : nothing
			checkArr = [wordSearch[l-1,c-1],wordSearch[l-1,c+1],wordSearch[l+1,c-1],wordSearch[l+1,c+1]]
			if checkArr in [['M','M','S','S'],['M','S','M','S'],['S','S','M','M'],['S','M','S','M']]
				appearances += 1
			end
		end
	end
	return appearances
end

wordSearch::Matrix{Char} = readWordSearch(inputFile
prinltn("part 1 appearances: $(countXMASAppearances(wordSearch))")
prinltn("part 2 appearances: $(countX_MASAppearances(wordSearch))")

[–] [email protected] 1 points 2 weeks ago

Smalltalk

I could have done it in 2 fns if I made them more generic, but couldn't be bothered

day4p1: input
    | lines sum w h|
    
    sum := ('XMAS' asRegex matchesIn: input) size. "forward"
    sum := sum + ('SAMX' asRegex matchesIn: input) size. "backwards sep cause overlapping"
    
    lines := input lines.
    h := lines size.
    w := (lines at: 1) size.
    
    1 to: h-3 do: [ :p1 |
        1 to: w do: [ :p2 |
            sum := sum + (self d4diag: lines p1: p1 p2: p2 dir: -1).
            sum := sum + (self d4diag: lines p1: p1 p2: p2 dir: 0).
            sum := sum + (self d4diag: lines p1: p1 p2: p2 dir: 1).    
        ]
    ].
    
    ^ sum.
d4diag: input p1: p1 p2: p2 dir: dir
    | reverse xm ii |
    
    xm := 'XMAS'.
    
    reverse := ((input at: p1) at: p2) = $S.
    
    0 to: 3 do: [ :i |
        ii := reverse ifTrue: [ 4 - i ] ifFalse: [ i + 1 ].
                                                    "if out of bounds, obv not possible"
        ((xm at: ii) = ((input at: p1 + i) at: i * dir + p2 ifAbsent: [^ 0])) ifFalse: [^ 0]
    ].

    ^ 1.

Part 2

day4p2: input
    | lines sum w h pos |
    "Find all diag mas, then check of 1 . -1 (we can look back on every -1"
    
    sum := 0.
    lines := input lines.
    h := lines size.
    w := (lines at: 1) size.
    
    1 to: h-2 do: [ :p1 |
        pos := Array new: w withAll: false.
        1 to: w do: [ :p2 |
            (self d42: lines p1: p1 p2: p2 dir: -1)
                ifTrue: [
                    sum := sum + ((pos at: p2-2) ifTrue:[1] ifFalse:[0]).
                ].
            
            (self d42: lines p1: p1 p2: p2 dir: 1) ifTrue: [pos at: p2 put: true].
        ]
    ].
    
    ^ sum.
d42: input p1: p1 p2: p2 dir: dir
    | reverse xm ii |
    
    xm := 'MAS'.
    
    reverse := ((input at: p1) at: p2) = $S.
    
    0 to: 2 do: [ :i |
        ii := reverse ifTrue: [ 3 - i ] ifFalse: [ i + 1 ].
                                                    "if out of bounds, obv not possible"
        ((xm at: ii) = ((input at: p1 + i) at: i * dir + p2 ifAbsent: [^ false])) ifFalse: [^ false]
    ].

    ^ true.
[–] [email protected] 1 points 3 weeks ago

This one was a little bit of a pain. I loved it.

TypeScript

Solution

import { AdventOfCodeSolutionFunction } from "./solutions";

enum Direction {
    UP,
    UP_RIGHT,
    RIGHT,
    BOTTOM_RIGHT,
    BOTTOM,
    BOTTOM_LEFT,
    LEFT,
    UP_LEFT,
};

const ALL_DIRECTIONS = [
    Direction.RIGHT,
    Direction.BOTTOM_RIGHT,
    Direction.BOTTOM,
    Direction.BOTTOM_LEFT,
    Direction.LEFT,
    Direction.UP_LEFT,
    Direction.UP,
    Direction.UP_RIGHT,
];

const check_coords = (grid: Array<Array<string>>, x: number, y: number) => {
    return y >= grid.length ||
        y < 0 ||
        x >= grid[y].length ||
        x < 0
}

const search_direction = (grid: Array<Array<string>>, x: number, y: number, direction: Direction, find: Array<string>) => {
    // exit conditions
    // no more to find
    if (find.length == 0)
        return 1; // found the end

    // invalid coords
    if (check_coords(grid, x, y))
        return 0;

    // make new mutable list
    const newFind = [...find];
    const searchChar = newFind.shift();

    // wrong character
    if (grid[y][x] !== searchChar)
        return 0;

    switch (direction) {
        case Direction.UP:
            return search_direction(grid, x, y + 1, direction, newFind);

        case Direction.UP_RIGHT:
            return search_direction(grid, x + 1, y + 1, direction, newFind);

        case Direction.RIGHT:
            return search_direction(grid, x + 1, y, direction, newFind);

        case Direction.BOTTOM_RIGHT:
            return search_direction(grid, x + 1, y - 1, direction, newFind);

        case Direction.BOTTOM:
            return search_direction(grid, x, y - 1, direction, newFind);

        case Direction.BOTTOM_LEFT:
            return search_direction(grid, x - 1, y - 1, direction, newFind);

        case Direction.LEFT:
            return search_direction(grid, x - 1, y, direction, newFind);

        case Direction.UP_LEFT:
            return search_direction(grid, x - 1, y + 1, direction, newFind);

        default:
            return 0;
    }
}

const part_1_search = (grid: Array<Array<string>>, x: number, y: number, find: Array<string>) => {
    return ALL_DIRECTIONS.reduce<number>(
        (instances, direction) =>
            instances + search_direction(grid, x, y, direction, find),
        0
    );
}

const part_2_search = (grid: Array<Array<string>>, x: number, y: number, find: Array<string>) => {
    return (
        search_direction(grid, x - 1, y + 1, Direction.BOTTOM_RIGHT, find) +
        search_direction(grid, x + 1, y + 1, Direction.BOTTOM_LEFT, find) +
        search_direction(grid, x - 1, y - 1, Direction.UP_RIGHT, find) +
        search_direction(grid, x + 1, y - 1, Direction.UP_LEFT, find)
    ) == 2 ? 1 : 0;
}

export const solution_4: AdventOfCodeSolutionFunction = (input) => {
    const grid = input.split("\n").map(st => st.trim()).map(v => v.split(""));

    let part_1 = 0;
    let part_2 = 0;

    const find_1 = "XMAS".split("");
    const find_2 = "MAS".split("");

    for (let y = 0; y < grid.length; y++) {
        for (let x = 0; x < grid[y].length; x++) {
            part_1 += part_1_search(grid, x, y, find_1);
            part_2 += part_2_search(grid, x, y, find_2);
        }
    }

    return {
        part_1,
        part_2,
    };
}

Felt like this code quality is better than what I usually output :)

[–] [email protected] 1 points 2 weeks ago

Zig

const std = @import("std");
const List = std.ArrayList;

const tokenizeScalar = std.mem.tokenizeScalar;
const parseInt = std.fmt.parseInt;
const print = std.debug.print;
const eql = std.mem.eql;

var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const alloc = gpa.allocator();

const Point = struct {
    x: isize,
    y: isize,
    fn add(self: *const Point, point: *const Point) Point {
        return Point{ .x = self.x + point.x, .y = self.y + point.y };
    }
};

// note: i have no idea how to use this or if it's even possible
// const DirectionType = enum(u8) { Up, Down, Left, Right, UpLeft, UpRight, DownLeft, DownRight };
// const Direction = union(DirectionType) {
//     up: Point = .{ .x = 0, .y = 0 },
// };

const AllDirections = [_]Point{
    .{ .x = 0, .y = -1 }, // up
    .{ .x = 0, .y = 1 }, // down
    .{ .x = -1, .y = 0 }, // left
    .{ .x = 1, .y = 0 }, // right
    .{ .x = -1, .y = -1 }, // up left
    .{ .x = 1, .y = -1 }, // up right
    .{ .x = -1, .y = 1 }, // down left
    .{ .x = 1, .y = 1 }, // down right
};

const Answer = struct {
    xmas: u32,
    mas: u32,
};

pub fn searchXmas(letters: List([]const u8), search_char: u8, position: Point, direction: Point) u32 {
    const current_char = getChar(letters, position);
    if (current_char == search_char) {
        const next = position.add(&direction);
        if (current_char == 'M') {
            return searchXmas(letters, 'A', next, direction);
        } else if (current_char == 'A') {
            return searchXmas(letters, 'S', next, direction);
        } else if (current_char == 'S') {
            return 1; // found all letters
        }
    }
    return 0;
}

pub fn countXmas(letters: List([]const u8), starts: List(Point)) u32 {
    var counter: u32 = 0;
    for (starts.items) |start| {
        for (AllDirections) |direction| {
            const next = start.add(&direction);
            counter += searchXmas(letters, 'M', next, direction);
        }
    }
    return counter;
}

pub fn countMas(letters: List([]const u8), starts: List(Point)) u32 {
    var counter: u32 = 0;
    for (starts.items) |start| {
        const a_char = getChar(letters, start) orelse continue;
        const top_left_char = getChar(letters, start.add(&AllDirections[4])) orelse continue;
        const down_right_char = getChar(letters, start.add(&AllDirections[7])) orelse continue;
        const top_right_char = getChar(letters, start.add(&AllDirections[5])) orelse continue;
        const down_left_char = getChar(letters, start.add(&AllDirections[6])) orelse continue;

        const tldr = [3]u8{ top_left_char, a_char, down_right_char };
        const trdl = [3]u8{ top_right_char, a_char, down_left_char };
        if ((eql(u8, &tldr, "MAS") or eql(u8, &tldr, "SAM")) and (eql(u8, &trdl, "MAS") or eql(u8, &trdl, "SAM"))) {
            counter += 1;
        }
    }
    return counter;
}

pub fn getChar(letters: List([]const u8), point: Point) ?u8 {
    if (0 > point.x or point.x >= letters.items.len) {
        return null;
    }
    const row = @as(usize, @intCast(point.x));

    if (0 > point.y or point.y >= letters.items[row].len) {
        return null;
    }
    const col = @as(usize, @intCast(point.y));
    return letters.items[row][col];
}

pub fn solve(input: []const u8) !Answer {
    var rows = tokenizeScalar(u8, input, '\n');

    var letters = List([]const u8).init(alloc);
    defer letters.deinit();
    var x_starts = List(Point).init(alloc);
    defer x_starts.deinit();
    var a_starts = List(Point).init(alloc);
    defer a_starts.deinit();

    var x: usize = 0;
    while (rows.next()) |row| {
        try letters.append(row);
        for (row, 0..) |letter, y| {
            if (letter == 'X') {
                try x_starts.append(.{ .x = @intCast(x), .y = @intCast(y) });
            } else if (letter == 'A') {
                try a_starts.append(.{ .x = @intCast(x), .y = @intCast(y) });
            }
        }
        x += 1;
    }

    // PART 1
    const xmas = countXmas(letters, x_starts);

    // PART 2
    const mas = countMas(letters, a_starts);

    return Answer{ .xmas = xmas, .mas = mas };
}

pub fn main() !void {
    const answer = try solve(@embedFile("input.txt"));
    print("Part 1: {d}\n", .{answer.xmas});
    print("Part 2: {d}\n", .{answer.mas});
}

test "test input" {
    const answer = try solve(@embedFile("test.txt"));
    try std.testing.expectEqual(18, answer.xmas);
}

[–] [email protected] 1 points 3 weeks ago

Python

def read_input(path):
    with open(path) as f:
        lines = f.readlines()
        for i, line in enumerate(lines):
            ln = line.replace("\n","")
            lines[i] = ln
    return lines

def find_X(lines):
    Xes = []
    for j, line in enumerate(lines):
        ind = [i for i, ltr in enumerate(line) if ltr == "X"]
        for i in ind:
            Xes.append((j,i))
    return Xes

def find_M(lines, x, dim):
    # Check for Ms
    M_dirs = []
    for i in [-1, 0, 1]:
        x_ind = x[0] + i
        if x_ind>=0 and x_ind<dim:
            for j in [-1, 0, 1]:
                y_ind = x[1]+j
                if y_ind>=0 and y_ind<dim:
                    if lines[x_ind][y_ind] == "M":
                        M = [(x_ind, y_ind), (i,j)]
                        M_dirs.append(M)
    return M_dirs

def check_surroundings(loc, lines, check_char, direction):
    max = len(lines)-1
    check_lock = [loc[i]+direction[i] for i in range(len(loc))]
    if all(i>=0 and i<=max for i in check_lock) and check_char in str(lines[check_lock[0]][check_lock[1]]):
        return True
    else:
        return False

def part_one(lines):
    ans = 0 

    X = find_X(lines)
    dim = len(lines[0])
    for x in X:
        M = find_M(lines, x, dim)
        for m in M:
            loc = m[0]
            dir = m[1]
            
            if not check_surroundings(loc, lines, 'A', dir):
                continue
            
            loc = [loc[0]+dir[0], loc[1]+dir[1]]
            if not all(i>=0 and i<=dim-1 for i in loc):
                continue
            if not check_surroundings(loc, lines, 'S', dir):
                continue
            
            ans+=1
    return ans

def extract_square(lines, loc):
    str = ""
    for i in range(-1,2,1):
        for j in range(-1,2,1):
            x_ind = loc[0]+i
            y_ind = loc[1]+j
            if not all(p>=0 and p<=len(lines[0])-1 for p in [x_ind, y_ind]):
                raise ValueError("The given lock is at the edge of the grid and therefore will not produce a square")
            str += lines[x_ind][y_ind]
    return str

def check_square(square):
    if not square[4]=="A":
        return False
    elif not ((square[0]=="M" and square[8]=="S") or (square[0]=="S" and square[8]=="M")):
        return False
    elif not ((square[2]=="M" and square[6]=="S") or (square[2]=="S" and square[6]=="M")):
        return False
    else: return True

def part_two(lines):
    ans = 0
    dim = len(lines[0])
    for i in range(1,dim-1):
        for j in range(1,dim-1):
            square = extract_square(lines, (i,j))
            if check_square(square):
                ans += 1
    return ans

path = r'Day_4\input.txt'
lines = read_input(path)
print("Answer part 1: ", part_one(lines))
print("Answer part 2: ", part_two(lines))

[–] [email protected] 1 points 2 weeks ago

Elixir

defmodule AdventOfCode.Solution.Year2024.Day04 do
  use AdventOfCode.Solution.SharedParse

  defmodule Map do
    defstruct [:chars, :width, :height]
  end

  @impl true
  def parse(input) do
    chars = String.split(input, "\n", trim: true) |> Enum.map(&String.codepoints/1)
    %Map{chars: chars, width: length(Enum.at(chars, 0)), height: length(chars)}
  end

  def at(%Map{} = map, x, y) do
    cond do
      x < 0 or x >= map.width or y < 0 or y >= map.height -> ""
      true -> map.chars |> Enum.at(y, []) |> Enum.at(x, "")
    end
  end

  def part1(map) do
    dirs = for dx <- -1..1, dy <- -1..1, {dx, dy} != {0, 0}, do: {dx, dy}
    xmas = String.codepoints("XMAS") |> Enum.with_index() |> Enum.drop(1)

    for x <- 0..(map.width - 1),
        y <- 0..(map.height - 1),
        "X" == at(map, x, y),
        {dx, dy} <- dirs,
        xmas
        |> Enum.all?(fn {c, n} -> at(map, x + dx * n, y + dy * n) == c end),
        reduce: 0 do
      t -> t + 1
    end
  end

  def part2(map) do
    for x <- 0..(map.width - 1),
        y <- 0..(map.height - 1),
        "A" == at(map, x, y),
        (at(map, x - 1, y - 1) <> at(map, x + 1, y + 1)) in ["MS", "SM"],
        (at(map, x - 1, y + 1) <> at(map, x + 1, y - 1)) in ["MS", "SM"],
        reduce: 0 do
      t -> t + 1
    end
  end
end
[–] [email protected] 1 points 3 weeks ago

Raku

Oof, my struggle to make custom index walking paths for part 1 did not pay off for part 2.

Solution

sub MAIN($input) {
    my $file = (open $input).slurp;
    my @grid is List = $file.lines».comb».list;
    my @transposedGrid is List = [Z] @grid;
    my @reversedGrid is List = @grid».reverse;
    my @transposedReversedGrid is List = @transposedGrid».reverse;

    my @horizontalScanRows is List = generateScanHorizontal(@grid);
    my @transposedHorizontalScanRows is List = generateScanHorizontal(@transposedGrid);

    my @part-one-counts = [];
    @part-one-counts.push(count-xmas(@grid, @horizontalScanRows)); # Right
    @part-one-counts.push(count-xmas(@transposedGrid, @transposedHorizontalScanRows)); # Down
    @part-one-counts.push(count-xmas(@reversedGrid, @horizontalScanRows)); # Left
    @part-one-counts.push(count-xmas(@transposedReversedGrid, @transposedHorizontalScanRows)); # Up

    my @diagonalScanRows is List = generateScanDiagonal(@grid);
    my @transposedDiagonalScanRows is List = generateScanDiagonal(@transposedGrid);
    @part-one-counts.push(count-xmas(@grid, @diagonalScanRows)); # Down Right
    @part-one-counts.push(count-xmas(@grid, @diagonalScanRows».reverse)); # Up Left
    @part-one-counts.push(count-xmas(@reversedGrid, @diagonalScanRows)); # Down Left
    @part-one-counts.push(count-xmas(@reversedGrid, @diagonalScanRows».reverse)); # Up Right

    my $part-one-solution = @part-one-counts.sum;
    say "part 1: $part-one-solution";


    my @part-two-counts = [];
    @part-two-counts.push(countGridMatches(@grid, (<M . S>,<. A .>,<M . S>)));
    @part-two-counts.push(countGridMatches(@grid, (<S . S>,<. A .>,<M . M>)));
    @part-two-counts.push(countGridMatches(@grid, (<S . M>,<. A .>,<S . M>)));
    @part-two-counts.push(countGridMatches(@grid, (<M . M>,<. A .>,<S . S>)));

    my $part-two-solution = @part-two-counts.sum;
    say "part 2: $part-two-solution";

}

sub count-xmas(@grid, @scanRows) {
    my $xmas-count = 0;
    for @scanRows -> @scanRow {
        my $xmas-pos = 0;
        for @scanRow -> @pos {
            my $char = @grid[@pos[0]][@pos[1]];
            if "X" eq $char {
                $xmas-pos = 1;
            }elsif <X M A S>[$xmas-pos] eq $char {
                if $xmas-pos == 3 {
                    $xmas-pos = 0;
                    $xmas-count += 1;
                } else {
                    $xmas-pos += 1;
                }
            } else {
                $xmas-pos = 0;
            }
        }
    }
    return $xmas-count;
}

sub generateScanHorizontal(@grid) {
    # Horizontal
    my $rows = @grid.elems;
    my $cols = @grid[0].elems;
    my @scanRows = ();
    for 0..^$rows -> $row {
        my @scanRow = ();
        for 0..^$cols -> $col {
            @scanRow.push(($row, $col));
        }
        @scanRows.push(@scanRow);
    }
    return @scanRows.List».List;
}

sub generateScanDiagonal(@grid) {
    # Down-right diagonal
    my $rows = @grid.elems;
    my $cols = @grid[0].elems;
    my @scanRows = ();
    for 0..^($rows + $cols - 1) -> $diag {
        my @scanRow = ();
        my $starting-row = max(-$cols + $diag + 1, 0);
        my $starting-col = max($rows - $diag - 1, 0);
        my $diag-len = min($rows - $starting-row, $cols - $starting-col);
        for 0..^$diag-len -> $diag-pos {
            @scanRow.push(($starting-row + $diag-pos, $starting-col + $diag-pos));
        }
        @scanRows.push(@scanRow);
    }
    return @scanRows.List».List;
}

sub countGridMatches(@grid, @needle) {
    my $count = 0;
    for 0..(@grid.elems - @needle.elems) -> $top {
        TOP-LEFT:
        for 0..(@grid[$top].elems - @needle[0].elems) -> $left {
            for 0..^@needle.elems -> $row-offset {
                for 0..^@needle[$row-offset].elems -> $col-offset {
                    my $needle-char = @needle[$row-offset][$col-offset];
                    next if $needle-char eq ".";
                    next TOP-LEFT if $needle-char ne @grid[$top+$row-offset][$left+$col-offset];
                }
            }
            $count += 1;
        }
    }
    return $count;
}

github

[–] [email protected] 1 points 3 weeks ago

Rust

One of those with running through tricky grid indices. The vector types from the euclid crate helped in dealing with positions.

Code

use euclid::{vec2, default::*};

fn count_xmas(grid: &[&[u8]], pos: (usize, usize)) -> u32 {
    if grid[pos.1][pos.0] != b'X' {
        return 0
    }

    let bounds = Rect::new(Point2D::origin(), Size2D::new(grid[0].len() as i32, grid.len() as i32));
    const DIRS: [Vector2D<i32>; 8] = [
        vec2(1, 0), vec2(-1, 0), vec2(0, 1), vec2(0, -1),
        vec2(1, 1), vec2(1, -1), vec2(-1, 1), vec2(-1, -1),
    ];
    let mut count = 0;
    for dir in DIRS {
        let mut cur = Point2D::from(pos).to_i32();
        let mut found = true;
        for letter in [b'M', b'A', b'S'] {
            cur += dir;
            if !bounds.contains(cur) || grid[cur.y as usize][cur.x as usize] != letter {
                found = false;
                break
            }
        }
        if found {
            count += 1;
        }
    }
    count
}

fn part1(input: String) {
    let grid = input.lines().map(|l| l.as_bytes()).collect::<Vec<_>>();    
    let count = (0..grid.len()).map(|y| {
            (0..grid[y].len()).map(|x| count_xmas(&grid, (x, y))).sum::<u32>()
        })
        .sum::<u32>();
    println!("{count}");
}

fn is_x_mas(grid: &[&[u8]], pos: (usize, usize)) -> bool {
    if grid[pos.1][pos.0] != b'A' {
        return false
    }

    const DIRS: [Vector2D<i32>; 4] = [vec2(1, -1), vec2(1, 1), vec2(-1, 1), vec2(-1, -1)];
    let pos = Point2D::from(pos).to_i32();
    (0..4).any(|d| {
        let m_pos = [pos + DIRS[d], pos + DIRS[(d + 1) % 4]]; // 2 adjacent positions of M
        let s_pos = [pos + DIRS[(d + 2) % 4], pos + DIRS[(d + 3) % 4]]; // others S
        m_pos.iter().all(|p| grid[p.y as usize][p.x as usize] == b'M') &&
        s_pos.iter().all(|p| grid[p.y as usize][p.x as usize] == b'S')
    })
}

fn part2(input: String) {
    let grid = input.lines().map(|l| l.as_bytes()).collect::<Vec<_>>();    
    let count = (1..grid.len() - 1).map(|y| {
            (1..grid[y].len() - 1).filter(|&x| is_x_mas(&grid, (x, y))).count()
        })
        .sum::<usize>();
    println!("{count}");
}

util::aoc_main!();

(also on github)

[–] [email protected] 1 points 3 weeks ago (1 children)

I tried to think of some clever LINQ to do this one, but was blanking entirely.

So naïve search it is.

C#

string wordsearch = "";
int width;
int height;

public void Input(IEnumerable<string> lines)
{
  wordsearch = string.Join("", lines);
  height = lines.Count();
  width = lines.First().Length;
}

public void Part1()
{
  int words = 0;
  for (int y = 0; y < height; y++)
    for (int x = 0; x < width; x++)
      words += SearchFrom(x, y);

  Console.WriteLine($"Words: {words}");
}
public void Part2()
{
  int words = 0;
  for (int y = 1; y < height - 1; y++)
    for (int x = 1; x < width - 1; x++)
      words += SearchCross(x, y);

  Console.WriteLine($"Crosses: {words}");
}

public int SearchFrom(int x, int y)
{
  char at = wordsearch[y * width + x];
  if (at != 'X')
    return 0;

  int words = 0;
  for (int ydir = -1; ydir <= 1; ++ydir)
    for (int xdir = -1; xdir <= 1; ++xdir)
    {
      if (xdir == 0 && ydir == 0)
        continue;

      if (SearchWord(x, y, xdir, ydir))
        words++;
    }

  return words;
}

private readonly string word = "XMAS";
public bool SearchWord(int x, int y, int xdir, int ydir)
{
  int wordit = 0;
  while (true)
  {
    char at = wordsearch[y * width + x];
    if (at != word[wordit])
      return false;

    if (wordit == word.Length - 1)
      return true;

    wordit++;

    x += xdir;
    y += ydir;

    if (x < 0 || y < 0 || x >= width || y >= height)
      return false;
  }
}

public int SearchCross(int x, int y)
{
  if (x == 0 || y == 0 || x == width - 1 || y == width - 1)
    return 0;

  char at = wordsearch[y * width + x];
  if (at != 'A')
    return 0;

  int found = 0;
  for (int ydir = -1; ydir <= 1; ++ydir)
    for (int xdir = -1; xdir <= 1; ++xdir)
    {
      if (xdir == 0 || ydir == 0)
        continue;

      if (wordsearch[(y + ydir) * width + (x + xdir)] != 'M')
        continue;
      if (wordsearch[(y - ydir) * width + (x - xdir)] != 'S')
        continue;

      found++;
    }

  if (found == 2)
    return 1;

  return 0;
}

[–] [email protected] 1 points 3 weeks ago

I haven't quite started yet, and this one does feel like a busy work kinda problem. I was wondering if I could write something to rotate the board and do the search, but I think that might be not worth the effort

[–] [email protected] 1 points 2 weeks ago

Rust

I'm a day behind on this one due to a lot of work with my job and school.

use std::iter::zip;

use crate::utils::read_lines;

pub fn solution1() {
    let puzzle = read_puzzle();

    let horizontal_sum = puzzle
        .iter()
        .map(|line| {
            line.windows(4)
                .filter(|window| {
                    matches!(window, [b'X', b'M', b'A', b'S'] | [b'S', b'A', b'M', b'X'])
                })
                .count() as u32
        })
        .sum::<u32>();
    let vertical_and_diagonal_sum = puzzle
        .windows(4)
        .map(|window| {
            count_xmas(window, (0, 0, 0, 0))
                + count_xmas(window, (0, 1, 2, 3))
                + count_xmas(window, (3, 2, 1, 0))
        })
        .sum::<u32>();

    println!(
        "XMAS count = {}",
        horizontal_sum + vertical_and_diagonal_sum
    );
}

pub fn solution2() {
    let puzzle = read_puzzle();

    let sum = puzzle
        .windows(3)
        .map(|window| {
            zip(
                window[0].windows(3),
                zip(window[1].windows(3), window[2].windows(3)),
            )
            .map(|(a, (b, c))| (a, b, c))
            .filter(|tuple| {
                matches!(
                    tuple,
                    ([b'M', _, b'M'], [_, b'A', _], [b'S', _, b'S'])
                        | ([b'S', _, b'M'], [_, b'A', _], [b'S', _, b'M'])
                        | ([b'M', _, b'S'], [_, b'A', _], [b'M', _, b'S'])
                        | ([b'S', _, b'S'], [_, b'A', _], [b'M', _, b'M'])
                )
            })
            .count() as u32
        })
        .sum::<u32>();

    println!("X-MAS count = {sum}");
}

fn count_xmas(
    window: &[Vec<u8>],
    (skip0, skip1, skip2, skip3): (usize, usize, usize, usize),
) -> u32 {
    zip(
        window[0].iter().skip(skip0),
        zip(
            window[1].iter().skip(skip1),
            zip(window[2].iter().skip(skip2), window[3].iter().skip(skip3)),
        ),
    )
    .map(|(a, (b, (c, d)))| (a, b, c, d))
    .filter(|tup| matches!(tup, (b'X', b'M', b'A', b'S') | (b'S', b'A', b'M', b'X')))
    .count() as u32
}

fn read_puzzle() -> Vec<Vec<u8>> {
    read_lines("src/day4/input.txt")
        .map(|line| line.into_bytes())
        .collect()
}

The standard library windows method and pattern matching have been carrying me this year so far.

[–] [email protected] 1 points 2 weeks ago