Hacker News new | past | comments | ask | show | jobs | submit login
Designing a programming language to speedrun Advent of Code (vero.site)
243 points by polyrand on Nov 14, 2023 | hide | past | favorite | 96 comments



I didn't realize who this was by the title, but this is betaveros, the guy who won 1st place in Advent of Code every single year since 2019: https://clist.by/account/32289/resource/adventofcode.com/


Including in 2022 with the self-created programming language the post is about, which is just amazing, and lives somewhere in my head near FlaSh's 2020 decision to switch to playing pro StarCraft Brood War tournaments as the Random race -- requiring him to become world-class at three races (nine race matchups) while his opponents only have to be world-class at one race (three race matchups). FlaSh came third in the largest tournament that year.

From the post:

> I think I predicted that requiring myself to use only Noulith on Advent of Code would make my median leaderboard performance better but my worst-case and average performances significantly worse. I don’t think my median performance improved, but my worst-case performance definitely got worse. Somehow it still didn’t matter and I placed top of the leaderboard anyway. (I will note that 2021’s second to fourth place all didn’t do 2022.)


It seems crazy that 2nd to 4th in 2021 didn't do 2022 at all! It's an annual ritual for me, I couldn't imagine being so heavily into it one year and then not competing at all the next. Was there a reason?


I heard something about a large competitive programming tournament (i.e. a commercial one) happening at a nearby time to AoC, I think it was that for at least one person.

