The Advention of Coding: Advent of Code Solutions in the Wolfram Language
Editor’s note: The following post is based on the 2020 Advent of Code challenge, “an Advent calendar of small programming puzzles for a variety of skill sets and skill levels that can be solved in any programming language you like.” Guest author Philip Maymin delves into why he chose to use the Wolfram Language to solve these queries with a how-to on learning the language.
First, an Objection
“Advention,” the purists among you might quibble, is not really a word:
Engage with the code in this post by downloading the Wolfram Notebook
✕
DictionaryWordQ["advention"] |
“Stop. What the [heck] are you talking about? Get that pretty word out of your mouth.” You may feel an urge to aggressively sing this to the tune of a certain Billie Eilish song, like a furious, squiggly red spellcheck line. But you shouldn’t! Her hit song is not actually about censorship. What is she, in fact, singing about? There has never been a better use case of advanced neural network technology:
✕
FindTextualAnswer[ WikipediaData["Therefore I Am (song)"], "What is she singing about?"] |
Indeed, a little sleuthing goes a long way in finding workable definitions of rare words:
✕
ResourceFunction["HighlightText"][ First@WebSearch["advention definition", "Snippets", MaxItems -> 1], "approach"] |
The Advent of Code by Eric Wastl is a delightful annual collection of small programming puzzles released on a daily basis each holiday season. Past questions and solutions in various programming languages are available online. For example, Michael Hale provides a wonderful set of solutions using the Wolfram Language in a featured Wolfram Community post.
But in this blog post, our goal isn’t a comprehensive set of solutions, but rather a showcase of an approach (ahem: advention) to how one might solve such delightful puzzles. The approach itself and the lessons and takeaways can be helpful in using the Wolfram Language for any task in any season.
The 2009 Ricky Gervais film The Invention of Lying imagined a world where only one person had the ability to say something other than the truth. Let’s imagine a world where you are the only person who has the ability to code. You can’t ask anyone for help. You can’t search the web for answers, only for memes.
✕
First@WebImageSearch[{"sportsmanlike", "alone"}, "Images", 1, Method -> "Google"] |
Let’s go through a few of the first Advent of Code puzzles, not to see what the final answer ultimately is, but to find out what the actual day-to-day process of programming in the Wolfram Language looks and feels like.
Video Killed the Programming Star
Before we even begin, how do we plan to record our screen? Sure, we could use general screen-recording software, but those record video, track the mouse and generally take up too much space. Really, all we need is some way to take a snapshot of the screen every few seconds.
That may ring some bells for you. Do you hear a ringing in your ears? You should probably get it checked out. But if thoughts like ScheduledTask or CurrentScreenImage penetrated your brain, welcome to the Wolfram headspace!
The following function will export a screen image to a specified directory. The image will be named based on the UnixTime timestamp, which is a standard concept defined as the number of seconds since January 1, 1970, in the Greenwich Mean Time time zone. The Wolfram AbsoluteTime timestamp is the number of seconds since January 1, 1900, in your local time zone, and is usually better for most things, but the Unix one is a shorter integer so it makes for a shorter file name.
✕
ExportScreenImage[dir_ : $HomeDirectory, fn_ : UnixTime, fmt_ : ".png"] := Export[FileNameJoin[{dir, ToString@fn[] <> fmt}], CurrentScreenImage[]] |
Are you a fan of recursion? If not, what about recursion? I heard you like recursion, so I put some recursion into your recursion:
✕
s = {}; Dynamic[s] |
✕
Do[AppendTo[s, Import@ExportScreenImage[]], 4] |
Now let’s run it in our session automatically every 15 seconds. This runs unobtrusively.
✕
SessionSubmit[ ScheduledTask[ExportScreenImage[], Quantity[15, "Seconds"]]]; |
This also highlights our first takeaway: follow your dreams! If certain words or phrases come to mind, if you hear whispers of possible Wolfram functions you may have once heard about, pull at that thread. Maybe you only remember CurrentImage, and you look up the help and see that that takes a snapshot from your webcam. Do not despair! The See Also section near the bottom of the function documentation page is your friend.
“Me No Want Cookie!”
One last preliminary note before our advention begins. Many sites, and the Advent of Code is one such example, require cookies or logins or other customized clickareedoo before they give up their secrets, so a plain vanilla Import may not always save the day. There are various ways of dealing with these cookie monsters, but perhaps the most general, fun and easy is to use WebExecute.
WebExecute is a powerful function that opens up a browser that can be programmatically controlled. You can tell the browser what elements to click, or you can interact with the browser manually, log in and then still be able to control it further.
In the timelapse programming video created from our automated snapshots, you can see an initial attempt to solve the first problem right away is interrupted when there’s no easy automated way to get the necessary input data. (Saving and then importing a file? Too manual!)
WebExecute lets us log in to the website and then get the input data file, stripped of the little bit of HTML that’s there:
✕
GetAOCInput[url_] := StringReplace[ Last@WebExecute[session, {"OpenPage" -> url, "PageSource"}], "<" ~~ Except[">"] .. ~~ ">" -> ""] |
There was a secret little second takeaway in that previous code. Did you spot it?
The documentation for WebExecute lists a lot of types of commands you can pass into the browser, such as navigating to a different page, clicking an entity or taking a snapshot of the window. But how do you get the actual raw source of the displayed page? You can always use some JavaScript to get to it, but it turns out there’s an undocumented command called "PageSource". How can you possibly guess that? All you have to do is ask yourself WWWD: What Would Wolfram Do? You see there are commands like "PageURL" and "PageTitle" and you know such text commands follow a standard pattern in the Wolfram Language. The lesson is: try just typing what you wish would give you the answer! The Wolfram Language is consistent and predictable, so your guess could just work out.
Backpack! I’m Not the Map!
The first Advent of Code puzzle involves finding two numbers from a long list that add up to a certain target. Using rule #1 shown previously, this might trigger thoughts of knapsacks with you. The knapsack problem along with the traveling salesman problem and others are famous computer science problems because they seem to require a comprehensive search to answer. If any general fast solution were found, that would mark the single greatest advance in humanity of all time, yet, ironically, heuristics exist to find quick solutions in most real-world cases.
So, if you type “knapsack” and hit your favorite F1 key to bring up the Wolfram help, the first result is KnapsackSolve. Most likely you’ve never used this function before. We are about to learn that it is a magical piece of luggage, much like the purple backpack that Dora the Explorer carries with her on her adventures. What does her backpack provide?
✕
FindTextualAnswer[ Import["https://dora.fandom.com/wiki/Backpack"], "What does the \ backpack provide?"] |
You can see in the video that a lot of “coding” time in the Wolfram Language is actually spent reading the help files. There are several advantages to this. First, the problem you face has likely already been solved, so why rewrite it? Often, if it’s not immediately solved by a built-in function, there’s a second or third optional parameter that will get the job done. Second, every minute you spend reviewing the Wolfram help documentation is an hour of increased human capital. It will prime your memory for the future, and it will teach you new concepts. It’s a win-win! Whereas with other programming languages you may spend days looking at and comparing different confusingly named libraries only to find that they will become obsolete in a year or two, rendering all your learning useless, with the Wolfram Language everything you learn will continue to be useful forever. Notebooks in any previous version will always run, and any changes or later improvements to functionality will always be well spelled out.
In any case, it turns out here that we need the fifth way of calling KnapsackSolve to solve the first Advent of Code problem by specifying the maximum payoff, cost and item count:
✕
With[{vals = {1721, 979, 366, 299, 675, 1456}}, Pick[vals, KnapsackSolve[{#, 1, 1} & /@ vals, {2020, 2020, 2}], 1]] |
Unfortunately, it doesn’t seem to work for the second part of the problem, in which we are looking for exactly three numbers that add up to a target. The reason it doesn’t work is—it is too good! It is able to solve the associated knapsack problem with just two numbers, and there’s no built-in parameter to tell it to do worse than it possibly can. So we need a different approach.
In this case, that different approach is much simpler: just take all the subsets of length 3 from the list of integers and check them each in turn to see if they add up to what you want:
✕
Select[Subsets[{1721, 979, 366, 299, 675, 1456}, {3}], Total@# == 2020 &] |
It works fine in this case, and of course for the simpler case of pairs of numbers, but if the list of integers became much longer, the KnapsackSolve solution for pairs would be far more efficient.
“Now This Looks Like a Job for Me, so Everybody Just Follow Me”
If you were to code like Eminem raps, you’d go super fast. So check out the timelapse video for some of the other questions. Since it’s one screenshot every 15 seconds, it takes less than a minute of video, or about 10 to 15 minutes of wall time, to casually solve each of the first few Advent of Code problems, after the initial part figuring out WebExecute and similar helper functions.
What are some other general lessons from this kind of quick coding exercise?
- Learn groups of functions at a time. If you were to resolve in the new year to learn one Wolfram Language function per day, you’d fall further and further behind! Recently, Wolfram has started to release more than 365 new functions each year, so at the rate of one function a day, you can never catch up. Instead, try to explore entire groups of functions. They are conveniently named, so one day you might explore all Image* functions, or all Audio* or Video* functions. The guides and tutorials are excellent and consistent.
- String functions are worth their weight in gold. If you were to only learn one group of functions, String* functions would be the way to go. They are far and away the most useful functions for cleaning and dealing with text, and text is the most common kind of data you’ll encounter. There are many convenient variants, from StringReplace to StringSplit to StringTake, as well as Characters and LetterCounts and more.
- Loop not lest ye be looped. You’ll notice not a single for, do or while loop in any of the Advent of Code solutions here or anywhere in the Wolfram Language. They occasionally have their uses, but the more “Wolframic” way of thinking is to use Map, Apply, Table and powerful function families like Fold, Nest and FixedPoint.
- Associations and rules are powerful. Particularly for question 4 of the Advent of Code, which has weird custom validation rules for different parts of a passport, having a list of those rules or patterns in a single entity makes debugging and applying it much easier. In the Wolfram Language, “data” and “code” are the same thing; use that to your advantage.
- Use simple idioms. How might you see if a list contains some number of copies of a single item? There’s approximately one and a half billion ways of doing this, but one simple way is to take the Union of the list and see if it’s equal to a list containing that one single element.
You can find the fast-paced video here, and the notebook that was created in that video is available here.
You might want to try out some of the puzzles yourself before searching out solutions. They’re a great exercise both in learning the Wolfram Language and in learning how to learn the Wolfram Language. One fun general insight you’ll get is this: many of the puzzles can be solved with a single call to a built-in function, and almost all can be solved in a single line of code. The amazing thing about the Wolfram Language is that that insight applies not only to programming puzzles, but to real-world tasks as well!
Guest author Philip Maymin is a professor of analytics and the director of the Master of Science in Business Analytics program at Fairfield University’s Dolan School of Business in Connecticut. He is also the founding managing editor of Algorithmic Finance, cofounder and co-editor-in-chief of the Journal of Sports Analytics and COO/CTO for Swipe.bet, which runs on the Wolfram Language.
For more fun in the Wolfram Language, check out Ultra Rapid Coding on Twitch or Philip Maymin’s Wolfram Community post on the topic. |
Comments