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
1 point
*
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.
2 points
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
1 point
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))
)))))
1 point
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);
}
1 point
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