lainchan / λ
Advent of Code 2025
Delilah · 2025-11-30 21:34
>Collect stars by solving puzzles. Two puzzles will be made available on each day; the second puzzle is unlocked when you complete the first. Each puzzle grants one star. Good luck!
https://adventofcode.com/
last year: >>40517
Y.T · 2025-11-30 21:50
my solution for day one: https://0x0.st/KJQH.py
Day -Part 1- -Part 2-
1 00:04:57 00:06:06
Nemissa · 2025-12-01 05:31
Using my utils and some common CL packages, as usual. Found part 2 strangely hard!
(defparameter *rotations* (read-from-string*
(ntranslate (uiop:read-file-string *input-path*)
'(#\L #\R) '(#\- #\+))
:reader (reader-all)))
(format t "Part 1: ~A~%"
(iter (with dial = 50)
(for rot in *rotations*)
(counting (= (mod (incf dial rot) 100) 0))))
(format t "Part 2: ~A~%"
(iter (with dial = 50)
(for rot in *rotations*)
(multiple-value-bind (q r) (floor (+ dial rot) 100)
(summing (+ (abs q)
(if (and (< rot 0) (= dial 0)) -1 0) ;; Start at 0
(if (and (< rot 0) (= r 0)) 1 0))) ;; End at 0
(setf dial r)))) (Too fast to matter)
queue · 2025-12-01 08:21
My solution for part 1. Can someone share part 2?
C
#include <stdio.h>
#define BUFF_SIZE 10
int main(void) {
char buffer[BUFF_SIZE + 1];
char dir;
unsigned int steps;
unsigned int pos = 50;
unsigned int count = 0;
while ( fgets(buffer, BUFF_SIZE, stdin) != NULL ) {
if (sscanf(buffer, "%c%u\n", &dir, &steps) != 2) {
fprintf(stderr, "Error: couldn't read input\n");
return -1;
}
steps %= 100;
pos += (dir == 'R') ? steps : 100 - steps;
pos %= 100;
count += (pos == 0);
}
printf("Password: %u\n", count);
return 0;
}
Joey · 2025-12-01 08:24
>Why did the number of days per event change?
>It takes a ton of my free time every year to run Advent of Code, and building the puzzles accounts for the majority of that time. After keeping a consistent schedule for ten years(!), I needed a change. The puzzles still start on December 1st so that the day numbers make sense (Day 1 = Dec 1), and puzzles come out every day (ending mid-December).
That's a bit lame, Advent lasts 24 days. I would have prefered if he made one puzzle per day or one every two days.
Liet Kynes · 2025-12-01 08:40
>>43596
One every two days would have been great, I think so too. But normal users are usually quite busy when Christmas proper arrives, so I kinda understand too...
Delilah · 2025-12-01 09:33
>>43595
```Haskell
part2 xs = sum . map (abs . snd) $ scanl f (50, 0) xs
where
f acc i = if head i == 'R' then
((fst acc + (read $ tail i :: Integer)) `mod` 100
,(fst acc + (read $ tail i :: Integer)) `div` 100)
else
((fst acc - (read $ tail i :: Integer)) `mod` 100
,(fst acc - (read $ tail i :: Integer)) `div` 100)
```
Ch1ko1 · 2025-12-01 10:16
Day 1 was pretty fun, I think I will try all 12 days this year just so I have an excuse to practice writing more in Rust!
rust
use std::fs;
fn main(){
let mut part1 = 0;
let mut part2 = 0;
let mut dPos: i32 = 50;
let input = fs::read_to_string("input").expect("TYPE THE FILE CORRECTLY");
for line in input.split_whitespace(){
let direction = line.chars().next().unwrap();
let distance: i32 = line[1..].parse().unwrap();
for _ in 0..distance{
if direction == 'R'{
dPos += 1;
} else if direction == 'L' {
dPos -= 1;
}
dPos = dPos.rem_euclid(100);
if dPos == 0 {
part2 += 1;
}
}
if dPos == 0 {
part1 += 1;
}
}
println!("Part 1 : {}", part1);
println!("Part 2 : {}", part2);
}
Meddler · 2025-12-01 10:16
>>43598
Haskell
part2 xs = sum . map (abs . snd) $ scanl f (50, 0) xs
where
f acc i = if head i == 'R' then
((fst acc + (read $ tail i :: Integer)) `mod` 100
,(fst acc + (read $ tail i :: Integer)) `div` 100)
else
((fst acc - (read $ tail i :: Integer)) `mod` 100
,(fst acc - (read $ tail i :: Integer)) `div` 100)
Ein · 2025-12-01 16:56
>>43590
p1 = p2 = 0
p = 50
for x in open(0).read().split():
raw = p + int(x[1:]) * (1 if x[0] == "R" else -1)
p2 += abs(raw) // 100 + (p and raw <= 0)
p1 += not (p := raw % 100)
print(f"silver: {p1}, gold: {p2}")
now we wait for day 2
Ein · 2025-12-01 22:17
My solution for day 2, slightly overengineered.
C
#include <stdio.h>
#include <stdlib.h>
#define BUFF_SIZE 10
enum read_mode {READ_START, READ_END};
int is_invalid(unsigned int i) {
char buffer[BUFF_SIZE + 1];
unsigned int size;
int error = snprintf(buffer, BUFF_SIZE + 1, "%u", i);
if (error <= 0) {
fprintf(stderr, "ERROR: Couldn't convert number\n");
exit(-1);
}
size = error;
if (size%2) {
return 0;
}
for (unsigned int i=0; i < size/2; i++) {
if (buffer[i] != buffer[size/2 + i]) {
return 0;
}
}
return 1;
}
unsigned int sum_invalids(unsigned int start, unsigned int end) {
unsigned int sum = 0;
printf("%u - %u\n", start, end);
for (unsigned int i = start; i <= end; i++) {
if (is_invalid(i)) {
printf("%u\n", i);
sum += i;
}
}
return sum;
}
int main(void) {
char buffer[BUFF_SIZE + 1];
int character;
unsigned int range_start, range_end;
unsigned int n = 0;
unsigned int sum = 0;
enum read_mode mode = READ_START;
buffer[0] = 0;
while ( 1 ) {
character = fgetc(stdin);
switch (mode) {
case READ_START:
if (character == '-') {
buffer[n] = 0;
range_start = atoi(buffer);
buffer[0] = n = 0;
mode = READ_END;
} else if (character >= '0' && character <= '9') {
buffer[n++] = character;
} else {
fprintf(stderr, "ERROR: Couldn't parse input\n");
return -1;
}
break;
case READ_END:
if (character == ',' || character == EOF) {
buffer[n] = 0;
range_end = atoi(buffer);
buffer[0] = n = 0;
mode = READ_START;
sum += sum_invalids(range_start, range_end);
} else if (character >= '0' && character <= '9') {
buffer[n++] = character;
} else {
fprintf(stderr, "ERROR: Couldn't parse input\n");
exit(-1);
}
}
if (character == EOF) break;
}
printf("Password: %u\n", sum);
return 0;
}
Can someone with an account share the second problem?
Tyrell Wellick · 2025-12-01 23:46
day 2 optimised and washed
import bisect, itertools
limit = 12 # max no. digits
def solve(p: bool) -> int:
def rec(old: str):
if len(old) == limit // 2:
return
for d in bool(old) * "0" + "123456789":
rec(new := old + d)
for i in range(2, 128 if p else 3):
s.add(int(i * new))
if i * len(new) > limit:
break
s = set()
rec("")
pre = [0, *itertools.accumulate(dp := sorted(s))]
sol = 0
for r in inp:
a, b = map(int, r.split("-"))
sol += pre[bisect.bisect_right(dp, b)] - pre[bisect.bisect_left(dp, a)]
return sol
inp = open(0).read().split(",")
print(f"silver: {solve(0)}, gold: {solve(1)}")
>>43607
my input: https://0x0.st/KJn1.txt
solution: silver: 44487518055, gold: 53481866137
The Oracle · 2025-12-02 10:17
>>43610
Impressive golfing! Maybe I tried to be too intelligent, because it took me some time...
(in-package :aoc2025)
(defparameter *ranges*
(mapcar (lambda (s) (mapcar #'parse-integer (str-split #\- s)))
(str-split #\, (uiop:read-file-string *input-path*))))
;; Up to 10 (digits), should be enough
(defconstant +prime-factors+ #(nil nil (2) (3) (2) (5) (3 2) (7) (2) (3) (5 2)))
(defun digits->number (digits base)
(reduce (lambda (acc d) (+ (* acc base) d)) digits :initial-value 0))
(defun number->digits (num base)
(iter (while (> num 0))
(multiple-value-bind (q r) (truncate num base)
(setf num q)
(collect r at start))))
(defun make-invalid-id (seq scount)
(digits->number (make-list scount :initial-element seq)
(expt 10 (digit-count seq))))
(defun next-invalid-seq (id scount)
(multiple-value-bind (q r) (truncate (digit-count id) scount)
(if (= r 0)
(let ((s (number->digits id (expt 10 q))))
(+ (car s) (if (< (make-invalid-id (car s) scount) id) 1 0)))
(expt 10 q))))
(defun prev-invalid-seq (id scount)
(multiple-value-bind (q r) (truncate (digit-count id) scount)
(if (= r 0)
(let ((s (number->digits id (expt 10 q))))
(- (car s) (if (> (make-invalid-id (car s) scount) id) 1 0)))
(- (expt 10 q) 1))))
(defun invalid-ids (range scount)
(destructuring-bind (from to) range
(iter (for seq from (next-invalid-seq from scount) to (prev-invalid-seq to scount))
(collect (make-invalid-id seq scount)))))
(format t "Part 1: ~A~%"
(reduce #'+ (mapcan (lambda (r) (invalid-ids r 2)) *ranges*)))
(format t "Part 2: ~A~%"
(reduce #'+
(mapcan
(lambda (range)
(destructuring-bind (from-dc to-dc) (mapcar #'digit-count range)
(assert (<= (- to-dc from-dc) 1)) ;; Noticed that, let's use it
(delete-duplicates
(mapcan #λ(invalid-ids r _) (union (aref +prime-factors+ from-dc)
(aref +prime-factors+ to-dc))))))
*ranges*)))
But well, I win on the performance front at least:
$ time python3 02/prog.py <02/input
silver: 38437576669, gold: 49046150754
python3 02/prog.py < 02/input 1.91s user 0.15s system 99% cpu 2.067 total
$ time sbcl --load rc --script 02/{prog.lisp,input}
Part 1: 38437576669
Part 2: 49046150754
sbcl --noinform --load rc --script 02/{prog.lisp,input} 0.21s user 0.04s system 99% cpu 0.244 total
Tequila · 2025-12-02 15:43
>>43613
yeah my precompute is a bit excessive
it's really for much larger inputs since the range sum lookups are only O(log n)
Miho Iwakura · 2025-12-02 16:22
>>43588
really soykaf the bed with this one lol
Haskell
toRanges :: Text -> [Text]
toRanges = splitOn (pack ",")
parseRange :: [Text] -> [[Integer]]
parseRange = map (range . (splitOn $ pack "-"))
where
range :: [Text] -> [Integer]
range r = [(read . unpack $ head r) .. (read . unpack $ last r)]
splitInTwo :: String -> (String, String)
splitInTwo s = (take at s, drop at s)
where
at = length s `div` 2 :: Int
isValidID :: Integer -> Bool
isValidID n
| (odd . length . show) n = True
| otherwise = (check . splitInTwo . show) n
where
check :: (String, String) -> Bool
check p = fst p /= snd p
part1 = sum . filter (not . isValidID)
-- kids look away.
isValid' :: Integer -> Bool
isValid' n = check (show n) (length $ show n)
where
isAll c s = all (== c) s
groupBy [] n = []
groupBy s n = [take n s] ++ groupBy (drop n s) n
check :: String -> Int -> Bool
check s 1 = False
check s 2 = isAll (Prelude.head s) s
check s 3 = isAll (Prelude.head s) s
check s 4 = (isAll (Prelude.head s) s)
|| (isAll (head $ groupBy s 2) (groupBy s 2))
check s 5 = (isAll (Prelude.head s) s)
check s 6 = (isAll (Prelude.head s) s)
|| (isAll (head $ groupBy s 2) (groupBy s 2))
|| (isAll (head $ groupBy s 3) (groupBy s 3))
check s 7 = (isAll (Prelude.head s) s)
check s 8 = (isAll (Prelude.head s) s)
|| (isAll (head $ groupBy s 4) (groupBy s 4))
|| (isAll (head $ groupBy s 2) (groupBy s 2))
check s 9 = (isAll (Prelude.head s) s)
|| (isAll (head $ groupBy s 3) (groupBy s 3))
check s 10 = (isAll (Prelude.head s) s)
|| (isAll (head $ groupBy s 5) (groupBy s 5))
|| (isAll (head $ groupBy s 2) (groupBy s 2))
check s 11 = (isAll (Prelude.head s) s)
check s 12 = (isAll (Prelude.head s) s)
|| (isAll (head $ groupBy s 6) (groupBy s 6))
|| (isAll (head $ groupBy s 4) (groupBy s 4))
|| (isAll (head $ groupBy s 3) (groupBy s 3))
|| (isAll (head $ groupBy s 2) (groupBy s 2))
part2 = sum . filter isValid'
main :: IO ()
main = do
content <- readFile "input.txt"
let intr = ((foldl (++) [] ) . parseRange . toRanges . pack) content
putStrLn "Part 1"
print (part1 intr)
putStrLn "Part 2"
print (part2 intr)
Neuromancer · 2025-12-02 18:16
>>43616
looks clean though, even if long
i don't know haskell, but i assume it's easy to reason about the same repeated pattern
good stuff!
HELIOS · 2025-12-02 19:43
>>43613
okay i further optimised mine:
import bisect, itertools
maxdig = 10 # max no. digits in input
maxnum = 10 ** (maxdig // 2)
def solve(p: bool) -> int:
tab = sorted({ int(j * str(i))
for i in range(1, maxnum)
for j in range(2, maxdig // len(str(i)) + 1 if p else 3) })
dp = (0, *itertools.accumulate(tab))
return sum(dp[bisect.bisect_right(tab, b)] - dp[bisect.bisect_left(tab, a)]
for r in inp for a, b in [map(int, r.split("-"))])
inp = open(0).read().split(",")
print(f"silver: {solve(0)}, gold: {solve(1)}") $ time python3 solution.py < input
silver: 44487518055, gold: 53481866137
real 0m0.066s
user 0m0.062s
sys 0m0.005s can shave off a further 0.02s by using math.log10 but it looks ugly
i love-hate python so much
Tank · 2025-12-03 00:37
for those without an account:
my input: https://0x0.st/KyLU.txt
solution: silver: 17452, gold: 173300819005913
>>43618
day 3 tabular dynamic programming
def solve(bat: list, need: int) -> int:
n = len(bat)
dp = [[-1] * 10 for _ in range(n)]
for i in reversed(range(n - 1)):
for j in range(10):
''' recurrence relation for closest greater rightward digit
⎧
⎪ n - i - 2, if j < bat(i+1)
dp(i,j) = ⎨
⎪ dp(i+1,j), otherwise
⎩
'''
dp[i][j] = n - i - 2 if j < bat[i + 1] else dp[i + 1][j]
sol = 0
for i in range(n):
if dp[i][bat[i]] < need - 1 or need and i == n - 1:
sol = sol*10 + bat[i]
need -= 1
return sol
p1 = p2 = 0
for bank in open(0).read().strip().split("\n"):
bat = list(map(int, bank))
p1 += solve(bat, 2)
p2 += solve(bat, 12)
print(f"silver: {p1}, gold: {p2}") real 0m0.030s
user 0m0.025s
sys 0m0.005s
Just another Random Lainon · 2025-12-03 00:44
>>43620
oops, here's the full page
John Rain · 2025-12-03 07:37
>>43620
Grug solution, not even optimized:
(in-package :aoc2025)
(defparameter *batteries* (mapcar #λ(map 'vector #'digit-char-p _)
(uiop:read-file-lines *input-path*)))
(defun max-joltage (bank bat-count)
(digits->integer
(iter
(with start = 0)
(for end from (- (length bank) (- bat-count 1)) to (length bank))
(multiple-value-bind (jolt pos) (max-element bank #'> :start start :end end)
(setf start (+ pos 1))
(collect jolt)))))
(time
(format t "~@{Part ~A: ~A~%~}"
1 (reduce #'+ (mapcar #λ(max-joltage _ 2) *batteries*))
2 (reduce #'+ (mapcar #λ(max-joltage _ 12) *batteries*))))
sbcl --load rc --script 03/prog.lisp 03/input
Part 1: 17776
Part 2: 175001979417429
Evaluation took:
0.001 seconds of real time
0.001031 seconds of total run time (0.001020 user, 0.000011 system)
100.00% CPU
65,520 bytes consed
Jean Dupont · 2025-12-04 07:06
>>43620
I need a time to understand your algorithm, you've got not an obvious scheme. After making a simple solution with divide and conquer recursion, I tried to solve day 3 problem in dp style, here is my solution with straightforward approach:
python
import sys
def conv_to_int_list(s):
lst = []
for i in s:
lst.append(int(i))
return lst
def to_num(l):
n = 0
pw = 1
for i in reversed(range(0, len(l))):
n += pw * l[i]
pw *= 10
return n
def joltage(s):
bat = conv_to_int_list(s)
n = len(bat)
tbl = []
for k in range(H + 1):
tbl.append([0] * n)
for j in range(n):
for k in range(H + 1):
if k == 0:
continue
if j == 0:
prev_k = 0
prev_k_1 = 0
else:
prev_k = tbl[k][j - 1]
prev_k_1 = tbl[k - 1][j - 1]
v_j = bat[j]
tbl[k][j] = max(prev_k, 10 * prev_k_1 + v_j)
return tbl[H][n - 1]
H = 12
with open(sys.argv[1], 'r') as f:
acc = 0
while True:
s = f.readline().strip()
if s == '':
break
j = joltage(s)
acc += j
print(acc)
Boris Grishenko · 2025-12-04 10:31
>>43622
Another extra easy day! I wonder if he'll get back at us during the weekend...
(in-package :aoc2025)
(load (merge-pathnames "../grid" *load-truename*))
(defparameter *map* (grid-read *input-path* :key #λ(char= _ #\@)))
(defun roll-stats () ;; Returns (list initially-removable removed)
(let ((adj-map (make-array (array-dimensions *map*) :initial-element -100)))
(dolist (rpos (grid-position-all t *map*))
(incf (grid-ref adj-map rpos) 100)
(dolist (npos (grid-neighbours *map* rpos))
(incf (grid-ref adj-map npos))))
(let ((removable (grid-position-all-if #λ(<= 0 _ 3) adj-map)))
(list (length removable)
(iter
(while removable)
(for rpos = (pop removable))
(counting t)
(decf (grid-ref adj-map rpos) 100)
(dolist (npos (grid-neighbours *map* rpos))
(when (= (decf (grid-ref adj-map npos)) 3)
(push npos removable))))))))
(time (format t "~{~A~%~}" (roll-stats)))
$ sbcl --load rc --script 04/{prog.lisp,input}
1467
8484
Evaluation took:
0.015 seconds of real time
0.015031 seconds of total run time (0.015029 user, 0.000002 system)
100.00% CPU
55,610,297 processor cycles
23,519,824 bytes consed
limited · 2025-12-04 20:21
>>43627
my algorithm just builds an `n' by `10' table where `dp(i,j)' holds the index of the closest rightward digit that is greater than `j'
then you select or discard digits based on:
1. is there a greater rightward digit?
2. are there enough digits following it?
O(n) in two passes
Random · 2025-12-05 01:43
day 5
rng, ing = open(0).read().split("\n\n")
rng = sorted((*map(int, x.split("-")),) for x in rng.split())
ing = [*map(int, ing.split())]
tem = [[-1, -2]]
for p, q in rng:
if p - 1 > (n := tem[-1][1]):
tem.append([p, q])
tem[-1][1] = max(n, q)
p1 = p2 = 0
for p, q in tem:
p1 += sum(p <= x <= q for x in ing)
p2 += q - p + 1
print(f"silver: {p1}, gold: {p2}") real 0m0.011s
user 0m0.011s
sys 0m0.000s
Meddler · 2025-12-05 05:31
>>43631
Like for grids, I decided to do a small library for ranges and range sets, since this comes up often during the AoC. Here's the result:
(in-package :aoc2025)
(load (merge-pathnames "../ranges" *load-truename*))
(destructuring-bind (p1 p2) (split-on "" (uiop:read-file-lines *input-path*) :test #'string=)
(defparameter *fresh* (parse-range-set p1 :inclusive-p t))
(defparameter *avail* (mapcar #'parse-integer p2)))
(format t "~@{~A~%~}"
(length (remove-if-not #λ(in-range-set-p _ *fresh*) *avail*))
(reduce #'+ (merge-range-set *fresh*) :key #'range-width))
And the library:
;; Ranges stored as (cons included-low excluded-high) and range sets as car-sorted lists of ranges
(deftype range () '(cons integer integer))
(declaim (ftype (function (string &key (:delim (or character string)) (:inclusive-p boolean)) range)
parse-range))
(defun parse-range (str &key (delim #\-) inclusive-p)
(destructuring-bind (lo hi) (str-split delim str)
(cons (parse-integer lo)
(+ (parse-integer hi)
(if inclusive-p 1 0)))))
(declaim (ftype (function ((or string list) &key
(:set-delim (or character string))
(:range-delim (or character string))
(:inclusive-p boolean))
list)
parse-range-set))
(defun parse-range-set (input &key (set-delim #\Newline) (range-delim #\-) inclusive-p)
(sort
(mapcar #λ(parse-range _ :delim range-delim :inclusive-p inclusive-p)
(if (stringp input)
(str-split set-delim input)
input))
#'< :key #'car))
(declaim (ftype (function (range) (integer 0)) range-width))
(defun-inline range-width (range)
(destructuring-bind (lo . hi) range
(- hi lo)))
(declaim (ftype (function (integer range) boolean) in-range-p))
(defun-inline in-range-p (val range)
(destructuring-bind (lo . hi) range
(<= lo val hi)))
(declaim (ftype (function (integer list) (or range null)) in-range-set-p))
(defun in-range-set-p (val range-set)
(iter
(for r in range-set)
(while (>= val (car r)))
(finding r such-that (< val (cdr r)))))
(declaim (ftype (function (list) list) merge-range-set))
(defun merge-range-set (range-set)
(let ((queue (make-queue))
(cur (car range-set)))
(dolist (nxt (cdr range-set))
(destructuring-bind (cur-lo . cur-hi) cur
(destructuring-bind (nxt-lo . nxt-hi) nxt
(if (<= nxt-lo cur-hi) ;; cur and nxt overlap ?
(maxf (cdr cur) nxt-hi) ;; extend cur
(progn
(push-queue queue cur)
(setf cur nxt))))))
(push-queue queue cur)
(cdr queue)))
As usual with SBCL, too fast to measure beyond I/O.
The Oracle · 2025-12-06 08:47
>>43632
Day 6:
(in-package :aoc2025)
(load (merge-pathnames "../grid" *load-truename*))
(defparameter *worksheet* (grid-read *input-path*))
(defun parse-worksheet (how)
(flet ((read-row (vec) (read-from-string* (coerce vec 'string) :reader (reader-all)))
(read-col (vec) (read-from-string* (coerce vec 'string))))
(ecase how
(:human
(mapcar #'rotate
(apply #'mapcar #'list
(mapcar #'read-row (grid-rows *worksheet*)))))
(:cephalopod
(let* ((ws (copy-array *worksheet*))
(op-row (grid-row ws (- (grid-height ws) 1)))
(ops (prog1 (read-row op-row)
(fill op-row #\Space))) ;; Erase operator row once read
(cols (grid-rows (grid-transpose ws)))
(colgrps (split-if #λ(every #'blank-p _) cols)))
(mapcar (lambda (op colgrp)
(cons op (map 'list #'read-col colgrp)))
ops colgrps))))))
(format t "~@{~A~%~}"
(reduce #'+ (parse-worksheet :human) :key #'eval)
(reduce #'+ (parse-worksheet :cephalopod) :key #'eval))
Gyula Kovacs · 2025-12-06 20:33
>>43633
very clean; very nice
i gave up golfing my day 6 because it was very ugly
Hououin Kyouma · 2025-12-07 00:31
>>43634
I did wonder if there was a "proper" way that reuses part 1, but I'm not in it for the golfing, personally.
Was a bit worried to not see you Pyanon post before me!
Milner · 2025-12-07 00:47
>>43638
if there is a clean way of doing it, then i'm not aware of it
my day 6 code was awful, though i got a good time:
Day -Part 1- -Part 2-
7 00:12:02 00:27:57
6 00:06:06 00:15:16
5 00:07:03 00:54:19
4 00:10:11 00:11:01
3 00:09:06 00:22:08
2 00:04:58 00:08:51
1 00:04:57 00:06:06
that said, here's day 7
inp = open(0).read().split()
dp = [x == 'S' for x in inp[0]]
w = len(dp)
p1 = 0
for x in inp:
for j in range(w):
if x[j] == '^':
p1 += bool(n := dp[j])
dp[j-1] += n
dp[j] = 0
dp[j+1] += n
p2 = sum(dp)
print(f"silver: {p1}, gold: {p2}") space optimised tabular dynamic programming
i had some golfing help from /g/
William Holtzer · 2025-12-07 02:09
>>43640
>day 7
Noice, I solved it in opposite direction and now I see it was a bad idea.
Von Neumann · 2025-12-07 13:03
>>43640
Well, another "blinking stones problem" it looks like. Could make it a lot faster by only using x as hash table key and double-buffering said hash-table (to reduce consing), but why bother when it's already sub-ms?
(in-package :aoc2025)
(load (merge-pathnames "../grid" *load-truename*))
(defparameter *manifold* (grid-read *input-path*))
(defparameter *source* (grid-position #\S *manifold*))
(defun manifold-characteristics ()
(let ((scanline (build-hash-table *source* 1))
(split-count 0))
(dotimes (y (- (grid-height *manifold*) 1))
(setf scanline
(let ((new-scanline (make-hash-table)))
(do-hash-table (pos count scanline new-scanline)
(if (char= (grid-ref *manifold* pos +S+) #\^)
(progn
(incf split-count)
(incf (gethash (+ pos +SW+) new-scanline 0) count)
(incf (gethash (+ pos +SE+) new-scanline 0) count))
(incf (gethash (+ pos +S+) new-scanline 0) count))))))
(list split-count (reduce #'+ (hash-table-values scanline)))))
(format t "~{~A~%~}" (manifold-characteristics))
() · 2025-12-07 14:04
>>43640
Oh soykaf, didn't think about simply using a vector for the scanline... makes everything cleaner and much faster!
(in-package :aoc2025)
(load (merge-pathnames "../grid" *load-truename*))
(defparameter *manifold* (grid-read *input-path*))
(defparameter *source* (grid-position #\S *manifold*))
(defun manifold-characteristics ()
(let ((scanline (make-array (grid-width *manifold*) :element-type 'fixnum :initial-element 0))
(split-count 0))
(setf (aref scanline (imagpart *source*)) 1)
(dotimes (y (- (grid-height *manifold*) 1))
(dotimes (x (grid-width *manifold*))
(let ((xc (aref scanline x)))
(when (and (> xc 0) (char= (aref *manifold* (+ y 1) x) #\^))
(incf split-count)
(setf (aref scanline x) 0)
(incf (aref scanline (- x 1)) xc)
(incf (aref scanline (+ x 1)) xc)))))
(list split-count (reduce #'+ scanline))))
(time (format t "~{~A~%~}" (manifold-characteristics))) Gives me
sbcl --load rc --script 07/{prog.lisp,input}
1581
73007003089792
Evaluation took:
0.000 seconds of real time
0.000074 seconds of total run time (0.000066 user, 0.000008 system)
100.00% CPU
270,914 processor cycles
0 bytes consed
Roger Pollack · 2025-12-08 13:01
Not exactly pretty but I've tried to gain some speed here. The whole script (bottleneck is the initial distance sorting) runs in ~0.78 s with a bit of massaging:
(in-package :aoc2025)
(defparameter *boxes*
(map 'vector #λ(map '(vector fixnum 3) #'parse-integer (str-split #\, _))
(uiop:read-file-lines *input-path*)))
(defparameter *sorted-box-pairs*
(sort (combinations (iota (length *boxes*)) :length 2) #'<
:key (lambda (pair)
(let ((b1 (svref *boxes* (car pair)))
(b2 (svref *boxes* (cadr pair))))
(declare (inline distance2) ;; 1.82 -> 0.78 s
(type (vector fixnum 3) b1 b2)
(optimize (speed 3) (safety 0)))
(distance2 b1 b2)))))
(defparameter *conn-count-target*
(eswitch ((pathname-name *input-path*) :test #'string=)
("sample" 10)
("input" 1000)))
(defun build-circuits (stop-fun)
(let ((circuits (map-to-hash-table #λ(values _ (list _)) (iota (length *boxes*))))
(circuit-count (length *boxes*)))
(iter
(for (bi1 bi2) in *sorted-box-pairs*)
(for c1 = (gethash bi1 circuits))
(for c2 = (gethash bi2 circuits))
(for conn-count from 1)
(when (not (eq c1 c2)) ;; Merge when not already part of the same circuit
(decf circuit-count)
(let ((new-circuit (nconc c1 c2)))
(dolist (i new-circuit)
(setf (gethash i circuits) new-circuit))))
(thereis (funcall stop-fun conn-count circuit-count circuits bi1 bi2)))))
(defun circuits-after-N-conn (N)
(build-circuits
(lambda (conn-count circuit-count circuits bi1 bi2)
(when (= conn-count N)
(delete-duplicates (hash-table-values circuits) :test #'eq)))))
(defun ultimate-conn ()
(build-circuits
(lambda (conn-count circuit-count circuits bi1 bi2)
(when (= 1 circuit-count)
(list (aref *boxes* bi1) (aref *boxes* bi2))))))
(format t "~@{~A~%~}"
(reduce #'* (sort (mapcar #'length (circuits-after-N-conn *conn-count-target*)) #'>) :end 3)
(reduce #'* (ultimate-conn) :key #'first-elt))
Hiro Protagonist · 2025-12-09 03:15
hint for day 9 part 2:
any straight line from any point within or without will intersect the boundary an odd or even number of times respectively
hardest day by far this year
if they get much harder, then i don't think i'm going to make it...
no pretty solution yet, sorry lispanon :(
car · 2025-12-09 06:52
>>43656
Yeah, that's a huge volume to compute in a straight way.
Spooky · 2025-12-09 10:32
>>43656
i really don't know how to make this efficient :/
from bisect import bisect_left, bisect_right
pts = [[*map(int, p.split(","))] for p in open(0).read().split()]
n = len(pts)
tem = {}
for i in range(n):
(a, b), (p, q) = pts[i], pts[(i + 1) % n]
# ignore horizontal segments
for r in range(min(b, q), max(b, q)):
tem.setdefault(r, set()).add(a)
# map row to sorted list of vertical segments
seg = { k: sorted(tem[k]) for k in tem }
# map row to sorted list of nonadjacent vertical segments
ges = { k: [ x for x in v if x - 1 not in v and x + 1 not in v ]
for k, v in seg.items() }
p1 = p2 = 0
# O(n^2 * m * n log n) --- not good!
for i in range(n):
for j in range(i + 1, n):
(a, b), (p, q) = pts[i], pts[j]
# represent points as top left / bottom right
tx, ty = min(a, p), min(b, q)
bx, by = max(a, p), max(b, q)
# find area
p1 = max(p1, area := (bx - tx + 1) * (by - ty + 1))
if area <= p2: continue
# 1. intersection no. must be odd
# 2. range must not contain nonadjacent segments
if not any(len(seg[r]) - bisect_right(seg[r], tx) & 1 ^ 1
or bisect_right(ges[r], tx) != bisect_left(ges[r], bx)
for r in range(ty + 1, by)):
p2 = area
print(f"silver: {p1}, gold: {p2}") real0m59.281s
user0m59.262s
sys0m0.020s
Ivan Petrov · 2025-12-09 12:57
>>43656
No worry, I didn't have the time to do P2 today.
JARGON · 2025-12-10 06:23
what a stupid day
seems intended to be solved with z3
wasted so much time looking for an alternative to
>import solution
i'll just turn off my computer next time
Case · 2025-12-10 11:27
>>43660
Well, this is probably the day that'll have separated men from boys! Hope the remaining days won't be all like this...
(in-package :aoc2025)
(declaim (optimize (speed 3) (safety 0) (debug 0)))
(defparameter *red-tiles* (mapcar #λ(mapcar #'parse-integer (str-split #\, _))
(uiop:read-file-lines *input-path*)))
(defconstant +room-dimensions+ '(100000 100000)) ;; Actually '(98315 98415)
(defun-inline area (pair)
(reduce #'* (mapcar #'distance (first pair) (second pair)) :key #'1+))
(defun make-bitmap ()
;; Needs at least 10 GB of heap on SBCL; switching to BIT lowers that to 1.25 GB but costs ~4s
(let ((bm (make-array +room-dimensions+ :element-type '(unsigned-byte 8) :initial-element 0)))
;; Draw the red/green tiles' outline
(iter
(for cur in *red-tiles*)
(for prv previous cur initially (lastcar *red-tiles*))
(bind ((xc yc) cur
(xp yp) prv)
(if (= xc xp)
(iter (for y from (min yc yp) to (max yc yp))
(setf (aref bm y xc) 1))
(iter (for x from (min xc xp) to (max xc xp))
(setf (aref bm yc x) 1)))))
;; Then fill it...
;;
;; ...which means first finding a point (any point) inside. So, I find the rightmost vertical
;; wall, pray that it is longer than 3 and use that point: |
;; x|
;; |
(bind ((&values rightmost1 i) (max-elt *red-tiles* #'> :key #'car)
rightmost2 (nth (+ i 1) *red-tiles*)
(xmax y1) rightmost1
(_ y2) rightmost2
xstart (- xmax 1)
ystart (progn (assert (> (distance y1 y2) 1))
(truncate (+ y1 y2) 2)))
;; https://en.wikipedia.org/wiki/Flood_fill#Span_filling
(labels ((scan (lx rx y s)
(iter
(with span-added = nil)
(for x from lx to rx)
(cond ((= (aref bm y x) 1)
(setf span-added nil))
((not span-added)
(push (cons x y) s)
(setf span-added t)))
(finally (return s))))
(scanfill (xs ys)
(iter outer
(with s = (list (cons xs ys)))
(while s)
(for (x . y) = (pop s))
(declare (fixnum x y))
(for lx = x)
(iter
(while (= (aref bm y (- lx 1)) 0))
(setf (aref bm y (- lx 1)) 1)
(in outer (decf lx)))
(iter
(while (= (aref bm y x) 0))
(setf (aref bm y x) 1)
(in outer (incf x)))
(setf s (scan lx (- x 1) (+ y 1) s))
(setf s (scan lx (- x 1) (- y 1) s)))))
(scanfill xstart ystart)))
bm))
(defun inside-red-and-green-p (bitmap pair)
(destructuring-bind ((x1 y1) (x2 y2)) pair
(declare ((simple-array (unsigned-byte 8) (100000 100000)) bitmap)
((integer 0 #.(- 100000 1)) x1 y1 x2 y2))
(and
(iter (for x from (min x1 x2) to (max x1 x2)) (always (= (aref bitmap y1 x) 1)))
(iter (for x from (min x1 x2) to (max x1 x2)) (always (= (aref bitmap y2 x) 1)))
(iter (for y from (min y1 y2) to (max y1 y2)) (always (= (aref bitmap y x1) 1)))
(iter (for y from (min y1 y2) to (max y1 y2)) (always (= (aref bitmap y x2) 1))))))
(time
(let* ((red-tile-pairs (combinations *red-tiles* :length 2))
(sorted (sort red-tile-pairs #'> :key #'area)))
(format t "~@{~A~%~}"
(area (car sorted))
(let ((bitmap (make-bitmap)))
(area (find-if #λ(inside-red-and-green-p bitmap _) sorted))))))
Output:
$ sbcl --dynamic-space-size 10000 --load rc --script 09/{prog.lisp,input}
4760959496
1343576598
Evaluation took:
19.670 seconds of real time
19.668361 seconds of total run time (17.252548 user, 2.415813 system)
[ Real times consist of 0.019 seconds GC time, and 19.651 seconds non-GC time. ]
[ Run times consist of 0.019 seconds GC time, and 19.650 seconds non-GC time. ]
99.99% CPU
72,778,256,633 processor cycles
10,066,124,720 bytes consed
Y.T · 2025-12-10 23:02
>>43673
don't worry; today was weak
but do worry; i think tomorrow will be brutal because it's not Christmas day
from functools import cache
# parse adjacency list
m = { k: v.split() for ln in open(0) for k, v in [ln.split(": ")] }
@cache
def dfs(dev, dac, fft, p):
if dev == "out": return 1 if p & 1 else dac and fft
dac |= dev == "dac"
fft |= dev == "fft"
return sum(dfs(rec, dac, fft, p) for rec in m[dev])
p1 = dfs("you", False, False, 1)
p2 = dfs("svr", False, False, 2)
print(f"silver: {p1}, gold: {p2}")
Hacker · 2025-12-11 04:25
>>43656
I'll try to solve this problem in this weekend, I'm thinking of building bounding boxes.
>>43673
>10,066,124,720 bytes
Jesus fuarrrking Christ!
>>43668
Well, I solved it buy hand and a list of paper, dunno how to program it, I think it will take me a whole evening or two.
Hacker · 2025-12-11 09:24
>>43678
>>10,066,124,720 bytes
>Jesus fuarrrking Christ!
As you can see in the comments, one can still use BIT to divide that by 8. But yeah, requiring at least 1.25 GB is a bit sad...
swap · 2025-12-12 03:44
>>43677
Yeah, I think I'll skip D10, but let's see for 12; 11 was indeed easy:
(in-package :aoc2025)
(defparameter *rack*
(map-to-hash-table #λ(values (car _) (cdr _))
(mapcar #λ(read-from-string* _ :reader (reader-all))
(mapcar #λ(nsubstitute #\Space #\: _)
(uiop:read-file-lines *input-path*)))
:make-args '(:test eq)))
(defun pt1 (dev)
(if (eq dev 'out)
1
(reduce #'+ (mapcar #'pt1 (gethash dev *rack*)))))
;; Manual memoization, could have used https://github.com/AccelerationNet/function-cache
(defparameter *pt2-cache* (make-hash-table :test 'equal))
(defun pt2 (dev &key dac-p fft-p &aux (cache-key `(,dev ,dac-p ,fft-p)))
(or (gethash cache-key *pt2-cache*)
(setf (gethash cache-key *pt2-cache*)
(if (eq dev 'out)
(if (and dac-p fft-p) 1 0)
(reduce #'+
(mapcar #λ(pt2 _ :dac-p (or dac-p (eq dev 'dac))
:fft-p (or fft-p (eq dev 'fft)))
(gethash dev *rack*)))))))
(format t "~@{~A~%~}" (pt1 'you) (pt2 'svr))
Mario Rossi · 2025-12-12 05:16
>>43668
Thanks for the warning, I hate this. Did part 1, at least:
(in-package :aoc2025)
(defun parse-machine (str)
(cl-ppcre:register-groups-bind (wanted buttons joltage)
("\\[(.*)\\] (\\(.*\\)) \\{(.*)\\}" (substitute #\Space #\, str))
(declare (ignore joltage))
(list
(read-from-string (str-cat "#b" (nreverse (ntranslate wanted ".#" "01"))))
(mapcar #λ(apply #'logior (mapcar #λ(ash 1 _) _))
(read-from-string* buttons :reader (reader-all))))))
(defparameter *machines* (mapcar #'parse-machine (uiop:read-file-lines *input-path*)))
(defun smallest-button-sequence-to-goal (machine)
(destructuring-bind (wanted buttons) machine
(find-if #λ(= (apply #'logxor _) wanted)
(mapcan #λ(combinations buttons :length _)
(iota (length buttons))))))
(format t "~A~%" (reduce #'+ (mapcar #'smallest-button-sequence-to-goal *machines*) :key #'length))
aliased · 2025-12-12 12:05
Day 3 with Uiua.
uiua
ReadBanks ← ⊜∘ ⊸(≠ @\n)
Max ← ⟜⊡ ⊸(⊢ ⍖)
Prefixes ← ≡⌟(□ ↘ ¯) ⇌ ⇡
Joltage ← ⇌ ◌ ∧(⊃(⊙◌ + ⊙◌|⊂ ⊙◌ ◌) +1 Max⊸˜↘ °□) ⊃(⋅⋅Prefixes|⊙⊙◌ ⊙⊙◌) 0 ""
# Part 1
/+ ⋕ ≡Joltage 2 (ReadBanks (&fras "../input/3"))
# Part 2
/+ ⋕ ≡Joltage 12 (ReadBanks (&fras "../input/3"))
Ivan Horvat · 2025-12-12 14:34
>>43689
Looks fun but somehow doesn't look as mystical as APL.
Cypher · 2025-12-16 10:10
OH MAN! I almost forgot about it.
Damn, it is already December. Time flies when you are a... nevermind.
How hard is it this year?
Did the problems evolve? Are they harder to solve?
I remember seeing someone on /g/ in 4chins doing some very idiomatic Rust implementations last year that looked really cool.>>43691
William Holtzer · 2025-12-25 07:37
Day2 in Prolog
:- module(day2, [solve_day2/3, parse_input/3]).
:- use_module(aoc(aoc_common)).
:- autoload(library(dcg/basics), [integer/3]).
:- autoload(library(dcg/high_order), [sequence/5]).
:- autoload(library(apply), [maplist/3]).
:- autoload(library(backcomp), [sumlist/2]).
:- autoload(library(aggregate), [aggregate_all/3]).
parse_input(Ranges) -->
sequence(range, ",", Ranges),
( "\n" ; [] ).
range((R1, R2)) -->
integer(R1), "-", integer(R2).
repeated(N, Codes) :-
length(Codes, L),
0 is L mod N, BlockLen is L // N,
length(Block, BlockLen), length(Blocks, N),
maplist(=(Block), Blocks),
append(Blocks, Codes).
repeat_pattern(R1, R2, Number) :-
between(R1, R2, Number),
number_codes(Number, CodeList),
repeated(2, CodeList).
repeat_patterns(R1, R2, Number) :-
between(R1, R2, Number),
number_codes(Number, CodeList),
length(CodeList, L),
once(
(between(2, L, N),
repeated(N, CodeList))).
answer_part1((R1, R2), N) :-
aggregate_all(sum(X), repeat_pattern(R1, R2, X), N).
answer_part2((R1, R2), N) :-
aggregate_all(sum(X), repeat_patterns(R1, R2, X), N).
solve_day2(part1, Input, Answer) :-
maplist(answer_part1, Input, New),
sumlist(New, Answer).
solve_day2(part2, Input, Answer) :-
maplist(answer_part2, Input, New),
sumlist(New, Answer).
Not the fastest of code
?- phrase_from_file(day2:parse_input(_Ranges), './AOC 2025/day2/input'),
time(solve_day2(_, _Ranges, X)).
% 40,629,530 inferences, 1.056 CPU in 1.058 seconds (100% CPU, 38469531 Lips)
X = 37314786486 ;
% 175,707,044 inferences, 4.521 CPU in 4.528 seconds (100% CPU, 38865347 Lips)
X = 47477053982 .
Turing · 2025-12-26 02:50
>>43737
Man... my brain really doesn't like reading Prolog!