<?xml version="1.0" encoding="UTF-8"?><feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://neilashford.dev/</id>
    <title>Neil Ashford</title>
    <updated>2021-12-08T00:00:00Z</updated>
    <link href="https://neilashford.dev/atom.xml" rel="self"/>
    <link href="https://neilashford.dev/"/>
  <entry><id>https://neilashford.dev/story/sudoku-01.html</id><title>A Path to a New Sudoku Algorithm</title><link href="https://neilashford.dev/story/sudoku-01.html"/><published>2021-12-08T00:00:00Z</published><updated>2021-12-08T00:00:00Z</updated><author><name>Neil Ashford</name></author><content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><main><h1>A Path to a New Sudoku Algorithm</h1><p><em>08/11/2021</em></p><br /><blockquote><p>Warning: This one's a pretty long read. I might add a table of contents to my blog generator one day, but don't hold out hope.</p><p>Sorry.</p></blockquote><p>You're all familiar with <a href="https://en.wikipedia.org/wiki/Sudoku">Sudoku</a>, the puzzle game? You're given a partially filled in grid of numbers, something like this:</p><pre><code>+-----+-+-----+-+-----+
|     | |9    | |2 1 8|
|  7  | |    4| |     |
|2    | |  3  | |  4  |
+-----+-+-----+-+-----+
|9 2  | |     | |8    |
|  1  | |  6  | |  7  |
|    8| |     | |  9 4|
+-----+-+-----+-+-----+
|  3  | |  5  | |    9|
|     | |7    | |  2  |
|5 9 4| |    2| |     |
+-----+-+-----+-+-----+
</code></pre><p>And then you need to turn it into a fully filled in grid of numbers, like this:</p><pre><code>+-----+-+-----+-+-----+
|3 4 5| |9 7 6| |2 1 8|
|8 7 9| |2 1 4| |3 5 6|
|2 6 1| |5 3 8| |9 4 7|
+-----+-+-----+-+-----+
|9 2 7| |1 4 5| |8 6 3|
|4 1 3| |8 6 9| |5 7 2|
|6 5 8| |3 2 7| |1 9 4|
+-----+-+-----+-+-----+
|7 3 2| |4 5 1| |6 8 9|
|1 8 6| |7 9 3| |4 2 5|
|5 9 4| |6 8 2| |7 3 1|
+-----+-+-----+-+-----+
</code></pre><p>Where the filled-in grid follows a few rules.</p><ul><li><p>Each 3×3 box in the Sudoku needs to contain the digits 1–9;</p></li><li><p>Each row in the Sudoku needs to contain the digits 1–9; and</p></li><li><p>Each column in the Sudoku needs to contain the digits 1–9.</p></li></ul><p>Sudoku is a very popular puzzle game, and a lot of very smart people have come up with algorithms to solve Sudokus either by hand or programmatically. When I decided I wanted to make my own solver, I started looking into those algorithms a lot. I nearly ended up just re-implementing an existing algorithm, but instead I ended up having a very interesting idea. I can't say that the idea I had was a particularly good one, and I haven't noticed anything spectacular in the performance of the solver that it led to, but it was a fun adventure.</p><p>What I'm about to describe isn't something I've seen before in other solvers, and so I hope that means I've come up with something unique. However, Sudoku solving algorithms are hardly a small or unexplored field, so it's quite possible that everything I'm about to do has already been done by someone else. If you can help direct me to a blog post or repo or person that's done this before, by all means go ahead, I'd be keen to see it.</p><h2>Mainstream Solvers</h2><p>Before we get into what I came up with, I want to quickly summarise how regular Sudoku solving algorithms work. This is partly so I can write some comparisons with them later<sup id="footnote-ref-0"><a href="#footnote-0">0</a></sup>, but it's also because I did a lot of research into this before swapping over to my current algorithm and I don't want that knowledge to go to waste. So with that said, and at risk of sounding a lot like my algorithms lecturer from uni, there are two key facts about Sudoku as a puzzle that influence how most solvers are designed.</p><ol><li><p>Sudoku (with an N×N grid, instead of 9×9) fits into the "Nondeterministic Polynomial" (<span><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi><mi>P</mi></mrow><annotation encoding="application/x-tex">NP</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height: 0.6833em;"></span><span style="margin-right: 0.1389em;" class="mord mathnormal">NP</span></span></span></span></span>) complexity class of problems. That is to say, if you had a computer with infinite CPU cores<sup id="footnote-ref-1"><a href="#footnote-1">1</a></sup>, you could solve a Sudoku puzzle in polynomial time.</p></li><li><p>Any other <span><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi><mi>P</mi></mrow><annotation encoding="application/x-tex">NP</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height: 0.6833em;"></span><span style="margin-right: 0.1389em;" class="mord mathnormal">NP</span></span></span></span></span> problem can be transformed into a Sudoku (again the N×N variant) and back again in polynomial time, with the solution to the Sudoku being transformed into a solution for the original problem.</p></li></ol><p>The first of these properties is reasonably simple, at least compared to the other — you could write a polynomial time algorithm to see if an assignment of digits is valid, and then just get each core to pick a different permutation of the 81 digits to test, reporting back when it's done.</p><p>The second property is incredibly complex. There is a small group of problems that have been discovered with this property, but it's only ever been directly proven for one problem (that I know of). Everything else, including N×N Sudoku, is shown to have this property transitively: you can reduce any <span><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi><mi>P</mi></mrow><annotation encoding="application/x-tex">NP</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height: 0.6833em;"></span><span style="margin-right: 0.1389em;" class="mord mathnormal">NP</span></span></span></span></span> problem to <span><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi></mrow><annotation encoding="application/x-tex">x</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height: 0.4306em;"></span><span class="mord mathnormal">x</span></span></span></span></span>, and you can reduce <span><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi></mrow><annotation encoding="application/x-tex">x</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height: 0.4306em;"></span><span class="mord mathnormal">x</span></span></span></span></span> to an N×N Sudoku, therefore you can reduce any <span><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi><mi>P</mi></mrow><annotation encoding="application/x-tex">NP</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height: 0.6833em;"></span><span style="margin-right: 0.1389em;" class="mord mathnormal">NP</span></span></span></span></span> problem to an N×N Sudoku.</p><p>If you know both of these properties about a problem, it gives you a shortcut solution that you can use instead of writing your own algorithm. A problem with both of these properties is called "<span><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi><mi>P</mi></mrow><annotation encoding="application/x-tex">NP</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height: 0.6833em;"></span><span style="margin-right: 0.1389em;" class="mord mathnormal">NP</span></span></span></span></span> Complete" and any <span><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi><mi>P</mi></mrow><annotation encoding="application/x-tex">NP</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height: 0.6833em;"></span><span style="margin-right: 0.1389em;" class="mord mathnormal">NP</span></span></span></span></span> Complete problem can be solved by transforming it into a different <span><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi><mi>P</mi></mrow><annotation encoding="application/x-tex">NP</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height: 0.6833em;"></span><span style="margin-right: 0.1389em;" class="mord mathnormal">NP</span></span></span></span></span> Complete problem without any real performance loss.</p><ul><li><p>Property 1 of Sudoku, combined with property 2 of our target <span><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi><mi>P</mi></mrow><annotation encoding="application/x-tex">NP</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height: 0.6833em;"></span><span style="margin-right: 0.1389em;" class="mord mathnormal">NP</span></span></span></span></span> Complete problem, shows that the transformation is possible in polynomial time.</p></li><li><p>Property 2 of Sudoku, combined with property 1 of our target <span><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi><mi>P</mi></mrow><annotation encoding="application/x-tex">NP</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height: 0.6833em;"></span><span style="margin-right: 0.1389em;" class="mord mathnormal">NP</span></span></span></span></span> Complete problem, shows that the solution for our target problem is most likely at least as fast as the solution for any direct Sudoku algorithm, as you could always solve it by transforming it back at only polynomial cost.</p></li><li><p>The unproved yet quite likely hypothesis that <span><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>P</mi><mo stretchy="false">!</mo><mo>=</mo><mi>N</mi><mi>P</mi></mrow><annotation encoding="application/x-tex">P != NP</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height: 0.6944em;"></span><span style="margin-right: 0.1389em;" class="mord mathnormal">P</span><span class="mclose">!</span><span class="mspace" style="margin-right: 0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right: 0.2778em;"></span></span><span class="base"><span class="strut" style="height: 0.6833em;"></span><span style="margin-right: 0.1389em;" class="mord mathnormal">NP</span></span></span></span></span> implies that the actual cost of solving the <span><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi><mi>P</mi></mrow><annotation encoding="application/x-tex">NP</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height: 0.6833em;"></span><span style="margin-right: 0.1389em;" class="mord mathnormal">NP</span></span></span></span></span> Complete problem is way higher than the cost of the polynomial transformation between the problems, so the overhead isn't worth considering.</p></li></ul><p>And all of this combines with the fact that the poster child of <span><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi><mi>P</mi></mrow><annotation encoding="application/x-tex">NP</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height: 0.6833em;"></span><span style="margin-right: 0.1389em;" class="mord mathnormal">NP</span></span></span></span></span> Complete problems, <a href="https://en.wikipedia.org/wiki/Boolean_satisfiability_problem">Boolean Satisfiability (SAT)</a>, has been solved by dozens of hyper-optimised engines that are almost always going to be faster than anything you write for yourself, and you're left with a neat solution to any <span><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi><mi>P</mi></mrow><annotation encoding="application/x-tex">NP</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height: 0.6833em;"></span><span style="margin-right: 0.1389em;" class="mord mathnormal">NP</span></span></span></span></span> Complete problem you're stuck with. You transform it into a SAT problem, you run the off-the-shelf SAT solver on it, and you transform your solution back.</p><p>And as far as I can tell, this is how most Sudoku-solving algorithms work today. There's some caveats where you might try to write a custom SAT solver that's more tuned to the exact type of SAT problems your Sudoku turns into, but it's all essentially the same underlying SAT algorithms underneath.</p><h2>The First Breakthrough</h2><p>So for a while, I was on a nice trajectory from reading how SAT algorithms work to implementing my own custom SAT solver. It was going to be a nice learning experience, and I'd have a shiny project to put on my GitHub. Where did it all go wrong?</p><p>What happened is, I was browsing the SAT-solver algorithm / Sudoku corner of Wikipedia, when I stumbled across a lovely page: <a href="https://en.wikipedia.org/wiki/Mathematics_of_Sudoku#Solutions">Mathematics of Sudoku — Solutions</a>. The discussion here is around the number of different ways to fill out a Sudoku grid. The first number they give is <span><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>6.67</mn><mo>×</mo><mn>1</mn><msup><mn>0</mn><mn>21</mn></msup></mrow><annotation encoding="application/x-tex">6.67 \times 10^{21}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height: 0.7278em; vertical-align: -0.0833em;"></span><span class="mord">6.67</span><span class="mspace" style="margin-right: 0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right: 0.2222em;"></span></span><span class="base"><span class="strut" style="height: 0.8141em;"></span><span class="mord">1</span><span class="mord"><span class="mord">0</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height: 0.8141em;"><span class="" style="top: -3.063em; margin-right: 0.05em;"><span class="pstrut" style="height: 2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">21</span></span></span></span></span></span></span></span></span></span></span></span></span>, which is ridiculous. But later down on the page, they give a number for how many "essentially different" Sudoku solutions are available (essentially different meaning that isn't just another Sudoku with some rows swapped around or something like that). And that number is just under five and a half billion. This is where the breakthrough happened, and the downward spiral began. Because, when I saw that number on my screen, I had a wonderful thought:</p><blockquote><p>That sounds like it would fit in ram!</p></blockquote><p>Now there are some caveats here: you'd need a lot of ram. Unfortunately my first estimate of "5.5 billion solutions" is roughly "5.5 GigaBytes" was way off, because you'd need more than one byte per Sudoku puzzle. There's also generally some overhead involved with indexing the data for fast lookups, but the bottom line is still the same. It might not fit in the ram on my computer, but the GCP instance I'd need to hire to fit it all in RAM would probably not break the bank if I didn't leave it running 24/7. I started coming up with a rough algorithm:</p><ol><li><p>Parse the Sudoku;</p></li><li><p>Perform a series of flips, row/column swaps and number-reassignments to get the board into a "canonical representation" that matches my database;</p></li><li><p>Perform a (hopefully) <span><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>log</mi><mo>⁡</mo><mi>N</mi></mrow><annotation encoding="application/x-tex">\log{N}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height: 0.8889em; vertical-align: -0.1944em;"></span><span class="mop">lo<span style="margin-right: 0.0139em;">g</span></span><span class="mspace" style="margin-right: 0.1667em;"></span><span class="mord"><span style="margin-right: 0.109em;" class="mord mathnormal">N</span></span></span></span></span></span> time lookup through the database to see which puzzle solution matched the hints I was provided with;</p></li><li><p>Apply the reverse of the transformations I did in step 2 to the canonical-form solution; and finally</p></li><li><p>Print the answer.</p></li></ol><p>When you write it out like that, it honestly seems pretty simple. There are only two teeny tiny issues that I completely underestimated.</p><ol><li><p>Indexing the solution database for fast lookups</p></li><li><p>Inventing a canonical representation of a solved Sudoku that I could transform partial puzzles into.</p></li></ol><h2>Everything Going Wrong</h2><p>I promise, I was going to write something here. But by the time I got to the end of the blog post and was ready to come back to it, everything was already way too long. In summary, I couldn't find a solution to either problem. I also almost think it might not be possible to solve the second problem, but I'm not sure there.</p><p>Moving on…</p><h2>A Path Forward</h2><p>I went back to the drawing board, and started playing with some easy Sudokus to clear my mind while I thought. And as I was playing, I realised that the way I looked at Sudokus while I was playing was different to how I was thinking about them while writing code. I came up with a (very preliminary) brand new idea that would fit a few fun criteria that I was looking for in my algorithm:</p><ul><li><p>Still involve generating a giant database in memory and searching it;</p></li><li><p>Stay close to how I think about Sudokus in my head while solving them; and</p></li><li><p>Not be too complicated to implement (or hopefully optimise).</p></li></ul><p>And it all revolves around a concept of Paths (made-up term, because I couldn't find anything online of people taking the same approach as me). So let's get into this, with a small example from the easy-level Sudoku I was working on at the time.</p><pre><code>+-----+-+-----+-+-----+
|  3  | |    4| |  7 8|
|     | |9 3  | |1    |
|2    | |1    | |  9 3|
+-----+-+-----+-+-----+
|3   9| |4    | |    7|
|  1  | |3 8 9| |5 4  |
|  2 8| |     | |  3  |
+-----+-+-----+-+-----+
|  9 7| |8 4 3| |    6|
|6 4 3| |5   1| |7    |
|  8  | |6    | |3 5  |
+-----+-+-----+-+-----+
</code></pre><p>At this point I've solved all the 3s, and I want to get started on the 1s. Let's take some of the clutter away from the board for a second, and have a new look.</p><pre><code>+-----+-+-----+-+-----+
|? x ?| |    x| |  x x|
|     | |x x  | |1    |
|x    | |1    | |  x x|
+-----+-+-----+-+-----+
|x   x| |x ?  | |  ? x|
|  1  | |x x x| |x x  |
|  x x| |  ?  | |  x ?|
+-----+-+-----+-+-----+
|? x x| |x x x| |  ? x|
|x x x| |x   1| |x    |
|? x ?| |x    | |x x ?|
+-----+-+-----+-+-----+
</code></pre><p>I've put a 1 in all the squares that contain a 1, an <code>x</code> in all the squares that contain a 2-9, and a <code>?</code> in all the squares that appear legally able to contain a 1 at this point in time. There are eleven question marks on the board so far, and of those five need to become 1s before the puzzle is completed. Naively, this is looking like (checks combinatorics notes that I haven't used in many years) 462 possible ways of filling in the question marks.</p><p>By thinking in Paths, we can reduce this to just three possibilities. I'm going to start by zooming in on the middle box, and the box immediately to its right.</p><pre><code>+-----+-+-----+
|x ?  | |  ? x|
|x x x| |x x  |
|  ?  | |  x ?|
+-----+-+-----+
</code></pre><p>On their own, these two boxes have four question marks, and need two 1s to be complete. The maths says there's six ways this could go down, but practically you can look at the Sudoku puzzle and narrow it down to two possibilities. I'll redraw the two boxes using <code>L</code> to show where the 1s would go for one of the possibilities, and using <code>R</code> to show where they go for the other possibility.</p><pre><code>+-----+-+-----+
|x R  | |  L x|
|x x x| |x x  |
|  L  | |  x R|
+-----+-+-----+
</code></pre><p>Any other way of choosing how to allocate the 1s aside from these two would lead to conflicts. Now that we know that the middle box depends entirely on the box to its right (or vice versa) I'm going to hide the middle box for a while, and focus on the center-right box and the bottom-right box.</p><pre><code>+-----+
|  L x|
|x x  |
|  x R|
+-----+
|  ? x|
|x    |
|x x ?|
+-----+
</code></pre><p>The two possibilities we had from our previous exercise carry across here: we can assign <code>L</code> and <code>R</code> to the question marks in this new box as well. The problem of selecting three 1s from six <code>?</code>s is ultimately still down to a binary choice between the <code>L</code> assignment and the <code>R</code> assignment.</p><pre><code>+-----+-+-----+
|x L  | |  R x|
|x x x| |x x  |
|  R  | |  x L|
+-----+-+-----+
        |  L x|
        |x    |
        |x x R|
        +-----+
