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
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
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.
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 ()
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
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
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
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...
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;
}
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
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
}
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;
}
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))
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))
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))
)))))
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.
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