Files
advent-of-code/2025/util.lisp
Aryadev Chavali 55a352a08e aoc 2025 - still need to 7
Forgot to push these all onto the cloud lol
2025-12-07 07:52:17 +00:00

158 lines
5.1 KiB
Common Lisp

(defpackage "aoc-util"
(:use :cl)
(:export
:=>
:--> :->> :-<>
:$-> :$<> :$>>
:alist-val :call-rev :fn
:split :rev-map :parse-integer* :remove-at-indices :range))
(in-package "aoc-util")
(deftype => (args result)
"Type level DSL for function types"
`(function ,(typecase args
(null args)
(symbol (list args))
(otherwise args))
,result))
(defmacro --> (placeholder &body forms)
"Lexically bind current form as `placeholder' for use in the next form, returning
the result of the last form.
i.e.
(--> (a1 a2...) (b1 b2...) (c1 c2...)) =
(let* ((placeholder (a1 a2 ...))
(placeholder (b1 b2 ...))
(placeholder (c1 c2 ...)))
_ )
Also includes transformer where symbols are considered unary functions i.e.
(--> x y) <-> (--> x (y placeholder)).
"
(if (null forms)
nil
(let ((assignment-forms
(loop :for i :from 0
:for f :in forms
:for canon-f := (if (and (> i 0) (symbolp f))
(list f placeholder)
f)
:collect `(,placeholder ,canon-f))))
`(let* ,assignment-forms
,placeholder))))
(defmacro ->> (&rest forms)
"Make current form the last argument of the next form, returning the last
form.
i.e.
(->> (a1 a2...) (b1 b2...) (c1 c2...)) == (c1 c2 ... (b1 b2 ... (a1 a2 ...)))
Also includes transformer where symbols are considered unary functions.
Like the `|>' operator in Ocaml."
(if (null forms)
nil
(loop :with acc = (car forms)
:for func :in (cdr forms)
:for canon-func = (if (symbolp func) (list func) func)
:do (setq acc (append canon-func (list acc)))
:finally (return acc))))
(defmacro -<> (&rest forms)
"Make current form the first argument of the next form, returning the last
form.
i.e.
(-<> (a1 a2...) (b1 b2...) (c1 c2...)) == (c1 (b1 (a1 a2 ...) b2 ...) c2 ...)
Also includes transformer where symbols are considered unary functions.
Like the `|>' operator in Ocaml."
(if (null forms)
nil
(loop :with acc = (car forms)
:for func :in (cdr forms)
:for canon-func = (if (symbolp func) (list func) func)
:do (push acc (cdr canon-func))
:do (setq acc canon-func)
:finally (return acc))))
(defmacro $-> (capture &rest forms)
"Given a sequence of FORMS, return a unary function which applies each form
sequentially via -->"
`(lambda (,capture)
(--> ,capture ,capture ,@forms)))
(defmacro $<> (&rest forms)
"Given a sequence of FORMS, return a unary function which applies each form
sequentially via -<>"
(let ((capture (gensym)))
`(lambda (,capture)
(-<> ,capture ,@forms))))
(defmacro $>> (&rest forms)
"Given a sequence of FORMS, return a unary function which applies each form
sequentially via ->>"
(let ((capture (gensym)))
`(lambda (,capture)
(->> ,capture ,@forms))))
(defmacro alist-val (key alist &key (test #'eq))
"Helper macro for getting the value of KEY in ALIST."
`(cdr (assoc ,key ,alist :test ,test)))
(defmacro call-rev (func-name &rest arguments)
"Call a function with arguments but in reverse
i.e. (call-rev f x1 x2 ... xn) => (f xn ... x2 x1)."
`(,func-name ,@(reverse arguments)))
(defmacro fn (name lambda-list type &body body)
"Construct a function `NAME' with a declared function type `TYPE' that takes
arguments `LAMBDA-LIST' with body `BODY'."
`(progn
(declaim (ftype ,type ,name))
(defun ,name ,lambda-list
,@body)))
(fn split (n lst) (=> (fixnum sequence) (values sequence sequence))
"Return CONS where CAR is the first N elements of LST and CDR is the rest."
(if (< (length lst) n)
(values nil nil)
(values (subseq lst 0 n)
(subseq lst n))))
(fn rev-map (indicator lst &key (test #'eq))
(=> (function sequence &key (:test function)) list)
"Given LST and INDICATOR: LST -> A, return an association list A -> 2^LST
where key x in A has associations {y in LST : INDICATOR(y) = x}."
(loop :with assoc-list := nil
:for element :in (coerce lst 'list)
:for key := (funcall indicator element)
:for value := (cdr (assoc key assoc-list :test test))
:if value
:do (setf (alist-val key assoc-list :test test)
(cons element value))
:else
:do (setq assoc-list (-<> (list key element) (cons assoc-list)))
:finally (return assoc-list)))
(fn parse-integer* (inp) (=> string (or integer list))
"Given string INP, attempt to parse an integer. Return NIL otherwise."
(parse-integer inp :junk-allowed t))
(fn remove-at-indices (indices lst) (=> (list sequence) list)
"Given a set of INDICES and a list LST, return a copy of LST without items at any
index in INDICES."
(loop :for i :from 0 :to (1- (length lst))
:for item :in (coerce lst 'list)
:if (not (member i indices))
:collect item))
(fn range (lower upper) (=> (fixnum fixnum) list)
(loop for i from lower to upper
collect i))