</code></pre><p>Once you add in the next connected box, the one on the bottom left (which shares rows with the bottom-right box, that you can use to cancel things out), things get more complicated. We go from two valid assignments of 1s among the question marks to three, and some of the assignments overlap, so I'm not going to be able to draw them in the same diagram any more. However, that makes this a good time to swap from talking about assignments to talking about Paths.</p><blockquote><p>What is a Path?</p></blockquote><p>A <strong>Path</strong> is a series of nine locations across the Sudoku grid, such that one location from the path is present in each row, column and box of the overall grid. In a solved Sudoku, you could assign one path to each digit, making the locations in the path the locations on the grid that that digit is present. Any set of nine paths that don't overlap with each other, and therefore fill up the board, would constitute a solution to the Sudoku.</p><p>Here's an example Path, which is the Path that I filled with 3s when I first started solving the example Sudoku. The exclamation marks show which locations in the grid are part of the Path.</p><pre><code>+-----+-+-----+-+-----+
|  !  | |     | |     |
|     | |  !  | |     |
|     | |     | |    !|
+-----+-+-----+-+-----+
|!    | |     | |     |
|     | |!    | |     |
|     | |     | |  !  |
+-----+-+-----+-+-----+
|     | |    !| |     |
|    !| |     | |     |
|     | |     | |!    |
+-----+-+-----+-+-----+
</code></pre><p>My next breakthrough, and what actually led to the workable solver that I have now, was deciding to throw away the "fill the grid with numbers" problem and replace it with a "find 9 Paths that don't overlap" problem. And that brings us to coding.</p><blockquote><h3>Feasibility of Paths</h3><p>I did stop to think, before diving straight into coding, about whether it would be feasible to solve path-based problems. I had an idea that my algorithm would probably involve looking at all possible different paths, so I did a quick calculation to see how many there were. I figured that any given Path would need one spot in each row, and those spots would need to be arranged so they each fell into a different column. The actual number is lower than this, because not all permuations are valid, but there are <span><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>9</mn><mo stretchy="false">!</mo></mrow><annotation encoding="application/x-tex">9!</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height: 0.6944em;"></span><span class="mord">9</span><span class="mclose">!</span></span></span></span></span> ways to permutate the "put each position into a different column" problem, which means we have an upper bound of 362,880 different Paths. Definitely a small enough number to store in memory or loop through, so we move on.</p></blockquote><h2>Representing States (the Glue Code)</h2><p>Before I could get hacking on anything like an algorithm, I needed some glue code and a bit of a foundation to build with. I started with these two abstractions:</p><ol><li><p>A <code>Bitfield</code> abstraction to make all my "does this intersect" maths faster, and give me a compact way of storing things in-memory.</p></li><li><p>A <code>generate_paths()</code> function which lists all possible Paths through a Sudoku, because I'm going to need it sooner or later surely.</p></li></ol><p>The code for these two steps is <a href="https://github.com/ashfordneil/sudoku/commit/8d0568a05e7f30897279b19fa6d4bf635b6ca304">here</a>. In particular look at <code>src/bitfield.rs</code> and <code>src/path.rs</code>. Fun fact, I did the testing and the upper bound of 362,880 valid Paths from before was very conservative. The actual number is only 46,656.</p><p>And then after that I needed a way to build and represent a board. A <strong>completed</strong> Sudoku could be represented as 9 Paths, one for each digit, but an <strong>incomplete</strong> Sudoku I don't have enough information to do that. Instead, I decided to represent it as 9 <code>Bitfield</code>s — the idea being that I could turn them into Paths (or lists of potential Paths) easily enough with some bitwise arithmetic.</p><p>You can check out the board representation <a href="https://github.com/ashfordneil/sudoku/commit/e6d9132e0ebd94eefc3bc0750bb229345acbffee">here</a>. The important thing (to me) is that I can write code like this:</p><pre><code>// Find a list of candidates for the Path that the digit 3 follows through the board
let potential_three_paths = generate_paths()
    .filter(|&amp;path| path.contains(board[Digit::_3]))
    .filter(|&amp;path| (path &amp; board[Digit::_1]).is_empty())
    .filter(|&amp;path| (path &amp; board[Digit::_2]).is_empty())
    .filter(|&amp;path| (path &amp; board[Digit::_4]).is_empty())
    .filter(|&amp;path| (path &amp; board[Digit::_5]).is_empty())
    // and so on
    .filter(|&amp;path| (path &amp; board[Digit::_9]).is_empty())
    .collect::&lt;Vec&lt;_&gt;&gt;();
