lainchan / λ

Advent of Code 2024

Special Agent Bob · 2024-11-30 21:50
>Collect stars by solving puzzles. Two puzzles will be made available on each day in the Advent calendar; the second puzzle is unlocked when you complete the first. Each puzzle grants one star. Good luck! https://adventofcode.com/ last year: >>37777
Dujour · 2024-11-30 22:36
>To play, please identify yourself via one of these services: >[GitHub] [Google] [Twitter] [Reddit] Yeah, I don't think I will...
bit · 2024-11-30 22:52
>>40518 So sad. So many cool things on the internet. I'll never use them for same reason.
dup · 2024-12-01 00:08
>>40518 Your loss, it's fun. Just make an empty GH account and never use it.
Lady 3Jane.. · 2024-12-01 00:48
>>40520 An empty github account, made with my empty e-mail address that I made with an empty phone number? Sure it is possible, but that's way more hastle than necessary (that would be a single cookie to link an answer to its input).
Turner · 2024-12-01 00:49
>>40518 Are you aware of the the notion of throwaway accounts? The elite don't want you to know this but you can create an email address and use it just one. I have 512 forgotten email adresses.
dup · 2024-12-01 01:32
>>40522 Stuff like oneminutemail where you're assigned a virtual mailbox address to receive a single email and forget about it, is mostly unusable. Even if site you register at allows it, one day it will say there's something sus about your activity and you should go get the numbers in confirmation email and you're fuarrrked. If you mean literally registering full featured email accounts, it's not as easy as it was 20 years ago, now you have to do phone confirmation almost everywhere. Last time I wanted an extra email account it took me hours and I ended up using an email service from small african country looking like it's from 90s. That was the only one that allowed me to get an email with no strings attached.
Sabo Engle · 2024-12-01 03:05
>>40521 you can easily make a throwaway reddit account when i did it i never had to verify my email >>40521 github needs a phone number now, unfortunately
nibble · 2024-12-01 04:54
>>40524 Phone number? Absolutely not. I've made a half dozen accounts over the last few years and none asked for that, in fact just made another one now over Tor, and it only asked me to solve a captcha.
Jens Hansen · 2024-12-01 05:14
>>40518 Yeah, it's such a shame. The hassle of making a burner account and the hassle of getting each day uploaded somewhere else just so lainons can have fun without having to deal with this soykaf. It's why I stopped doing these. Oh well.
Elliot Alderson · 2024-12-01 07:41
>>40524 You're fake news. You can use OTP
Debbie Charteris · 2024-12-01 08:07
Well, here we go. Decided to do it in all the languages I decently know for the first day only. Common Lisp. Exceptionally, I made two versions: an "idiomatic" one (using only uiop and iterate): (load "~/.local/lib/quicklisp/setup.lisp") (ql:quickload "iterate" :silent t) (use-package :iterate) (let (keys vals) (iter (for (k v) on (uiop:read-file-forms (car uiop:*command-line-arguments*)) by #'cddr) (push k keys) (push v vals)) (setf keys (sort keys #'<) vals (sort vals #'<)) (format t "Part 1: ~A~%" (iter (for k in keys) (for v in vals) (sum (abs (- k v))))) (format t "Part 2: ~A~%" (iter (with vtally = (iter (with ret = (make-hash-table)) (for v in vals) (incf (gethash v ret 0)) (finally (return ret)))) (for k in keys) (sum (* k (gethash k vtally 0)))))) And another with a rc file including more stuff (incl. my utils system) and reducing boilerplate for the next days: (load "../rc") (in-package #:q3cpma-user) (destructuring-bind (keys vals) (plist:separate (read:all-from-file *input-path*)) (misc:sortf keys #'<) (misc:sortf vals #'<) (format t "Part 1: ~A~%" (iter (for k in keys) (for v in vals) (sum (abs (- k v))))) (format t "Part 2: ~A~%" (iter (with vtally = (list:tally vals)) (for k in keys) (sum (* k (gethash k vtally 0)))))) rc file: (load "~/.local/lib/quicklisp/setup.lisp") (asdf:load-system "q3cpma-utils") ;; https://git.sr.ht/~q3cpma/cl-utils (in-package #:q3cpma-user) (ql:quickload '("array-operations") :silent t) (defparameter *input-path* (car uiop:*command-line-arguments*)) PS: don't forget to convert your tabs to spaces before posting in here!
Anonymous · 2024-12-01 08:25
>>40530 C99 (with a little trick to run via sh): #if 0 // POSIX script mode: run with `sh file.c ARGS...` set -eu bin=$(echo "mkstemp(/tmp/tmp.XXXXXX)" | m4) trap 'exit 1' HUP INT QUIT ABRT TERM trap 'rm "$bin"' EXIT if command -v gcc >/dev/null; then cc='gcc -Wall -Wextra -std=c99 -pedantic' elif command -v clang >/dev/null; then cc='clang -Wall -Wextra -std=c99 -pedantic' else cc=c99; fi $cc -O "$0" -o "$bin" && exec "$bin" "$@" #endif #include <assert.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> static int int_cmp(const void *a, const void *b) {return *(int *)a - *(int *)b;} #define VEC(T) struct {size_t l, c; T *d;} #define VEC_PUSH(V, EL) \ { \ if ((V).l == (V).c) \ (V).d = realloc((V).d, sizeof(*(V).d) * ((V).c = (V).c ? (V).c * 2 : 8)); \ (V).d[(V).l++] = EL; \ } #define VEC_SORT(V, CMPFUN) qsort((V).d, (V).l, sizeof(*(V).d), CMPFUN) int main(int argc, char **argv) { assert(argc == 2); FILE *f = fopen(argv[1], "r"); assert(f); VEC(int) keys = {0}; VEC(int) vals = {0}; for (int k, v; fscanf(f, "%d %d", &k, &v) != EOF; ) { VEC_PUSH(keys, k); VEC_PUSH(vals, v); } VEC_SORT(keys, int_cmp); VEC_SORT(vals, int_cmp); int dsum = 0; for (size_t i = 0; i < keys.l; ++i) dsum += abs(keys.d[i] - vals.d[i]); printf("Part 1: %d\n", dsum); struct kvpair {int k; size_t v;}; VEC(struct kvpair) vtally = {0}; for (size_t vi = 0; vi < vals.l; ++vi) { const int val = vals.d[vi]; bool found = false; for (size_t ti = 0; ti < vtally.l; ++ti) { if (vtally.d[ti].k == val) { ++vtally.d[ti].v; found = true; break; } } if (!found) VEC_PUSH(vtally, ((struct kvpair){val, 1})); } int scoresum = 0; for (size_t ki = 0; ki < keys.l; ++ki) { const int key = keys.d[ki]; for (size_t ti = 0; ti < vtally.l; ++ti) { if (vtally.d[ti].k == key) { scoresum += key * vtally.d[ti].v; break; } } } printf("Part 2: %d\n", scoresum); }
J.C. Denton · 2024-12-01 09:30
>>40531 (Initially tried to make a generic MAP macro, but it was too painful/large when I know I won't use it more than once, so linear search it was.) POSIX-2024 sh: #!/bin/sh set -eu # Poor man's mktemp -d i=0; until mkdir /tmp/dir$i; do i=$((i + 1)); done; d=/tmp/dir$i trap 'exit 1' HUP INT QUIT ABRT TERM trap 'rm -r "$d"' EXIT in=$(realpath -- "$1") cd "$d" awk '{print $1}' "$in" | sort -g >k awk '{print $2}' "$in" | sort -g >v printf '%s' "Part 1: " paste -d - k v | bc | tr -d - | paste -sd+ - | bc printf '%s' "Part 2: " uniq -c v | join -22 k - | tr ' ' '*' | paste -sd+ - | bc
Turing · 2024-12-01 09:38
>>40532 Tcl 8.6: #!/usr/bin/env tclsh namespace path {::tcl::mathop ::tcl::mathfunc} try { set chan [open [lindex $argv 0]] set data [read $chan] } finally { close $chan } set keys [lsort -int [lmap {1 2} $data {set 1}]] set vals [lsort -int [lmap {1 2} $data {set 2}]] set p1 [+ {*}[lmap k $keys v $vals {abs [- $k $v]}]] puts "Part 1: $p1" foreach v $vals {dict incr vtally $v} set p2 [+ {*}[lmap k $keys { # Tcl 9: use [dict getwidthdefault $vtally $k 0] if {![dict exists $vtally $k]} continue * $k [dict get $vtally $k] }]] puts "Part 2: $p2"
J.C. Denton · 2024-12-01 09:50
>>40533 Python (could have used collections.Counter, but fuarrrk that unintuitive crap). #!/usr/bin/env python import sys with open(sys.argv[1]) as f: data = [int(x) for x in f.read().rstrip().split()] keys = sorted(data[0::2]) vals = sorted(data[1::2]) p1 = sum(abs(k - v) for (k, v) in zip(keys, vals)) print(f"Part 1: {p1}") vtally = {} for v in vals: vtally[v] = vtally.get(v, 0) + 1 p2 = sum(k * vtally.get(k, 0) for k in keys) print(f"Part 2: {p2}") And that's it! No one else? Last year here had a lot more participants.
Hououin Kyouma · 2024-12-01 12:14
>>40534 That's super clean. I used go, and there's a lot more "boilerplate" in go than there is in python.
Donna Hawthorne · 2024-12-01 13:10
#include <stdio.h> #include <stdlib.h> #include <string.h> #define LINES 1000 #define LINE_SIZE 14 #define MAX_VALUE 100000 void count_sort(int* arr, int exp) { int out[LINES]; int count[10] = {0}; for (int i = 0; i < LINES; i++) { count[(arr[i] / exp) % 10]++; } for (int i = 1; i < 10; i++) { count[i] += count[i - 1]; } for (int i = LINES - 1; i >= 0; i--) { out[count[(arr[i] / exp) % 10] - 1] = arr[i]; count[(arr[i] / exp) % 10]--; } for (int i = 0; i < LINES; i++) { arr[i] = out[i]; } } void radix_sort(int* arr) { int max = arr[0]; for (int i = 1; i < LINES; i++) { if (arr[i] > max) max = arr[i]; } for (int exp = 1; max / exp > 0; exp *= 10) { count_sort(arr, exp); } } int parse_input(int* left, int* right, int* freqs) { FILE* fp; char* buf; char* ptr; fp = fopen("input.txt", "r"); if (fp == NULL) { perror("fopen"); return 1; } buf = malloc(LINES * LINE_SIZE); if (buf == NULL) { perror("malloc"); fclose(fp); return 1; } memset(freqs, 0, MAX_VALUE * sizeof(int)); fread(buf, LINE_SIZE, LINES, fp); fclose(fp); ptr = buf; for (int i = 0; i < LINES; i++) { left[i] = (ptr[0] - '0') * 10000 + (ptr[1] - '0') * 1000 + (ptr[2] - '0') * 100 + (ptr[3] - '0') * 10 + (ptr[4] - '0'); right[i] = (ptr[8] - '0') * 10000 + (ptr[9] - '0') * 1000 + (ptr[10] - '0') * 100 + (ptr[11] - '0') * 10 + (ptr[12] - '0'); freqs[right[i]]++; ptr += LINE_SIZE; } free(buf); return 0; } int main(void) { int left[LINES], right[LINES], freqs[MAX_VALUE], td, ss; if (parse_input(left, right, freqs) != 0) { return EXIT_FAILURE; } radix_sort(left); radix_sort(right); td = 0; ss = 0; for (int i = 0; i < LINES; i++) { td += abs(left[i] - right[i]); ss += left[i] * freqs[left[i]]; } printf("Travel Distance: %d\nSimilarity Score: %d\n", td, ss); return EXIT_SUCCESS; }
push · 2024-12-02 09:11
I feel REALLY dirty doing it that stupidly and consing that much, but whatever... (load (make-pathname :defaults *load-truename* :name "../rc")) (in-package #:q3cpma-user) (defun safe-report-p (report) (and (or (apply #'< report) (apply #'> report)) (every (lambda (Δ) (<= 1 (abs Δ) 3)) (mapcar #'- report (cdr report))))) (defun possible-holes (list) (flet ((punch-hole (list n) (if (zerop n) (cdr list) (let* ((fresh (copy-list list)) (cons (nthcdr (- n 1) fresh))) (setf (cdr cons) (cddr cons)) fresh)))) (iter (for i below (list-length list)) (collect (punch-hole list i))))) (let ((reports (read:all-from-file *input-path* :reader (reader:until-char #\Newline)))) (format t "Part 1: ~A~%" (count-if #'safe-report-p reports)) (format t "Part 2: ~A~%" (count-if (lambda (r) (or (safe-report-p r) (some #'safe-report-p (possible-holes r)))) reports)))
Unidentified · 2024-12-02 09:36
>>40543 "Less worse", but still not to my taste. Anyone did it the clever way? (load (make-pathname :defaults *load-truename* :name "../rc")) (in-package #:q3cpma-user) (named-readtables:in-readtable :q3cpma/readtable) (defun safe-report-p (report) (and (or (apply #'< report) (apply #'> report)) (every #λ(<= (abs _) 3) (mapcar #'- report (cdr report))))) (defun possible-holes (list) (mapcar #λ(remove-if (constantly t) list :start _ :count 1) (alexandria:iota (length list)))) (let ((reports (read:all-from-file *input-path* :reader (reader:until-char #\Newline)))) (format t "Part 1: ~A~%" (count-if #'safe-report-p reports)) (format t "Part 2: ~A~%" (count-if #λ(or (safe-report-p _) (some #'safe-report-p (possible-holes _))) reports)))
Tequila · 2024-12-03 06:01
Regex today! Used Go again, the regex was really easy with the standard library. r := regexp.MustCompile(`don't\(\)|do\(\)|mul\([0-9]*,[0-9]*\)`) matches := r.FindAllString(val, -1)
Crash Override · 2024-12-03 07:19
Day 3. Rolled out my own parser combinators, which I do not include for the sake of brevity. data Instruction = Mul Integer Integer | Do | Dont deriving Show eval :: [Instruction] -> [Integer] eval is = eval' is Do where eval' [] _ = [] eval' (Do : is) _ = eval' is Do eval' (Dont : is) _ = eval' is Dont eval' (m : is) Do = (evalMul m) : (eval' is Do) eval' (_ : is) Dont = eval' is Dont evalMul (Mul a b) = a * b isMul :: Instruction -> Bool isMul (Mul _ _ ) = True isMul _ = False digitsParser :: Parser [Integer] digitsParser = listParser digitParser integerFromDigits :: [Integer] -> Maybe Integer integerFromDigits (a : []) = Just a integerFromDigits (a : b : []) = Just (a * 10 + b) integerFromDigits (a : b : c : []) = Just (a * 100 + b * 10 + c) integerFromDigits _ = Nothing instructionsParser :: Parser [Instruction] instructionsParser = allParser instructionParser instructionParser :: Parser Instruction instructionParser = oneOfParser [mulParser, doParser, dontParser] -- mul(x,y) mulParser :: Parser Instruction mulParser = do stringParser "mul" charParser '(' x <- mapMaybe integerFromDigits digitsParser charParser ',' y <- mapMaybe integerFromDigits digitsParser charParser ')' return (Mul x y) -- do() doParser :: Parser Instruction doParser = (const Do) <$> (stringParser "do()") -- don't() dontParser :: Parser Instruction dontParser = (const Dont) <$> (stringParser "don't()") part1 = part1' <$> readInput 3 part1' :: String -> Integer part1' s = sum $ evalMuls $ parseOr [] instructionsParser s where evalMuls is = eval $ filter isMul is part2 = part2' <$> readInput 3 part2' :: String -> Integer part2' s = sum $ eval $ parseOr [] instructionsParser s
藤原純一 · 2024-12-03 07:51
>>40544 Piece of cake today. Hardest part was noticing that the second sample isn't the same as the first... (in-package #:q3cpma-user) (let ((mem (uiop:read-file-string (car uiop:*command-line-arguments*))) (mul-re "mul\\((\\d{1,3}),(\\d{1,3})\\)")) (format t "Part 1: ~A~%" (let ((acc 0)) (cl-ppcre:do-register-groups (x y) (mul-re mem acc) (incf acc (* (parse-integer x) (parse-integer y)))))) (format t "Part 2: ~A~%" (iter (with mul-on = t) (for ins in (cl-ppcre:all-matches-as-strings (uiop:strcat mul-re "|do(n't)?\\(\\)") mem)) (match ins ((ppcre mul-re x y) (when mul-on (sum (* (parse-integer x) (parse-integer y))))) ("do()" (setf mul-on t)) ("don't()" (setf mul-on nil)))))) >>40548 Technically wrong, "mul(X,Y)" is specified as taking 1-3 digit integers, you're lucky the author didn't fuarrrk with you. >>40550 Was this actually needed? Does Haskell (it's Haskell, right?) not have regexps and string->integer?
Hiro Protagonist · 2024-12-03 13:27
>>40552 Sure, I could have used regular expressions in Haskell. But I found the design of parser combinators much more enjoyable.
Yet Another Lainon · 2024-12-03 16:41
One of the ads (the one for CodeCrafters.io) on the right side asked to solve the following: PWFI YFRP, ZIK XVNVC% KTW GOXA MMQEMOTH Anyone know what this translates to?
Unidentified · 2024-12-04 06:26
day 4 part 1. this is the most braindead solution I could come up with but it works. regex didn't work for some reason. part 2 made me mad so I havent done it yet. func p1(vals []string) { xmas := 0 mx := len(vals) - 3 for i, line := range vals { for j, _ := range line { if j < mx { if (vals[i][j] == 'X' && vals[i][j+1] == 'M' && vals[i][j+2] == 'A' && vals[i][j+3] == 'S') || (vals[i][j] == 'S' && vals[i][j+1] == 'A' && vals[i][j+2] == 'M' && vals[i][j+3] == 'X') { xmas++ } } if i < mx { if (vals[i][j] == 'X' && vals[i+1][j] == 'M' && vals[i+2][j] == 'A' && vals[i+3][j] == 'S') || (vals[i][j] == 'S' && vals[i+1][j] == 'A' && vals[i+2][j] == 'M' && vals[i+3][j] == 'X') { xmas++ } } if i < mx && j < mx { if (vals[i][j] == 'X' && vals[i+1][j+1] == 'M' && vals[i+2][j+2] == 'A' && vals[i+3][j+3] == 'S') || (vals[i][j] == 'S' && vals[i+1][j+1] == 'A' && vals[i+2][j+2] == 'M' && vals[i+3][j+3] == 'X') { xmas++ } } if i >= 3 && j < mx { if (vals[i][j] == 'X' && vals[i-1][j+1] == 'M' && vals[i-2][j+2] == 'A' && vals[i-3][j+3] == 'S') || (vals[i][j] == 'S' && vals[i-1][j+1] == 'A' && vals[i-2][j+2] == 'M' && vals[i-3][j+3] == 'X') { xmas++ } } } } fmt.Printf("match: %d\n", xmas) }
↑ · 2024-12-04 08:33
>>40552 Not hard, but needed a bit of elbow grease to factorize the two parts. Remembered that global variables are cool for this. (in-package #:q3cpma-user) (defparameter *map* (apply #'array-operations:stack-rows* 'standard-char (uiop:read-file-lines *input-path*))) (defconstant +all-dirs+ '#.(delete '(0 0) (map-product #'list '(-1 0 1) '(-1 0 1)) :test 'equal)) (defconstant +diag-only+ '#.(delete-if (curry #'member 0) (map-product #'list '(-1 0 1) '(-1 0 1)))) (defun word-look (word pos dirs) (iter (with (y x) = pos) (with dist = (- (length word) 1)) (for (Δy Δx) in dirs) (when (and (array-in-bounds-p *map* (+ y (* Δy dist)) (+ x (* Δx dist))) (iter (for c in-string word with-index i) (always (char= c (aref *map* (+ y (* Δy i)) (+ x (* Δx i))))))) (collect (list (+ y Δy) (+ x Δx)))))) ;; Return the second letter position (defun word-search (word dirs) (mapcan (lambda (pos) (word-look word pos dirs)) (array:position-all (char word 0) *map*))) (format t "Part 1: ~A~%" (length (word-search "XMAS" +all-dirs+))) (format t "Part 2: ~A~%" (count 2 (hash-table-values (list:tally (word-search "MAS" +diag-only+) :test 'equal)))) >>40568 Lol, Go devs in a nutshell.
Junichi Fujiwara · 2024-12-05 08:54
Gofriend here. I ended up solving yesterday's part 2 as well, turned out to be pretty simple. could probably be more elegant but that's not really my priority, still trying to get familiar with go. func p2(vals []string) int { mas := 0 for i := 1; i < len(vals)-1; i++ { for j := 1; j < len(vals[i])-1; j++ { // only check around if we're on A if vals[i][j] == 'A' { vword := fmt.Sprintf("%c%c%c", vals[i-1][j], vals[i][j], vals[i+1][j]) hword := fmt.Sprintf("%c%c%c", vals[i][j-1], vals[i][j], vals[i][j+1]) if (vword == "MAS" || vword == "SAM") && (hword == "MAS" || hword == "SAM") { fmt.Printf("vertical cross %s %s\n", vword, hword) //mas++ } d1word := fmt.Sprintf("%c%c%c", vals[i-1][j-1], vals[i][j], vals[i+1][j+1]) d2word := fmt.Sprintf("%c%c%c", vals[i+1][j-1], vals[i][j], vals[i-1][j+1]) if (d1word == "MAS" || d1word == "SAM") && (d2word == "MAS" || d2word == "SAM") { fmt.Printf("diagonal cross %s %s\n", d1word, d2word) mas++ } } } } return mas } today's p1 was also pretty easy but I've also not done part 2 yet, still thinking about it.
Nanashi No Gombe · 2024-12-05 09:04
>>40590 Ha right ended up leaving my vertical cross checker in; I didn't realize I was only supposed to check for the X shape and not the + shape as well.
Apoc · 2024-12-05 10:22
Day 5. I immediately thought "topological sort", but maybe there was a simpler, brute force way of doing this? Did it in POSIX sh first: #!/bin/sh set -eu mkdir /tmp/aoc2024 trap 'exit 1' HUP INT QUIT ABRT TERM trap 'rm -r /tmp/aoc2024' EXIT in=$(realpath -- "$1") cd /tmp/aoc2024 sed '/^$/,$d; s/|/ /' "$in" >rules sed '1,/^$/d' "$in" | while IFS= read -r upd do pat=$(echo "$upd" | tr , '|') upd=$(echo "$upd" | tr , '\n') grep -E "^($pat) ($pat)\$" rules | tsort >ord echo "$upd" | grep -Fxf ord | cmp -s - ord && out=p1 || out=p2 { [ $out = p1 ] && echo "$upd" || cat ord; } | sed -n $(($(echo "$upd" | wc -l) / 2 + 1))p >>$out done printf 'Part 1: %d\n' $(paste -sd'+' p1 | bc) printf 'Part 2: %d\n' $(paste -sd'+' p2 | bc) Then translated to CL (cleaner, but painful interaction through uiop): (in-package #:q3cpma-user) (named-readtables:in-readtable :q3cpma/readtable) (defparameter *rules* nil) (defparameter *updates* nil) (destructuring-bind (rule-lines update-lines) (list:split-on "" (uiop:read-file-lines *input-path*) :test #'string=) (setf *rules* (mapcar #λ(mapcar #'parse-integer (str:split #\| _)) rule-lines)) (setf *updates* (mapcar #λ(mapcar #'parse-integer (str:split #\, _)) update-lines))) (defun tsort (rules) ;; I'm not writing a topological sort when POSIX has tsort... (let* ((input (apply #'str:join #\Space (mapcar #'princ-to-string (flatten rules)))) (output (uiop:run-program '("tsort") :input (list input) :output :lines))) (mapcar #'parse-integer output))) (defun order (update) (tsort (remove-if-not #λ(subsetp _ update) *rules*))) (iter (for update in *updates*) (for order = (order update)) (if (equal (remove-if-not #λ(member _ order) update) order) (sum (elt update (truncate (length update) 2)) into p1) (sum (elt order (truncate (length order) 2)) into p2)) (finally (format t "Part 1: ~A~%" p1) (format t "Part 2: ~A~%" p2)))
Sabo Engle · 2024-12-05 10:37
>>40592 A bit more golfing for sh: sed '/^$/,$d; s/|/ /' "$in" >rules sed '1,/^$/d' "$in" | while IFS= read -r update do pat=$(echo "$update" | tr , '\n' | tee upd | paste -sd'|' -) grep -Ex "($pat) ($pat)" rules | tsort >ord grep -Fxf ord upd | cmp -s - ord && out=p1 || out=p2 { [ $out = p1 ] && cat upd || cat ord; } | sed -n $(($(wc -l <upd) / 2 + 1))p >>$out done printf 'Part 1: %d\n' $(paste -sd'+' p1 | bc) printf 'Part 2: %d\n' $(paste -sd'+' p2 | bc)
Faye Valentine · 2024-12-06 06:34
Gofriend back again. part 1 finished today, decided to use structs halfway through. Here's some of my code. It's still pretty bad but I'm ok with that! And I'm not even going to attempt part2, it looks insane. func (g *guard) moveG(grid []string, turns *int) bool { mY := len(grid) mX := len(grid[0]) switch g.g { case '^': if g.y == 0 { fmt.Printf("reached an edge") return true } if grid[g.y-1][g.x] != '#' { g.y-- *turns++ } else { g.turnG() } case '>': if g.x+1 == mX { fmt.Printf("reached an edge") return true } if grid[g.y][g.x+1] != '#' { g.x++ *turns++ } else { g.turnG() } case 'v': if g.y+1 == mY { fmt.Printf("reached an edge") return true } if grid[g.y+1][g.x] != '#' { g.y++ *turns++ } else { g.turnG() } case '<': if g.x == 0 { fmt.Printf("reached an edge") return true } if grid[g.y][g.x-1] != '#' { g.x-- *turns++ } else { g.turnG() } } return false } func p1(lines []string) int { var gx, gy int for i, y := range lines { gp := indexOfS(y, '^') if gp > 0 { gx = gp gy = i break } } g := guard{ x: gx, y: gy, g: '^', } fmt.Printf("gp: %d %d\n", g.x, g.y) turns := 0 for { line := []rune(lines[g.y]) line[g.x] = 'X' lines[g.y] = string(line) fmt.Printf("guard: %c %d, %d: %d\n", g.g, g.x, g.y, turns) done := g.moveG(lines, &turns) if done { break } } steps := countX(lines) fmt.Printf("unique %d\n", steps) return steps }
Turing · 2024-12-06 13:13
>>40602 Lainchan doesn't like tabs, friend. 2nd part really isn't that hard if you brute force it (2s here on SBCL). (in-package #:q3cpma-user) (defparameter *map* (apply #'array-operations:stack-rows* 'standard-char (uiop:read-file-lines *input-path*))) (defparameter *start* (apply #'complex (array:position #\^ *map*))) (define-constant +directions+ (circular-list #c(-1 0) #c(0 1) #c(1 0) #c(0 -1)) :test 'equal) (defmacro map-ref (pos) `(aref *map* (realpart ,pos) (imagpart ,pos))) (defun map-in-bounds-p (pos) (array-in-bounds-p *map* (realpart pos) (imagpart pos))) (defun walk (&key path-table (pos *start*) (dirlist +directions+)) (if path-table ;; Can be provided to minimize GC pressure (clrhash path-table) (setf path-table (make-hash-table))) (symbol-macrolet ((dir (car dirlist)) (next (+ pos dir))) (iter outer (when (member dir (gethash pos path-table)) (leave (values path-table t))) (pushnew dir (gethash pos path-table)) (iter (in outer (while (map-in-bounds-p next))) (while (char= (map-ref next) #\#)) (setf dirlist (cdr dirlist))) (setf pos next) (finally (return-from outer (values path-table nil)))))) (let ((guard-path (walk))) (format t "Part 1: ~A~%" (hash-table-count guard-path)) (format t "Part 2: ~A~%" (iter (initially (remhash *start* guard-path)) (with table = (make-hash-table :size (hash-table-count guard-path))) (for (pos nil) in-hashtable guard-path) (setf (map-ref pos) #\#) (counting (nth-value 1 (walk :path-table table))) (setf (map-ref pos) #\.)))) Nobody else? Not sure I'll continue posting in a ghost town thread.
Zero Cool · 2024-12-06 17:19
>>40604 Just go to the 4/g/ if youre fine with cloudflare I would be participating but its a bad time for me
Tomohisa "Tom" Kanezaki · 2024-12-06 22:48
day 7, python f = (lambda t, a, b, p2=0: f(t + b[0], a, b[1:], p2) or f(t * b[0], a, b[1:], p2) or f(int(f'{t}{b[0]}'), a, b[1:], p2) * p2 if b else t == a) p1 = p2 = 0 for ln in open(0).readlines(): a, b = ln.split(':') a = int(a) b = tuple(map(int, b.split())) p1 += a * f(b[0], a, b[1:]) p2 += a * f(b[0], a, b[1:], 1) print(p1, p2)
queue · 2024-12-07 02:51
>>40592 I think that topological sorting is not needed to solve Day 5, regular sorting should be enough. I am not going to prove this, but my solution works using regular sorting, which gives me confidence. The reason I think it works is that given pages A, B, and C, where A < B && B < C, it is not necessary to enforce A < C. I can think of two examples: 1. A sequence of pages that contains A, B, and C. A must come before B, and B -- before C. As a result of a regular sort, A will naturally come before C. 2. A sequence that contains A and C, but not B. Here, the relative order of A and C doesn't matter. E.g., A may come either before or after C.
SHODAN · 2024-12-07 04:29
Code golf version, I also made a properly fast "concat-ints>=0" function not going through strings. (in-package #:q3cpma-user) (defparameter *eqns* (mapcar #λ(destructuring-bind (l r) (str:split #\: _) (cons (read-from-string l) (read:all-from-string r))) (uiop:read-file-lines *input-path*))) (defparameter *operators* (list #'+ #'*)) (defun check (expected nums &optional (acc 0) (op #'+)) (if nums (some #λ(check expected (cdr nums) (funcall op acc (car nums)) _) *operators*) (= expected acc))) (format t "Part 1: ~A~%" (reduce #'+ (remove-if-not #λ(check (car _) (cdr _)) *eqns*) :key #'car)) (format t "Part 2: ~A~%" (let ((*operators* (cons #λ(parse-integer #""_1""_2"") *operators*))) (reduce #'+ (remove-if-not #λ(check (car _) (cdr _)) *eqns*) :key #'car))) >>40605 As someone who left a little bit before GG, I went to check just for giggles. First I didn't find any AoC thread and holy soykaf, this is basically the same as Reddit. >>40607 Pretty clean! But I don't understand the " f(int(f'{t}{b[0]}'), a, b[1:], p2) * p2" line. Why "* p2"? Oh, that's a ternary...
Sipho Nkosi · 2024-12-07 04:39
I got some of part 1 with recursion! And yeah I don't really care that lainchan doesn't like tabs, I'm not bothering to replace them. My code being less readable is just a reflection on its already subpar quality I guess. func calibrate(t int, s int, v []int) bool { fmt.Printf("calibrate t:%d s:%d v:%d\n", t, s, v) if len(v) == 1 { if s*v[0] == t || s+v[0] == t { return true } else { return false } } ca := calibrate(t, s+v[0], v[1:]) cm := calibrate(t, s*v[0], v[1:]) if ca || cm { return true } return false } func p1(l1 []int, l2 [][]int) { evals := 0 sum := 0 for i, target := range l1 { if calibrate(target, l2[i][0], l2[i][1:]) { evals++ sum += target } } fmt.Printf("s %d e %d\n", sum, evals) }
Emmanuel · 2024-12-07 05:35
>>40609 the function returns true or false when it hits the `t == a` base case i only consider concatenation `int(f'{t}{b[0]}')` for part 2, so i multiply it by false for part 1 think of false as zero here
synchronized · 2024-12-07 06:08
>>40611 :%s/\t/ /g
Knuth · 2024-12-07 11:45
>>40613 my editor will change it back when I save the file, kind of unfortunate that lainchan doesn't support tabs. Anyway, got part 2 after an embarrassingly long time being confused why my recursion wasn't working. I was calling calibrate() in calibrate2() since I had just copied it. func calibrate2(t int, s int, v []int) bool { //fmt.Printf("calibrate t:%d s:%d v:%d\n", t, s, v) sv0 := iconcat(s, v[0]) if len(v) == 1 { return s*v[0] == t || s+v[0] == t || sv0 == t } ca := calibrate2(t, s+v[0], v[1:]) cm := calibrate2(t, s*v[0], v[1:]) cc := calibrate2(t, sv0, v[1:]) return ca || cm || cc } func d7p2(l1 []int, l2 [][]int) { sum := 0 for i, target := range l1 { if calibrate2(target, l2[i][0], l2[i][1:]) { sum += target } } fmt.Printf("s %d\n", sum) }
JARGON · 2024-12-07 20:55
>>40518 >>40519 >>40526 The reason they do this is that OAuth offloads a lot of work surrounding identity verification and not storing unencrypted personally identifiable information. To expect a stupid programming puzzle site that is popular once a year to implement proper auth, hashing, bot protection, etc. is a little ridiculous. I totally get where they're coming from. Idiots are going to send the same username/email/password to their service and now they are legally responsible for that info if they don't implement this sort of authentication method. Anyway, you can still easily find all the exercises and a sufficient test case online, you just won't get graded for speed.
Demeere · 2024-12-08 04:03
>>40609 Its under the name of /aocg/ or something I participated with them last year, the thread itself is a massive bump over the rest of the catalog, im assuming by virtue of being filled with people who can actually code
Sabo Engle · 2024-12-08 05:25
Was afraid today was going to get hard, but no, an easy one! (in-package #:q3cpma-user) (load (make-pathname :defaults *load-truename* :name "../grid")) (defparameter *map* (grid-read *input-path*)) (defparameter *antennas* (seq:classify (grid-position-all-if #λ(char/= #\. _) *map*) #λ(grid-ref *map* _))) (defparameter *frequency-model* :part1) (defun antinodes (a1 a2) (let ((a1→a2 (- a2 a1)) (a2→a1 (- a1 a2))) (ecase *frequency-model* (:part1 (list (+ a2 a1→a2) (+ a1 a2→a1))) (:part2 (nconc (iter (for an from (+ a1 a2→a1) by a2→a1) (while (grid-in-bounds-p *map* an)) (collect an)) `(,a1) (iter (for an from (+ a1 a1→a2) by a1→a2) (while (grid-in-bounds-p *map* an)) (collect an))))))) (defun antinodes-for-freq (f) (mapcon (lambda (subl) (mapcan #λ(antinodes (car subl) _) (cdr subl))) (gethash f *antennas*))) (defun all-positions-with-antinode () (remove-duplicates (remove-if-not #λ(grid-in-bounds-p *map* _) (mapcan #'antinodes-for-freq (hash-table:keys *antennas*))))) (format t "Part 1: ~A~%" (length (all-positions-with-antinode))) (format t "Part 2: ~A~%" (let ((*frequency-model* :part2)) (length (all-positions-with-antinode)))) >>40627 I see.
JARGON · 2024-12-09 13:54
After 5 hours of intense despair caused by multiple off-by-one errors, I can now shower, eat and go to sleep. Have to work tomorrow, after all. A little bird tells me this day will separate the men from the boys. (in-package #:q3cpma-user) (defparameter *files* nil) (defparameter *holes* nil) (iter (for c in-string (uiop:read-file-line *input-path*) with-index i) (for n = (digit-char-p c)) (if (evenp i) (push (cons (/ i 2) n) *files*) (push n *holes*)) (finally (nreversef *files*) (nreversef *holes*))) (defun print-disk (files holes) (iter (for (id . len) in files) (iter (repeat len) (format t "~A" id)) (for h in holes) (iter (repeat h) (format t ".")) (finally (terpri)))) (defun compact () (iter outer (with files = (dlist:make (copy-tree *files*))) (with holes = (copy-list *holes*)) (while (not (dlist:emptyp files))) (for h in holes) (collect (dlist:pop-front files)) (iter (while (and (> h 0) (not (dlist:emptyp files)))) (for (id . len) = (dlist:pop-back files)) (in outer (collect (cons id (min h len)))) (decf h len) (finally (when (< h 0) (dlist:push-back (cons id (- h)) files)))))) (defun defragment () (iter outer (with files = (dlist:make (copy-tree *files*))) (with holes = (dlist:make (copy-tree *holes*))) (with moved = (make-hash-table)) (with last-hole = (- (list-length *files*) 1)) (decf last-hole) (for fc initially (dlist:tail files) then (dlist:prev fc)) (while fc) (for f = (dlist:value fc)) (for (id . len) = f) (when (gethash id moved) (next-iteration)) (iter (for hc initially (dlist:head holes) then (dlist:next hc)) (while hc) (for hi from 0 to last-hole) (when (>= (dlist:value hc) len) ;; Shrink chosen hole (decf (dlist:value hc) len) ;; Enlarge destination hole (incf (dlist:value (dlist:nthcell last-hole holes)) len) ;; "Move" file; actually not, copy it and shrink the old one to nothing (dlist:insert-after (copy-list (dlist:value fc)) files (dlist:nthcell hi files)) ;; Keep the moved file but shrink it to nothing (setf (cdr f) 0) ;; Add a zero-length hole before the moved file (dlist:insert-before 0 holes hc) ;; Remember we moved this file to not move it again (setf (gethash id moved) t) ;; Account for the inserted zero-length hole (incf last-hole) (finish))) (finally (return-from outer (iter (for f in (dlist:to-list files)) (collect f) (for h in (dlist:to-list holes)) (collect (cons 0 h))))))) (defun checksum (files) (iter (for (id . len) in files) (for pos initially 0 then (+ pos len)) (sum (reduce #'+ (iota len :start pos) :key #λ(* id _))))) (format t "Part 1: ~A~%" (checksum (compact))) (format t "Part 2: ~A~%" (checksum (defragment))) Thank god I already had a double-linked list handy.
Church · 2024-12-10 09:55
Today was an easy one (as long as you can code a BFS). If you have trouble with yesterday, it's a nice change of pace. (in-package #:q3cpma-user) (load (make-pathname :defaults *load-truename* :name "../grid")) (defparameter *map* (grid-read *input-path* :key #'digit-char-p :type 'unsigned-byte)) (defconstant +bottom+ 0) (defconstant +summit+ 9) (defun next-nodes (pos) (let ((height (grid-ref *map* pos))) (remove-if-not #λ(and (grid-in-bounds-p *map* _) (= (grid-ref *map* _) (+ height 1))) (mapcar #λ(+ pos _) '(#c(-1 0) #c(0 1) #c(1 0) #c(0 -1)))))) (defun trail-ends (start) (iter (with to-visit = (queue:make (list start))) (while (not (queue:empty-p to-visit))) (for node = (queue:pop to-visit)) (let ((next (next-nodes node))) (cond (next (queue:pushm next to-visit)) ((= (grid-ref *map* node) +summit+) (collect node)))))) (let* ((starts (grid-position-all +bottom+ *map*)) (ends (mapcar #'trail-ends starts))) (format t "Part 1: ~A~%" (reduce #'+ (mapcar #'remove-duplicates ends) :key #'length)) (format t "Part 2: ~A~%" (reduce #'+ ends :key #'length)))
Nguoi La · 2024-12-10 11:59
Catching up 2 days at a time. Completed days 8 and 9 today. Day 8 was a breeze. For day 9 I ended up with a different approach for each part. Posting only part 2 of day 9 as it's quite long on its own. haskell -- Day 9. Part 2 readDiskMap :: String -> [Int] readDiskMap s = map digitToInt $ filter isDigit s data Block = U Int Int -- Used size fileId | F Int -- Free size deriving Show maybeFileId (U _ fid) = Just fid maybeFileId _ = Nothing hasFileId fid (U _ fid') = fid == fid' hasFileId _ _ = False hasFree size (F free) = free >= size hasFree _ _ = False putBlock :: Block -> Block -> [Block] putBlock (F free) u@(U used fid) | used > free = error "No free space!" | used == free = [u] | otherwise = [u, F (free - used)] putBlock u@(U _ _) f@(F _) = putBlock f u -- Like concatMap but only for the last element that matches a predicate. -- Nothing if there is no match. maybeReplaceLast :: (a -> Bool) -> (a -> [a]) -> [a] -> Maybe [a] maybeReplaceLast pred f xs = case (break pred xs) of (_, []) -> Nothing (pref, (x : xs')) -> case (maybeReplaceLast pred f xs') of Nothing -> Just $ pref ++ (f x) ++ xs' Just xs'' -> Just $ pref ++ (x : xs'') defrag2 :: [Block] -> [Block] defrag2 blocks = reverse $ defrag2' rblocks (head (mapMaybe maybeFileId rblocks)) where rblocks = (reverse blocks) defrag2' blocks 0 = blocks defrag2' blocks fid = defrag2' blocks' (fid - 1) where blocks' = case (maybeReplaceLast (hasFree bSize) (reverse . putBlock u) rest) of Just bs -> pref ++ [F bSize] ++ bs Nothing -> blocks where (pref, (u@(U bSize _) : rest)) = break (hasFileId fid) blocks checksum2 :: [Block] -> Int checksum2 bs = sum $ zipWith (*) (concatMap unroll bs) [0..] where unroll (F sz) = replicate sz 0 unroll (U sz fid) = replicate sz fid toBlocks :: [Int] -> [Block] toBlocks dmap = zipWith h dmap [0..] where h n idx | even idx = U n (idx `div` 2) | otherwise = F n part2 = part2' <$> readInput 9 part2' = checksum2 . defrag2 . toBlocks . readDiskMap
Koichiro · 2024-12-10 13:48
>>40643 Even if my heart is taken by Lisp and I can't understand most of it, Haskell always look nice. Especially in contrast to SML/(O)Caml. What's that u@ thing, for example? Looks very Perl-esque. What's the single quote I see after function symbols too?
drop · 2024-12-10 21:42
>>40644 The @ sign, when placed between a name and a destructuring pattern, binds the thing that is destructured to that name. In that code, u@(U _ _) binds `u` to the Block matched by `U _ _`. The single quote is just a part of the name, as any other character. This is a common naming convention in Haskell, inspired by mathematical notation, I guess, where functions that have common nature but differ slightly are named thus (e.g., a function's derivative is often named by appending a single tick to the function's name).
Batou · 2024-12-11 07:05
>>40550 For mul I did this: mul :: Parser Op mulP = do string "mul(" (a,b) <- integer `sepBy` (char ',') string ")" return $ Mul a b I rolled my own parser combinator lib years ago, but this time I'm just using Parsec.
Chani · 2024-12-11 07:10
>>40644 You can use the @ syntax like so: go :: String -> (Int, Char) go s@(x:_) = (length x, x) The pattern (x:_) will match non-empty lists. x will be bound to the head of the string and l to the whole string. The tail is discarded (_).
Pris · 2024-12-11 12:57
Day 11, implemented using double-buffered hash tables. (in-package #:q3cpma-user) (defparameter *stones* (read:all-from-file *input-path*)) (defun change-stone (s c dst) (macrolet ((add (n) `(incf (gethash ,n dst 0) c))) (cond ((= 0 s) (add 1)) ((evenp (num:digit-count s)) (multiple-value-bind (q r) (truncate s (expt 10 (/ (num:digit-count s) 2))) (add q) (add r))) (t (add (* s 2024)))))) (defun blink (n) (iter (repeat n) ;; Mandatory double-buffering, stones change all at once (with front = (seq:tally *stones*)) (with back = (make-hash-table)) (clrhash back) (maphash #λ(change-stone _1 _2 back) front) (rotatef front back) (finally (return (reduce #'+ (hash-table-values front)))))) (format t "Part 1: ~A~%" (blink 25)) (format t "Part 2: ~A~%" (blink 75)) >>40651 Interestingly, some guy did day 03 using two parser packages in CL, to compare them: https://github.com/vindarel/bacalisp/blob/master/advent/2024/day03.lisp https://github.com/vindarel/bacalisp/blob/master/advent/2024/day03-parseq.lisp >>40647 >>40652 Thanks for the tip!
Jim Hilger · 2024-12-12 09:39
>>40517 I was planning on posting my jai solutions, but starting a new job and life has delayed my start. I'm still a couple of days behind so I will be posting two a day and hopefully I get caught up in time as to not spam the thread. I will also be posting some stats about memory usage and number of cpu cycles used. If anyone wants the code that does these benchmarks I can post that to, but here we are the first two days. First time posting code blocks so hopefully the formatting ends up ok. // input allocated 14000 bytes // output allocated 81024 bytes // input executed 0.915 million cycles day1 :: (input: string) -> (part1: int, part2: int) { using Basic; using String; using Sort; using Math; using Hash_Table; as: [..]int; bs: [..]int; rhs := input; while true { ok:, line:, rhs = split_line(rhs); if !ok break; ok=, a_str, b_str := split_from_left(line, #char " "); assert(ok); a:, ok = string_to_int(a_str); assert(ok); b:, ok = string_to_int(b_str); assert(ok); array_add(*as, a); array_add(*bs, b); } assert(as.count == bs.count); id :: x => x; quick_sort(as, id); quick_sort(bs, id); part1 := 0; for 0..as.count-1 part1 += abs(as[it] - bs[it]); freq: Table(int, int); for bs { ptr := table_find_pointer(*freq, it); if ptr then ptr.* += 1; else table_add(*freq, it, 1); } part2 := 0; for as { ptr := table_find_pointer(*freq, it); if ptr then part2 += it * ptr.*; } return part1, part2; } // input allocated 19223 bytes // output allocated 64 bytes // input executed 0.710 million cycles day2 :: (input: string) -> (part1: int, part2: int) { using Basic; Check :: enum { FIRST; SECOND; INCREASING; DECREASING; UNSAFE; }; check :: (state: Check, last: int, current: int) -> Check, int { if #complete state == { case .FIRST; return .SECOND, current; case .SECOND; { if last == current return .UNSAFE, current; if last < current { if current - last > 3 return .UNSAFE, current; return .INCREASING, current; } else { if last - current > 3 return .UNSAFE, current; return .DECREASING, current; } } case .INCREASING; { delta := current - last; if delta < 1 || delta > 3 return .UNSAFE, current; } case .DECREASING; { delta := last - current; if delta < 1 || delta > 3 return .UNSAFE, current; } case .UNSAFE; } return state, current; } rhs := input; part1 := 0; part2 := 0; report : [..]int; state : Check; last : int; while true { ok:, line:, rhs = split_line(rhs); if !ok break; state = .FIRST; array_reset_keeping_memory(*report); while line { current:, ok, line = string_to_int(line); assert(ok); state, last = check(state, last, current); array_add(*report, last); } if state != .UNSAFE then part1 += 1; else for _, skip_index: report { state = .FIRST; for report { if it_index == skip_index continue; state, last = check(state, last, it); if state == .UNSAFE break; } if state != .UNSAFE { part2 += 1; break; } } } part2 += part1; return part1, part2; } Most of these are going to be caveman brute force solutions so don't expect anything crazy. Posting mainly to prove that jai isn't vaporware and to answer questions people might have about it.
Case · 2024-12-12 09:49
>>40660 Why is allocated memory described by 2 different numbers -- "input" and "output"? What is the difference between them? And what does "input" mean in the line that describes the number of execution cycles?
Tequila · 2024-12-12 09:57
>>40661 The input is just how big the puzzle input file is, output allocated is how much dynamic memory the solution allocated (not including the input file obviously). "input executed" is a mistake! It should be "output executed" and its just the difference between rdtsc() before and after the solution is ran. Loading the file does not contribute to this value.
Batou · 2024-12-12 11:22
>>40656 what do you use for data analysis ?
dup · 2024-12-12 12:36
Hated that type of problem last year, still do to this day. (in-package #:q3cpma-user) (load (make-pathname :defaults *load-truename* :name "../grid")) (defparameter *map* (grid-read *input-path* :type 'standard-char)) (define-constant +directions+ '(#c(-1 0) #c(0 1) #c(1 0) #c(0 -1)) :test 'equal) (defun in-region-p (type pos &optional (delta 0)) (and (grid-in-bounds-p *map* pos delta) (char= type (grid-ref *map* pos delta)))) (defun corner-p (type pos dir1 dir2) (match (mapcar #λ(in-region-p type pos _) (list dir1 (+ dir1 dir2) dir2)) ((or (list t nil t) (list nil _ nil)) t))) (defun corner-count (type pos) (count t (mapcar #λ(corner-p type pos _1 _2) +directions+ `(,@(cdr +directions+) ,(car +directions+))))) (defun survey-region (type surveyed pos) ;; Returns (area perimeter corner-count) (cond ((not (in-region-p type pos)) '(0 1 0)) ((grid-ref surveyed pos) '(0 0 0)) (t (setf (grid-ref surveyed pos) t) (apply #'mapcar #'+ (list 1 0 (corner-count type pos)) (mapcar #λ(survey-region type surveyed _) (mapcar #λ(+ pos _) +directions+)))))) (defun segment-garden () (iter (with surveyed = (make-array (array-dimensions *map*) :element-type 'boolean :initial-element nil)) (for start initially 0 then (grid-position nil surveyed :start start)) (while start) (for type = (grid-ref *map* start)) (collect (cons type (survey-region type surveyed start))))) (defun region-price (region &key discount-p) (destructuring-bind (type area perimeter sides) region (* area (if discount-p sides perimeter)))) (let ((regions (segment-garden))) (format t "Part 1: ~A~%" (reduce #'+ regions :key #'region-price)) (format t "Part 2: ~A~%" (reduce #'+ regions :key #λ(region-price _ :discount-p t)))) >>40660 Well, nice to see that it does indeed exist. Would have been truly interested a good 5 years ago, before discovering Lisp. How does it compare with Zig, since these are quite similar? Does it have keyword parameters? What about metaprogramming? How would you do parallel iteration (zip in Python, foreach in Tcl, `(iter (for ...) (for ...)`) in Lisp)? Pattern matching? Does it need a ternary (i.e. does `var = if true 1 else 2` work) or does it have one? >>40663 You're asking me? sh + mawk/gawk + gnuplot usually does the job, but I'm considering taking a look at https://lisp-stat.dev/ one of these days.
let · 2024-12-12 12:42
Part 2 of Day 11 took a considerable amount of time, when all I had to do was apply memoisation. Instead, I got hooked on the idea of a recursive tree data structure, which led me nowhere. After finally solving the problem, I took some time to generalise memoisation to arbitrary binary functions. The resulting code looks interesting, to say the least. Compared to how I would approach this in Common Lisp, these are completely different worlds. haskell -- Memoisation of results for binary functions. type MemoTable2 a b c = Map.Map (a,b) c -- Result of applying a memoised binary function: updated table and return value. type MemoR2 a b c = (MemoTable2 a b c, c) -- Memoisation environment. type Memo2 a b c = (MemoTable2 a b c -> MemoR2 a b c) -- Type of memoised binary functions. type MemoF2 a b c = a -> b -> Memo2 a b c -- Stores `c` as a result of applying the memoised function to `a` and `b`. returnM :: (Ord a, Ord b) => a -> b -> c -> Memo2 a b c returnM a b c = \tbl -> (Map.insert (a,b) c tbl, c) -- Applies `g` to the result of applying the memoised function to `a` and `b`. -- The returned environment reflects the effects of recursive calls on the table. recM :: (Ord a, Ord b) => (c -> Memo2 a b c) -> MemoF2 a b c -> a -> b -> Memo2 a b c recM g mf a b = \tbl -> case Map.lookup (a,b) tbl of Just c -> (g c) tbl Nothing -> (g c) tbl' where (tbl', c) = (mf a b) tbl -- Provides access to the return value of a memoised function. bindM :: (Ord a, Ord b) => (c -> Memo2 a b c) -> Memo2 a b c -> Memo2 a b c bindM g m = \tbl -> let (tbl', c) = m tbl in (g c) tbl' foldM :: (Ord a, Ord b) => (c -> c -> c) -> c -> MemoF2 a b c -> [(a,b)] -> Memo2 a b c foldM g c0 mf args = \tbl -> foldl' h (tbl,c0) args where h (tbl,c) (a,b) = (tbl', g c c') where (tbl', c') = (recM (returnM a b) mf a b) tbl -- Application of a memoisation environment. -- Starts with an empty table, and returns the final value produced by the environment. applyM :: (Ord a, Ord b) => Memo2 a b c -> c applyM m = snd $ m Map.empty -- Memoised version of `count`. countM :: MemoF2 Integer Integer Integer countM x 0 = returnM x 0 1 countM x n = bindM (\c -> returnM x n c) m where m = foldM h 0 countM (zip (evolve x) (repeat (n-1))) where h s yc = s + yc -- Counts the number of stones that `x` turns into after blinking `n` times. count x n = applyM (countM x n) -- Counts the number of stones that `xs` turn into after blinking `n` times. countAll xs n = applyM $ foldM (+) 0 countM (zip xs (repeat n)) evolve :: Integer -> [Integer] evolve 0 = [1] evolve n | even c = [x, y] | otherwise = [n * 2024] where c = countDigits n (x, y) = splitNumber n c countDigits :: Integer -> Int countDigits n = h n 1 where h n c = if n `div` 10 == 0 then c else h (n `div` 10) (c+1) splitNumber :: Integer -> Int -> (Integer, Integer) splitNumber n c = (n `div` h, n `mod` h) where h = (10 ^ (c `div` 2)) readNumbers = parseValue inputParser inputParser = separatedByParser (charParser ' ') integerParser part1 = part1' <$> readInput 11 part1' s = countAll (readNumbers s) 25 part2 = part2' <$> readInput 11 part2' s = countAll (readNumbers s) 75
Von Neumann · 2024-12-13 08:12
First day where I needed maths. Hope there won't be many of these, I'm doing the AoC, not project Euler. (in-package #:q3cpma-user) (defun parse-machine (string) (mapcar #'parse-integer (cl-ppcre:all-matches-as-strings "\\d+" string))) (defparameter *machines* (mapcar #'parse-machine (str:split #"\n\n" (uiop:read-file-string *input-path*)))) (defun solve (machine) (destructuring-bind (ax ay bx by px py) machine (let* ((a (/ (- (* px by) (* py bx)) (- (* ax by) (* ay bx)))) (b (/ (- px (* a ax)) bx))) (and (integerp a) (integerp b) (list a b))))) (defun correct (machine) (let ((new (copy-list machine))) (incf (fifth new) 10000000000000) (incf (sixth new) 10000000000000) new)) (defun cost (solution) (if solution (+ (* 3 (car solution)) (cadr solution)) 0)) (format t "Part 1: ~A~%" (reduce #'+ (mapcar #'solve *machines*) :key #'cost)) (format t "Part 2: ~A~%" (reduce #'+ (mapcar #'solve (mapcar #'correct *machines*)) :key #'cost)) >>40665 Still looking good, even if memoization wasn't necessary in this case. If something like https://www.simplehaskell.org/ took flight and cleaned the dumpster fire that is normal Haskell (there's a standard but nobody uses anything else but GHC and its bazillion extensions, the standard library itself is even more baroque and full of cruft than ANSI CL), same way that SML truly needs successor ML, I'd look at it. Also, I don't know if I'm being Lisp-brained, but I can barely stand operators as something syntactically and/or conceptually different from functions; give me my (+ 0 1 2)s!
Terzibashjian · 2024-12-13 11:11
>>40664 >How does it compare with Zig, since these are quite similar? Haven't used that much zig so I couldn't tell you to be honest. They are both "C++ Killer" languages and their differences boil down to the exact C++ subset their creator prefers. >Does it have keyword parameters? yes >What about metaprogramming? There is very rich support for metaprogramming. On the most basic level there are two kinds of metaprogramming that can be done, compile-time execution and compiler-interception. Any code prefixed with a #run directive is executed at compile-time and the there is the #insert directive which inserts compile-time generated program text or syntax tree into that point in the program. Compiler interception works by having the compiler as a library at compile time and allowing compilation tasks launched programmatically to listen to events emitted by the compiler. Events are raised when files get loaded, modules get imported, expressions are typechecked, the phase of compilation has changed, among other things. Any code passed through a message can also be arbitrarily rewritten on the syntax tree level. Hopefully this answers the question. >How would you do parallel iteration (zip in Python, foreach in Tcl, `(iter (for ...) (for ...)`) in Lisp)? There is nothing built in or in the standard modules, but it would be trivial to implement with the current macro system. >Pattern matching? Nope. But again as long as something parses as jai code, you can introspect and rewrite the syntax tree arbitrarily so writing macros for pattern matching tagged unions or structures can be implemented. I have implemented such pattern matching macros myself without too much fuss. >Does it need a ternary (i.e. does `var = if true 1 else 2` work) or does it have one? There is very much a delineation between statements and expressions if that's what you're asking, and there is a ternary operator ifx (you can see an example of it in my day 3 implementation) // input allocated 17700 bytes // output allocated 0 bytes // output executed 10.754 million cycles day3 :: (input: string) -> (part1: int, part2: int) { using Basic; using String; using Math; rhs := input; part1 := 0; part2 := 0; enabled := true; while rhs { found_mul, mul_lhs, mul_rhs := split_from_left(rhs, "mul("); found_do, do_lhs, do_rhs := split_from_left(rhs, "do()"); found_dont, dont_lhs, dont_rhs := split_from_left(rhs, "don't()"); m := min( ifx found_mul then mul_lhs.count else input.count, ifx found_do then do_lhs.count else input.count, ifx found_dont then dont_lhs.count else input.count, ); if m == mul_lhs.count { rhs = mul_rhs; a:, ok:, rhs = string_to_int(rhs); if !ok || a > 999 || a < 0 continue; chr := rhs[0]; rhs = advance(rhs); if chr != #char "," continue; b:, ok, rhs = string_to_int(rhs); if !ok || b > 999 || b < 0 continue; chr = rhs[0]; rhs = advance(rhs); if chr != #char ")" continue; part1 += a * b; if enabled then part2 += a * b; } else if m == do_lhs.count { rhs = do_rhs; enabled = true; } else if m == dont_lhs.count { rhs = dont_rhs; enabled = false; } else if m == input.count { break; } } return part1, part2; } // input allocated 19740 bytes // output allocated 2240 bytes // output executed 1.573 million cycles day4 :: (input: string) -> (part1: int, part2: int) { using Basic; grid_size :: 140; grid: [..]string; array_reserve(*grid, grid_size); rhs := input; while true { ok:, line:, rhs = split_line(rhs); if !ok break; array_add(*grid, line); } Direction :: enum { uu; ul; ur; dd; dl; dr; ll; rr; } move :: (dir: Direction, row: int, col: int) -> (ok: bool, new_row: int, new_col: int) { new_row: int = ---; new_col: int = ---; if #complete dir == { case .uu; #through; case .ul; #through; case .ur; if row then new_row = row-1; else return false, row, col; case .dd; #through; case .dl; #through; case .dr; if row < grid_size-1 then new_row = row + 1; else return false, row, col; case .ll; #through; case .rr; new_row = row; } if #complete dir == { case .ur; #through; case .rr; #through; case .dr; if col < grid_size-1 then new_col = col + 1; else return false, row, col; case .ul; #through; case .ll; #through; case .dl; if col then new_col = col-1; else return false, row, col; case .uu; #through; case .dd; new_col = col; } return true, new_row, new_col; } part1 := 0; xmas :: "XMAS"; start, end :: #run enum_range(Direction); for row, row_index: grid for starting_index, col_index: row for start..end { dir, r, c := cast(Direction) it, row_index, col_index; for xmas { if grid[r][c] != it break; if it_index == xmas.count - 1 then part1 += 1; else { ok:, r, c = move(dir, r, c); if !ok break; } } } part2 := 0; for row, row_index: grid for start, col_index: row if start == #char "A" for Direction.[.ul, .ur, .dl, .dr] { ok, r, c := move(it, row_index, col_index); if !ok || grid[r][c] != #char "M" continue; opposite: Direction = ---; orthog_a: Direction = ---; orthog_b: Direction = ---; if it == { case .ul; opposite = .dr; orthog_a = .ur; orthog_b = .dl; case .ur; opposite = .dl; orthog_a = .dr; orthog_b = .ul; case .dl; opposite = .ur; orthog_a = .ul; orthog_b = .dr; case .dr; opposite = .ul; orthog_a = .dl; orthog_b = .ur; }; ok, r, c = move(opposite, row_index, col_index); if !ok || grid[r][c] != #char "S" continue; ok, r, c = move(orthog_a, row_index, col_index); if !ok continue; to_find: u8 = ---; if grid[r][c] == { case #char "M"; to_find = #char "S"; case #char "S"; to_find = #char "M"; case; continue; } ok, r, c = move(orthog_b, row_index, col_index); if !ok || grid[r][c] != to_find continue; part2 += 1; } part2 /= 2; return part1, part2; }
Undisclosed · 2024-12-13 16:53
>>40664 >How would you do parallel iteration (zip in Python, foreach in Tcl, `(iter (for ...) (for ...)`) in Lisp)?
Case · 2024-12-14 01:24
Powering through... Completed Day 12. haskell type Pos = (Int,Int) type Grid = Array Pos Char inBounds pos grid = inRange (bounds grid) pos sameType grid pos1 pos2 = maybe False id (liftM2 (==) (grid `at` pos1) (grid `at` pos2)) neighbours :: Pos -> [Pos] neighbours (r,c) = [(r+1,c), (r-1,c), (r,c+1), (r,c-1)] sameNeighbours :: Pos -> Grid -> [Pos] sameNeighbours pos grid = filter (sameType grid pos) (neighbours pos) isAdjacent :: Pos -> Pos -> Bool isAdjacent (r1,c1) (r2,c2) = ((r1 == r2) && (1 == (abs (c1 - c2)))) || ((c1 == c2) && (1 == (abs (r1 - r2)))) -- aka Region type Shape = [Pos] area shape _ = length shape perim :: Shape -> Grid -> Int perim shape grid = sum $ map countForeign shape where countForeign pos = length $ filter (not . (sameType grid pos)) (neighbours pos) sides :: Shape -> Grid -> [Pos] sides shp grid = concatMap id $ zipWith ownEdges pairs (drop 1 (tails pairs)) where pairs = map (\pos -> (pos, edges pos)) shp edges pos = filter (not . (sameType grid pos)) (neighbours pos) ownEdges (pos, es) rest = filter (not . (isShared adjRest)) es where adjRest = filter (isAdjacent pos . fst) rest isShared rest e = isJust $ find (isAdjacent e) (concatMap snd rest) countSides = (length .) . sides findShapes :: Grid -> [Shape] -- Assign a shape ID to each position, then group positions into shapes by ID. -- Convoluted, but it works. findShapes grid = map snd $ Map.toList $ Map.fromListWith (++) (map (\(pos, s) -> (s, [pos])) (Map.assocs table)) -- table of (position, shape id) where table = h (drop 1 (indices grid)) (Set.singleton (head (indices grid))) 0 Map.empty h [] shp i tbl = tbl h poss@(posH:posT) shp i tbl = case filter (\x -> Set.notMember x shp) (concatMap (\x -> sameNeighbours x grid) (Set.toList shp)) of [] -> case dropWhile (\x -> Map.member x tbl) poss of [] -> tbl' (posH':posT') -> h posT' (Set.singleton posH') (i+1) tbl' nbrs -> h poss (Set.union shp (Set.fromList nbrs)) i tbl where tbl' = Map.union tbl (Map.fromSet (const i) shp) readGrid :: String -> Grid readGrid s = listArray ((1,1), (h,w)) (concat (lines s)) where h = length (lines s) w = length (head (lines s)) part1' s = sum $ map (\s -> (area s grid) * (perim s grid)) shapes where shapes = findShapes grid grid = readGrid s part2' s = sum $ map (\s -> (area s grid) * (countSides s grid)) shapes where shapes = findShapes grid grid = readGrid s
Hacker · 2024-12-14 08:09
Day 14, was fun trying to find an image without knowing its appearance. (in-package #:q3cpma-user) (defun parse-robot (line) (list:nchunk 2 (mapcar #'parse-integer (cl-ppcre:all-matches-as-strings "-?\\d+" line)))) (defparameter *robots* (mapcar #'parse-robot (uiop:read-file-lines *input-path*))) (define-constant +room-dimensions+ '(101 103) :test 'equal) (define-constant +quadrant-borders+ (mapcar #λ(truncate _ 2) +room-dimensions+) :test 'equal) (defun move (robot) (rplaca robot (mapcar #λ(mod (+ _1 _2) _3) (car robot) (cadr robot) +room-dimensions+))) (defun simulate (stop-pred) (iter (with robots-copy = (copy-tree *robots*)) (for cycle from 0) (until (funcall stop-pred cycle robots-copy)) (mapc #'move robots-copy) (finally (return robots-copy)))) (defun which-quadrant (robot) (match (mapcar #λ(signum (- _1 _2)) (car robot) +quadrant-borders+) ((list -1 -1) :NW) ((list 1 -1) :NE) ((list 1 1) :SE) ((list -1 1) :SW))) (defun safety-factor (robots) (reduce #'* (let ((res (seq:classify robots #'which-quadrant))) (remhash nil res) (hash-table:values res)) :key #'length)) (defun christmas-tree-p (cycle robots) (flet ((print-room (robots) (array:print-matrix (let ((canvas (make-array +room-dimensions+ :element-type 'standard-char :initial-element #\Space))) (mapc #λ(setf (apply #'aref canvas (car _)) #\*) robots) canvas)))) (iter (with ytally = (make-array (car +room-dimensions+) :element-type 'fixnum :initial-element 0)) (with xtally = (make-array (cadr +room-dimensions+) :element-type 'fixnum :initial-element 0)) (for ((x y) nil nil) in robots) (incf (aref ytally x)) (incf (aref xtally y)) (finally (when (and (> (standard-deviation ytally) 5) (> (standard-deviation xtally) 5)) (print-room robots) (when (y-or-n-p "You seein' a Christmas tree there?") (format t "Part 2: ~A~%" cycle) (return t))))))) (format t "Part 1: ~A~%" (safety-factor (simulate #2λ(<= 100 _1)))) (simulate #'christmas-tree-p) >>40667 Thanks for answering my question. >>Does it have keyword parameters? >yes Wow, that's good and quite rare in low-level languages! We're talking about the same thing, right? Optional, named at call-site and possibly order insensitive? >>What about metaprogramming? >...Any code passed through a message can also be arbitrarily rewritten on the syntax tree level. Hopefully this answers the question. Huh. Will probably have to see what a procedural macro looks like, when it becomes public. Sounds pretty cool, like this. >>How would you do parallel iteration (zip in Python, foreach in Tcl, `(iter (for ...) (for ...)`) in Lisp)? >There is nothing built in or in the standard modules, but it would be trivial to implement with the current macro system. That's a minus to me, having to reinvent the wheel ensures nobody will speak the same language until a library to implement better iteration becomes the overwhelming consensus. And then you're depending on an extension that should have been standard. >>Pattern matching? >Nope. But again as long as something parses as jai code, you can introspect and rewrite the syntax tree arbitrarily so writing macros for pattern matching tagged unions or structures can be implemented. I have implemented such pattern matching macros myself without too much fuss. Well yeah, but proper, optimized pattern matching is a bit more involved than that: it's basically writing a compiler (cf https://github.com/guicho271828/trivia/tree/master/balland2006). >>Does it need a ternary (i.e. does `var = if true 1 else 2` work) or does it have one? >There is very much a delineation between statements and expressions if that's what you're asking, and there is a ternary operator ifx (you can see an example of it in my day 3 implementation) Well, I'll go see elsewhere. One of the worst mistakes of C inspired languages (including Python), in my opinion. Hope it won't turn out to be just another Go/Hare. >>40674 Really love how the terseness of function declarations encourage you to name even the smallest one. Really improve readability over lambdas everywhere.
limited · 2024-12-14 13:10
>>40676 > We're talking about the same thing, right? Optional, named at call-site and possibly order insensitive? Yup, explicitly named parameters can be in any order and there are default arguments. > That's a minus to me, having to reinvent the wheel ensures nobody will speak the same language until a library to implement better iteration becomes the overwhelming consensus. And then you're depending on an extension that should have been standard. Going to have to side with >>40669 here. I've never felt compelled to reach for complex iterators even in languages that have them built in when basic looping constructs can do the job. > Well yeah, but proper, optimized pattern matching is a bit more involved than that: it's basically writing a compiler That may be true, but the point is that the metaprogramming system allows you inspect and modify the program in a way that makes all of the things lispers love so much like pattern matching and object systems doable with enough elbow grease. > One of the worst mistakes of C inspired languages (including Python), in my opinion. Eh, the creator has mentioned potentially switching to be purely expression based, but since a lot of the issues that come from the distinction in C are already fixed so it isn't high priority atm. You can still have a block that evaluates to an expression if you do something like x := ifx true { foo(); bar(); baz;() } runs all of the procedures and returns the result of baz to the expression. A lot of the syntax jank has accrued over the years with the justification that there will be a "major syntax overhaul" at some point in the future. Purely expression based syntaxes are nice, but it's such a minor thing to me I can't be bothered to care when the underlying semantics are so good. And now for days 5 and 6. If all goes according to plan I will be fully caught up tomorrow (just finished day 12) // input allocated 16132 bytes // output allocated 34944 bytes // output executed 3.794 million cycles day5 :: (input: string) -> (part1: int, part2: int) { using Basic; using String; using Hash_Table; order: Table(int, [..]int); rhs := input; while true { ok:, line:, rhs = split_line(rhs); assert(ok); if !line break; a:, ok, line = string_to_int(line); assert(ok); advance(*line); // the bar b:, ok, line = string_to_int(line); assert(ok); ptr := table_find_pointer(*order, a); dumb: [..]int; if !ptr then ptr = table_add(*order, a, dumb); array_add(ptr, b); } check :: (order: *Table(int, [..]int), before: []int, value: int) -> bool, int { should_be_later, found := table_find(order, value); if found for should_be_later { failed, index := array_find(before, it); if failed return false, index; } return true, -1; } part1 := 0; part2 := 0; update: [..]int; while true { ok:, line:, rhs = split_line(rhs); if !ok break; assert(xx line); array_reset_keeping_memory(*update); correct := true; while true { a:, ok, line = string_to_int(line); if !ok then break; if line then advance(*line); // the comma correct &&= check(*order, update, a); array_add(*update, a); } if correct { assert(xx update.count % 2 == 1); part1 += update[update.count / 2]; } else { before: []int = update; keep_going := true; while keep_going { keep_going = false; for < _, it_index: update { before.count = it_index; ok, index := check(*order, before, update[it_index]); if !ok { keep_going = true; update[it_index], update[index] = swap(update[it_index], update[index]); } } } assert(xx update.count % 2 == 1); part2 += update[update.count / 2]; } } return part1, part2; } // input allocated 17030 bytes // output allocated 998792 bytes // output executed 1746.391 million cycles day6 :: (input: string) -> (part1: int, part2: int) { using Basic; using Hash_Table; Tile :: enum u8 { PATH; BLOCK; }; Direction :: enum { UP; RIGHT; DOWN; LEFT; } Position :: struct { row: u32; col: u32; }; rotate :: (dir: Direction) -> Direction { return xx ((cast(int) dir + 1) % 4); } get :: (using pos: Position) -> *Tile #expand { assert(row >= 0 && row < grid_rows); assert(col >= 0 && col < grid_cols); return *grid[row*grid_cols + col]; } move :: (dir: Direction, using pos: Position) -> (ok: bool, new_pos: Position) #expand { if #complete dir == { case .UP; if row then return true, .{row-1, col}; else return false, .{row, col}; case .LEFT; if col then return true, .{row, col-1}; else return false, .{row, col}; case .DOWN; if row < grid_rows-1 then return true, .{row+1, col}; else return false, .{row, col}; case .RIGHT; if col < grid_cols-1 then return true, .{row, col+1}; else return false, .{row, col}; } } guard: struct { dir: Direction; pos: Position; } initial_guard: type_of(guard); grid_cols: u32; grid_rows: u32; rhs := input; while true { ok:, line:, rhs = split_line(rhs); if !ok break; assert(xx line); grid_rows += 1; if grid_cols then assert(grid_cols == line.count); else grid_cols = xx line.count; } grid := NewArray(grid_rows*grid_cols, Tile, initialized = false); rhs = input; i := cast(u32) 0; for input { if it == { case #char "^"; using initial_guard; pos.row, pos.col, dir = i / grid_cols, i % grid_cols, Direction.UP; #through; case #char "."; grid[i] = .PATH; case #char "#"; grid[i] = .BLOCK; case #char "\n"; continue; case; crash("`%`", string.{1, *it}); } i += 1; } occupied: Table(Key_Type = Position, Value_Type = void, hash_bytes, comp_bytes); guard = initial_guard; while true { using guard; if !table_find_pointer(*occupied, pos) then table_add(*occupied, pos, the_void); ok, new_pos := move(dir, pos); if !ok break; if #complete get(new_pos).* == { case .BLOCK; dir = rotate(dir); case .PATH; pos = new_pos; } } part1 := occupied.count; path: Table(type_of(guard), void, hash_bytes, comp_bytes); part2 := 0; i = 0; max_turns := 0; for _, it: occupied { i += 1; old_value := get(it).*; assert(old_value == .PATH); get(it).* = .BLOCK; defer get(it).* = old_value; table_reset(*path); guard = initial_guard; while true { using guard; if table_find_pointer(*path, guard) { part2 += 1; break; } table_add(*path, guard, the_void); ok, new_pos := move(dir, pos); if !ok break; if #complete get(new_pos).* == { case .BLOCK; dir = rotate(dir); case .PATH; pos = new_pos; } } } return part1, part2; }
list · 2024-12-15 02:24
Day 13 completed. Moving on to the next one. haskell mapBoth f (x,y) = (f x, f y) type Button = (Integer,Integer) -- (x,y) type Prize = (Integer,Integer) -- (x,y) data Machine = Machine { buttonA :: Button, buttonB :: Button, prize :: Prize } deriving Show inputParser = separatedByParser (stringParser "\n\n") machineParser machineParser = do btnA <- (buttonParser 'A') charParser '\n' btnB <- (buttonParser 'B') charParser '\n' prize <- prizeParser return (Machine btnA btnB prize) buttonParser c = do stringParser "Button " charParser c stringParser ": X+" x <- integerParser stringParser ", Y+" y <- integerParser return (x,y) prizeParser = do stringParser "Prize: X=" x <- integerParser stringParser ", Y=" y <- integerParser return (x,y) parseInput = parseValue inputParser type Point = (Rational,Rational) -- y = ax + c type Line = (Rational,Rational) -- (a,c), i.e. (slope, intercept) -- Get the numerator if denominator is 1. onlyNumerator :: Rational -> Maybe Integer onlyNumerator r | (denominator r) == 1 = Just (numerator r) | otherwise = Nothing intersect :: Line -> Line -> Maybe Point intersect (a,c) (b,d) | a == b = Nothing -- parallel | otherwise = Just (x,y) where x = (d - c) / (a - b) y = a * x + c play :: (Integer -> Integer -> Bool) -> Machine -> Maybe (Integer,Integer) play pred (Machine (xa,ya) (xb,yb) (xp,yp)) = (intersect lineX lineY) >>= fin where lineX = mkLine xa xb xp lineY = mkLine ya yb yp fin (a,b) = liftM2 (,) (onlyNumerator a) (onlyNumerator b) >>= (\(a,b) -> if pred a b then Just (a,b) else Nothing) -- (xk + yk + c) => (y = ax + c) mkLine xk yk c = (negate (xk % yk), c % yk) cost as bs = as*3 + bs part1' s = sum $ map (uncurry cost) $ mapMaybe (play checkMaxPresses) (parseInput s) where checkMaxPresses a b = a <= 100 && b <= 100 part1 = part1' <$> readInput 13 part2' s = sum $ map (uncurry cost) $ mapMaybe (play unlimitedPresses) (map (mapPrize (mapBoth (+ 10000000000000))) machines) where machines = parseInput s mapPrize f m = m { prize = f (prize m) } unlimitedPresses _ _ = True part2 = part2' <$> readInput 13 >>40666 Having spent much more time programming in Lisp than in Haskell, I sometimes write incorrect Haskell when operators are involved. In this regard, I appreciate the value of S-expressions. The same could be said about indentation, as I still haven't gotten used to Haskell's.
over · 2024-12-15 04:36
day 15, python i decided to rotate the grid on each step so that i only need to consider the case where the robot moves right plus some logic for vertical or horizontal for the vertical case i do a dfs and check if all leaf nodes can move; if so, then i dfs again and shift each node by 1 had to use pen and paper to check how rotations affect joining '[' and ']' characters, but otherwise a fairly straightforward day ''' > Measure-Command { cat .\input.txt | python .\aoc.py | Out-Default } 1487337 1521952 Days : 0 Hours : 0 Minutes : 0 Seconds : 7 Milliseconds : 69 Ticks : 70690900 TotalDays : 8.18181712962963E-05 TotalHours : 0.00196363611111111 TotalMinutes : 0.117818166666667 TotalSeconds : 7.06909 TotalMilliseconds : 7069.09 ''' def pos(g: list) -> tuple: for r in range(len(g)): for c in range(len(g[0])): if g[r][c] == '@': return (r, c) def rot(g: list, n: int) -> list: if not n: return g g = [list(x[::-1]) for x in zip(*g)] return rot(g, n - 1) def horizontal(g: list, p: tuple) -> None: (r, c) = p tmp = '.' + g[r][c] for cc in range(c + 1, len(g[0])): match x := g[r][cc]: case 'O' | '[' | ']': tmp += x continue case '#': return for i in range(len(tmp)): g[r][c + i] = tmp[i] break def vertical(g: list, p: tuple, mov: chr) -> None: (r, c) = p def dfs(r: int, c: int, safe: bool) -> bool: cc = c + 1 match g[r][cc]: case '#': ret = False case '.': ret = True case '[': ret = (dfs(r, cc, safe) and dfs(r + 1 if mov == '^' else r - 1, cc, safe)) case ']': ret = (dfs(r, cc, safe) and dfs(r - 1 if mov == '^' else r + 1, cc, safe)) if safe: g[r][cc] = g[r][c] g[r][c] = '.' return ret return dfs(r, c, dfs(r, c, False)) warehouse, movements = open(0).read().strip().split('\n\n') p1map = [list(x) for x in warehouse.split('\n')] p2map = [list(''.join(['[]' if y == 'O' else '@.' if y == '@' else y + y for y in x])) for x in warehouse.split('\n')] movs = movements.replace('\n', '') for i, mov in enumerate(movs): p1map = rot(p1map, '>^<v'.index(mov)) p2map = rot(p2map, '>^<v'.index(mov)) horizontal(p1map, pos(p1map)) p = pos(p2map) horizontal(p2map, p) if mov in "<>" else vertical(p2map, p, mov) p1map = rot(p1map, '>v<^'.index(mov)) p2map = rot(p2map, '>v<^'.index(mov)) gps = lambda g: sum([sum([r * 100 + c for c in range(len(g[0])) if g[r][c] in 'O[']) for r in range(len(g))]) print(gps(p1map)) print(gps(p2map))
Boole · 2024-12-15 04:52
>>40699 here's my input if anyone wants to debug using my answers: https://0x0.st/XFJV.txt
Sipho Nkosi · 2024-12-15 07:01
>>40699 >>40700 golfed a, b = open(0).read().strip().split('\n\n') a = a.split('\n') p1map = list(map(list, a)) p2map = [list(''.join(['[]' if y == 'O' else '@.' if y == '@' else y * 2 for y in x])) for x in a] pos = lambda g: [(r, c) for r, x in enumerate(g) for c, y in enumerate(x) if y == '@'][0] gps = lambda g: sum(r*100 + c for r, x in enumerate(g) for c, y in enumerate(x) if y in 'O[') rot = lambda g, n: rot([list(x[::-1]) for x in zip(*g)], n - 1) if n else g def horiz(g, r, c): tmp = '.' + g[r][c] for cc in range(c + 1, len(g[0])): match x := g[r][cc]: case 'O' | '[' | ']': tmp += x continue case '#': return for i in range(len(tmp)): g[r][c + i] = tmp[i] break def verti(g, r, c, mov): def dfs(r, c, safe): cc = c + 1 match g[r][cc]: case '#': ret = 0 case '.': ret = 1 case '[': ret = dfs(r, cc, safe) and dfs(r + 1 if mov == '^' else r - 1, cc, safe) case ']': ret = dfs(r, cc, safe) and dfs(r - 1 if mov == '^' else r + 1, cc, safe) if safe: g[r][cc] = g[r][c] g[r][c] = '.' return ret dfs(r, c, dfs(r, c, 0)) for mov in b.replace('\n', ''): p1map = rot(p1map, '>^<v'.index(mov)) p2map = rot(p2map, '>^<v'.index(mov)) r, c = pos(p1map) horiz(p1map, r, c) r, c = pos(p2map) horiz(p2map, r, c) if mov in '<>' else verti(p2map, r, c, mov) p1map = rot(p1map, '>v<^'.index(mov)) p2map = rot(p2map, '>v<^'.index(mov)) print(gps(p1map), gps(p2map))
x<->y · 2024-12-15 09:11
Lost too much time on a stupid mistake (realpart -> imagpart) that made me watch like a few hundred frames of retarded robot moving boxes. But well, what would be the AoC without hair-pulling? (in-package #:q3cpma-user) (load (make-pathname :defaults *load-truename* :name "../grid")) (defparameter *map* nil) (defparameter *moves* nil) (destructuring-bind (s1 s2) (str:split #"\n\n" (uiop:read-file-string *input-path*)) (setf *map* (grid-read s1 :type 'standard-char) *moves* (hash-table:keys-to-values (coerce (delete #\Newline s2) 'list) (hash-table:pair '(#\^ #\> #\v #\<) +orth-directions+)))) (defun move-cell (map from to) (rotatef (grid-ref map from) (grid-ref map to)) t) ;; Signals a successful move to simulate (defun execute-move-p1 (pos dir map) (case (grid-ref map pos) (#\. t) (#\# nil) (t (when (execute-move-p1 (+ pos dir) dir map) (move-cell map pos (+ pos dir)))))) ;; Can't vertically move boxes like in part 1 (while unwinding the recursive call stack), do a full ;; check _then_ move the visited cells starting from the furthest ones (defun execute-move-p2-vert (pos dir map) (let ((visited)) (labels ((move-possible-p (pos) (case (grid-ref map pos) (#\. t) (#\# nil) (t (let ((other-half (+ pos (case (grid-ref map pos) (#\[ #c(0 1)) (#\] #c(0 -1)))))) (pushnew pos visited) (pushnew other-half visited) (and (move-possible-p (+ pos dir)) (move-possible-p (+ other-half dir)))))))) (case (grid-ref map pos dir) (#\. (move-cell map pos (+ pos dir))) ((#\[ #\]) (when (move-possible-p (+ pos dir)) (misc:sortf visited (if (= dir #c(-1 0)) #'< #'>) :key #'realpart) (mapc #λ(move-cell map _ (+ _ dir)) visited) (move-cell map pos (+ pos dir)))))))) (defun execute-move-p2 (pos dir map) (if (/= (realpart dir) 0) (execute-move-p2-vert pos dir map) (execute-move-p1 pos dir map))) (defun simulate (map move-fun) (iter (with map-copy = (copy-array map)) (with pos = (grid-position #\@ map-copy)) (for move in *moves*) (when (funcall move-fun pos move map-copy) (incf pos move)) (finally (return map-copy)))) (defun gps-score (map) (iter _ (for y below (array-dimension map 0)) (iter (for x below (array-dimension map 1)) (when (member (aref map y x) '(#\O #\[)) (in _ (sum (+ (* 100 y) x))))))) (defun widen-map (map) (make-array (list (array-dimension map 0) (* (array-dimension map 1) 2)) :element-type 'standard-char :initial-contents (mapcar #λ(mapcan #λ(case _ (#\# (list #\# #\#)) (#\O (list #\[ #\])) (#\. (list #\. #\.)) (#\@ (list #\@ #\.))) (coerce _ 'list)) (grid-rows map)))) (format t "Part 1: ~A~%" (gps-score (simulate *map* #'execute-move-p1))) (format t "Part 2: ~A~%" (gps-score (simulate (widen-map *map*) #'execute-move-p2))) The good thing is that it gives me ideas for my utils' TODO. Here, I truly lament CL's poor support for multidimensional arrays (well, better than other languages that must vectors of vectors, I guess) and plan on writing me a way to generate a list of coordinates to map/loop over and some slicing functions. >>40698 Since you seem to know a bit of both worlds, did you look at Idris 2? From an outsider PoV, it seems like a cleaner Haskell in some regards (minus the laziness). >>40702 Definitely cool if convoluted approach!
Boole · 2024-12-15 20:59
// input allocated 24776 bytes // output allocated 208 bytes // output executed 56340.850 million cycles day7 :: (input: string) -> (part1: int, part2: int) { using Basic; using String; using Math; rhs := input; values: [..]int; part1 := 0; part2 := 0; sb: String_Builder; while true { ok:, line:, rhs = split_line(rhs); if !ok break; assert(xx line); result:, ok, line = string_to_int(line); advance(*line); // colon array_reset_keeping_memory(*values); while true { val:, ok, line = string_to_int(line); if !ok break; array_add(*values, val); } ops_needed := values.count-1; permutations := cast(int) pow(3, xx ops_needed); for i: 0..permutations-1 { acc := values[0]; used_concat := false; for j: 0..ops_needed-1 { value := values[j+1]; if (i / cast(int)pow(3, xx j)) % 3 == { case 0; acc *= value; case 1; acc += value; case 2; { used_concat = true; print(*sb, "%0%", acc, value); mark := get_temporary_storage_mark(); acc = string_to_int(builder_to_string(*sb)); set_temporary_storage_mark(mark); } } } if acc == result { if !used_concat then part1 += result; part2 += result; break; } } } return part1, part2; } // input allocated 2550 bytes // output allocated 48768 bytes // output executed 9.019 million cycles day8 :: (input: string) -> (part1: int, part2: int) { using Basic; using Hash_Table; Position :: struct { row, col: s32; }; grid_size: Position; rhs := input; while true { ok:, line:, rhs = split_line(rhs); if !ok break; assert(xx line); if grid_size.col then assert(grid_size.col == line.count); else grid_size.col = xx line.count; grid_size.row += 1; } get :: (using pos: Position) -> *u8 #expand { assert(row >= 0 && row < grid_size.row, "row % is out of bounds", row); assert(col >= 0 && col < grid_size.col, "col % is out of bounds", col); return *input[row * (grid_size.row+1) + col]; } antinodes: Table(Position, void, hash_bytes, comp_bytes); for row: 0..grid_size.row-1 for col: 0..grid_size.col-1 { chr := get(.{row, col}).*; if chr == #char "." continue; for test_row: 0..grid_size.row-1 for test_col: 0..grid_size.col-1 if get(.{test_row, test_col}).* == chr && (row != test_row) && (col != test_col) { delta := Position.{row-test_row, col-test_col}; new_pos := Position.{row+delta.row, col+delta.col}; if !table_find_pointer(*antinodes, new_pos) && new_pos.row >= 0 && new_pos.row < grid_size.row && new_pos.col >= 0 && new_pos.col < grid_size.col then table_add(*antinodes, new_pos, the_void); } } part1 := antinodes.count; table_reset(*antinodes); for row: 0..grid_size.row-1 for col: 0..grid_size.col-1 { ptr := get(.{row, col}); chr := ptr.*; if chr == #char "." continue; for test_row: 0..grid_size.row-1 for test_col: 0..grid_size.col-1 if get(.{test_row, test_col}).* == chr && (row != test_row) && (col != test_col) { delta := Position.{row-test_row, col-test_col}; space := Position.{test_row, test_col}; while true { space.row += delta.row; space.col += delta.col; if space.row < 0 || space.row >= grid_size.row || space.col < 0 || space.col >= grid_size.col then break; if !table_find_pointer(*antinodes, space) then table_add(*antinodes, space, the_void); } space = Position.{test_row, test_col}; while true { // subtract delta space.row -= delta.row; space.col -= delta.col; if space.row < 0 || space.row >= grid_size.row || space.col < 0 || space.col >= grid_size.col then break; if !table_find_pointer(*antinodes, space) then table_add(*antinodes, space, the_void); } } } part2 := antinodes.count; return part1, part2; }
Crash Override · 2024-12-15 23:56
Good lord... Day 14 part 2 was unusual. It _was not_ fun trying to find an image without knowing its appearance, but the reward was definitely felt at the end. I initially thought that the image of a christmas tree appears when all points on the middle lines (horizontal and vertical) are occupied by robots, so I tried searching for such a configuration but that yielded on results. Today, I tried the visual approach: wrote thousands of grid images to a file and searched for many 'x' characters in a row (I represented robots with 'x'), which finally led me to the image of that christmas tree. haskell type Robot = (Position, Velocity) type Position = (Integer,Integer) type Velocity = (Integer,Integer) robotParser = do stringParser "p=" x <- integerParser charParser ',' y <- integerParser stringParser " v=" vx <- integerParser charParser ',' vy <- integerParser return ((x,y), (vx,vy)) inputParser = allParser robotParser parseInput = parseValue inputParser simulate :: (Integer,Integer) -> [Robot] -> [[Position]] simulate dims robs = map (\n -> map ((wrap dims) . fst . (move n)) robs) [0..] move :: Integer -> Robot -> Robot move n ((x,y), (vx,vy)) = ((x + (vx*n), y + (vy*n)), (vx,vy)) wrap :: (Integer,Integer) -> Position -> Position wrap (h,w) (x,y) = (x `mod` w, y `mod` h) quadrants (h,w) poss = groupingBy fst $ mapMaybe (\pos -> (,pos) <$> quadrant (h,w) pos) poss groupingBy :: (Ord b, Eq a) => (a -> b) -> [a] -> [[a]] groupingBy f = groupOn f . (sortOn f) quadrant (h,w) (x,y) = if x < (w `div` 2) && y < (h `div` 2) then Just "UL" else if x < (w `div` 2) && y > (h `div` 2) then Just "DL" else if x > (w `div` 2) && y > (h `div` 2) then Just "DR" else if x > (w `div` 2) && y < (h `div` 2) then Just "UR" else Nothing safetyFactor (h,w) n robs = product $ map length $ quadrants (h,w) $ (simulate (h,w) robs) !! n mkGrid (h,w) poss = accum (\_ x -> x) (listArray ((0,0), (w-1,h-1)) (repeat '.')) (map (,'x') poss) rows grid = sizedGroups (fromInteger w) (elems grid) where (_, (_,w)) = bounds grid draw :: (Integer,Integer) -> [Position] -> String draw dims poss = unlines . rows $ mkGrid dims poss -- 230172768 part1 = part1' <$> readInput 14 part1' s = product $ map length $ quadrants (103,101) $ (simulate (103,101) (parseInput s)) !! 100 -- 8084 part2 = readInput 14 >>= part2' part2' s = sequence_ $ map ((appendFile "/tmp/day14.out") . draw1) $ zipWith (,) [0..] sims where robs = parseInput s sims = simulate (103,101) robs draw1 (i,poss) = ("Step " ++ show i ++ ":\n") ++ (draw (103,101) poss) ++ "\n" >>40703 I have not looked at Idris in detail, only watched some tech talks on it (some by Edwin Brady). I understand that it offers powerful facilities for type-level computation, which interests me, so I may explore it in the future.
Motoko Kusanagi · 2024-12-16 01:22
>>40706 nice consistency , are you also going to use prolog ?
Cypher · 2024-12-16 05:11
everyone is doing Dijkstra's for today, but dfs is enough: import math import sys sys.setrecursionlimit(int(1e9)) g = list(map(list, open(0).read().split('\n'))); g.pop() rows, cols = len(g), len(g[0]) cache = [[math.inf] * cols for _ in range(rows)] m = {} def dfs(pos, d, cost): if pos == (1, cols - 2): return cost (r, c) = pos cache[r][c] = min(cache[r][c], cost) dx = ((-1, 0), (0, +1), (+1, 0), (0, -1)) minimum = math.inf # continue (dr, dc) = dx[d] (rr, cc) = (r + dr, c + dc) if g[rr][cc] in '.E' and cache[rr][cc] > cost - 1e3: minimum = min(minimum, dfs((rr, cc), d, cost + 1)) # turn left and continue (dr, dc) = dx[tmp := d-1 & 3] (rr, cc) = (r + dr, c + dc) if g[rr][cc] in '.E' and cache[rr][cc] > cost: minimum = min(minimum, dfs((rr, cc), tmp, cost + 1001)) # turn right and continue (dr, dc) = dx[tmp := d+1 & 3] (rr, cc) = (r + dr, c + dc) if g[rr][cc] in '.E' and cache[rr][cc] > cost: minimum = min(minimum, dfs((rr, cc), tmp, cost + 1001)) if minimum not in m: m[minimum] = set() m[minimum].add((r, c)) return minimum p2 = len(m[p1 := dfs((rows - 2, 1), 1, 0)]) + 1 print(p1, p2)
dup · 2024-12-16 10:48
>>40709 Thanks anon, didn't want to do this one, but I hate being one day late... so I just converted yours =) ;; Translated from Anon https://lainchan.org/%CE%BB/res/40517.html#q40709 ;; because I'm tired and hate these path finding problems (in-package #:q3cpma-user) (load (make-pathname :defaults *load-truename* :name "../grid")) (defparameter *map* (grid-read *input-path* :type 'standard-char)) (defparameter *start* (grid-position #\S *map*)) (defparameter *end* (grid-position #\E *map*)) (defun turn-left (dir) (* dir #c(0 -1))) (defun turn-right (dir) (* dir #c(0 1))) (defparameter *cache* (make-hash-table)) (defparameter *mintiles* (make-hash-table)) (defun pathfind (pos dir cost) (if (= pos *end*) cost (macrolet ((lookup (pos) `(gethash ,pos *cache* most-positive-fixnum))) (minf (lookup pos) cost) (let ((min (apply #'min most-positive-fixnum ;; Dead-end => no available path (delete nil (mapcar #λ(and (char/= (grid-ref *map* pos _1) #\#) (> (lookup (+ pos _1)) (- (+ cost _2) 1000)) (pathfind (+ pos _1) _1 (+ 1 _2 cost))) `(,dir ,(turn-left dir) ,(turn-right dir)) '(0 1000 1000)))))) (setf (gethash pos (ensure-gethash min *mintiles* (make-hash-table))) t) min)))) (let ((mincost (pathfind *start* #c(0 1) #|East|# 0))) (format t "Part 1: ~A~%" mincost) (format t "Part 2: ~A~%" (+ (hash-table-count (gethash mincost *mintiles*)) 1))) You can use `read().rstrip()` instead of popping the last line and changing `in 'E.'` into `!= '#'` improves performance a little bit (250~500 ms here). Bonus, performance comparison: > lscpu | sed -n 's/ *Model name: *//p'; python -VV; sbcl --version AMD Ryzen 9 5900X 12-Core Processor Python 3.12.8 (main, Dec 13 2024, 13:16:08) [GCC 13.3.1 20241024] SBCL 2.4.11 > time python3 16/prog.py <16/input 102504 535 python3 16/prog.py < 16/input 5.96s user 0.75s system 99% cpu 6.733 total > time sbcl --load rc --script 16/prog.lisp 16/input Part 1: 102504 Part 2: 535 sbcl --noinform --load rc --script 16/prog.lisp 16/input 2.83s user 0.10s system 99% cpu 2.935 total
Wintermute · 2024-12-16 10:56
>>40714 >You can use `read().rstrip()` instead of popping the last line and changing `in 'E.'` into `!= '#'` improves performance a little bit cheers the biggest tank in performance is not having a separate cache graph for each direction; anything within ~1e3 cost is visited again, even if it really shouldn't. this is a naïve way of accounting for paths which arrive at the same cell from different directions
Pris · 2024-12-16 18:39
// input allocated 20000 bytes // output allocated 598080 bytes // output executed 15372.962 million cycles day9 :: (input: string) -> (part1: int, part2: int) { using Basic; files : [..]u8; array_reserve(*files, input.count/2); file_blocks, block_index := 0; for chr: cast([]u8) input { num := chr - #char "0"; if it_index % 2 == 0 { file_blocks += num; array_add(*files, num); } block_index += num; } number_of_blocks := block_index+1; part1, file_id, block_id := 0; for chr: cast([]u8) input { num := chr - #char "0"; if it_index % 2 == 0 { for 1..num { part1 += block_id * file_id; block_id += 1; if block_id >= file_blocks break chr; } file_id += 1; } else { for 1..num { file_id := files.count-1; part1 += block_id * file_id; block_id += 1; files[file_id] -= 1; if !files[file_id] then files.count -= 1; if !files break chr; if block_id >= file_blocks break chr; } } } reset_temporary_storage(); // don't need the part1 array anymore Span_Data :: struct { type : enum u16 { FILE; SPACE; }; file_id : s16; start : s16; width : s16; } Span :: struct { using data: Span_Data; next, prev: *Span; } append :: (old_end: *Span, data: Span_Data) -> (new_end: *Span) { new := New(Span); if old_end then old_end.next = new; new.data = data; new.prev = old_end; new.next = null; return new; } move :: (span: *Span, next: *Span) { new_span := New(Span); new_span.data = span.data; span.type = .SPACE; new_span.next = next; new_span.prev = next.prev; next.prev.next = new_span; next.prev = new_span; } last_span, first_span: *Span; file_id = 0; block_id = 0; for chr: cast([]u8) input { num := chr - #char "0"; if it_index % 2 == 0 { last_span = append(last_span, .{.FILE, xx file_id, xx block_id, num}); file_id += 1; } else { last_span = append(last_span, .{.SPACE, xx (file_id-1), xx block_id, num}); } if !first_span first_span = last_span; block_id += num; } file_to_move := last_span; while main_loop := file_to_move { defer file_to_move = file_to_move.prev; if file_to_move.type != .FILE then continue; gap_to_fill := first_span; while true { if !gap_to_fill break; if gap_to_fill == file_to_move then continue main_loop; defer gap_to_fill = gap_to_fill.next; if gap_to_fill.type != .SPACE || gap_to_fill.width < file_to_move.width then continue; gap_to_fill.start += file_to_move.width; gap_to_fill.width -= file_to_move.width; if file_to_move == last_span then last_span = last_span.prev; if file_to_move == first_span then first_span = first_span.next; move(file_to_move, gap_to_fill); gap_to_fill = first_span; file_to_move = last_span; assert(xx gap_to_fill); assert(xx file_to_move); continue main_loop; } } part2 := 0; block_id = 0; span := first_span; while span { defer span = span.next; if span.type == .FILE for 1..span.width { part2 += block_id * span.file_id; block_id += 1; } else { block_id += span.width; } } return part1, part2; } // input allocated 2070 bytes // output allocated 384 bytes // output executed 1.010 million cycles day10 :: (input: string) -> (part1: int, part2: int) { using Basic; using Hash_Table; Position :: struct { row, col: s32; } grid_size: Position; rhs := input; while true { ok:, line:, rhs = split_line(rhs); if !ok break; assert(xx line); grid_size.col = xx line.count; grid_size.row += 1; } Cache :: Table(Position, void, hash_bytes, comp_bytes); walk :: (using start: Position, expected: u8, grid_data: string, grid_size: Position, trail_ends: *Cache) -> int { if row < 0 || row >= grid_size.row || col < 0 || col >= grid_size.col then return 0; chr := grid_data[row*(grid_size.col+1) + col]; // grid_size.col+1 to account for newlines in input if chr != expected return 0; if chr == #char "9" { if !trail_ends return 1; if table_find_pointer(trail_ends, start) return 0; table_add(trail_ends, start, the_void); return 1; } acc := 0; for Position.[ .{row-1, col}, // up .{row+1, col}, // down .{row, col-1}, // left .{row, col+1} // right ] acc += walk(it, expected + 1, grid_data, grid_size, trail_ends); return acc; } trail_ends: Cache; part1, part2 := 0; for row: 0..grid_size.row-1 for col: 0..grid_size.col-1 { start, zero := Position.{row, col}, cast(u8) #char "0"; table_reset(*trail_ends); part1 += walk(start, zero, input, grid_size, *trail_ends); part2 += walk(start, zero, input, grid_size, null); } return part1, part2; }
Void · 2024-12-17 10:48
Completed day 15 after too much trial and error in part 2. Had to rewrite the whole thing today. Also, misunderstood how to calculate GPS for part 2: I was measuring distances to the _closest edge_ of the box. And that's exactly how it's described in the task! But what is actually meant is: distance from the top and left edges of the map to the left edge of a box... Well, I'm just glad it's over now. haskell data Dir = L | R | U | D deriving Show data Tile = Box | Wall | Robot | Floor | LBox | RBox deriving (Eq,Show) type Pos = (Int,Int) type Grid = Array Pos Tile simulate :: Grid -> Pos -> [Dir] -> (Grid, Pos) simulate grid pos dirs = foldl' h (grid,pos) dirs where h (grid',pos') dir = simulate1 grid' pos' dir simulate1 :: Grid -> Pos -> Dir -> (Grid, Pos) simulate1 grid pos dir = case (grid ! nextPos) of Wall -> (grid, pos) Floor -> (grid, nextPos) tile | isBox tile -> case shiftBox grid nextPos dir of Nothing -> (grid, pos) Just grid' -> (grid', nextPos) where nextPos = move dir pos shiftBox :: Grid -> Pos -> Dir -> Maybe Grid shiftBox grid pos dir = if any ((== Wall) . snd) adjTiles then Nothing else if all ((== Floor) . snd) adjTiles then Just (moveBox grid pos dir) else if any (isBox . snd) adjTiles then maybeFoldl' (\g p -> shiftBox g p dir) grid ((map fst (filter (isBox . snd) adjTiles)) ++ [pos]) else undefined where adjTiles = map (\p -> (p, grid ! p)) adjPoss adjPoss = case (grid ! pos, dir) of (Box,_) -> [move dir pos] (LBox, R) -> [move dir (move dir pos)] (RBox, L) -> [move dir (move dir pos)] (RBox, U) -> uniqBoxes grid (move L (move dir pos), move dir pos) (RBox, D) -> uniqBoxes grid (move L (move dir pos), move dir pos) (LBox, U) -> uniqBoxes grid (move dir pos, move R (move dir pos)) (LBox, D) -> uniqBoxes grid (move dir pos, move R (move dir pos)) (x,_) -> error ("not a box: " ++ (show x)) moveBox grid pos dir = case (grid ! pos) of Box -> grid // [(pos, Floor), (move dir pos, Box)] LBox -> let rpos = (move R pos) in grid // [(pos, Floor), (rpos, Floor), (move dir pos, LBox), (move dir rpos, RBox)] RBox -> let lpos = (move L pos) in grid // [(pos, Floor), (lpos, Floor), (move dir lpos, LBox), (move dir pos, RBox)] _ -> error "not a box" uniqBoxes grid (pos1,pos2) = case (grid ! pos1, grid ! pos2) of (LBox,RBox) -> [pos1] _ -> [pos1, pos2] isBox Box = True isBox LBox = True isBox RBox = True isBox _ = False maybeFoldl' f z xs = foldl' h (Just z) xs where h Nothing _ = Nothing h (Just z') x = f z' x move U (r,c) = (r-1,c) move D (r,c) = (r+1,c) move R (r,c) = (r,c+1) move L (r,c) = (r,c-1) gps :: Grid -> Pos -> Int gps grid pos@(r,c) = case grid ! pos of Box -> (100 * (r-1)) + (c-1) LBox -> (100 * (r-1)) + (c-1) mkGrid :: [[Tile]] -> (Grid, Pos) mkGrid tiles = (grid // [(robotPos, Floor)], robotPos) where grid = listArray ((1,1), (h,w)) (concatMap id tiles) robotPos = fst . fromJust $ find ((== Robot) . snd) (assocs grid) h = length tiles w = length (head tiles) showGrid :: Grid -> String showGrid grid = unlines $ map (map tileChar) rows where rows = sizedGroups w (elems grid) (_,(_,w)) = bounds grid tileChar tile = case tile of Box -> 'O' LBox -> '[' RBox -> ']' Floor -> '.' Wall -> '#' inputParser :: Parser (Grid, Pos, [Dir]) inputParser = do rows <- (filter (not . null)) <$> separatedByParser (charParser '\n') (listParser tileParser) (grid, robot) <- return (mkGrid rows) moves <- allParser moveParser return (grid, robot, moves) tileParser = oneOfParser (zipWith (\c d -> (const d) <$> charParser c) "#.O@" [Wall,Floor,Box,Robot]) moveParser = oneOfParser (zipWith (\c d -> (const d) <$> charParser c) "^><v" [U,R,L,D]) resize (grid, (r,c)) = (grid', (r, c*2 - 1)) where grid' = listArray ((1,1), (h,w*2)) (concatMap replaceTile (elems grid)) ((_,_), (h,w)) = bounds grid replaceTile tile = case tile of Box -> [LBox, RBox] _ -> [tile, tile] -- 1527563 part1 = part1' <$> readInput 15 part1' s = sum $ map ((gps grid') . fst) boxes where (grid,pos,dirs) = parseValue inputParser s (grid',_) = simulate grid pos dirs boxes = filter ((== Box) . snd) (assocs grid') -- 1521635 part2 = part2' <$> readInput 15 part2' s = sum $ map ((gps grid'') . fst) boxes where (grid,pos,dirs) = parseValue inputParser s (grid',pos') = resize (grid,pos) (grid'',_) = simulate grid' pos' dirs boxes = filter ((== LBox) . snd) (assocs grid'') >>40707 I can't tell if this is a real question. But no -- I am not going to use Prolog.
aliased · 2024-12-17 12:59
Failed today's part 2, as it's one of those "unless you get the Eureka, go fuarrrk yourself" gifts. If tomorrow is as soykafty, that's the end for me, lainons. But I had fun exploiting CL's party trick runtime compilation to go brrrr! (in-package #:q3cpma-user) (defparameter *RA* 0) (defparameter *prog* nil) (let* ((input (uiop:read-file-string *input-path*)) (ints (mapcar #'parse-integer (cl-ppcre:all-matches-as-strings "\\d+" input)))) (setf *RA* (nth 0 ints) *prog* (subseq ints 3))) (defun %translate-prog (is-quine-p) (assert (equal (last *prog* 2) '(3 0))) ;; *prog* always loops to the beginning! `(lambda (RA &aux (RB 0) (RC 0)) (declare (optimize (speed 3) (debug 0) (safety 0))) (declare (type (integer 0) RA RB RC)) (iter ,@(if is-quine-p `((with ref = (list ,@*prog*)))) (while (/= 0 RA)) ,@(flet ((combo (n) (case n ((0 1 2 3) n) (4 'RA) (5 'RB) (6 'RC)))) (iter (for (ins op) on (butlast *prog* 2) by #'cddr) (collect (case ins (0 `(setf RA (truncate RA (expt 2 ,(combo op))))) (1 `(setf RB (logxor RB ,op))) (2 `(setf RB (logand ,(combo op) #b111))) (3 (error "Loop inside!")) (4 `(setf RB (logxor RB RC))) (5 (if is-quine-p ;; Stop at the first output difference `(while (eql (pop ref) (logand ,(combo op) #b111))) `(collect (logand ,(combo op) #b111)))) (6 `(setf RB (truncate RA (expt 2 ,(combo op))))) (7 `(setf RC (truncate RA (expt 2 ,(combo op))))))))) ,@(if is-quine-p `((finally (return (null ref)))))))) (defun find-good-RA () (iter (with compiled = (compile-prog :is-quine-p t)) (with len = (length *prog*)) (for RA from (expt 2 (* 3 (- len 1)))) ;; Observation: 2^3(x-1) < RA < 2^(3x-1) => output len x (finding RA such-that (funcall compiled RA)))) (defun compile-prog (&key is-quine-p) (let ((tr (%translate-prog is-quine-p))) (format t "Input: ~A~%Output: ~S~%" *prog* tr) (compile nil tr))) (format t "Part 1: ~{~A~^,~}~%" (funcall (compile-prog) *RA*)) (format t "Part 2: ~A~%" (find-good-RA)) $ sbcl --load rc --script 17/prog.lisp 17/sample2 Input: (0 3 5 4 3 0) Output: (LAMBDA (RA &AUX (RB 0) (RC 0)) (DECLARE (OPTIMIZE (SPEED 3) (DEBUG 0) (SAFETY 0))) (DECLARE (TYPE (INTEGER 0) RA RB RC)) (ITER (WHILE (/= 0 RA)) (SETF RA (TRUNCATE RA (EXPT 2 3))) (COLLECT (LOGAND RA 7)))) Part 1: 5,7,3,0 Input: (0 3 5 4 3 0) Output: (LAMBDA (RA &AUX (RB 0) (RC 0)) (DECLARE (OPTIMIZE (SPEED 3) (DEBUG 0) (SAFETY 0))) (DECLARE (TYPE (INTEGER 0) RA RB RC)) (ITER (WITH REF = (LIST 0 3 5 4 3 0)) (WHILE (/= 0 RA)) (SETF RA (TRUNCATE RA (EXPT 2 3))) (WHILE (EQL (POP REF) (LOGAND RA 7))) (FINALLY (RETURN (NULL REF))))) Part 2: 117440 >>40726 I can feel your pain, mate. Still pretty even if there's much repetition. That shiftBox code makes me truly appreciate how pretty (cond) is compared to the usual if-else-if.
Delilah · 2024-12-17 13:19
>>40728 i really enjoyed part 2 today; it's one of those "keep making observations until the solution reveals itself" days definitely a day for pen and paper though
Nanashi No Gombe · 2024-12-18 06:50
really gave up with day 12... // input allocated 36 bytes // output allocated 16776192 bytes // output executed 99.383 million cycles day11 :: (input: string) -> (part1: int, part2: int) { using Basic; using String; using Math; using Hash_Table; Cache :: Table(U128, int, hash_bytes, comp_bytes); blink_at :: (stone: int, max_depth: int, cache: *Cache, depth := 0) -> int { p := table_find_pointer(cache, .{xx stone, xx (max_depth-depth)}); if p return p.*; if stone == 0 { if depth == max_depth return 1; x := blink_at(1, max_depth, cache, depth+1); table_add(cache, .{xx stone, xx (max_depth-depth)}, x); return x; } digits := count_digits(stone); if digits % 2 == 0 { if depth == max_depth return 2; mask := cast(int) pow(10, xx (digits / 2)); left := stone / mask; right := stone % mask; x := blink_at(left, max_depth, cache, depth+1) + blink_at(right, max_depth, cache, depth+1); table_add(cache, .{xx stone, xx (max_depth-depth)}, x); return x; } if depth == max_depth return 1; x := blink_at(stone*2024, max_depth, cache, depth+1); table_add(cache, .{xx stone, xx (max_depth-depth)}, x); return x; } cache: Cache; part1, part2 := 0; rhs := input; while true { ok:, stone_str:, rhs = split_from_left(rhs, #char " "); if !ok && !stone_str break; stone:, ok = string_to_int(stone_str); part1 += blink_at(stone, 24, *cache); part2 += blink_at(stone, 74, *cache); } return part1, part2; } // input allocated 19740 bytes // output allocated 786048 bytes // output executed 9.238 million cycles day12 :: (input: string) -> (part1: int, part2: int) { using Basic; using Hash_Table; Position :: struct { row, col: s32; } grid_size: Position; rhs := input; while true { ok:, line:, rhs = split_line(rhs); if !ok break; assert(xx line); grid_size.col = xx line.count; grid_size.row += 1; } Direction :: enum_flags { UP; DOWN; LEFT; RIGHT; }; Cache :: Table(Position, void, hash_bytes, comp_bytes); fill :: (using pos: Position, cache: *Cache, input: string, grid_size: Position) -> int, int, int { if table_find_pointer(cache, pos) return 0, 0, 0; // already counted table_add(cache, pos, the_void); get :: (p: Position) -> u8 #expand { return input[p.row * (grid_size.row+1) + p.col]; } val := get(pos); area := 0; outer := 0; inner := 0; dirs: Direction; for struct { dir: Direction; using pos: Position; }.[ .{.UP, .{row-1, col}}, .{.DOWN, .{row+1, col}}, .{.LEFT, .{row, col-1}}, .{.RIGHT, .{row, col+1}} ] { if it.row < 0 || it.row >= grid_size.row || it.col < 0 || it.col >= grid_size.col { area += 1; dirs |= it.dir; continue; } neighbor := get(it.pos); if neighbor == val { a, b, c := fill(it.pos, cache, input, grid_size); area += a; outer += b; inner += c; } else { area += 1; dirs |= it.dir; l_col := it.col - 1; r_col := it.col + 1; u_row := it.row - 1; d_row := it.row + 1; if it.dir & .UP { if l_col >= 0 && l_col < grid_size.col && get(.{it.row, l_col}) == val && d_row >= 0 && d_row < grid_size.row && get(.{d_row, l_col}) == val then inner += 1; if r_col >= 0 && r_col < grid_size.col && get(.{it.row, r_col}) == val && d_row >= 0 && d_row < grid_size.row && get(.{d_row, r_col}) == val then inner += 1; } else if it.dir & .DOWN { if l_col >= 0 && l_col < grid_size.col && get(.{it.row, l_col}) == val && u_row >= 0 && u_row < grid_size.row && get(.{u_row, l_col}) == val then inner += 1; if r_col >= 0 && r_col < grid_size.col && get(.{it.row, r_col}) == val && u_row >= 0 && u_row < grid_size.row && get(.{u_row, r_col}) == val then inner += 1; } else if it.dir & .LEFT { if u_row >= 0 && u_row < grid_size.col && get(.{u_row, it.col}) == val && r_col >= 0 && r_col < grid_size.row && get(.{u_row, r_col}) == val then inner += 1; if d_row >= 0 && d_row < grid_size.col && get(.{d_row, it.col}) == val && r_col >= 0 && r_col < grid_size.row && get(.{d_row, r_col}) == val then inner += 1; } else if it.dir & .RIGHT { if u_row >= 0 && u_row < grid_size.col && get(.{u_row, it.col}) == val && l_col >= 0 && l_col < grid_size.row && get(.{u_row, l_col}) == val then inner += 1; if d_row >= 0 && d_row < grid_size.col && get(.{d_row, it.col}) == val && l_col >= 0 && l_col < grid_size.row && get(.{d_row, l_col}) == val then inner += 1; } } } if (dirs & .UP) && (dirs & .LEFT) outer += 1; if (dirs & .UP) && (dirs & .RIGHT) outer += 1; if (dirs & .DOWN) && (dirs & .LEFT) outer += 1; if (dirs & .DOWN) && (dirs & .RIGHT) outer += 1; return area, outer, inner; } cache: Cache; to_test: Position; part1, part2 := 0; while true { defer { to_test.col += 1; if to_test.col == grid_size.col { to_test.col = 0; to_test.row += 1; if to_test.row == grid_size.row break; } } if table_find_pointer(*cache, to_test) continue; count := cache.count; fence_count, outer, inner := fill(to_test, *cache, input, grid_size); area_count := (cache.count-count); part1 += area_count * fence_count; part2 += area_count * (outer + ((inner+1)/2)); } return part1, part2; }
Davis · 2024-12-18 12:16
I'm stuck on Day 16. Tried naive DFS, too slow. DFS with caching (store paths from each position to the maze's exit), still too slow for real input, and also incorrect, as part 2 revealed. Tried something similar to Dijkstra's algorithm, solved part 1, but don't see how to apply it to part 2. Damn mazes... Can't be losing sleep anymore, so I'll be catching up on the weekend.
Shogun · 2024-12-19 02:24
very simple dynamic programming problem today; Eric has gone soft patterns, designs = open(0).read().strip().split('\n\n') patterns = set(patterns.split(', ')) p1 = p2 = 0 for design in designs.split('\n'): n = len(design) dp = [1] + [0]*n for i in range(n): for pattern in patterns: m = len(pattern) if design[i:i+m] == pattern: dp[i+m] += dp[i] p1 += bool(dp[-1]) p2 += dp[-1] print(p1, p2) >>40748 if you want spoilers, i solved it using dfs here: >>40709 you have to be careful with your cache as branches can reach the same cell from different directions; a branch with a higher cost may beat a branch with a lower cost if it has to turn Dijkstra's is the "correct" solution, i recommend reading: https://cp-algorithms.com/graph/dijkstra.html all other resources (e.g. wikipedia, computerphile, etc.) are garbage
Incognito · 2024-12-19 13:46
>>40751 space-optimised patterns, designs = open(0).read().strip().split('\n\n') patterns = set(patterns.split(', ')) p1 = p2 = 0 for design in designs.split('\n'): n = len(design) dp = [1] + [0]*max(map(len, patterns)) k = len(dp) for i in range(n): dp[(i - 1) % k] = 0 if not dp[i % k]: # nothing to push if not any(dp): # no solution break continue for pattern in patterns: m = len(pattern) if design[i:i+m] == pattern: dp[(i + m) % k] += dp[i % k] p1 += bool(dp[n % k]) p2 += dp[n % k] print(p1, p2)
Delilah · 2024-12-19 21:58
simply not enough hours in the day to keep doing these after a certain point. I lucked out with day 13 being very easy, but the rest will have to wait until the weekend. // input allocated 20991 bytes // output allocated 0 bytes // output executed 0.227 million cycles day13 :: (input: string) -> (part1: int, part2: int) { using Basic; rhs := input; part1, part2 := 0; while true { parse_line :: (name: string) -> (x: int, y: int) #expand { ok:, line:, rhs = split_line(rhs); assert(ok); advance(*line, name.count); x:, ok, line = string_to_int(line); assert(ok); advance(*line, ", Y+".count); y:, ok, line = string_to_int(line); assert(ok); return x, y; } xa, ya := parse_line("Button A: X+"); xb, yb := parse_line("Button B: X+"); xp, yp := parse_line("Prize: X="); det := (xa*yb) - (xb*ya); if det { a := (+yb*xp) + (-xb*yp); b := (-ya*xp) + (+xa*yp); if !(a % det) && !(b % det) then part1 += ((a/det)*3) + (b/det); xp += 10000000000000; yp += 10000000000000; a = (+yb*xp) + (-xb*yp); b = (-ya*xp) + (+xa*yp); if !(a % det) && !(b % det) then part2 += ((a/det)*3) + (b/det); } ok:, _, rhs = split_line(rhs); if !ok break; } return part1, part2; }
Hopper · 2024-12-20 03:04
slooow but simple day 20; just bfs for distances without cheats, then try all jumps of manhattan distance <= 20 there are some fairly obvious optimisations, but i'm happy with the aesthetics of my solution g = list(map(list, open(0).read().strip().split('\n'))) rows = len(g); cols = len(g[0]) dx = ((-1, 0), (0, +1), (+1, 0), (0, -1)) out = lambda r, c : not 0 <= r < rows and 0 <= c < cols pos = lambda ch : [(r, c) for r, x in enumerate(g) for c, y in enumerate(x) if y == ch][0] visited = [[False] * cols for _ in range(rows)] picosec = [[ -1 ] * cols for _ in range(rows)] (r, c) = pos('S') visited[r][c] = True picosec[r][c] = 0 q = [(r, c)] while q: (r, c) = q.pop(0) for dr, dc in dx: (rr, cc) = (r + dr, c + dc) if out(rr, cc) or visited[rr][cc] or g[rr][cc] == '#': continue visited[rr][cc] = True picosec[rr][cc] = picosec[r][c] + 1 q.append((rr, cc)) p1 = p2 = 0 for rr in range(rows): for cc in range(cols): for r in range(rows): for c in range(cols): if (not visited[r][c] or (manhattan := abs(rr - r) + abs(cc - c)) > 20): continue if picosec[rr][cc] - manhattan - picosec[r][c] >= 100: p1 += manhattan == 2 p2 += 1 print(p1, p2) Days : 0 Hours : 0 Minutes : 1 Seconds : 10 Milliseconds : 686 Ticks : 706860700 TotalDays : 0.000818125810185185 TotalHours : 0.0196350194444444 TotalMinutes : 1.17810116666667 TotalSeconds : 70.68607 TotalMilliseconds : 70686.07
Otto Normalverbraucher · 2024-12-21 12:36
>>40748 I did it. Finally solved part 2 of day 16 using some nth variation of Dijkstra's algorithm and arduously retracing the best paths. I must admit it took me more than 100 lines of Haskell ... and it runs in reasonable time only when compiled with the optimise flag :) One more search problem and I swear... >>40709 I'm having a hard time understanding your solution. I can see that the general strategy is: for a given position, explore adjacent ones only if their cached cost is greater than some value computed from the current cost. This last part gets me confused. Specifically, the right-hand sides in comparisons with cache[rr][cc]. Why is it (cost - 1e3) for a move in the same direction? Why not (cost + 1)? And for moves that change direction -- why is it (cost) and not (cost + 1001) ?
Roy Batty · 2024-12-21 12:44
>>40777 Not him, but since I translated his program: from my understanding, the cache is here to detect loops and abort early. The assumption is that if you already visited this tile in the other direction, the actual correct path is either to the left or to the right (cache + 1000).
Netrunner · 2024-12-21 15:42
>>40778 Aha! This makes sense. Thank you. I don't think I could have come up with that.
Kay · 2024-12-22 03:43
Here is my Day 17. I implemented a very specific solution for part 2 that only works for my kind of input. Generalising it is an interesting problem, where logic programming could be applied, so maybe I will use Prolog after all ;) haskell nth n xs = maybeHead (drop n xs) data Computer = Computer { regA :: Int, regB :: Int, regC :: Int, ip :: Int, output :: [Int] } deriving Show type Instr = (Opcode,Operand) type Operand = Int data Opcode = Adv | Bxl | Bst | Jnz | Bxc | Out | Bdv | Cdv deriving (Show,Eq) opcodes = [Adv, Bxl, Bst, Jnz, Bxc, Out, Bdv, Cdv] opcode x = opcodes !! x instruction op rand = (opcode op, rand) step comp = comp { ip = (ip comp) + 2 } out x comp = comp { output = (output comp) ++ [x] } run :: Computer -> [Instr] -> Computer run comp inss = case nextIns of Nothing -> comp Just ins -> run (eval comp ins) inss where nextIns | (ip comp) `mod` 2 /= 0 = error "Instruction pointer is odd!" | otherwise = nth (ip comp `div` 2) inss eval comp (opcode, rand) = case opcode of Adv -> step $ comp { regA = (regA comp) `div` (2 ^ comboVal) } Bxl -> step $ comp { regB = (regB comp) `xor` rand } Bst -> step $ comp { regB = comboVal `mod` 8 } Jnz -> if (regA comp) == 0 then step comp else comp { ip = rand } Bxc -> step $ comp { regB = (regB comp) `xor` (regC comp) } Out -> step $ out (comboVal `mod` 8) comp Bdv -> step $ comp { regB = (regA comp) `div` (2 ^ comboVal) } Cdv -> step $ comp { regC = (regA comp) `div` (2 ^ comboVal) } where comboVal = combo comp rand combo comp rand = xs !! rand where xs = [0,1,2,3,regA comp,regB comp,regC comp] inputParser = do nums <- allParser $ fromInteger <$> integerParser let ((regA : regB : regC : []), prog) = splitAt 3 nums in return (Computer regA regB regC 0 [], map (uncurry instruction) (pairs prog)) pairs (a : b : xs) = (a,b) : pairs xs pairs _ = [] parseInput = parseValue inputParser part1 = part1' <$> readInput 17 part1' s = output (run comp inss) where (comp, inss) = parseInput s -- Part 2 -- The solution is specialised for a specific input, which has the following properties: -- * There is a single jump instruction, it is the last one and jumps to 0. -- * At each iteration A = A / 8, and other registers can be computed from A alone. -- * At the last iteration A = 0 (true for any input). -- To solve the thing we find possible values of A for each iteration, starting from the last one. -- For each iteration, given the value of A at the end of the iteration and the expected output symbol, -- find all values that A could have at the beginning of the iteration. part2 = part2' <$> readInput 17 part2' s = minimum $ foldr h [0] (concatMap unparseInstr inss) where (comp, inss) = parseInput s h eo eas = map regA $ concatMap (\ea -> revStep comp inss ea eo) eas revStep :: Computer -> [Instr] -> Int -> Int -> [Computer] revStep comp inss ea eo = filter good $ map (\a -> comp {regA = a}) as where as = range (max (ea * 8) 1, (ea * 8) + 7) good comp = (regA comp') == ea && (output comp') == [eo] where comp' = run1 comp inss -- Run all instructions sequentially. -- Since jump is the last one and jumps to 0, it has no effect. run1 comp inss = foldl' eval comp inss unparseInstr (opcode,rand) = [fromJust $ elemIndex opcode opcodes, rand]
Kay · 2024-12-22 06:54
And here goes Day 18. It's a search problem, but much more friendlier than the nightmare of Day 16. haskell type Grid = Array Pos Char type Pos = (Int,Int) adjacent (r,c) = [ (r-1,c), (r+1,c), (r,c-1), (r,c+1) ] -- Builds a map of distances from `start` analyse :: Grid -> Pos -> Pos -> M.Map Pos Int analyse grid start end = analyse' grid start end (M.singleton start 0) analyse' grid pos dst dists | pos == dst = dists | otherwise = foldl' h dists adjs where adjs = filter (maybe False (== '.') . (grid `at`)) $ adjacent pos posDist = dists M.! pos adjDist = 1 + posDist h dists' adj = maybe (analyse' grid adj dst dists'') (const dists'') (filterMaybe (<= adjDist) oldAdjDist) where (oldAdjDist, dists'') = insertLookupWith min adj adjDist dists' shortestPath grid start end = dists M.! end where dists = analyse grid start end inputParser :: Parser [Pos] inputParser = pairs <$> allParser (fromInteger <$> integerParser) where pairs xs = map (\[a,b] -> translatePos (a,b)) $ sizedGroups 2 xs translatePos (x,y) = (r,c) where (r,c) = (y,x) mkGrid (h,w) bytePoss = accum (flip const) (listArray ((0,0), (h,w)) (repeat '.')) (map (,'#') bytePoss) part1' s = shortestPath grid (0,0) (70,70) where grid = mkGrid (70,70) (take 1024 (parseValue inputParser s)) -- Part 2 part2' s = firstBadByte (0,0) (70,70) 1024 (70,70) bytePoss where bytePoss = parseValue inputParser s hasPath grid start end = M.member end dists where dists = analyse grid start end firstBadByte start end offset dims bytePoss = badByte where badByte = snd $ fromJust $ binSearch (\(grid,_) -> hasPath grid start end) xs xs = map (\bs -> (mkGrid dims bs, last bs)) (drop (offset + 1) (inits bytePoss)) binSearch :: (a -> Bool) -> [a] -> Maybe a binSearch pred [] = Nothing binSearch pred xs = if pred c then binSearch pred r else (binSearch pred l) <|> (Just c) where (l, c, r) = binSplit xs binSplit [] = error "empty" binSplit [x] = ([], x, []) binSplit xs = (l, c, r) where (l, (c:r)) = splitAt (length xs `div` 2) xs
Spooky · 2024-12-23 04:06
>>40751 Impressive. How did you come up with such a concise solution? I had to think hard to understand it. Mine is not so elegant. Makes Haskell look bad :) haskell type Pattern = String type Design = String possibles patts designs = filter (possible (S.fromList patts)) designs possible patts [] = True possible patts design = S.member design patts || isJust (find match $ splits design) where match (l,r) = (S.member l patts) && (possible patts r) combo :: Design -> S.Set Pattern -> M.Map Pattern Int -> (M.Map Pattern Int, Int) combo [] _ _ = undefined combo d@[_] patts cache = swap $ insertLookup d v cache where v = if S.member d patts then 1 else 0 combo design patts cache = case M.lookup design cache of Just n -> (cache,n) Nothing -> (M.insert design v cache', v) where v = (if S.member design patts then 1 else 0) + (sum ns) (cache',ns) = mapAccumL h cache (splits design) h c (l,r) = if S.member l patts then combo r patts c else (c,0) combos patts designs = mapAccumL h M.empty designs where h cache d = combo d (S.fromList patts) cache insertLookup k a m = case M.insertLookupWithKey (\_ _ old -> old) k a m of (Nothing, m') -> (a, m') (Just o, m') -> (o, m') swap (a,b) = (b,a) splits xs = if len <= 1 then [] else map (\i -> splitAt i xs) [1..(len - 1)] where len = length xs inputParser :: Parser ([Pattern], [Design]) inputParser = do patts <- separatedByParser (stringParser ", ") wordParser designs <- allParser wordParser return (patts, designs) parseInput = parseValue inputParser wordParser = test (not . null) $ listParser (test isLetter anyCharParser) part1' s = length $ possibles patts designs where (patts, designs) = parseInput s part2' s = sum $ snd $ combos patts designs where (patts, designs) = parseInput s
Lukasiewicz · 2024-12-24 15:32
unwashed day 21 part 2: https://0x0.st/8rxU.py unwashed day 24 part 2: https://0x0.st/8rxf.py >>40777 >why is it (cost) and not (cost + 1001) ? sorry, it was golfed from something intuitive to derive to something... shorter but not intuitive >>40791 >How did you come up with such a concise solution? I had to think hard to understand it i've seen similar patterns before, so i didn't have to think about it at all actually once you've found the state variables, recurrence relations, and base cases, it "just works" for harder dp problems the trick is to think in terms of "which states can reach me?" (pull dp), or "which states can i reach?" (push dp) then your recurrence relations are just some operation on those states
Anamika · 2024-12-24 16:47
>>40802 slightly washed day 24 part 2 import math inputs, gates = open(0).read().strip().split('\n\n') inputs = inputs.split('\n') gates = gates.split('\n') candidates = [] togates = {} # maps wire <-> ops for gate in gates: a, op, b, _, c = gate.split() if a not in togates: togates[a] = [] if b not in togates: togates[b] = [] togates[a].append(op) togates[b].append(op) axorb = set() for gate in gates: a, op, b, _, c = gate.split() if c[0] == 'z' and c in togates: candidates.append(c) if a[0] in 'xy' or b[0] in 'xy': if c in ('z00', 'kqn'): # skip half adder continue if op == 'XOR': axorb.add(c) if c not in togates or sorted(togates[c]) != ['AND', 'XOR']: candidates.append(c) if op == 'AND': if c not in togates or sorted(togates[c]) != ['OR']: candidates.append(c) if op == 'OR': if c in ('z45'): # skip last carry continue if c not in togates or sorted(togates[c]) != ['AND', 'XOR']: candidates.append(c) for gate in gates: a, op, b, _, c = gate.split() if op == 'XOR' and c not in axorb and c in togates: candidates.append(c) if op == 'AND' and a[0] not in 'xy': if c not in togates or sorted(togates[c]) != ['OR']: candidates.append(c) print(','.join(sorted(candidates)))
Nobody special · 2024-12-27 01:28
Day 22. I was intimidated by part 2 at first, but then found a simple solution. It's nice to construct maps from association lists. [code] haskell mix :: Int -> Int -> Int mix = xor prune :: Int -> Int prune = (`mod` 16777216) step :: Int -> Int step n = n3 where n1 = prune $ (n * 64) `mix` n n2 = prune $ (n1 `div` 32) `mix` n1 n3 = prune $ (n2 * 2048) `mix` n2 nthSecret n seed = (iterate step seed) !! n inputParser :: Parser [Int] inputParser = allParser (fromInteger <$> integerParser) part1' s = sum $ map (nthSecret 2000) ns where ns = parseValue inputParser s -- Part 2 part2' s = maximumOn snd $ M.assocs priceMap where ns = parseValue inputParser s series = map (take 2000 . iterate step) ns priceMap = M.unionsWith (+) $ map priceMapFrom series priceMapFrom :: [Int] -> M.Map [Int] Int priceMapFrom ns = M.fromListWith (\_ old -> old) $ map mkAssoc $ windows 4 pairs where pairs = zip (drop 1 prices) changes mkAssoc win = (map snd win, fst $ last win) prices = map (`mod` 10) ns changes = zipWith (-) (drop 1 prices) prices windows n xs = filter ((== n) . length) $ map (take n) $ tails xs [\code]
x<->y · 2024-12-27 05:25
Day 23. Easy part 1, but resorted to cheating for part 2 by visiting https://en.wikipedia.org/wiki/Clique_problem I wonder why code formatting didn't work for my Day 22 post. type Computer = String inputParser :: Parser [(Computer, Computer)] inputParser = pairs <$> allParser computerParser where pairs xs = map (\[a,b] -> (a,b)) $ sizedGroups 2 xs computerParser = test (not . null) $ listParser (test isLetter anyCharParser) part1' s = S.filter (any3 ((== 't') . head)) $ computerTriples computerPairs where computerPairs = parseValue inputParser s any3 pred (a,b,c) = pred a || pred b || pred c sort3 (a,b,c) = (a',b',c') where [a',b',c'] = sort [a,b,c] computerTriples conns = S.fromList $ map sort3 $ filter connected triples where connMap = M.fromListWith (++) $ map (\(a,b) -> (a, [b])) $ concatMap (\(a,b) -> [(a,b), (b,a)]) conns triples = concatMap triples' $ M.assocs connMap triples' (a, bs) = map (\(b,c) -> (a,b,c)) $ pairs bs connected (a,b,c) = isJust $ filterMaybe (elem c) $ M.lookup b connMap pairs :: [a] -> [(a,a)] pairs xs = concat $ zipWith (\x xs' -> map (x,) xs') xs (drop 1 (tails xs)) -- Part 2 part2' s = sort $ S.toList $ maxClique connMap where conns = parseValue inputParser s connMap = M.fromListWith (++) $ map (\(a,b) -> (a, [b])) $ concatMap (\(a,b) -> [(a,b), (b,a)]) conns maxClique graph = maximumOn S.size $ cliques graph cliques :: M.Map Computer [Computer] -> [S.Set Computer] cliques graph = map (clique graph) (M.assocs graph) clique graph (a, bs) = foldl' add (S.singleton a) bs where add clq b = if all (connected b) (S.toList clq) then S.insert b clq else clq connected x y = isJust $ filterMaybe (elem y) $ M.lookup x graph
Sabo Engle · 2024-12-27 09:41
>>40817 thanks for the link, i brootforced day 23 to be honest your code didn't embed because you typed \code not /code
Torvalds · 2024-12-27 23:39
>>40821 Aha... Haskell got me in the habit of typing backslash frequently (for lambda parameters) and I couldn't tell it apart from the regular slash :)