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
while
loop, 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 callingcallable
for each element initerable
sum
:sum(iterable)
just adds all the elements of the iterable- A bunch of stuff in
itertools
:starmap
andtakewhile
are very useful
functools.reduce
- Functions from the
operator
module - Stuff in the
collections
module
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_container
was initialized to[x]
. - Infinite loops:
itertools.repeat(None)
- While
True
loops:itertools.takewhile(lambda work: ..., (do_work() for _ in itertools.repeat(None)))
Note thattakewhile
means you'll go "one past" yourbreak
condition, so plan accordingly. - Classes: Use the
type
constructor with lambdas as your functions E.g.type("Whatever", (), {"__init__": lambda self: ..., ...})