This repo hosts my solutions to 2023's Advent of Code.
However, there's a twist.
All of the solutions are implemented as Python "one-liners" (more specifically, one-statementers).
I've also done my best attempt at code that is moderately performant (considering the constraints). That means trying to return-early when possible, not parsing strings if unnecessary, etc....
No AI or cheating (such as writing the code and then using a tool to transform it) of any nature was used to make these. I also refuse to accept hints on the puzzles with "tricks" (which really seems like most of them, eh?). These things would ruin the value of this repo as an artistic showcase.
For most of these, I just wrote the one-liner as-is, without debugging or tests.
I do this because I find the constraints very fun and challenging.
- Day 2: I realized that you could just immediately call a lambda for "variables". On Day 1 I used
next(itertools.starmap(lambda var1, var2: ..., [(var1_expor, var2_expr)]) - Day 5: I need a
whileloop, which turned out to be of the formitertools.takewhile(..., (x for x in itertools.repeat(None))) - Day 11: Instead of a lambda for variables, the walrus operator
:=is actually much more convenient (and likely faster) So. I'm committing to no lambdas for the day.
This wouldn't be possible without tricks. I'l try and document them here as I go along.
The basic toolbox for one-liners is crafted almost exclusively from functional programming:
- lambdas:
lambda x, y .... Note that a lambda is a callable whose return is the value of the sole expression evaluated - comprehensions:
x for y in z. map:map(callable, iterable)lazily yields the result of callingcallablefor each element initerablesum:sum(iterable)just adds all the elements of the iterable- A bunch of stuff in
itertools:starmapandtakewhileare very useful
functools.reduce- Functions from the
operatormodule - Stuff in the
collectionsmodule
The tricks are as follows:
- Imports: Just use
__import__("<modname>") - Getting the first thing out of an iterable:
next(iterable) - Consuming an entire iterable just for its side-effects:
collections.deque(iterable, maxlen=0) - Variables: Declare a lambda and immediately call it. The variable is the parameter,
and all inner lambdas (which become closures) will be able to reference it. This looks like:
(lambda x, y: <expr>)(<expr>, <expr>)- On problem 1, I hadn't figured this out yet, and instead used
next(starmap(lambda x, y, (<pair>)))
- On problem 1, I hadn't figured this out yet, and instead used
- Statements for assignment: Use the corresponding dunder method. E.g.
__setitem__forx[i] = y. - Evaluating Multiple expressions: lambdas can only contain one expression. However,
that expression could be a list, and that list could have multiple elements, and those elements
can be expressions which must be evaluated in order to compute their corresponding values
in the list. 😉
- Or perhaps you know expressions will return a
False-y value.(<expr> or <expr>)is an easy way to have both be evaluated with the result being the result of the second expression, given you know the first expression will always beFalse-y Same goes if you know they will returnTrue-thy values using(<expr> and <expr>).
- Or perhaps you know expressions will return a
- Assigning to arguments (e.g. during iteration): Anytime you want to iterate and change an input while
you're iterating, shove the input into a list, then change that list at index 0.
E.g. instead of
x = y(a statement), dox_container.__setitem__(0, y)wherex_containerwas initialized to[x]. - Infinite loops:
itertools.repeat(None) - While
Trueloops:itertools.takewhile(lambda work: ..., (do_work() for _ in itertools.repeat(None)))Note thattakewhilemeans you'll go "one past" yourbreakcondition, so plan accordingly. - Classes: Use the
typeconstructor with lambdas as your functions E.g.type("Whatever", (), {"__init__": lambda self: ..., ...})