</code></pre><p>Or this:</p><pre><code>// We found a new cell to fill in
board[Digit::_3] |= Bitfield::new(5, 5);
</code></pre><p>And I'm hopeful that those two operations are all I'll really need to write a Sudoku solver. Oh, I also have helper code to parse and pretty-print boards, which is pretty important. So let's jump right in and start solving!</p><h2>Some Experimentation with Real Sudokus</h2><p>We're at the point where I'm gonna need to start looking at concrete Sudokus now, and so I've put together a small list of boards to test with. Here they are:</p><p>I've got a Sudoku app on my phone. It's called Sudoku Master Edition and it's available <a href="https://apps.apple.com/us/app/sudoku-master-edition-logic/id1260190370">here</a>. Most of the puzzles I've started testing with came straight from the generator in this app. This one comes from the "Easy" difficulty.</p><pre><code>+-----+-+-----+-+-----+
|2   3| |    5| |8   1|
|  4 6| |     | |  3 2|
|1 5  | |    4| |7    |
+-----+-+-----+-+-----+
|    1| |8 4 7| |9 5  |
|    5| |  1  | |  2  |
|4    | |  2 3| |     |
+-----+-+-----+-+-----+
|     | |     | |1   5|
|     | |  6 9| |     |
|9 8  | |4 5  | |  7  |
+-----+-+-----+-+-----+
</code></pre><h3>Moderate Sudoku</h3><p>Same app, next difficulty up: "Moderate". This is the difficulty that I spent most of my hours grinding away Sudokus at when I was first getting into the game.</p><pre><code>+-----+-+-----+-+-----+
|5    | |8   7| |2    |
|     | |9 2  | |6    |
|7 4 2| |     | |  8  |
+-----+-+-----+-+-----+
|     | |  9 5| |    6|
|9 3 5| |7 6  | |    2|
|1 7  | |4   2| |    8|
+-----+-+-----+-+-----+
|     | |     | |     |
|2 9  | |     | |4    |
|3   1| |    6| |     |
+-----+-+-----+-+-----+
</code></pre><h3>Master Sudoku</h3><p>This is the highest difficulty offered by the app. I skipped a few difficulties in the middle here, as I'm trying to keep the dataset down to just a few Sudokus so I can look at results by hand.</p><pre><code>+-----+-+-----+-+-----+
|    6| |2    | |    9|
|  4  | |6 7  | |2   3|
|     | |    9| |    4|
+-----+-+-----+-+-----+
|4    | |     | |  2  |
|    5| |  8  | |1    |
|  7  | |     | |    6|
+-----+-+-----+-+-----+
|2    | |9    | |     |
|6   1| |  3 4| |  7  |
|5    | |    6| |4    |
+-----+-+-----+-+-----+
</code></pre><p>Now that I've got these puzzles, I wanted to get a feel for how helpful the Path approach actually is. Using all the glue code from before, I put together this little script:</p><pre><code>fn analyse_board(board: &amp;Board) {
    println!("{}", board);
    let total_clues = Digit::iter()
        .map(|digit| board[digit])
        .fold(Bitfield::default(), BitOr::bitor);

    for digit in Digit::iter() {
        let our_clues = board[digit];
        let opposing_clues = total_clues &amp; !our_clues;

        let possible_paths = sudoku::generate_paths()
            .filter(|&amp;path| path.contains(our_clues))
            .filter(|&amp;path| (path &amp; opposing_clues).is_empty())
            .count();

        println!("There are {} possible Paths for {}", possible_paths, digit)
    }
}
</code></pre><p>And so let's check out how many possible Paths there are for each digit across the sample Sudokus.</p><p>Easy Sudoku:</p><pre><code>1 possible Paths for 1
3 possible Paths for 2
18 possible Paths for 3
2 possible Paths for 4
1 possible Paths for 5
12 possible Paths for 6
15 possible Paths for 7
5 possible Paths for 8
1 possible Paths for 9
</code></pre><p>Moderate Sudoku:</p><pre><code>55 possible Paths for 1
2 possible Paths for 2
55 possible Paths for 3
2 possible Paths for 4
20 possible Paths for 5
1 possible Paths for 6
11 possible Paths for 7
12 possible Paths for 8
3 possible Paths for 9
</code></pre><p>Master Sudoku:</p><pre><code>42 possible Paths for 1
2 possible Paths for 2
56 possible Paths for 3
2 possible Paths for 4
42 possible Paths for 5
2 possible Paths for 6
8 possible Paths for 7
154 possible Paths for 8
8 possible Paths for 9
</code></pre><p>All in all, this is awesome! The worst scenario is 154 possible paths for 8, in the master difficulty Sudoku — but looping over all 154 times… and looping over the total number of Paths for all the other digits… is still only… okay it's around seven billion iterations. Still, that means that the master Sudoku is gonna get solved in a handful of seconds, which is way faster than I can solve it by hand.</p><p>Just to confirm that this hypothesis that I can loop through possible path combinations to solve a Sudoku, I wrote up the following little solver:</p><pre><code>fn solver_helper(mask: Bitfield, paths: &amp;[Vec&lt;Bitfield&gt;]) -&gt; Option&lt;Vec&lt;Bitfield&gt;&gt; {
    match paths {
        [] =&gt; panic!(),
        [one] =&gt; {
            let &amp;choice = one.iter().find(|&amp;&amp;path| (mask &amp; path).is_empty())?;
            Some(vec![choice])
        },
        [head, tail @ ..] =&gt; {
            head.iter().filter(|&amp;&amp;path| (mask &amp; path).is_empty()).find_map(|&amp;path| {
                let mut tail_solution = solver_helper(mask | path, tail)?;
                tail_solution.insert(0, path);
                Some(tail_solution)
            })
        }
    }
}

