<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2026-05-31T23:35:08+00:00</updated><id>/feed.xml</id><title type="html">andrewshaw.nl</title><subtitle>Personal site</subtitle><entry><title type="html">Reviving Genius</title><link href="/2025/07/07/reviving-genius/" rel="alternate" type="text/html" title="Reviving Genius" /><published>2025-07-07T00:00:00+00:00</published><updated>2025-07-07T00:00:00+00:00</updated><id>/2025/07/07/reviving-genius</id><content type="html" xml:base="/2025/07/07/reviving-genius/"><![CDATA[<blockquote>
  <p><strong>EDIT</strong>: I’ve had some feedback from <a href="https://news.ycombinator.com/item?id=44540724">HN readers</a>, and I’ve learned some interesting things since writing this post!</p>

  <p>For one, after this whole journey I thought that <em>surely</em> it is easy enough to type in answers on Anki, and indeed it is. In the card’s template, you can add an entry <code class="language-plaintext highlighter-rouge">{type:FieldName}</code> to the front, where you are asked to type in whatever <code class="language-plaintext highlighter-rouge">FieldName</code> is. So in my “Frequency Dictionary of Dutch” deck, I’ve added <code class="language-plaintext highlighter-rouge">type:Definition</code>.</p>

  <p>Secondly, I actually <a href="https://docs.ankiweb.net/deck-options.html#learning-steps">read the manual for Anki</a>, and they have a great explanation of what the <em>Again / Hard / Good / Easy</em> buttons actually <em>do</em> to the underlying repetition model. What I liked about Genius was how straightforward it was to understand <em>why</em> you’re seeing or not something during a review session.</p>

  <p>Finally the post had some typos, and a couple of errors. Thanks to <code class="language-plaintext highlighter-rouge">aaronbrethorst</code> for pointing out that <code class="language-plaintext highlighter-rouge">+</code> and <code class="language-plaintext highlighter-rouge">-</code> are for instance, and class methods, not public/private.</p>
</blockquote>

<p>In my recent foray into learning Dutch for my relocation to the Netherlands – I’m sure there’ll be much to blog about – I fell victim to the siren song of the Green Owl again.</p>

<p>It’s too tempting - you get showered with instant feedback, encouragement in-app rewards, social stuff etc. To be fair it is fun, and you really feel that you’re doing something useful.</p>

<p>…except.</p>

<p>I last used it in earnest it to ‘learn’ German before a trip to Berlin. I racked up a 150+ day streak or something nuts like that, a good 15 mins per day which they deem pretty high, and then turned up utterly unable to speak or read anything of actual utility</p>

<blockquote>
  <p>die Frau ist kein Klavier</p>

  <p>am Dienstag spreche ich nicht mit dem Apfel</p>
</blockquote>

<p>Now I don’t think it was always like this, and really it’s for another blog post to enumerate the all of the ways it’s been enshittified but suffice to say it’s just not for me anymore.</p>

<h2 id="flashcards-and-anki">Flashcards and Anki</h2>

<p>On the other end of this gamification spectrum is just hard flashcards. Learn the thing on the other side of this card. Your reward is the number of stuff you’ve learned, and if you ‘break your streak’, that’s on you - you’re just not learning anything.</p>

<p>Now, people swear by Anki.</p>

<p>I’ve friends who’ve learned all the world flags, taught themselves to recognise basically any painter by their style etc. Really cool stuff. I think though, for whatever way I ingest language and information, it’s not so good for me.</p>

<p>Specifically I think I’m missing clear right/wrong feedback (rather than judging if I was ‘good’ or ‘bad’ at remembering it), as well as the mechanics of actually having to type out the answer.</p>

<p>In school and university I found that unless I was actually taking notes, and actually writing text, or even typing, I was just <em>not</em> engaging enough.</p>

<p>And so, when flipping over Anki cards and saying them in my head, it just sort of feels like temporarily stimulation of short-term memory.</p>

<p>Despite the spaced repetition, nothing seems to go in, and so I’ve just not been able to build a good habit with it.</p>

<p>(I think there are cards where you have to type the answer in, but truth be told I haven’t really dug into that)</p>

<p>Now though this brings me to my holy grail, white whale app, from <strong>years</strong> gone by.</p>

<h2 id="genius">Genius</h2>

<p>Genius was a spaced repetition app originally written by <a href="https://jrcpl.us/">John R Chang</a> for his own needs. It uses a comparatively simple (to Anki) but still very effective spaced repetition app for learning flashcard-like associations.</p>

<p>And, it was <em>so simple</em>. You have 2 columns to enter the things you want to learn (the cue), and you’re quizzed on them (the answer).</p>

<p>Plus, it had <em>just</em> the right amount of instant-gratification, reward conditioning sounds in the form of the OS X <em>Basso</em>, <em>Blow</em>, <em>Funk</em>.</p>

<p><img src="https://a.fsdn.com/con/app/proj/genius/screenshots/152532.jpg/max/max/1" width="600" /></p>

<p>Alas, development stopped sometime around 2008, and despite an impressive run, it no longer runs on macOS:</p>

<pre><code class="language-txt">/Volumes/Genius/Genius.app/Contents/MacOS
&gt; file Genius
Genius: Mach-O universal binary with 2 architectures: [ppc:
- Mach-O executable ppc] [i386:
- Mach-O executable i386]
Genius (for architecture ppc):  Mach-O executable ppc
Genius (for architecture i386): Mach-O executable i386
</code></pre>

<p>Yes, PowerPC, and i386 (32 bit) only. We’re a few architectures past that!</p>

<h2 id="reviving-old-software">Reviving Old Software</h2>

<p>As an aside, I’ve been really impressed by some recent really cool software/tech conservation, people are doing.</p>

<p>See stuff like <a href="https://www.youtube.com/watch?v=gthm-0Av93Q">MattKC and co’s Lego Island decompilation</a> where they have reverse-engineered and entirely reimplemented whole swathes of the game. Or the <a href="https://github.com/Kneesnap/onstream-data-recovery/blob/main/info/INTRO.MD">Frogger source code recovery</a></p>

<p>Or on the hardware side, <a href="https://www.youtube.com/watch?v=JfZxOuc9Qwk">the saving of the world’s largest CRT TV from a closing-down soba restaurant in Osaka</a> (this is seriously a good watch).</p>

<p>Anyway I figured, if feckin <em>Lego Island</em> can be decompiled, and people can save code from ancient proprietary magnetic tapes, surely it’s not beyond the wit of man, me, to get Genius to work again.</p>

<p><img src="/assets/images/blog/07/genius-needs-to-be-updated.png" width="300" /></p>

<p><em>Maybe I can be that developer :)</em></p>

<h2 id="source-code">Source Code</h2>

<p>The homepage is <a href="https://genius.sourceforge.net">https://genius.sourceforge.net</a>. With some clicking about, you’ll find the link to the <a href="https://sourceforge.net/p/genius/code/HEAD/tree/">source code</a>, an SVN repo. I’d only ever heard of SVN from my elders in university - something about locking, a ‘check out’ being an actual ‘check out’ as one acquires an exclusive lock on a library book.</p>

<p>I’m not interested in plumbing those depths, but luckily <a href="https://git-scm.com/docs/git-svn"><code class="language-plaintext highlighter-rouge">git-svn</code></a> is.</p>

<p>So after much waiting, I ended up with a neat git repo. It’s jarring to pop open a familiar git log and see commits from <em>2004</em>:</p>

<pre><code class="language-txt">commit 5d5624365ab06b08bb83257e78d1bc44bbca7664
Author: jrc &lt;jrc@f73e0bdd-8638-0410-8bb3-c52e70a1423d&gt;
Date:   Sat Nov 6 02:29:42 2004 +0000

    loc 1

commit a2045bf7f2d5ed93f73893edce72665ea260453e
Author: jrc &lt;jrc@f73e0bdd-8638-0410-8bb3-c52e70a1423d&gt;
Date:   Sat Nov 6 02:28:42 2004 +0000

    *** empty log message ***

commit 1d9e71e3fafe91d182a244b3bf5fc1173348dfcb
Author: jrc &lt;jrc@f73e0bdd-8638-0410-8bb3-c52e70a1423d&gt;
Date:   Tue Oct 19 21:53:33 2004 +0000

    This commit was generated by cvs2svn to compensate for changes in r2,
    which included commits to RCS files with non-trunk default branches.

commit 62485d30ab237f05d04e2791a9e6871b9adbf701
Author: (no author) &lt;(no author)@f73e0bdd-8638-0410-8bb3-c52e70a1423d&gt;
Date:   Tue Oct 19 21:53:33 2004 +0000

    New repository initialized by cvs2svn.
</code></pre>

<p>I love how everything repeats itself. I wonder what was in the CVS source.</p>

<h2 id="building-for-macos">Building for macOS</h2>

<p>As it turns out, it was surprisingly easy to get the project to build!</p>

<p>The <code class="language-plaintext highlighter-rouge">.xcodeproj</code> format still seems to be entirely forwards-compatible, and it opened up completely fine on the latest developer tools, on my arm64 macbook.</p>

<p>Building failed, as it was unable to locate the old OS X 10.4 SDK it was hard-coded to require.</p>

<p>But changing this to the latest tooling let the obj-c compiler actually run, with a compiler error!</p>

<div class="language-objective-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">GeniusAssociationEnumerator</span><span class="p">.</span><span class="n">m</span><span class="o">:</span><span class="mi">265</span><span class="o">:</span><span class="mi">87</span> <span class="n">Incompatible</span> <span class="n">function</span> <span class="n">pointer</span> <span class="n">types</span> <span class="n">sending</span> 
  <span class="err">'</span><span class="kt">int</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">id</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="err">'</span> <span class="n">to</span> <span class="n">parameter</span> <span class="n">of</span> <span class="n">type</span> 
  <span class="err">'</span><span class="n">NSInteger</span> <span class="p">(</span><span class="o">*</span> <span class="n">_Nonnull</span><span class="p">)(</span><span class="n">id</span> <span class="n">_Nonnull</span><span class="p">,</span> <span class="n">id</span> <span class="n">_Nonnull</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span> <span class="n">_Nullable</span><span class="p">)</span><span class="err">'</span> 
    <span class="p">(</span><span class="n">aka</span> <span class="err">'</span><span class="kt">long</span> <span class="p">(</span><span class="o">*</span><span class="p">)(</span><span class="n">id</span> <span class="n">_Nonnull</span><span class="p">,</span> <span class="n">id</span> <span class="n">_Nonnull</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span> <span class="n">_Nullable</span><span class="p">)</span><span class="err">'</span><span class="p">)</span>
</code></pre></div></div>

<p>Looks like type checking got a little bit more strict.</p>

<p>A <em>single line change</em> fixed the build:</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code>//! randomly returns NSOrderedAscending or NSOrderedDescending
<span class="gd">-int RandomSortFunction(id object1, id object2, void * context)
</span><span class="gi">+NSComparisonResult RandomSortFunction(id object1, id object2, void * context)
</span> {
     BOOL x = random() &amp; 0x1;
     return (x ? NSOrderedAscending : NSOrderedDescending);
</code></pre></div></div>

<p><a href="https://github.com/shawa/genius/commit/39f26bed7e5bde146bd05aadacbe33e2d2432386#diff-dd1391d7c026c37eb147d2c8adace5dbd76bbfb738780d502584f51b9f6c281eR16-L37"><code class="language-plaintext highlighter-rouge">GeniusAssociationEnumerator.m</code></a></p>

<p><em>As an aside, surely it’s really confusing as an Objective-C developer reading diffs when it uses <code class="language-plaintext highlighter-rouge">+</code> and <code class="language-plaintext highlighter-rouge">-</code> to denote class/instance methods?</em></p>

<p>…and it <strong>ACTUALLY BUILT</strong>.</p>

<p><img src="/assets/images/blog/07/broken.png" alt="Broken Genius build screenshot" width="600" /></p>

<p>It looks a bit janky, but it works!</p>

<p>The next hurdle was getting Xcode to read the old <code class="language-plaintext highlighter-rouge">nib</code> files for the UI. I’ve admittedly little idea how it works, but they seem so old that even Xcode can’t convert them. Fortunately there is <code class="language-plaintext highlighter-rouge">ibtool</code>, which has an <code class="language-plaintext highlighter-rouge">--upgrade</code> option to replace them.</p>

<p>After a little bit of messing about in Interface Builder, upscaling the icons, and fixing some text clipping, we get a fairly snazzy, modern looking macOS app! And it’s completely functional, fully compatible with the original <code class="language-plaintext highlighter-rouge">.genius</code> card decks I created years ago.</p>

<p><img src="/assets/images/blog/07/working.png" alt="It works! Learning Dutch vocab" width="600" /></p>

<p>Projects like this are certainly fun, and valuable in their own right. I’ve learned a relative ton (compared to my zero knowledge) about mac development, took a nice trip down memory lane with the different architectures, etc.</p>

<p>However ultimately Genius was written to aid memorisation, and now I’m happily using it to do just that, just as I did back in 2008!</p>

<p><img src="/assets/images/blog/07/quiz.png" alt="Genius Quiz" width="600" /></p>

<p><img src="/assets/images/blog/07/festeta.gif" /></p>

<p>You can browse the updated source code at <a href="https://github.com/shawa/genius">https://github.com/shawa/genius</a>.</p>]]></content><author><name></name></author><category term="code" /><category term="objective-c" /><category term="macOS" /><summary type="html"><![CDATA[EDIT: I’ve had some feedback from HN readers, and I’ve learned some interesting things since writing this post! For one, after this whole journey I thought that surely it is easy enough to type in answers on Anki, and indeed it is. In the card’s template, you can add an entry {type:FieldName} to the front, where you are asked to type in whatever FieldName is. So in my “Frequency Dictionary of Dutch” deck, I’ve added type:Definition. Secondly, I actually read the manual for Anki, and they have a great explanation of what the Again / Hard / Good / Easy buttons actually do to the underlying repetition model. What I liked about Genius was how straightforward it was to understand why you’re seeing or not something during a review session. Finally the post had some typos, and a couple of errors. Thanks to aaronbrethorst for pointing out that + and - are for instance, and class methods, not public/private.]]></summary></entry><entry><title type="html">Resorting Pages with Graphs</title><link href="/2025/01/15/resorting-pages-with-graphs/" rel="alternate" type="text/html" title="Resorting Pages with Graphs" /><published>2025-01-15T00:00:00+00:00</published><updated>2025-01-15T00:00:00+00:00</updated><id>/2025/01/15/resorting-pages-with-graphs</id><content type="html" xml:base="/2025/01/15/resorting-pages-with-graphs/"><![CDATA[<p>There’s a really neat graph-based solution to <a href="https://adventofcode.com/2024/day/5">Day 5 of 2024’s Advent of Code</a>.</p>

<p>I’m going to really heavily paraphrase, so you should read the problem spec if this is unclear. Essentially, we’re given a list of ‘page updates’, as pairs of page IDs, for example:</p>

<pre><code class="language-txt">47|53
97|13
97|61
</code></pre>

<p>Which means “page 47 must occur before 53, page 97 before 13, page 97 before 61”.</p>

<p>The next set of inputs are proposed sets of page orderings:</p>

<pre><code class="language-txt">97,61,53,29,13
75,29,13,97
</code></pre>

<p>So in line one, we place page ID 75, then page ID 47, … etc.</p>

<p>Part one of the problem is to <em>find the orderings which are valid</em>. So, given the set of rules we just ingested, check that the proposed orderings don’t break any of those rules. For example, the second one is invalid, since the last placement tells us to put page 97 after page 13, but our rules say 97 must always occur <em>before</em> 13.</p>

<p>In the second part, we’re asked to take all of the ones which are invalid, and then <em>put them in the right order</em> according to our rules.</p>

<hr />

<p>Naively, I started doing something like the following, take the rules as literal Prolog facts:</p>

<div class="language-prolog highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="ss">before</span><span class="p">(</span><span class="m">74</span><span class="p">,</span> <span class="m">53</span><span class="p">).</span>
<span class="ss">before</span><span class="p">(</span><span class="m">97</span><span class="p">,</span> <span class="m">13</span><span class="p">).</span>
<span class="ss">before</span><span class="p">(</span><span class="m">97</span><span class="p">,</span> <span class="m">61</span><span class="p">).</span>
</code></pre></div></div>

<p>Then checking the validity involves some sort of search. This encoding is incomplete though, since if we query <code class="language-plaintext highlighter-rouge">before(X, Y)</code> we only get solutions for if <code class="language-plaintext highlighter-rouge">X</code> and <code class="language-plaintext highlighter-rouge">Y</code> are <em>directly adjacent</em>.</p>

<p><img src="/assets/images/blog/01/total-order.png" alt="three node graph with edges, showing a thinner edge from the first to the last node" /></p>

<p>We’re missing the fact that a coming before b is valid; the thin edge in the graphic above.</p>

<p>We can add another predicate:</p>

<div class="language-prolog highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="ss">before</span><span class="p">(</span><span class="nv">X</span><span class="p">,</span> <span class="nv">Y</span><span class="p">)</span> <span class="p">:-</span>
    <span class="ss">before</span><span class="p">(</span><span class="nv">X</span><span class="p">,</span> <span class="nv">A</span><span class="p">),</span>
    <span class="ss">before</span><span class="p">(</span><span class="nv">A</span><span class="p">,</span> <span class="nv">Y</span><span class="p">).</span>
</code></pre></div></div>

<p>This captures the idea that <code class="language-plaintext highlighter-rouge">X</code> appears before <code class="language-plaintext highlighter-rouge">Y</code> if we have some <code class="language-plaintext highlighter-rouge">A</code> which <code class="language-plaintext highlighter-rouge">X</code> is before, and that <code class="language-plaintext highlighter-rouge">A</code> itself is before <code class="language-plaintext highlighter-rouge">Y</code> (either as a direct match to one of the earlier facts, or as a recursive call to this rule.</p>

<p>Then we can validate an ordering by taking pairwise entries, and checking it’s correct by querying <code class="language-plaintext highlighter-rouge">before/2</code>.</p>

<p>This should work, but is still a bit too unidirectional. I’m not quite a good enough Prolog programmer to take this further (in the given day’s time to solve the challenge), so I switched gears to Elixir. As is usually the case, if we can find a better representation of our data, the mechanics of the solution should hopefully just fall out.</p>

<hr />

<p>We can rephrase the problem to be a bit more formal. Let’s define:</p>

<ul>
  <li>The set of page ids I, which in our case is just integers, but these can be arbitrary labels.</li>
  <li>The set of rules R, which are pairs of page IDs (a, b), indicating that a must appear before b.</li>
  <li>The set of orderings O, which is a proposed ordering of page IDs.</li>
</ul>

<p>Now here’s the graph bit, we can read R to be a <em>set of edges</em> on a <em>directed graph</em> G, where (a, b) indicates a appearing before b. The graph G now encodes quite some detail:</p>

<ul>
  <li>What page IDs are covered</li>
  <li>What page IDs are directly adjacent</li>
  <li>What page IDs <em>may</em> appear before another</li>
</ul>

<p>The last part is the cool part - (a, b) is valid if b is <em>reachable</em> from a, that is you can walk a path from a going through arbitrarily many nodes along the way, and end up at b (what our rule <code class="language-plaintext highlighter-rouge">before/2</code>) encoded earlier.</p>

<p>Using LibGraph for Elixir (and having turned the input into a graph as the “parse” step made the solution to part A stupidly succinct:</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">Enum</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="n">updates</span><span class="p">,</span> <span class="o">&amp;</span><span class="no">Graph</span><span class="o">.</span><span class="n">is_subgraph?</span><span class="p">(</span><span class="nv">&amp;1</span><span class="p">,</span> <span class="n">ordering</span><span class="p">))</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">ordering</code> being a subgraph of the <code class="language-plaintext highlighter-rouge">updates</code> is expressing that all of the nodes in <code class="language-plaintext highlighter-rouge">ordering</code> have to appear in the same order as defined by <code class="language-plaintext highlighter-rouge">updates</code>, as explained above.</p>

<p>Another way to conceptualise graph H being a subgraph of graph G, is if we can <em>remove</em> intermediate nodes from G to eventually arrive at H:</p>

<p><img src="/assets/images/blog/01/vertex-removal.png" alt="sucessively removing vertices of G to make H" /></p>

<p>The second part asks us to fix the out-of-order orderings, per the rules. This again ends up being really simple when modelled as graphs:</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">updates</span>
<span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">reject</span><span class="p">(</span><span class="o">&amp;</span><span class="no">Graph</span><span class="o">.</span><span class="n">is_subgraph?</span><span class="p">(</span><span class="nv">&amp;1</span><span class="p">,</span> <span class="n">ordering</span><span class="p">))</span>
<span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="k">fn</span> <span class="n">g</span> <span class="o">-&gt;</span>
    <span class="n">g</span>
    <span class="o">|&gt;</span> <span class="no">Graph</span><span class="o">.</span><span class="n">vertices</span><span class="p">()</span>
    <span class="o">|&gt;</span> <span class="n">then</span><span class="p">(</span><span class="o">&amp;</span><span class="no">Graph</span><span class="o">.</span><span class="n">subgraph</span><span class="p">(</span><span class="n">ordering</span><span class="p">,</span> <span class="nv">&amp;1</span><span class="p">))</span>
    <span class="o">|&gt;</span> <span class="no">Graph</span><span class="o">.</span><span class="n">topsort</span><span class="p">()</span> 
<span class="k">end</span><span class="p">)</span>
</code></pre></div></div>

<p>We first filter out the ones that are correct, then perform a <em>topological sort</em> on the vertices of the updates which appear in the ordering. A topological sort is what it sounds like, rather than sorting e.g. by some comparator, we sort them by the order in which they show up in the graph. Essentially, walk it from the start to the end, listing out the edges as you see them.</p>

<p>And that’s about it! I’ve probably not explained this too well, but I thought it was really neat. The full code for the solution is on <a href="https://github.com/shawa/advent-of-code-2024">my GitHub repo</a> which contains partial solutions for the rest of 2024. Onward to more graphs.</p>]]></content><author><name></name></author><category term="code" /><category term="advent-of-code" /><category term="graph-theory" /><category term="internet" /><summary type="html"><![CDATA[There’s a really neat graph-based solution to Day 5 of 2024’s Advent of Code.]]></summary></entry></feed>