(I also don't think it's unimaginable; lives change, everyone's going to have a point where they played one year and not the next, most obviously illness, but also life changes like marriage, kids, stressful new job, etc?)


All it takes is someone convincing you to take a travel vacation around Christmas so that you are too busy to be competitive. (And maybe you don’t bother to play if you can’t be competitive at it.) Nothing LIVES CHANGE sized has to be the case...


"I don't know what his total will be when he's finished because life gets in the way. Things happen."

One great golf on another younger great golfer that might catch up to him.


I think it’s interesting how similar a lot of this stuff is to what I do, except I’m not very good at Advent of Code and also I decided to hack Python to do this instead of writing my own language. For example:

* I couldn’t really make operators first class functions, but I just autoimported operator which has this but in words

* I wanted partial application and hated lambda syntax, so I hacked it together with some magic. “_0 + 1” is basically equivalent to lambda x: x + 1

* Python really likes to make everything a free function, which messes with the whole left-to-right thing. So I monkey-patched functional methods onto all the collections

Together this means that if I have like a comma separated list of numbers in str and I want to, idk, count how many are above five I’d do something like

  str.split(",").map(int).filter(_0 > 5).len
which matches how my brain things about it far better than how Python would like me to write it. It uses some tricks but it’s not actually that bad of a hack IMO: https://github.com/saagarjha/advent-of-code/blob/main/aoc.py


reads a bit like javascript now. You might want to consider switching languages


Oh it definitely does. The reason I stick with Python is that it has a somewhat decent standard library, and IMO fewer surprises (I don’t know Python or JavaScript all that well, since I basically never use it professionally, so I need to reduce footguns for myself).


Type cohersion and type juggling can bring a few surprises. But usually only when you do something weird to hit those edge cases. Otherwise, it's standard library is actually very decent now. Especially when working with arrays. You have all the nice to have methods, like, split, find, filter, map, reduce, every, some, etc... Which makes it a really nice language for these kind of tasks.

The example above would like this in modern javascript:

      str.split(',').map(char => Number(char)).filter(num => num > 5).length


Or if you like to live dangerously:

    str.split(',').filter(n => +n > 5).length


Not all that dangerous, tbh. My main concern would be NaN potentially cascading through, but not only is `NaN > 5` false, `!!NaN` is too.


To be clear by “standard library” I mean that Python has like actual data structures that are useful. Also while not part of the standard library arbitrary precision integers and associated functions (e.g. modular pow) can be quite helpful.


Javascript now has Map, Set, and bigint. But no powmod.


Python also has niceties like defaultdict or heapq


Honestly I'm jealous of JavaScript's arrow syntax. I don't really need anything else but the arrows are nice (and Turing complete).


I really wish python would steal 'let' from JS, specifically in a block scoped form - coming from perl's 'my' as a way of life, python's (and ruby's) function level scoping and "surprise! variable just popped into existence on assignment!" behaviour really messed me up.


Can you tell me more about the usefulness of 'let'? I'm an intermediate(?) programmer but I've worked mostly in Python, C and C++. I did one JS project and everything I saw about 'let' seemed redundant.


Firstly (compared to python at least) there's the obvious 'typo in variable name barfs early' advantage, which honestly is not to be sniffed at on its own.

But my big thing is about being able to scope values to the specific block where they belong:

    ...
    let foo;
    {
        let x = blah();
        let y = frob(x);
        foo = munge(y);
    }
and the temporaries 'x' and 'y' have gone away as soon as the } happens.

also, if you've ever had "fun" trying to capture a loop variable,

    for (let val of things) {
      callbacks.push(() => val.finish());
    }
has a different 'val' for each iteration of the loop so you can do that safely without requiring some sort of copying or an intermediate function invocation to create an extra scope.

Oh, and it also renders impossible certain weird constructs like

    if <test>:
        some_name = ...
where now whether the some_name variable even -exists- after that if block is indeterminate without knowing which way the <test> went.

I also like being able to do e.g.

    for (let thing of things) {
        let blah = thing.blah;
        callbacks.push(() => blah.doThing());
    }
Without 'let' you'd have to write (for the first 'for' example)

    for (var val of things) {
      callbacks.push(((val) => {
        return () => { val.finish() }
      })(val));
    }
to perpetrate let-over-lambda to get a separate scope for the callback function.

This is not my favourite thing to write, and -very- far from my favourite thing to read later.

Hopefully that gives you a better idea of why I, at least, prefer having 'let' available.


I always figured that once Python got the walrus operator it would be a matter of time until arrow functions of some sort made more and more sense on Python.


I really hope not tbh, once you break that seal it will consume the entire language into nested anonymous function soup without all the facilities JS has accumulated over the years to mitigate it. Python is an iterator based language and it leads to some very nice code if you design with that in mind.

It's really incredible how much such a small feature in the grand scheme of things `(function() { })()` influences all API and library design. Right now passing around functions in Python is ugly and reads as such which is enough of a deterrent for most people.


Ever played with a more stack-oriented / concatenative / postfix language, like Factor?

  "," split [ string>number 5 > ] count
or

  "," split [ string>number ] map [ 5 > ] count
or

  "," split [ string>number 5 > ] filter length


I think Ruby might be your next language :)

But I do do the following sort of thing in Python: ``` from chainzzz import C

C(some_list).group_by(lambda x: x + 1).map(lambda key: key % 2).size() ```

Where C(...) gives us the same object but with some methods added to it as you can see


Hmm, that's an interesting way of doing it. I'll keep it in my back pocket if they ever break this :)


> The title is clickbait. I did not design and implement a programming language for the sole or even primary purpose of leaderboarding on Advent of Code.

I did: https://github.com/lukechampine/slouch

"Find all ten-letter words that contain each of the letters A, B, and C exactly once and that have the ninth letter K"

  :load wordlist wordlist.txt
  words wordlist | filter -:(len == 10 and .8 == "k")
A more interesting example: https://www.youtube.com/watch?v=i_zDbInYOpQ

AoC solutions here: https://github.com/lukechampine/advent/tree/master/2022

(The language has builtin commands for fetching inputs and submitting solutions)


How does that do the fiddly bit "contain each of the letters A, B, and C exactly once" ?


Shoot, I totally overlooked that part. Here's the proper version:

  :load wordlist wordlist.txt
  =hasABC { all (count _ x == 1) "abc" }
  words wordlist | filter -:(len == 10 and hasABC and .8 == "k")