let solution = solver_helper(Bitfield::default(), &amp;possible_paths_per_digit).unwrap();
for (digit, bits) in Digit::iter().zip(solution) {
    board[digit] = bits;
}
</code></pre><p>And then I ran it. Good news and bad news: It did work! But it took six to seven seconds for all three Sudokus.</p><blockquote><p>Hey, what if you tried <code>cargo run --release</code> instead of <code>cargo run</code>?</p></blockquote><p>We are now down to 260ms, 291ms, and 288ms per Sudoku<sup id="footnote-ref-2"><a href="#footnote-2">2</a></sup>. This has been yet another reminder that LLVM is magical, and that I should always run my code through its optimisers before I do anything.</p><p>Issues with the compiler aside, though, we've now got a proof of concept that Paths are viable as an option. Now it's time to go back to the drawing board and see if there's a way we can speed this up further.</p><h2>Easy Wins</h2><p>I was halfway through writing the section <em>after</em> this one, on the algorithmic improvements I figured would be the next step in making the solver fast, when I realised something: I forgot to actually profile my code. Tragically, clicking the "Profile" button in my IDE didn't help much — I am being too fancy with lazy iterators for the profiler to know where I'm <em>actually</em> spending time beyond "inside the <code>iterator.next</code> function" or "inside the <code>Vec::from_iter</code> function". However, that's not going to stop me, because I have cheap hacks on my side!</p><pre><code>let start = Instant::now();

