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
- What is this?: Here is a post with a large amount of details: https://programming.dev/post/6637268
- Where do I participate?: https://adventofcode.com/
- Is there a leaderboard for the community?: We have a programming.dev leaderboard with the info on how to join in this post: https://programming.dev/post/6631465
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.
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
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
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 =: ((' ' & ,) @: (, & ' '))"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 {~ <"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 {~ <"1 x +"1 y *"1 0/ _1 + i. 3
)"1 2
result2 =: +/, positions x_matches/ x_directions
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 ()