Notes:

{ } defines a lambda with parameters named x,y,z,a,b,c...

_ is the same as in noulith -- it turns any expression into a lambda. Values can also be omitted from most expressions (e.g. len == 10) for the same effect.

-: takes a lambda with n parameters and turns it into a lambda that takes 1 parameter and replicates it n times.


That is neat!


You should write a longer post about it!


Are you participating in AoC maybe? I might write some stuff.


aight


It definitely has a kind of "I have something I want to do, let me hack at it" feel. Love anything that helps with that. :)

The lambda formation is something that will have to be gnawed at though.


> words wordlist | filter -:(len == 10 and .8 == "k")

This feels very similar to an APL or K solution.

Are those languages you're aware of?


Oh yes -- I've never developed serious competency in them, but Arthur Whitney and Aaron Hsu are among my programming heroes :)


> Before we move on, I want to point out that “being able to write code from left to right without backtracking” is a completely bonkers thing to optimize a programming language for. This should not be anywhere in the top hundred priorities for any “serious programming language”!

There are plenty of serious languages that are written this way. Most noticeably are shell scripting languages but I’ve seen stack based and functional languages that are written like this too.


Yeah, Smalltalk also works this way...mostly.

  3 negated + 2 negated.
Alas, as with the post, this also breaks down when you have more than 2 arguments (or in Smalltalk parlance, 1 argument in addition to the message receiver), as those are handled by keyword arguments and you can't tell where the keywords for one message stop and the ones for the next one start. Let's say we have some nested arrays, which are accessed with at: in Smalltalk:

   array at:4 at:2 at:1.
Alas, that doesn't get interpreted as 3 messages, but as the single message at:at:at:. As it kind of has to be as there is no way to disambiguate. Surprisingly, Smalltalk does have a way to chain messages and thus separate the keywords, the semicolon:

   array at:4;
         at:2;
         at:1.
Alas, this sends the subsequent messages to the original receiver, so it is equivalent to:

   array at:4.
   array at:2.
   array at:1.
(And so this example doesn't actually make sense, it's just a syntax example). So what you have to do is add parens:

   ((array at:4) at:2) at:1.