// run part of the code

let first_part_time = start.elapsed();

// run next part of the code

let second_part_time = start.elapsed() - first_part_time;
println!("First part took {:?}", first_part_time);
println!("Second part took {:?}", second_part_time);
</code></pre><p>And here's what I learned:</p><p>Easy Sudoku:</p><pre><code>Finding total clues: 76ns
Finding possible paths: 279.055611ms
Solving: 20.696µs
</code></pre><p>Moderate Sudoku:</p><pre><code>Finding total clues: 74ns
Finding possible paths: 307.142538ms
Solving: 86.654µs
</code></pre><p>Master Sudoku:</p><pre><code>Finding total clues: 76ns
Finding possible paths: 317.606837ms
Solving: 85.373µs
</code></pre><p>I had really thought that the actual <em>solving</em> would take the bulk of the program time. Apparently that was wrong, and I guess that just goes to show why you should never trust your hunches about performance in code.</p><p>Alright so let's look at the "finding possible paths" code — and apply the traditional (and completely trustworthy) method of "staring at it until I think I can guess what's taking so long".</p><pre><code>let possible_paths = Digit::iter()
    .map(|digit| {
        let &amp;our_clues = &amp;board[digit];
        let opposing_clues = total_clues &amp; !our_clues;

        sudoku::generate_paths()
            .filter(|&amp;path| path.contains(our_clues))
            .filter(|&amp;path| (path &amp; opposing_clues).is_empty())
            .collect::&lt;Vec&lt;_&gt;&gt;()
    })
    .collect::&lt;Vec&lt;_&gt;&gt;();
</code></pre><p>So my first thought is "why am I calling <code>generate_paths()</code> every single time?" Let's calculate that once at the start — <strong>cache it</strong> — and go again. It's worth noting here that I <em>am</em> still just guessing, which isn't a great way to go about optimisations, but at least I'm guessing and measuring. It's a step up. Anyway, here's our results:</p><pre><code>Generation of all Paths: 32.999076ms

