Advent of Code with Emacs Lisp — Day 1
Intro
Today is that time of the year! The 1. Advent and with it we have got yet
another Advent of Code, a set of programming puzzles made by Eric Wastl, released one at a day as an advent calendar.
Since its initial release in 2015, the event has become quite popular, each year there seem to be hundreds if not thousands of programmers solving those puzzles. The popularity is not unsurprising. The puzzles are presented with a witty humorous tone, and offer somewhat varying technical difficulty, so everyone can find something interesting. If you like solving puzzles and riddles and have a few minutes a day, I suggest taking a look and solving at least some. Either as a programming practice, or just a time pass, you can solve puzzles from previous years, or if you are in competitive mode, be ready each day from 1st to 25th December.
If you are looking for solutions to puzzles, you might be disappointed, this text is more about a few thoughts I got while answering a question in a Reddit forum for Emacs users, and weathering those thoughts, than about puzzles itself. I will spend a few words about the puzzle for the day 1, the solution being uploaded to a GitHub repo, but this is more about some other thoughts.
Can I use ELisp (or language X) to solve puzzles?
Someone asked if they could solve AoC puzzles with Emacs Lisp, or should they choose some other programming language. The answer is, as long as you can read a file (needed to read your puzzle input), and you can perform a computation, you can solve AoC puzzles. That qualifies probably any programming language, as a possible language to solve AoC puzzles with. In my personal opinion, due to being a text processing language, Emacs Lisp is rather well suited for solving those puzzles which come as textual input.
As a more general answer, if you are using AoC puzzles to practice programming in some language X, then, of course, use that language. If your goal is to just solve some entertaining puzzles, and do not care about the programming language used, then I suggest using any “RAD” language with a good debugger. Of course, if you are an experienced coder you use what you prefer, you don’t need this advice at all.
Using Emacs and Emacs Lisp to solve puzzles
My personal choice is GNU Emacs as the text editor and its built-in scripting Lisp dialect, Emacs Lisp, to solve those puzzles. The language and the debugger are tightly integrated with the text editor which means I can use text editor itself to solve the domain problem (the puzzle). Built-in debugger, Edebug, let me step through the code while at the same time I can see cursor as it moves through the input file in separate buffer, and potentially see output in yet another buffer. Everything is also very dynamic, introspective and interactive when it comes to debugging, and as a bonus, docs for both variables and functions for needed libraries and tools are easy to lookup directly in the editor, which is very nice and minimizes switching between the editor and browser. Another plus is that Emacs is really much more than just a text editor. The Lisp machine it is, it acts as a shell into the operating system, and lets you automate a lot of work.
In this regard, I have made a small script to help me generate a file and some code skeleton for each solution and to help me download the input file, all from a single command in Emacs. The codegen is just for Emacs Lisp, but it wouldn’t be too much work to make it generate code for some other programming language. However, since this is just recreation in Emacs Lisp, I’ll leave that to interested parties. The code is free to use and modify, just read the docs in the comment at the top of the file. My personal solutions are on GitHub as well.
Day 1 Solution
About the first-day challenge, I have found it to be rather simple, but I see some posts on Reddit about some struggles. Perhaps it was a choice of the
language, or I just got lucky to think in the right direction and right terms,
I don’t know, but I have found the first day to be quite easy.
As I understand it, it is about finding a sub-string in a string. They did a little twist and made you search for two strings, one from the beginning of the line, and one from its end. You will have to search for simple strings of one character, digits 1 through 9, for the first part, or slightly longer words, one, two, three, and so on for the second part. Once you have both digits you have to convert them into a two-digit number and sum all those numbers. Since Emacs Lisp is rather good at text processing, with built-in regular expressions this is relatively simple to do.
Emacs Lisp is usually quite self-documenting as code, to the point it almost feels like pseudo-code:
(doit (rgx)
(let ((digits nil) (acc 0))
(goto-char (point-min))
(while (re-search-forward rgx (line-end-position) t)
(cl-incf acc (* 10 (match-to-digit (match-string 0))))
(goto-char (line-end-position))
(re-search-backward rgx (line-beginning-position))
(cl-incf acc (match-to-digit (match-string 0)))
(forward-line))
acc))
The meat of the work is this little function “doit”. We pass it a regular expression to search for, and it searches from the beginning of the line, and than from the end of the line and do appropriate summing in the accumulator which is returned as the result.
We need to convert string from the text buffer (input file) into numbers on which Emacs can perform the calculation. Since we are searching for either a number in its digit form like “1” or as a word, like “one”, we need to know which one we have found. We can do it with simple pattern matching for which Emacs as something called “pcase” which stands for “pattern case”. We have wrapped it in a helper routine “match-to-digit” so we can call it from different places:
(match-to-digit (in)
(string-to-number
(pcase in
("one" "1") ("two" "2") ("three" "3") ("four" "4") ("five" "5")
("six" "6") ("seven" "7") ("eight" "8") ("nine" "9") (_ in))))
We match the word to its corresponding digit in each case. The little (_ in) will match anything, which means it will return the “in” string unchanged, if it does not match any other case. We convert it to a number with “string-to-number”. This is basically it. Those interested in all the details, may take a look at the solution in the GitHub repository.