Hmm, not nice. For Objective-S (https://objective.st), I introduced the pipe for message chaining:

   array at:4 | at:2 | at:1.
One way of looking at this is as a syntactic device that allows left-to-right typing without backtracking, which it is. And that is both nice to write and quite readable, IMNSHO.

A second way of looking at it is as a version of the pipe/filter architectural style, with each message expression being a filter, the results from the filter on the left piped into the filter on the right as the receiver. This is a little bit like |> in some FP languages. But really only a little bit, because in Objective-S this is not the whole story, but just a way of integrating messaging into the way the pipe/filter architectural style is supported at the language level.


I don't really even understand what is meant by "being able to write code from left to right without backtracking”, much less why that is bonkers. Scrolling down and seeing the code examples, the language even seems quite conventional, so I'm even more confused.


Here's an example. In SQL the "select" clause comes at the start. In C# LINQ queries, "select" comes at the end. A major resulting difference is autocomplete works in the latter case but not the former.

A more pervasive example is how OOP languages do x.f(...) instead of f(x, ...). This also helps with autocomplete. Also it results in calls chaining like x.y(...).z(...) instead of nesting like z(y(x(...), ...), ...).

These kinds of things have noticeable effects on how easy it is to discover relevant methods and how fast they are to type.


Isn't that a whole lot more "being able to write code without making a lot of dumb mistakes", which is a feature of the programer.

So it boils down to getting gud and practice?


No, what the author is discussing is a syntax thing.

Lets take a hypothetical C-like language:

    result = C(B(A()))
In this your result is on the left hand side. The first function to be executed is A and then B and lastly C, which also reads right to left. This is a pretty common way to write code, it's by no means unique to C-like languages. So you'd have gotten so good at reading code like this that you probably don't even realise you're reading right to left.

Now lets look at a POSIX-like shell language:

    result = $(A | B | C)
Here the result is still on the left hand side but now you're reading the functions from left to right (ie pipes)

I'm not the article author by my own programming language takes things a step further from conventional shells and you can do the following:

    A -> B -> C -> set result
Here it reads fully left to right. There is zero confusion about which order to read this.

----------------

Going back to the more general point about reading left to right, it's worth noting that even math operators can be extended this way. For example with polish notation (https://en.wikipedia.org/wiki/Polish_notation) your operators precede your values. Effectively turning those symbols into function names:

    + 2 5
...would return 7

Though personally I prefer the more traditional format of operators sitting between their values (2+5), but that's purely because that is what I'm used to.


You can write A | B | C | result=$(cat) in shell.

Edit: this only works if the shell runs the last command in the current environment (as opposed to a subshell), and only ksh and zsh seem to do that...


Or with uniform function call syntax

result = A().B().C()


The post gives an example of how python doesn't have this feature in the section [https://blog.vero.site/post/noulith#coding-with-and-without-...]

Python fails this criteria because if you type as you think through the process, you have to move the cursor to the beginning to prefix the 'map' around the input.

For example this series of transforming the input:

puzzle_input.split("\n\n")

map(ints, puzzle_input.split("\n\n"))

map(sum, map(ints, puzzle_input.split("\n\n")))

max(map(sum, map(ints, puzzle_input.split("\n\n"))))

----------------------

Compare to this postfix syntax where you can write this incrementally as you think through the operations:

puzzle_input split "\n\n" map ints map sum then max


Thanks for pointing out that specific example and comparison. The solution seems like it's just a less readable pipeline.

    puzzle_input split "\n\n" map ints map sum then max;
    puzzle_input split "\n\n" map ints map sum then sort then (_[-3:]) then sum;


after a while, my brain reorganized and i think of the map() first, before thinking about the content of the map, basically to avoid the keystrokes required to go to beginning of line, then back to where I was typing.


That's what it sounds like to me. Thinking about a line of code before writing it down, or knowing what the code will do instead of trial and error (which... is what I often do, lol)


Talking of shell, the preference for left-to-right is often used to justify "useless use of cat", i.e. `cat file | command` instead of `command <file`, but it can just be written `<file command` instead.


Is `<file` shorthand for `cat file`, or is that only true when used in a sub-shell?


In that fun way that shells are, for bare redirection it depends.

In bash/ksh, it matches frou_dh's explanation. In zsh, it depends on how you've set the various *NULLCMD options and parameters. In csh, it is an error.

Even in zsh, when configured to behave like `cat file` it still doesn't quite do that as it won't concatenate files as it only passes the first shell word following the `<` to `cat`(or whatever you've set NULLCMD to). Guess that means you could be evil by making a wrapper that allows weird Unicode separators to pass multiple files in one word to get the concatenation back ;)


Because I hate myself I guess, I use zsh as my interactive shell, but generally write scripts for bash. The latter is mostly for portability (although with Macs stuck on 3.2 unless updated manually, that’s less true over time), and also because shellcheck doesn’t work on zsh.

This means I get to remember an increasingly unfortunate amount of differences. I’ll add this one to the pile, thanks.


I've mentioned this in previous posts here, but I write zsh scripts utilising all the extras that zsh gives(alternate form syntax, extensive extended globbing, zsh module support for extra functionality). It sounds stupid but it makes it really obvious that what you're writing isn't a generic shell script, and you can forgo having to think too much about whether something will work in modern bash, or MacOS pre-GPL3 bash, or {b,d,k,z}ash as /bin/sh symlink.

I definitely get the argument about shellcheck though, it would be nice if it supported zsh.


That’s fair. I wrote a script at work recently for zsh that relied on some of its niceties, like glob qualifiers. The reason for using zsh was we only use Macs, so it was reasonable to assume everyone would have a decently modern version of zsh available.

It definitely looks foreign, but then, so does parameter substitution in bash or zsh. There are shell scripts, and then there are those that make you break out the man pages to figure out what they’re doing.


I guess it's equivalent to `: <file` i.e. hooking up the stdin of the no-op command.


Doesn't seem to be the case. For example, `< file cat` shows the file, whereas `: < file cat` or `: < file | cat` does nothing.


Not sure what's surprising you in those snippets. `:` itself is a command and it does nothing with its arguments or stdin.

https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V...


I know. The only thing surprising me is your previous claim that `< file` is equivalent to `: < file`.


My interpretation of sgarland's question was that he meant `<file` being the entirety of the statement, i.e. typing only that at a shell prompt and pressing return.


Ah. Now I understand where the misunderstanding was. It confused me that we were talking about redirection as a replacement for useless use of cat.


Yep I can appreciate the misunderstanding. For completeness of the discussion, I think where sgarland mentioned a sub-shell, he was possibly referencing the Bashism `$(<` detailed on this page https://www.gnu.org/software/bash/manual/html_node/Command-S... in addition to what I called a "statement" above.


Correct, that’s what I had in mind.


No, `< file command` is completely equivalent to `command < file`.


I'll often commit 'useless use of cat' there for three reasons:

1) Muscle memory

2) Often when I don't I end up needing two files later