# Possible path times
Easy Sudoku: 279ms -&gt; 656µs
Moderate Sudoku: 307ms -&gt; 669µs
Master Sudoku: 317ms -&gt; 712µs
</code></pre><p>On the bright side, I've cut down the slowest part of my solving process by a factor of approximately 450! On the confusing side, generating the path list on its own is ten times faster than generating it and immediately filtering it down. This is gonna have to be one of those "out of scope" things, I think. And now I think we've hit the "Neil, this blog post is too long" point, so let's wrap up where the solver is at.</p><h2>State of the Solver</h2><p>So the code is <a href="https://github.com/ashfordneil/sudoku/commit/3c59f3572168d4be94834cd4cba09ed82baa78a7">here</a>. I've built it into a little command-line tool, that takes the Sudoku pattern in text representation as a command line argument and prints it out on screen once it's solved. The whole thing looks like this:</p><pre><code>λ sudoku .......49.....38..7.6.2.1.....3...6.6..784..5.9...1.....2.5.4.8..84.....37.......
+-----+-+-----+-+-----+
|8 2 3| |1 7 5| |6 4 9|
|5 1 9| |6 4 3| |8 7 2|
|7 4 6| |8 2 9| |1 5 3|
+-----+-+-----+-+-----+
|4 8 5| |3 9 2| |7 6 1|
|6 3 1| |7 8 4| |9 2 5|
|2 9 7| |5 6 1| |3 8 4|
+-----+-+-----+-+-----+
|1 6 2| |9 5 7| |4 3 8|
|9 5 8| |4 3 6| |2 1 7|
|3 7 4| |2 1 8| |5 9 6|
+-----+-+-----+-+-----+
Solution took 845.83µs
</code></pre><p>Based on the limited testing I've done, most of the time taken by the solver is spent in one of two places:</p><ol><li><p>Generating a list of Paths that fit the initial set of clues; and</p></li><li><p>Testing all selections of those Paths to see which set we can use to fill up the board.</p></li></ol><p>Of those two, I'm very surprised to report that the first step takes much longer than the second. I'll need to look into that… And that brings me to my next point: future posts! I've never written a series of blog posts before, so I don't know what this is going to look like. However, here are some things I'd like to investigate in future. If I get the chance, I'll update the list below with some hyperlinks as we progress.</p><ul><li><p>How does this Sudoku solver perform against larger (and harder) sets of puzzles? Was I just really lucky to find some testing Sudokus that work really well with my algorithm?</p></li><li><p>Can I index the Path database in a way that makes searching it for Paths that match the clues faster?</p></li><li><p>Can I do better than a brute-force search through the possible paths for the final part of my algorithm? Do I need to, to speed things up? And how come it isn't prohibitively slow at the moment?</p></li><li><p>How does this algorithm compare to mainstream solvers? From a performance perspective and a simplicity perspective.</p></li></ul></main><footer><hr id="footer-line" /><div id="footnote-0"><p><sup class="footnote-idx">0</sup>Yeah so this didn't happen. Maybe in a future post?<a href="#footnote-ref-0" class="footnote-return"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M240.971 130.524l194.343 194.343c9.373 9.373 9.373 24.569 0 33.941l-22.667 22.667c-9.357 9.357-24.522 9.375-33.901.04L224 227.495 69.255 381.516c-9.379 9.335-24.544 9.317-33.901-.04l-22.667-22.667c-9.373-9.373-9.373-24.569 0-33.941L207.03 130.525c9.372-9.373 24.568-9.373 33.941-.001z"/></svg>return</a></p></div><div id="footnote-1"><p><sup class="footnote-idx">1</sup>This isn't technically what <span><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi><mi>P</mi></mrow><annotation encoding="application/x-tex">NP</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height: 0.6833em;"></span><span style="margin-right: 0.1389em;" class="mord mathnormal">NP</span></span></span></span></span> means. There are differences between a computer with infinite CPU cores and a nondeterministic Turing Machine (which is what the actual <span><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi><mi>P</mi></mrow><annotation encoding="application/x-tex">NP</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height: 0.6833em;"></span><span style="margin-right: 0.1389em;" class="mord mathnormal">NP</span></span></span></span></span> definition references), and what each of them can do in polynomial time. However, those differences are too subtle to be in scope for this post.<a href="#footnote-ref-1" class="footnote-return"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M240.971 130.524l194.343 194.343c9.373 9.373 9.373 24.569 0 33.941l-22.667 22.667c-9.357 9.357-24.522 9.375-33.901.04L224 227.495 69.255 381.516c-9.379 9.335-24.544 9.317-33.901-.04l-22.667-22.667c-9.373-9.373-9.373-24.569 0-33.941L207.03 130.525c9.372-9.373 24.568-9.373 33.941-.001z"/></svg>return</a></p></div><div id="footnote-2"><p><sup class="footnote-idx">2</sup>This is actually faster than I'd expected it would be, especially for the Master Sudoku. I think I underestimated just how many combinations of Paths would be cancelled out before I got too far into recursing.<a href="#footnote-ref-2" class="footnote-return"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M240.971 130.524l194.343 194.343c9.373 9.373 9.373 24.569 0 33.941l-22.667 22.667c-9.357 9.357-24.522 9.375-33.901.04L224 227.495 69.255 381.516c-9.379 9.335-24.544 9.317-33.901-.04l-22.667-22.667c-9.373-9.373-9.373-24.569 0-33.941L207.03 130.525c9.372-9.373 24.568-9.373 33.941-.001z"/></svg>return</a></p></div></footer></div></content></entry><entry><id>https://neilashford.dev/story/blog-pack.html</id><title>A new, static, blog</title><link href="https://neilashford.dev/story/blog-pack.html"/><published>2021-09-10T00:00:00Z</published><updated>2021-09-10T00:00:00Z</updated><author><name>Neil Ashford</name></author><content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><main><h1>A new, static, blog</h1><p><em>10/08/2021</em></p><br /><p>It's time, finally, for me to do what every other developer with a blog does — write a blog post about my blog setup. I have unquestionably over-engineered this, but that's gotta be half the fun, right? So without any further ado, let's go through the process of how I put this together.</p><h2>A window into the developer</h2><p>The main reason this blog exists is to show people what sort of developer I am. Shockingly, that's not a constant. My old blog setup, which I've archived but not deleted for the sake of being honest with myself, was that of a React developer. It looks like a giant mess to me now, but I guess hindsight is always more critical of bundle size and code complexity or something like that. The point is, I'm not a React developer any more, so what sort of developer am I? And what sort of blog does that developer create?</p><blockquote><p>One day, I'm going to be a practical sort of developer. The sort that clicks the "Can I have a Jekyll site please" button on GitHub Pages, and never thinks about it again. I'm not that developer yet though, so for now the fun (read: over-engineering) continues.</p></blockquote><p>For the last year, I've been working for <a href="https://tiny.cloud">Tiny Technologies</a>. I might even write a blog post about it some time. For now though, I'll say that building a rich text editor that's optimised for compatibility with different site setups has changed every view I held about browsers<sup id="footnote-ref-0"><a href="#footnote-0">0</a></sup>. Some key differences in how I think now vs earlier:</p><table><thead><tr><th>Before</th><th>After</th></tr></thead><tbody><tr><td>Nothing can be done without React or another framework.</td><td>The native browser APIs are very powerful these days.</td></tr><tr><td>Nesting 30 layers of divs to get the layout you want is normal.</td><td>Sometimes people actually want to look at the inspector to debug something.</td></tr><tr><td>I can just put a banner up telling people not to use old browsers.</td><td>I wish I could just put a banner up telling people not to use old browsers.</td></tr><tr><td>And most importantly…</td></tr><tr><td>I only care about the HTML that fits my use-case.</td><td>I need to understand everything a user could conceivably make out of HTML, and how it's meant to work.</td></tr></tbody></table><p>Now that's not to say that I hate React now. I really miss using it, and having that comfort layer between me and the DOM — especially for things like forms or quickly reorganising HTML structures when I change my mind about something. What I'm trying to say here isn't that I hate having a framework between me and the DOM, I'm just not so scared of the alternative any more.</p><p>And that lack of fear is what led me to believe that <strong>this</strong> would be a good idea:</p><blockquote><p>My website isn't going to render on the client side</p></blockquote><p>That's not a particularly courageous statement though. Server side rendering is a well-established pattern, and even predates client side rendering. Except, because I'm pretty cheap when it comes to hosting for side projects, I don't want to have a server. So where will the rendering happen? <strong>In the compiler.</strong></p><h2>Blog-pack</h2><blockquote><p>At time of writing, "Blog-pack" doesn't really mean anything and doesn't come up when I search for it, if that's no longer the case then I'm sorry.</p></blockquote><p>You've heard of webpack? That tool that you use to package things for the web? I set it up to package things for my blog. Writing it out, that sounds like such a simple task, but it was anything but. What it comes down to is that I wanted to operate on HTML files, and webpack wanted to operate on JavaScript files.</p><p>I really wanted 3 tools to come together and work for me with this:</p><ul><li><p><a href="https://github.com/jantimon/html-webpack-plugin">HtmlWebpackPlugin</a></p></li><li><p><a href="https://github.com/webpack-contrib/mini-css-extract-plugin">MiniCssExtractPlugin</a></p></li><li><p><a href="https://github.com/webpack-contrib/html-loader">html-loader</a></p></li></ul><p>And I gotta say, 2/3 of the way through that list we were having a real good time. Both of those plugins are built to emit good HTML files from webpack. Unfortunately, html-loader is built to turn your HTML file into a pretty JavaScript module. It does a fantastic job of finding all of the <code>&lt;img src</code> and <code>&lt;link href</code> elements that you want the build system to resolve… and then it turns them into <code>require</code> calls.</p><p>Immediately, my zero-practicality brain jumped in with a solution: just write your own HTML loader! How hard could it be to coerce webpack into importing those assets <em>without</em> shelling out to <code>require</code>, right? So I tried it. I really did. I read (some of) the source, I read the docs, I logged semi-private webpack objects and APIs at runtime to see how they worked. You ever tried swimming against a strong current? Webpack <strong>is not</strong> built for this use case, and it let me know every chance it got.</p><p>So we turn to my zero-practicality brain's least favourite solution: compromise. I did still need to do some webpack-based processing of certain elements (extracting SVG icons from the <code>@fortawesome/...</code> folders in my node modules, for example). But for some things, like stylesheets, I just had to go with the flow a little. Behold, my entire <code>index.js</code>:</p><pre><code>import './styles/page_layout.scss';
import './styles/content.scss';
import './styles/navbar.scss';