3) It annoys merlyn (Randal Schwartz)


I'm surprised that file> command isn't the preferred idiom. Does that mean something else?


That would execute the command `file` and put the output into the file `command`.


Oh, now I re-read it - of course! Hah.


Yes, that would write to a file called "command". So the correct way to write that would be:

    command > file
https://www.gnu.org/software/bash/manual/html_node/Redirecti...


The syntax reads similar to R with pipes


One key point I overlooked on first reading:

> [--] I wanted access to Haskell’s list monad in a sloppier language.

> I like static types, but only if they’re sufficiently expressive and supported by good inference, and I like not having to implement any of that stuff even more, so I settled for dynamic typing.

Then it's some of the power of Haskell without any of the safeguards. Plus being able to write "x f y" to mean a function call to f with arguments x and y (whereas in Haskell you'd write "x `f` y").


Inspired me to have another run at the second half of Crafting Interpreters.


> I solve and write a lot of puzzlehunts, and I wanted a better programming language to use to search word lists for words satisfying unusual constraints, such as, “Find all ten-letter words that contain each of the letters A, B, and C exactly once and that have the ninth letter K.”

So... Perl?


    words←⊃⎕nget 'wordlist.txt' 1        ⍝ read lines
    tenK←'^........k.$' ⎕S '&' ⊢ words   ⍝ regex filter
    ↑tenK/⍨{∧/1='abc'(+/∘.∊)⍵}¨tenK      ⍝ count abc and filter
Dyalog APL, but it's still kinda ... heavy.


> tenK←'^........k.$' ⎕S '&' ⊢ words ⍝ regex filter

Is tenK←{(10=≢⍵)∧⍵[9]='k'}¨words better?

Feel that better matches the problem spec, although requires the each


⍵[9] INDEX ERRORs on the shorter words, and that code needs a compress to do the filtering. It's a more traditional and faster approach, which is better from those viewpoints. If faster and avoiding each and using array concepts is desirable, how about:

    words←↑⊃⎕nget 'wordlist.txt' 1 
    tenK←words⌿⍨('k'=words[;9])∧(' '≠words[;10])∧(' '=words[;11])
    tenK⌿⍨{∧/1='abc'(+/∘.∊)⍵}⍤1⊢tenK
Mix ↑ turns the nested wordlist into a 2D array by padding short strings out to the length of the longest using spaces, which I don't like but am (ab)using here to tell when the words end.


  W in Wordlist,
  length(W,10),
  maplist(\L^memberchk(L,W),[a,b,c]),
  nth(10,W,k).


I think memberchk will do one check for each letter (not checking the rest of the list and leaving no choice point) rather than checking each letter is present only once.


Oh, yeah, my brain skipped over that part of the constraint. Does ruin the simplicity a bit.

  (findall(C,member(L,W),S),length(S,1))


    sls '^........k.$' .\wordlist.txt -raw |where { $W=$_;
      ('a','b','c' |where
         { $W.Contains($_) -and $W.IndexOf($_) -eq $W.LastIndexOf($_) }
       ).Count -eq 3 }


    grep { len($_) == 10 && /^[^a]*a[^a]*$/i && /^[^b]*b[^b]*$/i && /^[^c]*c[^c]*$/i && /k.$/i } @words;
Is there a simpler way?


Might as well do it on the command line at that point:

    $ grep '^........k.$' /usr/share/dict/words | grep a | grep b | grep c | grep -v 'a.*a' | grep -v 'b.*b' | grep -v 'c.*c'
    backstroke
    bailiwicks
    benchmarks
    branchlike
    bushwhacks
    greenbacks
    matchbooks
    piggybacks
    roadblocks
    scrapbooks
    slingbacks
    throwbacks
    thumbtacks


  [me@fedora ~]$ time perl -n -e 'length($_) == 10 && /^[^a]*a[^a]*$/i && /^[^b]*b[^b]*$/i && /^[^c]*c[^c]*$/i && /k.$/i && print' /usr/share/dict/words | wc -l
  26

  real 0m0,168s
  user 0m0,162s
  sys 0m0,007s


  [me@fedora ~]$ time perl -n -e '/^(?=.{8}k.$)(?=[^a]*a[^a]*$)(?=[^b]*b[^b]*$)(?=[^c]*c[^c]*$)/ && print' /usr/share/dict/words | wc -l
  17

  real 0m0,260s
  user 0m0,254s
  sys 0m0,006s


  [me@fedora ~]$ time perl -n -e '/^.{8}k.$/ && /a/ && /b/ && /c/ && !/a.*a/ && !/b.*b/ && !/c.*c/ && print' /usr/share/dict/words | wc -l
17

  real 0m0,115s
  user 0m0,109s
  sys 0m0,008s


  [me@fedora ~]$ time bash -c "grep '^........k.\$' /usr/share/dict/words | grep a | grep b | grep c | grep -v 'a.*a' | grep -v 'b.*b' | grep -v 'c.*c'" | wc -l
  17

  real 0m0,015s
  user 0m0,010s
  sys 0m0,020s

  [me@fedora ~]$ time awk '/^.{8}k.$/ && /a/ && /b/ && /c/ && !/a.*a/ && !/b.*b/ && ! /c.\*c/ { print }' /usr/share/dict/words | wc -l
  17

  real 0m0,129s
  user 0m0,124s
  sys 0m0,006s

  [me@fedora ~]$ time perl -n -e 'local $_ = lc; length == 11  && substr($_,8,1) eq "k" && join("",sort [/([abc])/g]->@*) eq "abc" && print' /usr/share/dict/words | wc -l
17

  real 0m0,234s
  user 0m0,228s
  sys 0m0,007s


You can also do:

    sed -e '/^........k.$/!d; /a/!d; /b/!d; /c/!d; /a.*a/d; /b.*b/d; /c.*c/d' /usr/share/dict/words 
I wouldn't expect it to be much faster than AWK, but not much slower either.

Interesting that the pipeline is the fastest! I would expect the sheer number of separate processes to slow things down considerably, but apparently it doesn't really matter. Probably because the first `grep` already filters out most of the dictionary, so there isn't a lot of I/O through the pipes.