// This file doesn't do any processing. It just plays nice with webpack.
</code></pre><p>And if you look in your inspector, you'll see a little JS bundle filled with sweet nothings that's been downloaded as part of this page.</p><h2>My templating system</h2><p>One other important thing I've learned at Tiny is how scary HTML is. You know <a href="https://stackoverflow.com/a/1732454">the infamous stackoverflow post</a> about how you shouldn't parse HTML with regex? Turns out you shouldn't parse it with anything else either if you can get away with it. HTML is incredibly complex to deal with programmatically, and the browser (which does not make it look easy) makes it look so much easier than it really is. And so you can imagine my shock to see that it's pretty much industry standard for static site generators to ask handlebars to interpolate some strings together and call it HTML. Like I'm sure it works, but at what cost? And how fragile is it?</p><p>I'm not a fan of treating HTML as strings, and I am a fan (now) of using DOM APIs to make the scary bits of HTML go away. So my "templating engine" just loads the HTML up in <code>jsdom</code>, and calls <code>document.querySelector('main').innerHTML =</code><sup id="footnote-ref-1"><a href="#footnote-1">1</a></sup>. It seemed a little weird to not have any <code>{{}}</code> in my templates at all, but honestly the more I work with it the more I like it.</p><ul><li><p>No escaping any special "template characters" in my template or in my content,</p></li><li><p>I can open the <code>template.html</code> file on its own in a browser and debug it without seeing any template issues,</p></li><li><p>No questions about whitespace and where it will or won't need to be removed, and</p></li><li><p>When I inevitably reached some disagreements with various parts of my stack, (over things like how to render a footnote in HTML) I already had the fake DOM loaded to do some quick tweaks.</p></li></ul><h2>Performance</h2><p>The other nice advantage of pre-rendering everything is that suddenly my website is fast. I'm serving up<sup id="footnote-ref-2"><a href="#footnote-2">2</a></sup> plain HTML files, with plain CSS, all from a plain old CDN. There's nothing here to slow it down.</p><p>I'm not going to quantify things, because it's not worth the effort it would take to set up a meaningful benchmark. But I will say this: <em>there's no more loading spinners here</em>.</p></main><footer><hr id="footer-line" /><div id="footnote-0"><p><sup class="footnote-idx">0</sup>Excluding, of course, the view that they're riddled with bugs. If anything working on TinyMCE has made that more apparent, because of how often we push the limits and how strict we have to be on cross-browser compatibility.<a href="#footnote-ref-0" class="footnote-return"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M240.971 130.524l194.343 194.343c9.373 9.373 9.373 24.569 0 33.941l-22.667 22.667c-9.357 9.357-24.522 9.375-33.901.04L224 227.495 69.255 381.516c-9.379 9.335-24.544 9.317-33.901-.04l-22.667-22.667c-9.373-9.373-9.373-24.569 0-33.941L207.03 130.525c9.372-9.373 24.568-9.373 33.941-.001z"/></svg>return</a></p></div><div id="footnote-1"><p><sup class="footnote-idx">1</sup>Well actually <code>jsdom</code> doesn't seem to support setting <code>.innerHTML</code> or <code>.innerText</code>. Fortunately I have no problem with creating a second document and moving all the child nodes over using low level APIs.<a href="#footnote-ref-1" class="footnote-return"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M240.971 130.524l194.343 194.343c9.373 9.373 9.373 24.569 0 33.941l-22.667 22.667c-9.357 9.357-24.522 9.375-33.901.04L224 227.495 69.255 381.516c-9.379 9.335-24.544 9.317-33.901-.04l-22.667-22.667c-9.373-9.373-9.373-24.569 0-33.941L207.03 130.525c9.372-9.373 24.568-9.373 33.941-.001z"/></svg>return</a></p></div><div id="footnote-2"><p><sup class="footnote-idx">2</sup>Oh no, I sound like I'm on Ru Paul's Drag Race.<a href="#footnote-ref-2" class="footnote-return"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M240.971 130.524l194.343 194.343c9.373 9.373 9.373 24.569 0 33.941l-22.667 22.667c-9.357 9.357-24.522 9.375-33.901.04L224 227.495 69.255 381.516c-9.379 9.335-24.544 9.317-33.901-.04l-22.667-22.667c-9.373-9.373-9.373-24.569 0-33.941L207.03 130.525c9.372-9.373 24.568-9.373 33.941-.001z"/></svg>return</a></p></div></footer></div></content></entry><entry><id>https://neilashford.dev/story/Mychro.html</id><title>Mychro</title><link href="https://neilashford.dev/story/Mychro.html"/><published>2020-05-09T00:00:00Z</published><updated>2020-05-09T00:00:00Z</updated><author><name>Neil Ashford</name></author><content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><main><h1>Mychro</h1><p><em>09/04/2020</em></p><br /><h2><em>An X-Ray for your DNA</em></h2><p>Under the wing of the <a href="https://ventures.aginic.com">Aginic Ventures</a> team, I've been developing a little something to help make life easier for geneticists. The idea started off as a fusion between Aginic's skill at getting insight from data and a geneticist's need to get insight from a whole lot of data (the human genome). Just over a year later and the product – Mychro – is my masters thesis topic, my day job, and the largest codebase I've had this much of a hand in building.</p><p><img src="/asset/6af3188c9dcc60202238.gif" alt="An image of Mychro in action" style="background-image: url();" data-aspect="0.59375" /></p><p>In building Mychro, I've experimented with GraphQL, Kubernetes, and even a brief foray into Rust. These days the GraphQL is still there (provided by <a href="https://www.graphile.org">postgraphile</a>), but the backend is now provided by GCP managed App Engine and Cloud SQL. The frontend is all React, just like this site, only with a bit more technical debt and a bit less reliance on bleeding edge technology like <code>Suspense</code>. The frontend also messes with enough SVGs to make me give up hope on ever finding a browser that is able to follow the SVG spec.</p><p>But while I am the only developer committed to the project, I'm not building this alone. I'm lucky enough to have <a href="https://jasonmclaren.me">Jason</a> working his magic in the product design department, and it's been a pleasure trying to use osmosis to learn just how he does it. In the management department, we have Alan Robertson running the show: our resident geneticist turned startup founder, as well as the Aginic Ventures team.</p><p>I've committed a lot of my time to this project since we kicked it off. I asked for it to be brought off the Aginic Ventures' back-burners and into the light of day so I could work on it for my thesis, and I've been giving the project all I have in technical skills since then. I've done this because while the risks are always high in the startup world, I believe the potential payoff for this is huge. If Mychro has the chance to help make the people out there fighting genetic diseases and cancers more efficient at the work they do, I can't not do my best to build it.</p><p>Anyway, <a href="https://mychro.io">check out the product</a> and by all means get in touch with me or the rest of the team.</p></main><footer><hr id="footer-line" /></footer></div></content></entry><entry><id>https://neilashford.dev/story/Tutoring.html</id><title>Tutoring</title><link href="https://neilashford.dev/story/Tutoring.html"/><published>2020-05-09T00:00:00Z</published><updated>2020-05-09T00:00:00Z</updated><author><name>Neil Ashford</name></author><content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><main><h1>Tutoring</h1><p><em>09/04/2020</em></p><br /><h2><em>This is the story of how my life got turned upside down</em></h2><p>At the beginning of my third year of university, I got my first in-industry job in software: I became an official <em>Casual Demonstrator</em> at The University of Queensland. I was hired by a lecturer to walk around during lab demonstrations in a common second year course: <a href="https://my.uq.edu.au/programs-courses/course.html?course_code=csse2010">CSSE2010</a>, and answer any questions people had. I stuck with the job until I graduated, six semesters later, and during that time things got a little out of hand.</p><p><img src="/asset/8e85e3785829c853d281.png" alt="My lecturer reviews from 2018" style="background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAICAYAAADA+m62AAAAAklEQVR4AewaftIAAAEnSURBVBXBwUrCAACA4X9shStmNUqCDjpQ8ZKXoA5DCbz5DCXRzQfo7qUX8NjJS7ceoKJLV6FjhoMWm1ua6Fxmuqms8PuEer3+p2katm1jWRamaeI4DrIsE4YhURSxIo5Go5pt26RSKcrlMoVCgWQyia7rBEGAqqqIooiUy+XQi6dkj4u48zlG65VB38f7emMlFoshiiKSZVkk2m2y+SOeH55AENhJ7JPJn7C3q7KY/fJ4e4NUrVbRNA3zvcWsZ7JcLpk4Bi/3d7juJ1e1a84uLpGazSZRFKGqKueVCooSZ7FYsLO9RaPRoNMbMOy6SKVSiYPMIYn4BuL6GpuyTBCEDPtd0uk0nc4Hc0FAMgwDZ/DN2PeZjH2mPz6z6ZQgCFAUBc/z8DyPf0teg5EuRUHVAAAAAElFTkSuQmCC);" data-aspect="0.75" /></p><p>While I started out with a pretty small role, the job gave me plenty of room to grow, and I took every chance I was given. By the end my regular job involved a little bit of:</p><ul><li><p>Marking exam papers, and supervising / marking practical exams.</p></li><li><p>Presenting content in front of 100 student classes.</p></li><li><p>Marking assignments for 30-50 students.</p></li></ul><p>And if that had been all I'd done as a tutor, I would have still gained a tonne of experience and learned a lot. But things didn't really end there. Twice during my time as a tutor, the lecturer supervising me (and running the course I was meant to be tutoring) got unwell enough to need considerable time off. As a result, the <em>Casual Academic</em> section of my resume looks a little more like this:</p><ul><li><p>Prepared and presented lectures to a course of 500 students.</p></li><li><p>Supervised teams of tutors in the preparation of course content.</p></li><li><p>Collaborated on specifications and support code for major programming assignments.</p></li><li><p>Produced and presented an example solution to a major programming assignment.</p></li><li><p>Automated compilation and JUnit testing of 1200 student assignments.</p></li></ul><p>It was a drain, but it was a blast. And I think my resume looks a little better for all this extra experience.</p><h2>The Content</h2><p>During my time as a tutor, I ended up working as a part of four different courses. They were all great learning experiences, but one holds a special place in my heart as being my best academic experience at uni (both as a student and a teacher).</p><p><em>Computer Systems Principles and Programming</em> – <a href="https://my.uq.edu.au/programs-courses/course.html?course_code=csse2310">CSSE2310</a> – was the course I got the most involved in. I tutored this one for three years, and it was here that I got my lecturing experience. This course could be a blog post all on its own, but in summary we taught the students a crash course in what the operating system (Unix) looks like from the user-space. We started the semester by teaching C, and ended it by teaching BSD sockets and the difference between a hard and a soft link in a unix filesystem. I definitely wasn't an expert on any of these topics when I got the job, but over three years I've seen a lot of weird things (and even found a way to explain or fix most of them).</p><p>Outside of <em>2310</em>, I got involved in the high performance computing course, the intro to OOP course, and the intro to computer systems course. I explained the difference between white-box and black-box testing, and taught people the importance of basic cable management while they built logic circuits on breadboards.</p><p>Tutoring really kick-started my knowledge on development. By working through an assignment with 30-90 students, I'd get to see 30-90 ways of solving a problem (well, maybe fifteen to twenty), and pick up experience that much faster. I also was forced to figure out how to debug code that I hadn't written, and how to communicate my thought process as I worked through a problem (really helps during the technical interviews). But if nothing else, I'd recommend the tutoring experience to anyone just for the information you can pick up by working alongside talented lecturers and senior tutors.</p></main><footer><hr id="footer-line" /></footer></div></content></entry><entry><id>https://neilashford.dev/story/UQCS.html</id><title>University of Queensland Computing Society</title><link href="https://neilashford.dev/story/UQCS.html"/><published>2020-04-26T00:00:00Z</published><updated>2020-04-26T00:00:00Z</updated><author><name>Neil Ashford</name></author><content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><main><h1>University of Queensland Computing Society</h1><p><em>26/03/2020</em></p><br /><p>As someone who spent the past five years getting my software engineering degree from UQ, I saw a fair few student societies. The computing society was hands down my favourite.</p><p>When I first joined UQCS I was just starting my second year of engineering. I skipped a lecture to show up to a presentation from some Atlassian employees on how to use git. A few years later I gave them my own spin on the git talk to a fresh bunch of first and second years.</p><p><img src="/asset/643f8b2fa24a9717e604.png" alt="The committee tried giving out coffee mugs to people who did talks" style="background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAICAYAAADA+m62AAAAAklEQVR4AewaftIAAAEVSURBVAXBvU7CQADA8X+v195VKwokKmoCakAHP+Lkou/gc/gUri6+gs/g6uDqIpOTiYvxA5NaSJGCvaM9fz/v8uLUhYGiMn9kRvKalOzvrHF+dkKn22WUDuk/3CNdWVLMC3COwU/K23cGIufguMdeXGfRWJpb2/jtrfbV3VPC8/svrZrDCwVeVfKRGK5vbnnsv3DY20BgxizrMSsLEyrpMxmmxF6JGX+y3hDEQc7ga4CInGF3s4mzU2bDhM4CHLVb2NmUtUZE4FtGWYZQlORpQiwF0lrqkcbmOdrkZMMUNZugpxlSUNGrSQqtCHzJYhwTRxEtLVi1BXFticDNkc45lNYoFaGVIlQhc1sQKoX1JZXnY6zhH8PvdPdaFW0gAAAAAElFTkSuQmCC);" data-aspect="0.75" /></p><p>Being a UQCS member really helped me build my professional network up from nothing. I got my first real industry related job as a <a href="/story/Tutoring">tutor</a> through people that I met in the UQCS slack. I can go into <strong>#webdev</strong> on slack and bounce my weird ideas off of high quality industry professionals working at Google and Microsoft and the like for free. I even get to share the <strong>#c</strong> and <strong>#rust</strong> channels with high quality embedded systems engineers and kernel developers.</p><p>For all that UQCS has given me, I'm really proud that I'm able to give back to the society now. <a href="/story/Mychro">Clear Sky Genomics</a> might not be a big name company, but I'm now one of the alumni giving advice to bright-eyed young students. I also really enjoyed spending a year on the committee (taxing as it was) and helping to organise events and sponsors for the year.</p><p>So if you're reading this, and I've managed to interest you in UQCS, awesome! It'd be really cool if you were to <a href="https://join.uqcs.org">join the society</a>, or convince your workplace to <a href="https://sponsor.uqcs.org">sponsor them</a> so you can get access to rooms full of grads like me.</p></main><footer><hr id="footer-line" /></footer></div></content></entry></feed>