> Interesting that the pipeline is the fastest! I would expect the sheer number of separate processes to slow things down considerably, but apparently it doesn't really matter. Probably because the first `grep` already filters out most of the dictionary, so there isn't a lot of I/O through the pipes.

The IO doesn’t matter so much because they’re done in parallel. Whereas the other examples have to filter each word throw the entirety of their steps before they can proceed onto the next word.


  [me@fedora ~]$ time sed -e '/^........k.$/!d; /a/!d; /b/!d; /c/!d; /a.*a/d; /b.*b/d; /c.*c/d' /usr/share/dict/words | wc -l
  17

  real 0m0,075s
  user 0m0,070s
  sys 0m0,006s


The pipeline wasn't just the fastest; it left everything else in the proverbial dust!


Bug fixed.

  [me@fedora ~]$ time perl -n -e 'length($_) == 11 && /^[^a]*a[^a]*$/i && /^[^b]*b[^b]*$/i && /^[^c]*c[^c]*$/i && /k.$/i && print' /usr/share/dict/words | wc -l
  17

  real 0m0,160s
  user 0m0,153s
  sys 0m0,008s


Not sure if it would be considered simpler but lookaheads can be used to express it in a single pattern.

    ^(?=.{8}k.$)(?=[^a]*a[^a]*$)(?=[^b]*b[^b]*$)(?=[^c]*c[^c]*$)


It does lead to a very concise invocation:

    $ perl -ne 'print if /^(?=.{8}k.$)(?=[^a]*a[^a]*$)(?=[^b]*b[^b]*$)(?=[^c]*c[^c]*$)/' /usr/share/dict/words


  [me@fedora ~]$ time perl -ne 'print if /^(?=.{8}k.$)(?=[^a]*a[^a]*$)(?=[^b]*b[^b]*$)(?=[^c]*c[^c]*$)/' /usr/share/dict/words | wc -l
  17

  real 0m0,250s
  user 0m0,245s
  sys 0m0,006s


and to squash a little further:

    perl -ne '/^(?=([abc].*){3})(?!.*([abc]).*\2).{8}k.$/&&print' /usr/share/dict/words


Bug: 11 words are missing.

  [me@fedora ~]$ time perl -ne '/^(?=([abc].*){3})(?!.*([abc]).*\2).{8}k.$/&&print' /usr/share/dict/words | wc -l
  6

  real 0m0,118s
  user 0m0,112s
  sys 0m0,007s


Perl has `length`, not `len` :) Also I can't resist a bit of TIMTOWTDI:

    my @found = grep {
        local $_ = lc;
        length         == 10  &&
        substr($_,8,1) eq "k" &&
        join("",sort [/([abc])/g]->@*) eq "abc"
    } @words;


I'm not a competitive Advent of Coder, but as a corporate trainer, I appreciate it for giving me insights into my coding style. Hopefully, I can use those insights for my students.

The last time I did AOC, I tracked every error I made to reflect on my mistakes when coding.

I think this year I will do "Advent of AI" and see how far AI tooling can get me.


Amazing, the cross inspiration from python and rust is cool. Good work!


Why are people so insistent on having unary minus? In my experience, "0-whatever" is just as good as "-whatever" except in a single case where you're trying to write an INT_MIN but then again, unary minus doesn't help you in this case either — unless you, as e.g. SML does, make the unary minus the lexical part of the number itself. However, SML has to actually use the tilde "~" instead of the minus for the negation because otherwise "1 -2" would be ambiguous.

IIRC Elm didn't have unary minus for quite some time just fine unless its author decided he really would like to have specifically unary minues but, weirdly, not any other unary operator. So, what's the deal with unary minus, why do people want it so much?


Such a fantastic and inspiring post, and a very cool language. Kudos!


In terms of speedrunning, couple of friends and I are trying to speedrun AoC using LLMs (sacrilegious i know).

It’s been growing a bit; if interested, feel free to shoot me an email to joshcho@stanford.edu

Or @eating_entropy on X




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: