<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>James Adam Buckland</title><link>https://jbuckland.com/blog/</link><description>Recent content on James Adam Buckland</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Sat, 09 Apr 2022 00:00:00 -0400</lastBuildDate><atom:link href="https://jbuckland.com/blog/index.xml" rel="self" type="application/rss+xml"/><item><title>Three-Dimensional Prints with Blender and OpenStreetMap</title><link>https://jbuckland.com/blog/plotting-blender/</link><pubDate>Sat, 09 Apr 2022 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/plotting-blender/</guid><description>Summary I used blender-osm and Freestyle to render three-dimensional geodata from OpenStreetMap to a pen plotter.
Here is the finished product, a 11in x 17in print on vellum. (Click through for high resolution.)
Here is an overview of the data pipeline I used to generate this print:
┌─────────────┐ │OpenStreetMap│ └┬────────────┘ │ ┌▼──────────┐ │blender-osm│ └┬──────────┘ │ ┌▼──────┐ │Blender│ └┬──────┘ │ ┌▼─────────────────────┐ │Freestyle SVG Exporter│ └┬─────────────────────┘ │ ┌▼──────┐ │AxiDraw│ └───────┘ OpenStreetMap OpenStreetMap is a Wikipedia-style repository of map data.</description><content>&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>I used &lt;a href="https://prochitecture.gumroad.com/l/blender-osm">&lt;code>blender-osm&lt;/code>&lt;/a> and &lt;a href="https://docs.blender.org/manual/en/2.79/render/freestyle/export_svg.html">Freestyle&lt;/a> to render three-dimensional geodata from &lt;a href="https://openstreetmap.org/">OpenStreetMap&lt;/a> to a &lt;a href="https://shop.evilmadscientist.com/productsmenu/890">pen plotter&lt;/a>.&lt;/p>
&lt;p>Here is the finished product, a 11in x 17in print on vellum. (Click through for high resolution.)&lt;/p>
&lt;figure class="center" >
&lt;img src="images/full.jpeg" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>Here is an overview of the data pipeline I used to generate this print:&lt;/p>
&lt;pre tabindex="0">&lt;code>┌─────────────┐
│OpenStreetMap│
└┬────────────┘
│
┌▼──────────┐
│blender-osm│
└┬──────────┘
│
┌▼──────┐
│Blender│
└┬──────┘
│
┌▼─────────────────────┐
│Freestyle SVG Exporter│
└┬─────────────────────┘
│
┌▼──────┐
│AxiDraw│
└───────┘
&lt;/code>&lt;/pre>&lt;h2 id="openstreetmap">OpenStreetMap&lt;/h2>
&lt;p>&lt;a href="https://openstreetmap.org/">OpenStreetMap&lt;/a> is a Wikipedia-style repository of map data. I have used it &lt;a href="https://jbuckland.com/2022/02/06/osm.html">before&lt;/a> to download and print map data with a pen plotter. It contains high-quality, free, volunteered and compiled 2D and 3D geodata about the real world.&lt;/p>
&lt;h2 id="blender-osm">blender-osm&lt;/h2>
&lt;p>&lt;a href="https://prochitecture.gumroad.com/l/blender-osm">&lt;code>blender-osm&lt;/code>&lt;/a> is a plugin written by &lt;a href="https://prochitecture.gumroad.com/">&lt;code>prochitecture&lt;/code>&lt;/a> which imports geodata from OSM into &lt;a href="https://blender.org/">Blender&lt;/a>, a free 3D computer graphics software suite.&lt;/p>
&lt;p>After installing the plugin and using it to create a new document, &lt;code>blender-osm&lt;/code> links the user to an &lt;a href="http://prochitecture.com/blender-osm/extent/?blender_version=3.1&amp;amp;addon=blender-osm&amp;amp;addon_version=2.5.3">area selection website&lt;/a> which makes it simple to select an area of the map to download.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/blender-osm-selection.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>We can copy/paste the lat/long area selection (in this case, a string like &lt;code>-74.02041,40.73327,-73.98792,40.75015&lt;/code>) into the &lt;code>blender-osm&lt;/code> interface and select which map layers to import (buildings, forests, water, etc.)&lt;/p>
&lt;h2 id="blender">Blender&lt;/h2>
&lt;p>We end up with a detailed 3D model&amp;hellip;&lt;/p>
&lt;figure class="center" >
&lt;img src="images/blender-nyc-overhead.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>&amp;hellip;but of course it looks better in perspective.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/blender-nyc-perspective.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>I knew I wanted to draw a multicolor print, so I grouped the downloaded layers by pen color. All the buildings went in one group, all primary roads in another, etc. Later I print these layers on the plotter one by one, swapping out the pen in between layers.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/blender-nyc-groups.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>Using separate object collections means we can toggle on/off items by layer in the Blender UI. Here is a render of the map with only 3D buildings visible:&lt;/p>
&lt;figure class="center" >
&lt;img src="images/blender-nyc-buildings-only.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>and another render of the map with only primary and secondary highways visible:&lt;/p>
&lt;figure class="center" >
&lt;img src="images/blender-nyc-roads-1-only.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>and another render of the map with only pedestrian roads visible.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/blender-nyc-roads-2-only.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>Of course, we can render this for any combination of the layers.&lt;/p>
&lt;p>The result is a 3D model in Blender which can be exported as a raster image or as an animation. But the pen plotter expects vector data, so we have to use a plugin.&lt;/p>
&lt;h2 id="freestyle-svg-exporter">Freestyle SVG Exporter&lt;/h2>
&lt;p>&lt;a href="https://docs.blender.org/manual/en/2.79/render/freestyle/export_svg.html">Freestyle&lt;/a> is a Blender plugin which plots the edges of visible objects as SVG lines.&lt;/p>
&lt;p>This is perfect for pen plotting, since pen plotters expect vector input.&lt;/p>
&lt;p>Freestyle is very powerful but a bit hard to use. First I enabled it like so:&lt;/p>
&lt;figure class="center" >
&lt;img src="images/blender-freestyle-enable.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>Then I played with the settings until it handled crease angles in the way I expected.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/blender-freestyle-settings.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>I also applied a Bezier filter which gave the finished edges a cartoonish curve.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/blender-freestyle-settings-2.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>It&amp;rsquo;s hard to tell the effects of these settings until Blender actually renders the scene to SVG.&lt;/p>
&lt;h3 id="camera">Camera&lt;/h3>
&lt;p>Finally I played with the camera location and perspective settings until I had a scene composition I liked. The focal length of the virtual camera makes a big difference:&lt;/p>
&lt;p>
&lt;figure class="center" >
&lt;img src="images/focal-length-1.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/focal-length-2.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/focal-length-3.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;/p>
&lt;p>It is also possible to swap out a perspective camera for an orthographic one, which looks isometric like a video game. This is a nice effect but I felt it gave the scene some undesirable flatness.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/focal-orthographic.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;h2 id="svg">SVG&lt;/h2>
&lt;p>The default exported PNG has both the raster image and the overlaid vector lines, which makes it easy to see why Freestyle drew lines in certain places.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/png-export.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>The default exported SVG is what we eventually send to the plotter.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/svg-export.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;h3 id="layers">Layers&lt;/h3>
&lt;p>We can pop into the SVG file and separate the layers manually, since AxiDraw requires printing one SVG file at a time and cannot filter by layer. Here is the layer for buildings-only:&lt;/p>
&lt;figure class="center" >
&lt;img src="images/svg-export-buildings.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>roads-only:&lt;/p>
&lt;figure class="center" >
&lt;img src="images/svg-export-roads.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>and highways-only:&lt;/p>
&lt;figure class="center" >
&lt;img src="images/svg-export-highways.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>Blender rendered these all in the same pass, so objects in the foreground correctly block objects in the background.&lt;/p>
&lt;p>Foreground:&lt;/p>
&lt;figure class="center" >
&lt;img src="images/frustrum-foreground.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>Background:&lt;/p>
&lt;figure class="center" >
&lt;img src="images/frustrum-background.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;h2 id="axidraw">AxiDraw&lt;/h2>
&lt;p>That&amp;rsquo;s pretty much it. We can send each layer to the plotter one at a time, swapping out pen colors, and we know that objects in the foreground will appear to obscure objects in the background. Here is the final product:&lt;/p>
&lt;figure class="center" >
&lt;img src="images/full.jpeg" style="border-radius: 8px;" />
&lt;/figure></content></item><item><title>OpenStreetMap with a Pen Plotter</title><link>https://jbuckland.com/blog/plotting-open-street-map/</link><pubDate>Sun, 06 Feb 2022 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/plotting-open-street-map/</guid><description>I wrote some Rust code to render GeoJSON data to SVG for use with a pen plotter.
┌─────────────┐ ┌───────┐ ┌───┐ ┌───────┐ │OpenStreetMap├─►GeoJSON├─►SVG├─►Printer│ └─────────────┘ └───────┘ └───┘ └───────┘ Along the way I learned about OpenStreetMap, the Overpass API, the Overpass query language (OverpassQL), and a web-based interface for quickly trying out OverpassQL queries. I also wrote some trivial and some nontrivial geometry algorithms for point/segment/polygon collision, intersection, cropping, and crosshatching.
Background Pen Plotters I recently bought an AxiDraw V3/A3.</description><content>&lt;p>I wrote some Rust code to render GeoJSON data to SVG for use with a &lt;a href="https://shop.evilmadscientist.com/productsmenu/846">pen plotter&lt;/a>.&lt;/p>
&lt;pre tabindex="0">&lt;code>┌─────────────┐ ┌───────┐ ┌───┐ ┌───────┐
│OpenStreetMap├─►GeoJSON├─►SVG├─►Printer│
└─────────────┘ └───────┘ └───┘ └───────┘
&lt;/code>&lt;/pre>&lt;p>Along the way I learned about &lt;a href="https://openstreetmap.org/">OpenStreetMap&lt;/a>, the &lt;a href="https://wiki.openstreetmap.org/wiki/Overpass_API">Overpass API&lt;/a>, the &lt;a href="https://wiki.openstreetmap.org/wiki/Overpass_API/Overpass_QL">Overpass query language (OverpassQL)&lt;/a>, and a &lt;a href="https://overpass-turbo.eu/">web-based interface&lt;/a> for quickly trying out OverpassQL queries. I also wrote some trivial and some nontrivial geometry algorithms for point/segment/polygon collision, intersection, cropping, and crosshatching.&lt;/p>
&lt;h2 id="background">Background&lt;/h2>
&lt;h3 id="pen-plotters">Pen Plotters&lt;/h3>
&lt;p>I recently bought an &lt;a href="https://shop.evilmadscientist.com/productsmenu/890">AxiDraw V3/A3&lt;/a>. AxiDraw is a line of pen plotters sold by &lt;a href="https://shop.evilmadscientist.com/">Evil Mad Scientist&lt;/a>.&lt;/p>
&lt;p>The AxiDraw V3/A3 pen plotter is a &lt;a href="https://en.wikipedia.org/wiki/Numerical_control">numerical control&lt;/a> machine with a large drawing area (16.93 × 11.69 inches), a surprisingly fast arm (15 in per second), and an unusually high precision (2032 steps per inch).&lt;/p>
&lt;figure class="center" >
&lt;img src="https://cdn.evilmadscientist.com/catalog/emskits/axidraw/v3a3r1/imgmed/1@2x.jpg" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>A pen plotter comes with general-purpose mount which you can tighten around any kind of writing implement.&lt;/p>
&lt;figure class="center" >
&lt;img src="https://cdn.evilmadscientist.com/catalog/emskits/axidraw/v3a3r1/imgmed/6@2x.jpg" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>I suspect you could mount something more exotic (watercolor, laser cutter, knife) if you wanted to. I mostly use pens and markers.&lt;/p>
&lt;p>Pen plotters have been around since &lt;a href="https://en.wikipedia.org/wiki/Calcomp_plotter">at least 1959&lt;/a>. They generally inspect to be fed low-level instructions such as &amp;lsquo;go here&amp;rsquo;, &amp;lsquo;pen down&amp;rsquo;, &amp;lsquo;go there&amp;rsquo;, &amp;lsquo;pen up&amp;rsquo;, etc.&lt;/p>
&lt;p>A modern pen plotter such as the AxiDraw V3/A3 comes with utilities for interpreting &lt;a href="https://en.wikipedia.org/wiki/Scalable_Vector_Graphics">SVGs&lt;/a>, reordering instructions for faster printing, estimating print time, controlling print speed to avoid ink blots, etc.&lt;/p>
&lt;h3 id="openstreetmap">OpenStreetMap&lt;/h3>
&lt;p>&lt;a href="https://openstreetmap.org/">OpenStreetMap&lt;/a> began in the mid-2000s as an effort to create a Wikipedia-style repository of map data. It comprises a backend database for storing map data (locations, paths, areas, and associated information), a series of APIs for querying that information, a &lt;a href="https://www.openstreetmap.org/way/427818536">general-purpose web frontend&lt;/a> for viewing map data (think &lt;a href="http://maps.google.com/">Google Maps&lt;/a> or &lt;a href="https://mapquest.com/">MapQuest&lt;/a>), and a large set of tools for inserting, editing, and retrieving data.&lt;/p>
&lt;h3 id="overpass-api">Overpass API&lt;/h3>
&lt;p>The &lt;a href="https://wiki.openstreetmap.org/wiki/Overpass_API">Overpass API&lt;/a> is an API for querying OpenStreetMap geodata. The easiest way to understand it is to&lt;/p>
&lt;ol>
&lt;li>visit &lt;a href="https://overpass-turbo.eu/#">overpass-turbo.eu&lt;/a>,&lt;/li>
&lt;li>enter an example query such as &lt;code>(node(51.249,7.148,51.251,7.152);&amp;lt;;);out meta;&lt;/code>&lt;/li>
&lt;li>press &lt;strong>▶️ Run&lt;/strong>, and&lt;/li>
&lt;li>press &lt;strong>🔎&lt;/strong> to center the preview window around the returned data.&lt;/li>
&lt;/ol>
&lt;p>The example query above is written in &lt;a href="https://wiki.openstreetmap.org/wiki/Overpass_API/Overpass_QL">Overpass QL&lt;/a>, the Overpass query language. I find it a bit complex, but I understand that it&amp;rsquo;s very powerful.&lt;/p>
&lt;h2 id="work">Work&lt;/h2>
&lt;h3 id="generative-art">Generative Art&lt;/h3>
&lt;p>When I first started to use the pen plotter, I began by developing a set of low-level libraries in Rust for manipulating points, line segments and open/closed polygons in a vector format. I made some generative art with simple lines and curves:&lt;/p>
&lt;p>
&lt;figure class="center" >
&lt;img src="images/arcs-001.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/loops.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/pts-001.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/thing-001.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;/p>
&lt;p>(Most of this stuff is inspired by &lt;a href="https://en.wikipedia.org/wiki/Sol_LeWitt">Sol LeWitt&lt;/a>.)&lt;/p>
&lt;p>This was a really good starting point. I began to rewrite some of my low-level libraries for more ergonomic usage (simpler construction of points and rectangles relative to each other, automatic centering, predictable cropping behavior) and began to squash floating-point bugs. I also started to write more complex generative art which required computing bounding boxes, line/line collisions, and other geometry problems.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/circs-001.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/foo.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>Absolutely every problem I needed to solve here had a solution on Wikipedia (&lt;a href="https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection">line-line intersection&lt;/a>, &lt;a href="https://en.wikipedia.org/wiki/Intersection_(Euclidean_geometry)#Two_line_segments">line segment intersection&lt;/a>). Vector graphics are well-understood and well-trodden territory.&lt;/p>
&lt;h3 id="dealing-with-geodata">Dealing with Geodata&lt;/h3>
&lt;p>Eventually I began to try to print OpenStreetMap data instead. I learned that plotting unprocessed lat/long data results in a squashed, stretched map, like so:&lt;/p>
&lt;figure class="center" >
&lt;img src="images/bar-centralpark.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>This is because points in geographic databases are generally stored as (latitude, longitude) (or, sometimes, (longitude, latitude)) pairs.&lt;/p>
&lt;p>Because of issues with &lt;a href="https://en.wikipedia.org/wiki/Map_projection">map projection&lt;/a> and especially &lt;a href="https://wiki.openstreetmap.org/wiki/EPSG:3857">ESPG:3857&lt;/a> (the specific spherical mercator projection OpenStreetMap uses), areas further from the equator become more distorted.&lt;/p>
&lt;p>I ended up building some utilities to rotate, scale, shift, and project the vector data. I often found that I would pull down map data, render a plot, and later decide that I wanted to zoom in a bit, or pan over a bit.&lt;/p>
&lt;h3 id="layers-and-tags">Layers and tags&lt;/h3>
&lt;p>Next I realized I wanted to use colored pens / colored markers to draw multicolor prints. It seems natural that streets might be one color, water another, etc.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/FJtb8rQXEAcEUxJ.jpeg" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>OpenStreetMap tags all features with &lt;a href="https://wiki.openstreetmap.org/wiki/Tags">tags&lt;/a>, which areconvention-driven key/value pairs. These might annotate some line as a highway or as a footway, or they might annotate an area as a park vs. a garden, etc.&lt;/p>
&lt;p>I began to pull down geodata and process each element as a polygon, multipolygon, linestring, etc. while preserving the tags associated with that original bit of geodata. This let me apply colors, crosshatching, etc. first, then apply color and separate into layers near the end.&lt;/p>
&lt;h3 id="crosshatching">Crosshatching&lt;/h3>
&lt;figure class="center" >
&lt;img src="images/FJuCBgKXoAcog5v.jpeg" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>I wrote a &lt;a href="https://en.wikipedia.org/wiki/Hatching">crosshatcher&lt;/a> to transform a closed polygon into a set of straight lines which fill its interior with crosshatching at some spacing and angle.&lt;/p>
&lt;p>Crosshatching is computational tricky &amp;ndash; given some angle and distance, you have to find the set of all lines which could pass through the polygon and truncate each line into one or more line segments. Because the polygon isn&amp;rsquo;t necessarily convex, a given line might intersect the polygon zero, one, or more than one times. It might intersect the polygon along an edge, or precisely at a vertex. A crosshatch line might run along one of the edges of the polygon.&lt;/p>
&lt;p>You can probably see in the print above that this algorithm used to fail whenever a crosshatch line was unfortunate enough to meet its surrounding polygon at a vertex rather than along an edge.&lt;/p>
&lt;h3 id="deduplication">Deduplication&lt;/h3>
&lt;figure class="center" >
&lt;img src="images/FKW-kzcXIAMfwmV.jpeg" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>When plotting transit data, I found that the pen plotter spent a lot of time retracing its path. Transit data is very layered &amp;ndash; the train going uptown, the same train going uptown express, the same train going downtown, etc. all occupy the same points and lines in space. When the pen plotter retraces its steps, the ink smears and bleeds in an ugly way.&lt;/p>
&lt;p>Deduplication is another surprisingly hard problem. I ended up quantizing each point to a fine grid, hashing the data in order to deduplicate it, and then restoring the data to its original position.&lt;/p>
&lt;p>Quantization is determined by the size of the underlying grid. If you make the grid too coarse, it mangles curved input data into blocky output data.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/FKYY-ErXEAEiDfF.jpeg" style="border-radius: 8px;" />
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/FKZYLRkXoAAfVvo.jpeg" style="border-radius: 8px;" />
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/FKZYLEMWYAA0Wiw.jpeg" style="border-radius: 8px;" />
&lt;/figure>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>This is a pretty fun project. I ended up with a a very fast piece of software which can convert OpenStreetMap GeoJSON data into an SVG suitable for plotting. It&amp;rsquo;s easy to dial in an address or an area of land, toggle on and off layers, and produce a large, detailed print as a piece of art or a gift.&lt;/p>
&lt;p>I plan to add some more interesting features &amp;ndash; crosshatching is only one way to fill an area with a line, for example. I also think it would be really fun to plot data which has been stretched or transformed in some obviously mathematical way (projected onto a hyperbolic plane so that things get more dense further from the center, for example).&lt;/p></content></item><item><title>Simulated Annealing for Image Tracing</title><link>https://jbuckland.com/blog/graphics-annealing/</link><pubDate>Mon, 18 Oct 2021 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/graphics-annealing/</guid><description>Github https://gist.github.com/ambuc/3374d271444a303612d86d08801291be I wrote a bit of Python to help &amp;ldquo;trace&amp;rdquo; an image (perform a raster-to-vector transformation) using simulated annealing.
If you already know about image tracing, image formats, and simulated annealing, feel free to skip ahead to work. Otherwise, read on.
Background Image tracing Raster images Raster images (such as digital photos) are stored in the computer as grids of pixels. Since a raster image lives in a grid, it necessarily has a fixed size (number of pixels) and resolution (number of pixels per inch).</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">Github&lt;/th>
&lt;td> &lt;a href="https://gist.github.com/ambuc/3374d271444a303612d86d08801291be">https://gist.github.com/ambuc/3374d271444a303612d86d08801291be&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;p>I wrote a bit of Python to help &amp;ldquo;trace&amp;rdquo; an image (perform a raster-to-vector transformation) using simulated annealing.&lt;/p>
&lt;p>If you already know about image tracing, image formats, and simulated annealing, feel free to skip ahead to &lt;a href="#work">work&lt;/a>. Otherwise, read on.&lt;/p>
&lt;h2 id="background">Background&lt;/h2>
&lt;h3 id="image-tracing">Image tracing&lt;/h3>
&lt;h4 id="raster-images">Raster images&lt;/h4>
&lt;p>Raster images (such as digital photos) are stored in the computer as grids of pixels. Since a raster image lives in a grid, it necessarily has a fixed size (number of pixels) and resolution (number of pixels per inch). Image resolution relates to how much detail a photograph has. An image with higher resolution can be printed larger before it begins to appear pixellated or blocky.&lt;/p>
&lt;figure class="center" >
&lt;img src="https://upload.wikimedia.org/wikipedia/commons/thumb/3/3b/Rgb-raster-image.svg/1200px-Rgb-raster-image.svg.png" style="border-radius: 8px;" />
&lt;figcaption class="center" style="color: inherit; background-color: inherit;" >Here is a (low-resolution) raster image of a smiley face.&lt;/figcaption>
&lt;/figure>
&lt;h4 id="vector-images">Vector images&lt;/h4>
&lt;p>Vector images are not grids of pixels. They are instructions to a graphics processor for visiting points, drawing lines between them, shading areas, etc. Since points and lines are stored in the vector format as &lt;a href="https://en.wikipedia.org/wiki/Floating-point_arithmetic">floating-point numbers&lt;/a>, vector images have dramatically higher resolution than raster images. They can be printed at arbitrary size without pixellation since they do not consist of pixels.&lt;/p>
&lt;figure class="center" >
&lt;img src="https://upload.wikimedia.org/wikipedia/commons/6/6b/Bitmap_VS_SVG.svg" style="border-radius: 8px;" />
&lt;figcaption class="center" style="color: inherit; background-color: inherit;" >When a raster is enlarged, it becomes pixellated.&lt;/figcaption>
&lt;/figure>
&lt;h4 id="rasterization">Rasterization&lt;/h4>
&lt;p>&lt;a href="https://en.wikipedia.org/wiki/Rasterisation">&lt;em>Rasterization&lt;/em>&lt;/a> refers to the process of converting a vector image to a raster image. This might be useful for rendering a vector to any destination which requires a grid of inputs such as an LCD screen or an inkjet printer. Rasterization is an interesting field full of optimizations (&lt;a href="https://en.wikipedia.org/wiki/Spatial_anti-aliasing">antialiasing&lt;/a> or &lt;a href="https://en.wikipedia.org/wiki/Sub-pixel_resolution">subpixel resolution&lt;/a>) but it is essentially straightforward: we want to find a target raster which looks most like its source vector to the human eye.&lt;/p>
&lt;h4 id="image-tracing-1">Image tracing&lt;/h4>
&lt;p>&lt;a href="https://en.wikipedia.org/wiki/Image_tracing">Image tracing&lt;/a> refers to the process of converting a raster image to a vector image. It is also essentially straightforward: we want to find the target vector which looks most like its source raster to the human eye.&lt;/p>
&lt;p>But image tracing is inherently more imprecise since it involves extrapolating information from the raster which isn&amp;rsquo;t there. (i.e. image content from in-between pixels.)&lt;/p>
&lt;figure class="center" >
&lt;img src="https://zenithclipping.com/wp-content/uploads/2019/10/One-color-vector-2.jpg" style="border-radius: 8px;" />
&lt;figcaption class="center" style="color: inherit; background-color: inherit;" >Human-assisted image tracing. Note that some extra detail was extrapolated from the source raster.&lt;/figcaption>
&lt;/figure>
&lt;h4 id="simulated-annealing">Simulated annealing&lt;/h4>
&lt;p>Simulated annealing is an optimization technique inspired by a physical phenomenon, so let&amp;rsquo;s discuss the physical phenomenon first.&lt;/p>
&lt;h4 id="annealing">Annealing&lt;/h4>
&lt;p>&lt;a href="https://en.wikipedia.org/wiki/Annealing_(materials_science)">Annealing&lt;/a> is a metallurgical process for making metal more ductile (softer) by heating it up and letting it cool slowly.&lt;/p>
&lt;figure class="center" >
&lt;img src="https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/Crystalline_polycrystalline_amorphous.svg/288px-Crystalline_polycrystalline_amorphous.svg.png" style="border-radius: 8px;" />
&lt;figcaption class="center" style="color: inherit; background-color: inherit;" >Atoms in a metal packed regularly, semi-irregularly, and irregularly.&lt;/figcaption>
&lt;/figure>
&lt;p>Metals usually consist of small regions of atoms called &lt;a href="https://en.wikipedia.org/wiki/Crystallite">&lt;em>grains&lt;/em>&lt;/a>, where all the atoms in a grain are packed regularly. But adjacent grains are often misaligned (see the &amp;ldquo;polycrystalline&amp;rdquo; example above), leading to fracture lines called &lt;em>discontinuities&lt;/em>.&lt;/p>
&lt;p>When a metal is heated above some recrystallization &lt;a href="https://en.wikipedia.org/wiki/Recrystallization_(metallurgy)">temperature&lt;/a>, bonds within the metal begin to break.&lt;/p>
&lt;p>Cooling that metal again slowly heals fracture lines between grains, aligns adjacent grains with each other to create bigger grains, and eventually leads to a more regular (and more ductile) metal.&lt;/p>
&lt;h4 id="simulated-annealing-1">Simulated Annealing&lt;/h4>
&lt;p>&lt;a href="https://en.wikipedia.org/wiki/Simulated_annealing">Simulated Annealing&lt;/a> is an optimization technique for finding a local maximum within some search space which is too large to search completely. It is named after metallurgical annealing.&lt;/p>
&lt;figure class="center" >
&lt;img src="https://upload.wikimedia.org/wikipedia/commons/d/d5/Hill_Climbing_with_Simulated_Annealing.gif" style="border-radius: 8px;" />
&lt;figcaption class="center" style="color: inherit; background-color: inherit;" >Finding the maximum value of an irregular function using simulated annealing.&lt;/figcaption>
&lt;/figure>
&lt;p>For a large and complicated search space where finding &lt;em>a&lt;/em> maximum is sufficient (i.e. finding &lt;em>a short path&lt;/em> between points but not necessarily &lt;em>the shortest possible&lt;/em>), simulated annealing is a surprisingly effective technique.&lt;/p>
&lt;p>In physical annealing, we raise and then slowly lower the temperature of a metal. In simulated annealing, temperature corresponds to the internal energy of the system, i.e. how often we spontaneously hop between candidate positions.&lt;/p>
&lt;p>When simulated temperature is high, our cursor is flightier and hops between positions at random. As temperature lowers, we still seek out more optimal neighboring candidates, but our cursor &amp;ldquo;settles&amp;rdquo; and becomes increasingly unlikely to pick a new point at random.&lt;/p>
&lt;p>Simulated annealing and other optimization techniques are complicated and interesting. Read much more about the techniques and history behind it &lt;a href="https://en.wikipedia.org/wiki/Simulated_annealing#Selecting_the_parameters">here&lt;/a>.&lt;/p>
&lt;p>I won&amp;rsquo;t go into the math because this article is about a possible application of the technique rather than the technique itself.&lt;/p>
&lt;h2 id="work">Work&lt;/h2>
&lt;p>With a bit of simulated annealing, we can partially automate the process of image tracing.&lt;/p>
&lt;p>Here are the four kinds of image we&amp;rsquo;ll deal with:&lt;/p>
&lt;pre tabindex="0">&lt;code>Source ( don&amp;#39;t have ) Candidate ( we generate )
vector ( this data ) vector ( this candidate )
│ │
│ ( rasterized ) │ ( rasterized )
│ ( by someone ) │ ( by us )
│ │
▼ ▼
Source ◀────(compared to)────► Candidate
raster raster
&lt;/code>&lt;/pre>&lt;p>The plan is as follows: We guess a series of candidate vectors and trivially rasterize them to produce a series of candidate rasters. Once we have generated a candidate raster which sufficiently resembles the source raster, we can assume its associated candiate vector also sufficiently resembles the source vector (which we cannot see).&lt;/p>
&lt;h3 id="overview">Overview&lt;/h3>
&lt;p>Our whole algorithm runs in a loop:&lt;/p>
&lt;pre tabindex="0">&lt;code> Initial guess
parameters
│
│
Guess new │
use as input ╭──► parameters ───┤ use them
for annealing │ │ to make a new
│ ▼
Fitness Candidate
▲ vector
│ │
compare to │ │ rasterize
target raster ╰─── Candidate ◀──╯ this to produce
to calculate raster a new
&lt;/code>&lt;/pre>&lt;p>Eventually our measure of &amp;lsquo;fitness&amp;rsquo; (more on that later) becomes sufficiently low and we decide that our candidate vector is close enough.&lt;/p>
&lt;h3 id="caveats">Caveats&lt;/h3>
&lt;p>Here are some of the important caveats:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>we need a reasonable initial vector guess. This guess can be generated by a human or by some low-quality heuristic.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>we need a very fast way to evaluate fitness. This is the bottleneck, i.e. the majority of the time spent in the loop will be spent doing this. (Running the annealing algorithm, generating vectors, and rasterizing images are all extremely well-optimized already.)&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="worked-example">Worked Example&lt;/h2>
&lt;p>Take as a &lt;em>source vector&lt;/em> this crude vector illustration of an ampersand, and as a &lt;em>source raster&lt;/em> this even cruder rasterization.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/target.svg" style="border-radius: 8px;" />
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/target.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>Hopefully you can see that the raster image has lower resolution. If not, zoom in on both images until the one on the right becomes pixellated.&lt;/p>
&lt;p>This is our source. Now let&amp;rsquo;s make a reasonable initial vector guess:&lt;/p>
&lt;figure class="center" >
&lt;img src="images/init_guess.svg" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>It doesn&amp;rsquo;t look very close. But it&amp;rsquo;s a good start.&lt;/p>
&lt;p>As our simulated annealing cycle runs, we mutate each point in this initial candidate vector until the candidate raster image starts to look more and more like our source raster.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/out.gif" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>Until eventually we arrive at a final raster guess and a final vector guess.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/guess.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/guess.svg" style="border-radius: 8px;" />
&lt;/figure>
&lt;h2 id="measuring-image-similarity">Measuring image similarity&lt;/h2>
&lt;p>How do we measure image similarity?&lt;/p>
&lt;h3 id="measuring-image-similarity-for-humans">Measuring image similarity, for humans&lt;/h3>
&lt;p>For a human, it&amp;rsquo;s really handy to look at two images with a red/blue overlay. (A red/blue overlay is a blend of two images where one input becomes the red channel of the output, and the other input becomes the blue channel of the output. Pixels which are present in both images are rendered black.)&lt;/p>
&lt;p>Here are two overlays: one of the initial guess against the source image, and another of the final guess against the source image.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/superimposed_initial.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/superimposed_final.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>You can see that the initial guess (in blue) is quite poor. But our final guess is much better. You can see only a few blue or red edges peeking out.&lt;/p>
&lt;h3 id="measuring-image-similarity-for-computers">Measuring image similarity, for computers&lt;/h3>
&lt;p>It might be tempting to define fitness as &amp;ldquo;number of black pixels in an overlay&amp;rdquo;. But it turns out that this definition has some serious shortcomings.&lt;/p>
&lt;p>Under this scheme, if two shapes don&amp;rsquo;t overlap at all, it doesn&amp;rsquo;t matter how far apart they are. The pixel overlap of two squares with an inch between them is zero, but the pixel overlap of two squares with three inches betweeen them is also zero.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/overlap.svg" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>Put another way: if we measure fitness in this way, then we&amp;rsquo;re not rewarding incremental improvement.&lt;/p>
&lt;h3 id="blur">Blur&lt;/h3>
&lt;p>The way around this is to &lt;em>blur&lt;/em> both images before comparing them. Now we have a smooth transition between different colors in the image, so pixel overlap increases smoothly as our candidate converges.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/gradients_overlap.svg" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>There is obviously a sweet spot here. If you blur an image too much, then it becomes a mess and the annealing algorithms have no incentive to position the shapes precisely. If you blur them too little then you don&amp;rsquo;t get the benefit. This almost certainly needs to be tuned to the specific use case.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/superimposed_blurred_initial.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/superimposed_blurred_final.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>This is a powerful technique for a very specific set of circumstances and inputs. But I don&amp;rsquo;t think this is a useful general-purpose technique for image tracing, since most people who want to trace images want the entire task automated. And the application domain needs a very fast evaluation loop, so this is probably unsuitable for scripting heavyweight professional vector editors. But for certain circumstances, it seems like a fast way to fine-tune the contents of a vector.&lt;/p>
&lt;h2 id="code">Code&lt;/h2>
&lt;p>Here is some (lightly commented) code I used to perform the simulated annealing discussed in this article, as well as generate the illustrations above.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="784132965" type="checkbox" />
&lt;label for="784132965">
&lt;span class="collapsable-code__language">python&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-python" >&lt;code>
#!/usr/bin/python3
from PIL import Image
from scipy import optimize
from skimage import metrics
import cairo
import cv2
import numpy as np
import random
import scipy
import skimage
import tempfile
# Data class to hold lists of points, transform them, and print them to different kinds of images.
class DrawingData():
def __init__(self, list_of_points):
self._list_of_points = list_of_points
self._degrees_of_freedom = sum(len(p) for p in self._list_of_points)
# The width and height (in pixels) of the image.
self._dimension = 90
def transform(self, l):
# Apply a mutator lambda |l| to each point.
self._list_of_points = [l(p) for p in self._list_of_points]
def degreesOfFreedom(self):
# Return the number of degrees of freedom in the system.
return self._degrees_of_freedom
def applyDeltas(self, list_of_deltas) -&amp;gt; &amp;#34;DrawingData&amp;#34;:
# Apply a list of floating-point deltas to each x and y coordinate in
# order. This application order must be consistent so that annealing
# converges.
return DrawingData([
(x &amp;#43; list_of_deltas.pop(), y &amp;#43; list_of_deltas.pop())
for (x, y) in self._list_of_points
])
def mutate(self, n=5) -&amp;gt; &amp;#34;DrawingData&amp;#34;:
# Take each point and randomly displace it by up to |n| pixels.
deltas = [random.uniform(-n, n)
for _ in range(2*self.degreesOfFreedom())]
return self.applyDeltas(deltas)
def writeToPng(self, list_of_filepaths):
with cairo.ImageSurface(cairo.Format.RGB24, self._dimension, self._dimension) as surface:
ct = cairo.Context(surface)
ct.rectangle(0, 0, self._dimension, self._dimension)
ct.set_source_rgb(1, 1, 1)
ct.fill()
ct.set_line_width(10)
ct.set_source_rgb(0, 0, 0)
ct.move_to(*self._list_of_points[0])
for x, y in self._list_of_points[1:]:
ct.line_to(x, y)
ct.stroke()
for filepath in list_of_filepaths:
surface.write_to_png(filepath)
def writeToSvg(self, list_of_filepaths):
for filepath in list_of_filepaths:
with cairo.SVGSurface(filepath, 90, 90) as surface:
surface.set_document_unit(cairo.SVGUnit.PX)
ct = cairo.Context(surface)
ct.set_source_rgb(1, 1, 1)
ct.paint()
ct.set_line_width(10)
ct.set_source_rgb(0, 0, 0)
ct.move_to(*self._list_of_points[0])
for x, y in self._list_of_points[1:]:
ct.line_to(x, y)
ct.stroke()
def blur_image(image, s=(33, 33), f=10, n=0.5):
# Take an image, blur it, and return a blend of the original and blurred images.
# I think that this is slightly superior to the completely blurred image,
# since it preserves a sharp cliff at the boundary of the unblurred shapes.
return cv2.addWeighted(image, n, cv2.GaussianBlur(image, s, f), 1-n, 0.0)
def main():
# Pointwise drawing of an ampersand. I sketched this on paper on an 8x8 grid, so...
target_drawing = DrawingData([(6.3, 6.3), (5, 6), (2, 3), (1.5, 2), (2, 1), (3, .5), (4, .75), (4.5, 1.5),
(4, 2.5), (1.75, 5), (2, 6), (2.5, 6.5), (3.5, 6.25), (5.5, 5), (7, 3), ])
# ...we have to scale it up to fit on an 80x80 image.
target_drawing.transform(lambda p: (3 &amp;#43; (p[0]*10), 7 &amp;#43; (p[1]*10)))
target_drawing.writeToPng([&amp;#39;target.png&amp;#39;])
target_drawing.writeToSvg([&amp;#39;target.svg&amp;#39;])
# Keep track of the frame number, so that we can render a gif of only every
# tenth frame. (Otherwise we store too much data in memory, and the GIF
# takes too long to watch.)
global frame_num
frame_num = 0
frame_files = []
# This is the function scipy.optimize.minimize is minimizing, so it has to
# return fitness as a floating-point number.
def eval(ts, initial_guess_drawing, target_file):
global frame_num
frame_num &amp;#43;= 1
guess_file = tempfile.SpooledTemporaryFile(suffix=&amp;#34;png&amp;#34;)
guess_drawing = initial_guess_drawing.applyDeltas(list(ts.tolist()))
guess_drawing.writeToPng([guess_file, &amp;#34;guess.png&amp;#34;])
guess_drawing.writeToSvg([&amp;#34;guess.svg&amp;#34;])
# Open both images, blur them, and return the mean squared error.
mse = skimage.metrics.mean_squared_error(
blur_image(cv2.imdecode(np.frombuffer(
guess_file._file.getbuffer(), np.uint8), -1)),
blur_image(cv2.imdecode(np.frombuffer(
target_file._file.getbuffer(), np.uint8), -1)))
# The image contents of tenth frame gets moved into a big list instead
# of garbage collected.
if frame_num % 10 == 0:
frame_files.append(guess_file)
else:
del guess_file
return mse
# This is a bit nontraditional -- we store target and guess images both on
# disk (for human analysis later) and in spooled temporary files, for very
# fast inmemory access.
with tempfile.SpooledTemporaryFile(suffix=&amp;#34;png&amp;#34;) as target_file:
target_drawing.writeToPng([target_file])
with tempfile.SpooledTemporaryFile(suffix=&amp;#34;png&amp;#34;) as init_guess_file:
init_guess_drawing = target_drawing.mutate(n=5)
init_guess_drawing.writeToPng([init_guess_file, &amp;#39;init_guess.png&amp;#39;])
init_guess_drawing.writeToSvg([&amp;#39;init_guess.svg&amp;#39;])
# It typically takes 3-8 rounds before one converges. If a round
# doesn&amp;#39;t converge within 1000 iterations (see `maxiter` below), we
# make another random guess and try some more.
round = 0
while True:
round &amp;#43;= 1
# Reset our list of frames.
frames = []
for frame_file in frame_files:
del frame_file
frame_num = 0
# Make another random guess. These guesses are drawn from the
# initial random guess, so they are one step further removed
# from our source image.
random_start_drawing = init_guess_drawing.mutate(n=5)
r = scipy.optimize.minimize(
fun=eval,
# Since the set of parameters we&amp;#39;re tuning are x/y
# translations for the points in the image, our intial state
# should be a vector of zeros.
x0=[0]*2*random_start_drawing.degreesOfFreedom(),
# COBYLA was experimentally selected. There might be better methods.
method=&amp;#39;COBYLA&amp;#39;,
args=(random_start_drawing, target_file),
# Also experimentally selected.
tol=0.000001,
options={
# Also experimentally selected.
&amp;#39;maxiter&amp;#39;: 1000,
}
)
# If our image is within 20 pixels MSE (also experimentally
# selected), it is close enough.
if r.fun &amp;lt; 20:
print(&amp;#34;Num Trials: &amp;#34;, round)
print(&amp;#34;Success: &amp;#34;, r.success)
print(&amp;#34;Minimum error: &amp;#34;, r.fun)
print(&amp;#34;# Trials&amp;#34;, r.nfev)
print(&amp;#34;Tuned parameters&amp;#34;, r.x)
break
else:
print(&amp;#34;Minimum error: &amp;#34;, r.fun)
# Open each of the spooled temp files as a PIL image.
frames = [Image.open(f._file) for f in frame_files]
# Trim the back half of the list. We spent a lot of time fine-tuning this,
# which is important but uninteresting to watch in a GIF.
frames = frames[:int(len(frames)/4)]
# Render the list of images as a GIF.
frames[0].save(&amp;#39;out.gif&amp;#39;, save_all=True, append_images=frames[1:], loop=0)
# And finally clean up the images manually.
for f in frame_files:
del f
if __name__ == &amp;#39;__main__&amp;#39;:
main()
&lt;/code>&lt;/pre>
&lt;/div></content></item><item><title>video2anki: OCR for onscreen multilanguage audio captions</title><link>https://jbuckland.com/blog/language-video2anki/</link><pubDate>Mon, 12 Jul 2021 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/language-video2anki/</guid><description>Github https://github.com/ambuc/pleco-to-anki/blob/master/video2anki.py Background Pleco is an Android Chinese/English dictionary app. Users can look up and save words to a personal dictionary, and export an XML file containing those words and their definitions.
Anki is a flashcard app (and website) which implements spaced repetition. People use it to learn all sorts of things, but mostly languages.
Last year I wrote a custom pipeline which takes as input Pleco&amp;rsquo;s exported XML and produces as output a set of flashcards suitable for consumption by Anki.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">Github&lt;/th>
&lt;td> &lt;a href="https://github.com/ambuc/pleco-to-anki/blob/master/video2anki.py">https://github.com/ambuc/pleco-to-anki/blob/master/video2anki.py&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;h2 id="background">Background&lt;/h2>
&lt;p>&lt;a href="https://www.pleco.com/">Pleco&lt;/a> is an Android Chinese/English dictionary app. Users can look up and save words to a personal dictionary, and export an XML file containing those words and their definitions.&lt;/p>
&lt;p>&lt;a href="https://en.wikipedia.org/wiki/Anki_(software)">Anki&lt;/a> is a flashcard app (and website) which implements &lt;a href="https://en.wikipedia.org/wiki/Spaced_repetition">spaced repetition&lt;/a>. People use it to learn all sorts of things, but mostly languages.&lt;/p>
&lt;p>Last year I wrote a &lt;a href="https://jbuckland.com/2020/11/27/pleco-to-anki.html">custom pipeline&lt;/a> which takes as input Pleco&amp;rsquo;s exported XML and produces as output a set of flashcards suitable for consumption by Anki. I could now mark a character or phrase in Pleco and expect to review it in Anki some time later that week.&lt;/p>
&lt;h3 id="types-of-information-in-anki">Types of information in Anki&lt;/h3>
&lt;p>Anki presents information in the form of flash cards: given the front of some card, one is meant to recall the contents on the back. For the Chinese language, the important forms of a word are:&lt;/p>
&lt;dl>
&lt;dt>hanzi&lt;/dt>
&lt;dd>written characters in the original script&lt;/dd>
&lt;dt>pinyin&lt;/dt>
&lt;dd>romanized characters which phonetically imitate the original pronunciation&lt;/dd>
&lt;dt>meaning&lt;/dt>
&lt;dd>usually a definition in English&lt;/dd>
&lt;dt>audio&lt;/dt>
&lt;dd>usually a recording of a human or machine speaking the word or phrase&lt;/dd>
&lt;/dl>
&lt;h3 id="types-of-review-in-anki">Types of review in Anki&lt;/h3>
&lt;p>I configured Anki to help me review words and phrases in each of the following directions:&lt;/p>
&lt;dl>
&lt;dt>hanzi+meaning → pinyin+audio&lt;/dt>
&lt;dd>(tests my ability to pronounce a word or phrase)&lt;/dd>
&lt;dt>hanzi+pinyin+audio → meaning&lt;/dt>
&lt;dd>(tests my ability to understand a word or phrase)&lt;/dd>
&lt;dt>pinyin+audio+meaning → hanzi&lt;/dt>
&lt;dd>(tests my ability to recall and write a character)&lt;/dd>
&lt;dt>hanzi → pinyin+meaning+audio&lt;/dt>
&lt;dd>(tests my ability to read, pronounce, and understand a character)&lt;/dd>
&lt;dt>audio → meaning+pinyin+hanzi&lt;/dt>
&lt;dd>(tests my ability to hear and understand a character)&lt;/dd>
&lt;/dl>
&lt;h3 id="shortcomings">Shortcomings&lt;/h3>
&lt;p>This process improved my reading and writing abilities for short words and phrases. But you can&amp;rsquo;t learn grammar from context-free words, and you can&amp;rsquo;t train your ability to hear and understand a full sentence from single words or phrases. What I want is a new kind of card:&lt;/p>
&lt;dl>
&lt;dt>audio → meaning+pinyin+hanzi&lt;/dt>
&lt;dd>(tests my ability to hear and understand a &lt;strong>sentence&lt;/strong>)&lt;/dd>
&lt;/dl>
&lt;h3 id="resources">Resources&lt;/h3>
&lt;p>Many languages have large amounts of free educational content. Sometimes this free content comes with oncreen subtitles, which many people use while watching to check their understanding. To watch a piece of television and quiz myself as effectively as Anki, I would need to hide and show the subtitles while rewinding to the beginning of each sentence fragment. I found that this got in the way of learning, and made it difficult to review a large volume of material easily.&lt;/p>
&lt;h2 id="video2anki">video2anki&lt;/h2>
&lt;p>My &lt;a href="https://github.com/ambuc/pleco-to-anki/blob/master/video2anki.py">solution&lt;/a> was this:&lt;/p>
&lt;ol>
&lt;li>Import a video containing both Chinese-language audio and onscreen hanzi captions.&lt;/li>
&lt;li>Determine the timestamps at which sentence breaks appear.&lt;/li>
&lt;li>Extract the hanzi from the on-screen subtitles via &lt;a href="https://en.wikipedia.org/wiki/Optical_character_recognition">OCR&lt;/a>.&lt;/li>
&lt;li>Extract the audio from the video.&lt;/li>
&lt;li>Split the audio and the extracted hanzi by timestamp, creating pairs of audio fragments and written sentence fragments.&lt;/li>
&lt;li>Format those pairs and import them into Anki.&lt;/li>
&lt;/ol>
&lt;p>I used Python because I knew all of the requisite libraries (video and audio manipulation, fast OCR, video anlysis for cutscene detection) would be available. This script only needs to run once per video, so performance is not a priority.&lt;/p>
&lt;h3 id="consuming-video">Consuming video&lt;/h3>
&lt;p>I used &lt;a href="https://pytube.io/">&lt;code>pytube&lt;/code>&lt;/a> to download the highest-resolution version of a video from YouTube.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="584769321" type="checkbox" />
&lt;label for="584769321">
&lt;span class="collapsable-code__language">python&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-python" >&lt;code>
from pytube import YouTube
yt = YouTube(&amp;#34;some_youtube_url&amp;#34;)
download_path = yt.streams
.get_highest_resolution()
.download(output_path=&amp;#34;some_path&amp;#34;)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h3 id="detecting-scene-transitions">Detecting scene transitions&lt;/h3>
&lt;p>I used &lt;a href="https://pyscenedetect.readthedocs.io/projects/Manual/en/latest/index.html">&lt;code>pyscenedetect&lt;/code>&lt;/a> to detect scene transitions: points in time when some percentage of pixels change.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="156283974" type="checkbox" />
&lt;label for="156283974">
&lt;span class="collapsable-code__language">python&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-python" >&lt;code>
video_manager = scenedetect.VideoManager([&amp;#34;some_local_file&amp;#34;])
scene_manager = scenedetect.SceneManager()
scene_manager.add_detector(
scenedetect.detectors.ContentDetector(threshold=1))
video_manager.set_downscale_factor()
video_manager.start()
scene_manager.detect_scenes(frame_source=video_manager)
scenelist = scene_manager.get_scene_list()
imagepaths_by_scenenum = scenedetect.scene_manager
.save_images(scenelist, video_manager,
output_dir=&amp;#34;some_output_directory&amp;#34;, show_progress=True)
with open(&amp;#34;some_output_file&amp;#34;, &amp;#34;w&amp;#34;) as f:
scenedetect.scene_manager.write_scene_list(f, scenelist)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>I found that a threshold of &lt;code>1&lt;/code> was necessary. I also found I needed to crop the video before running scene detection, since I didn&amp;rsquo;t want to detect when the speaker moved their hands or when the stock photo changed.&lt;/p>
&lt;p>A low threshold meant I often accidentally inferred a scene transition mid-sentence. We deduplicate these later on.&lt;/p>
&lt;p>One side-effect of cutscene detection is a dictionary which maps scene number to an individual midpoint frame from that scene. We can extract the image of the sentence from that frame.&lt;/p>
&lt;h3 id="cropping-the-video">Cropping the video&lt;/h3>
&lt;p>As mentioned above, I cropped the video before running scene detection.&lt;/p>
&lt;p>I used &lt;a href="https://www.ffmpeg.org/">&lt;code>ffmpeg&lt;/code>&lt;/a> to perform video manipulation. I also used &lt;code>ffplay&lt;/code> to preview manipulation in real time before undergoing the expensive conversion process.&lt;/p>
&lt;p>Specific crop geometry is expressed as a command of the form &lt;code>width:height:xoffset:yoffset&lt;/code>. video2anki.py accepts a flag to tune the crop command so that I can preview the crop and tune the command when rerunning.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="418725396" type="checkbox" />
&lt;label for="418725396">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
CROP_COMMAND=&amp;#34;in_w:in_h/3:0:1.9*in_h/3&amp;#34;
ffplay -i &amp;#34;&amp;lt;video_path&amp;gt;&amp;#34; -vf &amp;#34;crop=$CROP_COMMAND&amp;#34;
ffmpeg -i &amp;#34;&amp;lt;video_path&amp;gt;&amp;#34; -filter:v &amp;#34;crop=$CROP_COMMAND&amp;#34; -c:a copy &amp;#34;&amp;lt;output_path&amp;gt;&amp;#34;
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h3 id="ocr">OCR&lt;/h3>
&lt;p>Reading plaintext out of an image is called OCR (optical character recognition). There are many libraries and toolkits for doing this. I began with &lt;a href="https://pypi.org/project/pytesseract/">&lt;code>pytesseract&lt;/code>&lt;/a>, but found low-quality results. Small errors with image margin, spacing, and resolution often led to inaccuracies. I eventually found &lt;a href="https://pypi.org/project/cnocr/">&lt;code>cnocr&lt;/code>&lt;/a> which had much higher accuracy for recognizing hanzi.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="176958423" type="checkbox" />
&lt;label for="176958423">
&lt;span class="collapsable-code__language">python&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-python" >&lt;code>
cn = cnocr.cn_ocr.CnOcr()
hanzi_obj = cn.ocr(img_fp=FOO)
hanzi_txt = &amp;#39;&amp;#39;.join(flatten([chrs for (chrs, _) in hanzi_obj]))
# Example: `汉字`
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>I decided not to use OCR to consume the pinyin from the image. Pinyin isn&amp;rsquo;t a real language, so there is no pre-trained OCR model which knows how to read its diacritics. I ended up pulling in another library (&lt;code>pinyin&lt;/code>) to convert my extracted hanzi into pinyin.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="197352468" type="checkbox" />
&lt;label for="197352468">
&lt;span class="collapsable-code__language">python&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-python" >&lt;code>
pinyin_txt = pinyin.get(hanzi_txt, delimiter=&amp;#34; &amp;#34;).strip()
# Example: `hànzì`
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h3 id="audio-extraction">Audio extraction&lt;/h3>
&lt;p>I needed to convert the downloaded video into an audio file. I have found that &lt;a href="https://en.wikipedia.org/wiki/FLAC">FLAC&lt;/a> works as expected on every platform, so I encoded all audio clips to FLAC. I also found that &lt;code>ffmpeg&lt;/code> required two passes in order to convert &lt;code>m4a&lt;/code> video into &lt;code>flac&lt;/code> audio.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="378149652" type="checkbox" />
&lt;label for="378149652">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
ffmpeg -i IN_PATH -c copy OUT_M4A_PATH
ffmpeg -i OUT_M4A_PATH -c:a flac OUT_FLAC_PATH
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h3 id="audio-clipping">Audio clipping&lt;/h3>
&lt;p>I then needed to trim the audio file into sentence fragments. I used &lt;a href="https://github.com/rabitt/pysox">sox&lt;/a> to perform audio trimming and compression.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="684391527" type="checkbox" />
&lt;label for="684391527">
&lt;span class="collapsable-code__language">python&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-python" >&lt;code>
fm = sox.Transformer()
tfm.trim(start_sec, end_sec)
tfm.compand()
tfm.build_file(&amp;#34;input_file&amp;#34;, &amp;#34;output_file&amp;#34;)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Then we write the trimmed files to the Anki media collection, write a CSV with the audio path, the hanzi, the pinyin, and the source video, and import that CSV into Anki.&lt;/p>
&lt;p>Here is a &lt;a href="https://jbuckland.com/assets/videos/video2anki.mp4">video recording&lt;/a> in which I review a single card in Anki which was produced with this method. The video shows me opening a card and listening to the autoplayed audio. Then I type in what I think was said. Then I press &amp;ldquo;Show Answer&amp;rdquo;, and Anki presents a diff between my answer and the canonical one. Then I press the &amp;ldquo;Link&amp;rdquo; button and watch that sentence in the original video on YouTube.&lt;/p>
&lt;video controls preload="auto" width="100%" playsinline class="html-video">
&lt;source src="https://jbuckland.com/blog/language-video2anki/videos/video2anki.mp4" type="video/mp4">
&lt;span>Your browser doesn't support embedded videos, but don't worry, you can &lt;a href="https://jbuckland.com/blog/language-video2anki/videos/video2anki.mp4">download it&lt;/a> and watch it with your favorite video player!&lt;/span>
&lt;/video></content></item><item><title>Lion Wall: Building a character-matching game</title><link>https://jbuckland.com/blog/game-lionwall/</link><pubDate>Sun, 18 Apr 2021 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/game-lionwall/</guid><description>App https://jbuckland.com/games/lionwall Background Lion Wall is an online game inspired by Only Connect, a British TV quiz show. In the third round of Only Connect, contestants are faced with a four-by-four grid of cards. These cards can be grouped into four sets of four, where group is connected by some common thread.
An example &amp;ldquo;Connecting Wall&amp;rdquo; puzzle. The answer requires dividing the items into &amp;ldquo;Terms for zero&amp;rdquo;, &amp;ldquo;Poker terms&amp;rdquo;, &amp;ldquo;Flying ___&amp;rdquo; and &amp;ldquo;Things made of rubber&amp;rdquo;.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">App&lt;/th>
&lt;td> &lt;a href="https://jbuckland.com/games/lionwall">https://jbuckland.com/games/lionwall&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;figure class="center" >
&lt;img src="images/screenshot.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;h2 id="background">Background&lt;/h2>
&lt;p>&lt;strong>Lion Wall&lt;/strong> is an online game inspired by &lt;a href="https://en.wikipedia.org/wiki/Only_Connect#Round_3:_Connecting_Wall">&lt;em>Only Connect&lt;/em>&lt;/a>, a British TV quiz show. In the third round of &lt;em>Only Connect&lt;/em>, contestants are faced with a four-by-four grid of cards. These cards can be grouped into four sets of four, where group is connected by some common thread.&lt;/p>
&lt;figure class="center" >
&lt;img src="https://upload.wikimedia.org/wikipedia/commons/e/e2/Only_Connect_Round3_Unsolved.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>An example &amp;ldquo;Connecting Wall&amp;rdquo; puzzle. The answer requires dividing the items into &amp;ldquo;&lt;em>Terms for zero&lt;/em>&amp;rdquo;, &amp;ldquo;&lt;em>Poker terms&lt;/em>&amp;rdquo;, &amp;ldquo;&lt;em>Flying ___&lt;/em>&amp;rdquo; and &amp;ldquo;&lt;em>Things made of rubber&lt;/em>&amp;rdquo;.&lt;/p>
&lt;h3 id="red-herrings">Red Herrings&lt;/h3>
&lt;p>To make the puzzle harder, some of the cards seem to belong to more than one category. In the example above, the category &amp;ldquo;&lt;em>Poker terms&lt;/em>&amp;rdquo; applies to &amp;ldquo;&lt;a href="https://www.pokerzone.com/dictionary/deuce">Deuce&lt;/a>&amp;rdquo;, &amp;ldquo;&lt;a href="https://www.pokerzone.com/dictionary/bullet">Bullet&lt;/a>&amp;rdquo;, &amp;ldquo;&lt;a href="https://www.pokerzone.com/dictionary/crab">Crab&lt;/a>&amp;rdquo;, and &amp;ldquo;&lt;a href="https://www.pokerzone.com/dictionary/cowboy">Cowboy&lt;/a>&amp;rdquo;.&lt;/p>
&lt;p>This category also seems as though it ought to apply to &amp;ldquo;Ace&amp;rdquo;, but &amp;ldquo;Ace&amp;rdquo; actually belongs to the category &amp;ldquo;&lt;em>Flying ___&lt;/em>&amp;rdquo;.&lt;/p>
&lt;p>On &lt;em>Only Connect&lt;/em>, these cards are called &lt;a href="https://en.wikipedia.org/wiki/Red_herring">red herrings&lt;/a>.&lt;/p>
&lt;h2 id="generalizing-the-_connecting-wall_">Generalizing the &lt;em>Connecting Wall&lt;/em>&lt;/h2>
&lt;p>I first wanted to lift this game out of the domain of English-language trivia.&lt;/p>
&lt;ul>
&lt;li>Let each of [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] represent a category in the abstract.
&lt;ul>
&lt;li>The values [1, 2, 3, 4] will be special &amp;ldquo;real&amp;rdquo; categories. The others will be &amp;ldquo;red herring&amp;rdquo; categories.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Let a card consist of an unordered list of categories.
&lt;ul>
&lt;li>Each card must contain exactly one of [1, 2, 3, 4].&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>I constructed a generalized &lt;em>Connecting Wall&lt;/em> game:&lt;/p>
&lt;pre tabindex="0">&lt;code>[ [ (1, 5), (1, 6), (1, 7), (1, 8) ]
, [ (2, 6), (2, 7), (2, 8), (2, 9) ]
, [ (3, 0), (3, 7), (3, 8), (3, 9) ]
, [ (4, 0), (4, 5), (4, 6), (4, 9) ]
]
&lt;/code>&lt;/pre>&lt;p>such that the special &amp;ldquo;real&amp;rdquo; categories [1, 2, 3, 4] are mentioned in exactly four cards each, with no overlaps, and the &amp;ldquo;red herring&amp;rdquo; categories [0, 5, 6, 7, 8, 9] are mentioned in three or fewer cards each.&lt;/p>
&lt;p>If we wanted to map this generalized game &lt;em>back&lt;/em> to an English-language trivia game, the real categories [1, 2, 3, 4] might become [&amp;quot;&lt;em>Terms for zero&lt;/em>&amp;quot;, &amp;ldquo;&lt;em>Poker terms&lt;/em>&amp;rdquo;, &amp;ldquo;&lt;em>Flying ___&lt;/em>&amp;rdquo;, &amp;ldquo;&lt;em>Things made of rubber&lt;/em>&amp;rdquo;], and the red herring categories might become more loosely-defined categories such as:&lt;/p>
&lt;ul>
&lt;li>&amp;ldquo;&lt;em>Starts with B&lt;/em>&amp;rdquo; (Buttress, Bullet),&lt;/li>
&lt;li>&amp;ldquo;&lt;em>Animal&lt;/em>&amp;rdquo; (Crab, Fox, Fish, Goose-egg?), or&lt;/li>
&lt;li>&amp;ldquo;&lt;em>Contains a repeating letter&lt;/em>&amp;rdquo; (Goose-egg, Buttress, Bullet), etc.&lt;/li>
&lt;/ul>
&lt;h2 id="specifying-for-cjk-characters">Specifying for CJK characters&lt;/h2>
&lt;p>Many CJK characters consist of two subcomponents: if we map each of the categories 0-9 to a &lt;a href="https://en.wikipedia.org/wiki/Kangxi_radical">component&lt;/a>, we can usually find characters which contain only those two components.&lt;/p>
&lt;h3 id="by-hand">By hand&lt;/h3>
&lt;p>Before writing an automated solution, I stepped through the process of assembling a valid puzzle by hand. I picked ten common radicals and mapped them to the numbers 0-9:&lt;/p>
&lt;pre tabindex="0">&lt;code>0 九
1 疒
2 宀
3 犭
4 口
5 了
6 丁
7 主
8 由
9 示
&lt;/code>&lt;/pre>&lt;p>Then, for each card in the generalized grid above, I tried to find a character which matched:&lt;/p>
&lt;pre tabindex="0">&lt;code>15 疒了 疗
26 宀丁 宁
30 犭九 犰
40 口九 㕤
16 疒丁 疔
27 宀主 宔
37 犭主 㹥
45 口了 𠮩
17 疒主 疰
28 宀由 宙
38 犭由 㹨
46 口丁 叮
18 疒由 㾄
29 宀示 宗
39 犭示 狋
49 口示 呩
&lt;/code>&lt;/pre>&lt;p>This got me a valid puzzle:&lt;/p>
&lt;pre tabindex="0">&lt;code>疗 宁 犰 㕤
疔 宔 㹥 𠮩
疰 宙 㹨 叮
㾄 宗 狋 呩
&lt;/code>&lt;/pre>&lt;p>This puzzle is unshuffled as drawn above. You can see that each character in the first row contains &lt;code>疒&lt;/code>; each character in the second row contains &lt;code>宀&lt;/code>; then &lt;code>犭&lt;/code>, then &lt;code>口&lt;/code>.&lt;/p>
&lt;h3 id="automating">Automating&lt;/h3>
&lt;p>Similar to &lt;a href="https://github.com/google/autocjk">AutoCJK&lt;/a>, I used the phenomenal resource &lt;a href="https://www.babelstone.co.uk/CJK/IDS.TXT">IDS.TXT&lt;/a> as a backing dataset. I reused the &lt;a href="https://github.com/ambuc/autocjk/blob/main/src/utils/decomposer.py">decomposition utilities&lt;/a> from AutoCJK which know how to read/parse &lt;code>IDS.TXT&lt;/code>. In short, running &lt;code>decomposer.Decomposer()&lt;/code> returns a &lt;a href="https://networkx.org/">networkx&lt;/a> &lt;a href="https://networkx.org/documentation/stable//reference/classes/digraph.html">digraph&lt;/a> such that &lt;code>子&lt;/code> and &lt;code>宀&lt;/code> are ancestors of &lt;code>字&lt;/code>, and vice versa for descendants.&lt;/p>
&lt;p>Then I wrote this script to generate valid puzzles.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="298567314" type="checkbox" />
&lt;label for="298567314">
&lt;span class="collapsable-code__language">python&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-python" >&lt;code>
from operator import itemgetter
from typing import *
import collections
import decomposer as decomposer_lib
import random
# Get the thirty most common radicals from the graph, weighted by how often
# they appear as subcomponents. (That&amp;#39;s why we iterate thru predecessors(n)
# below.)
def most_common_radicals(graph) -&amp;gt; List[Text]:
common = collections.defaultdict(int)
for n in graph.nodes():
for k in list(graph.predecessors(n)):
common[k] &amp;#43;= 1
res = dict(sorted(common.items(), key=itemgetter(1), reverse=True)[:30])
return list(res.keys())
# If a character is in the first Unicode CJK plane, it probably has a
# rendering in most web browsers. (This assumption is flawed.)
def has_rendering(i: int) -&amp;gt; bool:
return 0x4E00 &amp;lt;= i &amp;lt;= 0x9FFF
# Given a character, find the set of characters with which it shares a descendant.
# Example: 宀 and 子 are coparents of 字.
def coparents(graph, node): # returns set[node]
cpts = set()
for succ in graph.successors(node):
if not has_rendering(ord(succ)):
continue
preds = list(graph.predecessors(succ))
if len(preds) != 2:
continue
for pred in graph.predecessors(succ):
if not has_rendering(ord(pred)):
continue
if pred != node:
cpts.add(pred)
return cpts
# Given two nodes, returns a list of successors they share.
def children(graph, na, nb): # returns node
s = set.intersection(set(graph.successors(na)), set(graph.successors(nb)))
return list(filter(lambda c: has_rendering(ord(c)), list(s)))
class BoardBuilder:
def __init__(self, graph):
self._graph = graph
# This is the default game described above.
self._cells = [[[1, 5], [1, 6], [1, 7], [1, 8]],
[[2, 6], [2, 7], [2, 8], [2, 9]],
[[3, 0], [3, 7], [3, 8], [3, 9]],
[[4, 0], [4, 5], [4, 6], [4, 9]], ]
# Store our selected categories below.
self._extant = set()
def Build(self):
# Pick four &amp;#34;real categories&amp;#34;.
[a, b, c, d] = random.sample(most_common_radicals(self._graph), 4)
# Assign these categories.
[self.assign(n, l) for n, l in zip([1, 2, 3, 4], [a, b, c, d])]
# Assign the red herring categories.
[self.assign(n, self.find_assignment(n)) for n in [5, 6, 7, 8, 9, 0]]
for r_idx, row in enumerate(self._cells):
for c_idx, [a, b] in enumerate(row):
# Pick a random descendant shared by the two components.
self._cells[r_idx][c_idx] = random.sample(
children(self._graph, a, b), 1)[0]
return self._cells
def assign(self, num, ch):
# Replace all instances of |num| with |ch| in self._cells.
for r_idx, row in enumerate(self._cells):
for c_idx, cell in enumerate(row):
for v_idx, v_val in enumerate(cell):
if v_val == num:
self._cells[r_idx][c_idx][v_idx] = ch
self._extant.add(ch)
# Given a category, what other categories share a card with it?
# Example: In the example above, neighbors(1) =&amp;gt; [5,6,7,8].
def neighbors(self, num):
others = []
for r_idx, row in enumerate(self._cells):
for c_idx, cell in enumerate(row):
if num not in cell:
continue
others.append(list(filter(lambda n: n != num, cell))[0])
return others
# For our red herring categories, find a CJK character which can sit in for
# |num|, i.e. is a coparent with each of the neighbors.
def find_assignment(self, num):
others = self.neighbors(num)
coparents_list = [coparents(self._graph, o) for o in others]
candidates = set.intersection(*coparents_list)
candidates -= self._extant
return random.sample(list(candidates), 1)[0]
if __name__ == &amp;#34;__main__&amp;#34;:
d = decomposer_lib.Decomposer()
for row in BoardBuilder(d._graph).Build():
print(row)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Running this script generates a valid, unshuffled puzzle such as &lt;code>窠窒岤空洷溄江沚食仚仝企踉踝跮趾&lt;/code>.&lt;/p>
&lt;h2 id="making-the-game">Making the game&lt;/h2>
&lt;p>Then I wrote a webpage which could consume these puzzles and display them in an interactive format. You can visit &lt;a href="https://jbuckland.com/games/lionwall.html">https://jbuckland.com/games/lionwall.html&lt;/a> to play the game or you can view the &lt;a href="https://github.com/ambuc/ambuc.github.io/blob/master/games/lionwall.html">source of the webpage&lt;/a> to see how it works.&lt;/p>
&lt;h3 id="semaphores">Semaphores&lt;/h3>
&lt;p>I wanted cards to animate (flip over, fade out, wait, reappear at the top of the page), but I didn&amp;rsquo;t want the UI to be interactive during those times. So I wrote a &lt;a href="https://en.wikipedia.org/wiki/Semaphore_(programming)">semaphore&lt;/a> to ensure that the UI could not be changed while animations or timeouts were ongoing. My solution was a global counter &lt;code>semaphore&lt;/code> and two methods:&lt;/p>
&lt;ul>
&lt;li>a &lt;code>sleep()&lt;/code> timeout which incremented the semaphore, ran the callback, then
waited until it was done to decrement the semaphore, and&lt;/li>
&lt;li>a &lt;code>ensureSemaphoreZero()&lt;/code> blocker which waited until &lt;code>semaphore&lt;/code> was 0
until running.&lt;/li>
&lt;/ul>
&lt;div class="collapsable-code">
&lt;input id="193485672" type="checkbox" />
&lt;label for="193485672">
&lt;span class="collapsable-code__language">javascript&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-javascript" >&lt;code>
var semaphore = 0
function ensureSemaphoreZero() {
return new Promise(function (resolve, reject) {
(function waitForSemaphoreZero() {
if (semaphore == 0) return resolve();
setTimeout(waitForSemaphoreZero, 10);
})();
});
}
const sleep = (milliseconds) =&amp;gt; {
semaphore&amp;#43;&amp;#43;
return new Promise(resolve =&amp;gt; {
setTimeout(resolve, milliseconds)
}).then(() =&amp;gt; {
semaphore--
})
}
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>This let me chain sleep callbacks for running spaced-out animations:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="428519637" type="checkbox" />
&lt;label for="428519637">
&lt;span class="collapsable-code__language">javascript&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-javascript" >&lt;code>
sleep(100).then(() =&amp;gt; {
// Manipulate the webpage.
sleep(100).then(() =&amp;gt; {
// Once that&amp;#39;s done, manipulate the webpage again.
})
})
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>But also react to keystrokes in a blocking way:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="432895761" type="checkbox" />
&lt;label for="432895761">
&lt;span class="collapsable-code__language">javascript&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-javascript" >&lt;code>
function onClickCallback() {
ensureSemaphoreZero().then(() =&amp;gt; {
// React to callback.
});
}
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h2 id="future-work">Future work&lt;/h2>
&lt;p>I generated ~100 games for &lt;a href="https://jbuckland.com/games/lionwall.html">Lion Wall&lt;/a> but I ought to go back and generate more. I could also make this game more like the &lt;em>Only Connect&lt;/em> version, with a timer, limited guesses, and extra points for specifying the connecting themes.&lt;/p></content></item><item><title>Predicting CJK Character Shapes</title><link>https://jbuckland.com/blog/language-autocjk/</link><pubDate>Mon, 08 Mar 2021 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/language-autocjk/</guid><description>Github http://github.com/google/autocjk AutoCJK is a tool for generating low-resolution predictions of uncommon CJK characters, given full-width images of their components.
Example:
Left-to-right: (a) source left-hand component, (b) source right-hand component, (c) expected composition, (d) predicted composition, and (e) (c) / (d) difference.
NB: If you already know about the Chinese language, feel free to skip the Background section and read onwards, starting from AutoCJK below. Also, if you already know about the Chinese language and notice something below is wrong, please open an issue against this repository and let me know!</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">Github&lt;/th>
&lt;td> &lt;a href="http://github.com/google/autocjk">http://github.com/google/autocjk&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;p>&lt;a href="https://github.com/google/autocjk">AutoCJK&lt;/a> is a tool for generating low-resolution predictions of uncommon CJK characters, given full-width images of their components.&lt;/p>
&lt;p>Example:&lt;/p>
&lt;figure class="center" >
&lt;img src="https://raw.githubusercontent.com/google/autocjk/main/docs/images/0x134772.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>Left-to-right: &lt;em>(a)&lt;/em> source left-hand component, &lt;em>(b)&lt;/em> source right-hand component, &lt;em>(c)&lt;/em> expected composition, &lt;em>(d)&lt;/em> predicted composition, and &lt;em>(e)&lt;/em> (c) / (d) difference.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>NB:&lt;/strong> If you already know about the Chinese language, feel free to skip the &lt;em>Background&lt;/em> section and read onwards, starting from &lt;a href="#autocjk">&lt;em>AutoCJK&lt;/em>&lt;/a> below. Also, if you already know about the Chinese language and notice something below is wrong, please open an issue against &lt;a href="https://github.com/ambuc/ambuc.github.io">this repository&lt;/a> and let me know!&lt;/p>
&lt;/blockquote>
&lt;h2 id="background">Background&lt;/h2>
&lt;h3 id="chinese-language">Chinese Language&lt;/h3>
&lt;p>The written Chinese language comprises tens of thousands of &lt;a href="https://en.wikipedia.org/wiki/Logogram">logograms&lt;/a> (汉字; hànzì), each with a (non-unique) pronunciation and meaning. &lt;a href="https://en.wikipedia.org/wiki/Chinese_characters">Hanzi&lt;/a> are used in many other East Asian scripts. CJK scripts are usually fixed-width, and character proportions and spacings exist on a regular grid.&lt;/p>
&lt;p>It is often useful to think of Hanzi as &amp;ldquo;decomposing&amp;rdquo; into &lt;a href="https://en.wikipedia.org/wiki/Radical_(Chinese_characters)">radicals&lt;/a> (部首; bùshǒu), which can be combined to create all other characters. The most common system of radicals is the set of 214 Kangxi radicals (康熙部首; kāngxī bùshǒu). &lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>&lt;/p>
&lt;h4 id="radical-composition">Radical composition&lt;/h4>
&lt;p>Radicals compose in about a dozen regular ways to form new characters. A new character produced by one of these compositions is often phonetically or semantically linked to one of its components, but this is not always the case.&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Two radicals can be composed horizontally (女 + 子 = 好) or vertically
(几 + 木 = 朵). &lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Three radicals can be composed horizontally (氵+ 方 + 字 = 游) or vertically (日 + 罒 + 又 = 曼). In the horizontal case, it is common for two adjacent radicals to first combine into another character (方 + 字 = 斿; 氵+ 斿 = 游).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>One radical can fully surround another (囗 + 玉 = 国).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>One radical can surround another from above, (门 + 日 = 间), from the left (匚 + 矢 = 医), or from below (凵 + 乂 = 凶) (but never from the right).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>One radical can surround another from the upper-left (广 + 木 + 床), from the upper-right (丁 + 口 = 可), or from the lower-left (辶 + 文 = 这) (but never from the lower-right).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Finally, two radicals can be overlaid. Overlaid compositions are generally ambiguous: for example: 一 + 木 = 未, but also 一 + 木 = 末. Note the widths of the horizontal strokes in each resultant character.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>As can be seen from the 未 vs. 末 distinction above, there is sometimes more than one way to compose the same pair of components. Clearly, composition should be seen as &lt;em>a&lt;/em> way to understand complex characters, rather than &lt;em>the&lt;/em> way to parse and understand these characters.&lt;/p>
&lt;p>Finally, composition is often recursive. Examine the sequence: (乛 + 头 = 买, 十 + 买 = 卖, 讠+ 卖 = 读). Composition depth rarely exceeds four or five levels.&lt;/p>
&lt;h4 id="how-many-chinese-characters-are-there">How many Chinese characters are there?&lt;/h4>
&lt;p>A newspaper often contains between 2,000 and 3,000 unique characters. An educated Chinese person knows around 8,000 characters. A standard Chinese dictionary contains around 20,000 characters. A comprehensive Chinese dictionary contains around 80,000 characters.&lt;/p>
&lt;h3 id="chinese-and-unicode">Chinese and Unicode&lt;/h3>
&lt;p>&lt;a href="https://unicode.org/">Unicode&lt;/a> is an international standard for encoding text. Here, ‘encoding’ means associating each character with a unique number. Unicode is divided into disjoint ranges called “blocks”, which usually group characters by script or by language. New blocks are released over time as the Unicode standard evolves.&lt;/p>
&lt;p>In 1992, the Unicode consortium released an initial CJK&lt;sup id="fnref:3">&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref">3&lt;/a>&lt;/sup> block, titled &lt;a href="https://en.wikipedia.org/wiki/CJK_Unified_Ideographs_(Unicode_block)">CJK Unified Ideographs&lt;/a>. This block contains around 20,000 code points representing common CJK characters. Unicode continues to release “extension” blocks containing less-common characters; these blocks are named &lt;a href="https://en.wikipedia.org/wiki/CJK_Unified_Ideographs_Extension_A">CJK Unified Ideographs Extension A&lt;/a>, &lt;a href="https://en.wikipedia.org/wiki/CJK_Unified_Ideographs_Extension_B">CJK Unified Ideographs Extension B&lt;/a>, etc. and act as supplements to the original 20,000 characters. Later blocks tend to contain CJK characters of increasing rarity.&lt;/p>
&lt;p>As of 2020, there are around 93,000&lt;sup id="fnref:4">&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref">4&lt;/a>&lt;/sup> CJK unified ideographs in Unicode. Around two-thirds of all the code points in Unicode are reserved for CJK characters.&lt;sup id="fnref:5">&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref">5&lt;/a>&lt;/sup>&lt;/p>
&lt;h4 id="kangxi-radicals-and-unicode">Kangxi radicals and Unicode&lt;/h4>
&lt;p>Even though Unicode aims to include most CJK characters, there will always be rare characters such as place names and proper nouns which do not appear in Unicode.&lt;/p>
&lt;p>Unicode offers a system called IDS (&lt;a href="https://en.wikipedia.org/wiki/Ideographic_Description_Characters_(Unicode_block)">Ideographic Description Characters&lt;/a>) which helps express (but not render) rare CJK characters.&lt;/p>
&lt;p>IDS is a set of &lt;em>ideographic description characters&lt;/em>. An ideographic description character might represent the act of joining characters horizontally, vertically, etc. These characters can be rendered as dotted-line divisions of a unit square (ex: ⿰, ⿱, ⿲, ⿳, ⿴, …)&lt;/p>
&lt;p>A series of glyphs combined by ideographic description characters is known as an &lt;em>ideographic description sequence&lt;/em>.&lt;/p>
&lt;p>Thus a character like 好 can also be expressed as ⿰女子. This is a useful tool for communicating rare characters, but does not help in actually rendering them.&lt;/p>
&lt;h4 id="why-doesnt-the-unicode-standard-adopt-a-compositional-model-for-encoding-han-ideographs-wouldnt-that-save-a-large-number-of-code-points">Why doesn’t the Unicode standard adopt a compositional model for encoding Han ideographs? Wouldn’t that save a large number of code points?&lt;/h4>
&lt;p>In short, Unicode decided that the burden on font authors and the difficulty of “normalization”, i.e. transforming characters into a normalized form to allow for searching and comparison, was too great. For a more detailed answer, see &lt;a href="http://www.unicode.org/faq/han_cjk.html#16">the original question and answer&lt;/a> from the Unicode CJK FAQs.&lt;/p>
&lt;h4 id="ideographic-description-sequence-shaping">Ideographic description sequence shaping&lt;/h4>
&lt;p>Since Unicode prefers individual code points over composition, fonts and the platforms which enable them (Opentype, Truetype, etc.) generally do not implement “ideographic description sequence shaping”, i.e. the process of rendering “⿰女子” as “好” at display time. Instead we rely on Unicode to provide code points for the most common characters, and on font authors to provide renderings for those characters.&lt;/p>
&lt;h3 id="complications-of-cjk-font-rendering">Complications of CJK Font Rendering&lt;/h3>
&lt;p>Since modern text encoding for CJK characters is non-compositional, font authors need to create a rendering for each individual character in question. As noted above, there are (currently) around 93,000 CJK unified ideographs. Due to the number of characters and the difficulty of researching and creating character renderings, most fonts lag behind the latest Unicode releases by several years. &lt;sup id="fnref:6">&lt;a href="#fn:6" class="footnote-ref" role="doc-noteref">6&lt;/a>&lt;/sup>&lt;/p>
&lt;p>Why? CJK font design often tries to faithfully replicate the way natural handwriting works: adjacent strokes are joined, complex radicals take up more space than simple ones, and very complex radicals get simplified. These complications occur naturally in handwriting, but are often difficult to represent algorithmically.&lt;/p>
&lt;p>Here are some examples of complications:&lt;/p>
&lt;h4 id="complication-1-variable-proportion-radicals">Complication 1: Variable-proportion radicals&lt;/h4>
&lt;p>A character made up of components rarely gives the same amount of space to each component. Often these changes in proportion occur in response to the relative complexity and rarity of the components.&lt;/p>
&lt;p>Examples:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>When the left component of a character is less dense than the right component, the right component will take up around ⅔ of the horizontal space. (讠+ 吾 = 语).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>When the left and right components of a character are of equal density, each component will take up around ½ of the horizontal space (身 + 朵 = 躲).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>When the left and right components of a character are the same, each component will take up around ½ of the horizontal space (月 + 月 = 朋).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>When the top and bottom components of a character are the same shape, they will often “nest”. (Example: 夕 + 夕 = 多) This helps squash the overall character to better fill out a square.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>These are just a few of &lt;a href="https://www.writtenchinese.com/how-to-make-sure-your-chinese-characters-are-balanced/">dozens of rough guidelines&lt;/a>, each with exceptions. Radicals can be squashed, stretched, scaled, and translated (although never rotated) during composition.&lt;/p>
&lt;p>The exact proportions and placement of radicals is not regular. I personally don’t believe there is an algorithm for deriving them. But, as you will see in the next section, we can approximate one.&lt;/p>
&lt;h4 id="complication-2-variable-form-radicals">Complication 2: Variable-form radicals&lt;/h4>
&lt;p>Often radicals will change form as well as proportion when composed.&lt;/p>
&lt;ul>
&lt;li>When 刀 appears on the right, it can be written as刂 (as in 刖). (Exception: 切.)&lt;/li>
&lt;li>When 人 appears on the left, it can be written as 亻(as in 他). (Exception: 从.)&lt;/li>
&lt;li>When 手 appears on the left, it can be written as 扌(as in 扡). (Exception: 拜, or 帮.)&lt;/li>
&lt;li>When 心 appears on the left, it can be written as 忄(as in 快).&lt;/li>
&lt;li>When 水 appears on the left, it can be written as 氵(as in 池).&lt;/li>
&lt;li>When 火 appears on the bottom, it can be written as 灬 (as in 黑).&lt;/li>
&lt;li>When 犬 appears on the left, it can be written as 犭(as in 猪).&lt;/li>
&lt;/ul>
&lt;p>These are just a few examples from a longer list. Like written English’s “&lt;a href="https://en.wikipedia.org/wiki/I_before_E_except_after_C">i before e except after c&lt;/a>”, these rules tend to have as many exceptions as they do examples.&lt;/p>
&lt;p>Because some radicals change form when they change form (i.e. 火 =&amp;gt; 灬, as seen above), the IDS rules are flexible. You could write ⿱占火 to mean 点, but you could also choose to specify ⿱占灬 = 点.&lt;/p>
&lt;h3 id="the-long-tail">The Long Tail&lt;/h3>
&lt;p>There is a &lt;a href="https://en.wikipedia.org/wiki/Long_tail">long tail&lt;/a> of characters in the CJK Unified Ideographic Extension blocks which (a) have a Unicode representation, but (b) don’t yet have a rendering in most fonts. A font which fails to implement CJK Unified Ideographic Extension blocks B through G is missing roughly 65,000 characters. &lt;sup id="fnref:7">&lt;a href="#fn:7" class="footnote-ref" role="doc-noteref">7&lt;/a>&lt;/sup> This gulf mirrors the difference in characters between a standard Chinese dictionary and a “complete” Chinese dictionary. These characters are disproportionately likely to be proper nouns, place names, and other rare words.&lt;/p>
&lt;h2 id="autocjk">AutoCJK&lt;/h2>
&lt;p>I wrote and published &lt;a href="https://github.com/google/autocjk/">AutoCJK&lt;/a> a tool which can predict what composite CJK characters should look like. These predictions take the form of low-resolution raster images, which, while unsuitable for use in fonts, can be a useful input for a font design tool (more to come on that later).&lt;/p>
&lt;h3 id="generative-adversarial-networks-gans">Generative adversarial networks (GANs)&lt;/h3>
&lt;p>AutoCJK uses a GAN which is based on &lt;a href="https://phillipi.github.io/pix2pix/">Pix2Pix&lt;/a>. Pix2Pix is a &lt;a href="https://en.wikipedia.org/wiki/Generative_adversarial_network">generative adversarial network&lt;/a> (GAN). In a GAN, two neural networks train to fool each other: one network trains to produce accurate, realistic examples like those in the dataset in order to fool the discriminator, which trains to more accurately differentiate between real elements and the generated examples. Once a GAN is trained, the generator can be used to produce novel outputs similar to, but not the same as, those in the input dataset. GANs are often used to create images.&lt;/p>
&lt;p>Thinking about &lt;a href="https://en.wikipedia.org/wiki/Face">faces&lt;/a> actually led me to the approach of using a GAN. Faces and CJK characters are similar in a few ways: both are roughly visually balanced, both are easily recognizable, and both have both macro- and micro-scale features which contribute heavily to their apparent realism. When it comes to computationally generating new faces, even a tiny mistake can betray an image’s inauthenticity.&lt;/p>
&lt;p>In short, faces and CJK characters both seem to follow subtle rules which don&amp;rsquo;t cleanly fit into an &lt;a href="https://en.wikipedia.org/wiki/Imperative_programming">imperative algorithm&lt;/a> but which nonetheless can be generally predicted.&lt;/p>
&lt;h3 id="generating-custom-training-data">Generating custom training data&lt;/h3>
&lt;p>I fed the GAN a custom dataset&lt;sup id="fnref:8">&lt;a href="#fn:8" class="footnote-ref" role="doc-noteref">8&lt;/a>&lt;/sup> of images of CJK characters and their decompositions.&lt;/p>
&lt;p>Take &lt;a href="https://en.wiktionary.org/wiki/%E3%90%96">U+3416 㐖&lt;/a> as an example.&lt;/p>
&lt;figure class="center" >
&lt;img src="http://www.unicode.org/cgi-bin/refglyph?24-3416" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>This character can be decomposed as ⿰吉乚; it is a vertical composition of U+5409 吉 and U+4E5A 乚.&lt;/p>
&lt;p>
&lt;figure class="center" >
&lt;img src="http://www.unicode.org/cgi-bin/refglyph?24-5409" style="border-radius: 8px;" />
&lt;/figure>
&lt;figure class="center" >
&lt;img src="http://www.unicode.org/cgi-bin/refglyph?24-4E5A" style="border-radius: 8px;" />
&lt;/figure>
&lt;/p>
&lt;p>When I trained the GAN on a particular font, I rendered all three characters in that font and produced an image with 吉, 乚, and 㐖 side-by-side:&lt;/p>
&lt;figure class="center" >
&lt;img src="https://raw.githubusercontent.com/google/autocjk/main/docs/images/0000022.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>I refitted the Pix2Pix &lt;a href="https://www.tensorflow.org/tutorials/generative/pix2pix">tutorial&lt;/a> to accept a 256x256x2 input image and predict a 256x256x1 output image.&lt;/p>
&lt;ul>
&lt;li>
&lt;p>The input image was two 256x256 greyscale bitmaps of the full-width input characters. In the example above, picture the superposition of the first two thirds of the image, i.e. the segments containing &lt;code>吉&lt;/code> and &lt;code>乚&lt;/code>.&lt;/p>
&lt;p>By &amp;ldquo;superposition&amp;rdquo; I just mean that there were two layers to the image. (These are layers you could use to hold red / blue / green in a color image, for example.)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>The output image was one 256x256 greyscale bitmap of the full-width output character. In the example above, picture the right third of the image, containing &lt;code>㐖&lt;/code>.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>The vast majority of the Pix2Pix model was unchanged in &lt;a href="https://github.com/google/autocjk/blob/main/src/model.py">AutoCJK&amp;rsquo;s model&lt;/a>.&lt;/p>
&lt;h3 id="training-and-using-a-model">Training and using a model&lt;/h3>
&lt;p>The v1 model &lt;a href="https://github.com/google/autocjk/blob/d11d27f8ee69cf68e5f8e55cec53b900e8fe8c58/models/generator.h5">currently published&lt;/a> as a part of &lt;a href="https://github.com/google/autocjk">https://github.com/google/autocjk&lt;/a> was trained for around 48 &lt;a href="https://en.wikipedia.org/wiki/Tensor_Processing_Unit">TPU&lt;/a>-hours.&lt;/p>
&lt;p>This model is definitely not perfect. The rasters it generates have rough edges, and the proportions and kerning between the components are biased towards the styles of the fonts I trained it on (in this case, &lt;a href="https://en.wikipedia.org/wiki/Language_localisation">&lt;code>zh-CN&lt;/code>&lt;/a>). Over time I will revisit this model and experiment with loss functions, higher and lower resolution predictions, etc.&lt;/p>
&lt;p>The model was trained on a large number of &lt;a href="https://www.google.com/get/noto/help/cjk/">Noto CJK&lt;/a> fonts, but is by no means limited to predicting outputs given inputs from that particular font. I found &lt;a href="https://en.wikipedia.org/wiki/Out-of-bag_error">cachebusting&lt;/a> (testing with inputs not in the training data) very useful while building confidence in this model.&lt;/p>
&lt;p>In some cases this meant rendering character pairs not in the training data. In other cases this meant using fonts not in the training data. The &lt;a href="https://raw.githubusercontent.com/google/autocjk/main/docs/images/0x134772.png">thumbnail image&lt;/a> is actually trained from glyphs downloaded from &lt;a href="https://glyphwiki.org">GlyphWiki&lt;/a>.&lt;/p>
&lt;p>For instructions on installing and running AutoCJK, see the &lt;a href="https://github.com/google/autocjk/blob/main/README.md">README.md&lt;/a> and the installation &lt;a href="https://github.com/google/autocjk/wiki">wiki&lt;/a>.&lt;/p>
&lt;h2 id="acknowledgements">Acknowledgements&lt;/h2>
&lt;p>I would be remiss if I didn&amp;rsquo;t mention &lt;a href="https://www.babelstone.co.uk/">Andrew West (魏安)&lt;/a>&amp;rsquo;s &lt;a href="https://www.babelstone.co.uk/CJK/IDS.TXT">dataset&lt;/a> which offers per-character decomposition for all 92,856 CJK unified ideographs defined in Unicode version 13.0. This extraordinary resource powers the character de/composition algorithm which underlies the training image generation utility.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>Kangxi radicals were developed as an indexing system in the 1600s, but Chinese indexing systems are at least two millennia old.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>]: &lt;a href="https://en.wikipedia.org/wiki/Chinese_characters#Phono-semantic_compounds">Sometimes (but not always)&lt;/a> the left half of a character has some semantic hint at the meaning, and the right half of a character has some phonetic hint at the pronunciation.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:3">
&lt;p>In Unicode, characters which are used across many East Asian languages are broadly termed CJK, which stands for Chinese-Japanese-Korean.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:4">
&lt;p>See &lt;a href="https://en.wikipedia.org/wiki/CJK_Unified_Ideographs">https://en.wikipedia.org/wiki/CJK_Unified_Ideographs&lt;/a>.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:5">
&lt;p>See &lt;a href="https://www.unicode.org/faq/han_cjk.html#16">https://www.unicode.org/faq/han_cjk.html#16&lt;/a>.&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:6">
&lt;p>See &lt;a href="https://github.com/googlefonts/noto-cjk/issues/13">https://github.com/googlefonts/noto-cjk/issues/13&lt;/a> and &lt;a href="https://github.com/adobe-fonts/source-han-sans/issues/222">https://github.com/adobe-fonts/source-han-sans/issues/222&lt;/a>.&amp;#160;&lt;a href="#fnref:6" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:7">
&lt;p>The characters in these extension blocks are unevenly distributed across traditional and simplified Chinese scripts as well as Japanese, Korean, Vietnamese, and other East Asian scripts.&amp;#160;&lt;a href="#fnref:7" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:8">
&lt;p>You can generate training data like it with &lt;a href="https://github.com/google/autocjk/blob/d11d27f8ee69cf68e5f8e55cec53b900e8fe8c58/src/make_dataset.py">&lt;code>make_dataset.py&lt;/code>&lt;/a> and some local fonts of your own.&amp;#160;&lt;a href="#fnref:8" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></content></item><item><title>Anki flash cards with audio</title><link>https://jbuckland.com/blog/language-pleco-audio/</link><pubDate>Sat, 09 Jan 2021 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/language-pleco-audio/</guid><description>Github http://github.com/ambuc/pleco-to-anki Here I discuss how I got audio samples working in Anki (for Mac OS X) and AnkiDroid (for Android).
Using say Mac OS comes with a text-to-speech utility, say. Say supports a few voices, many of which are tailored to specific languages. I ran:
bash $ say -v &amp;#34;?&amp;#34; | grep zh_ Mei-Jia zh_TW # 您好，我叫美佳。我說國語。 Sin-ji zh_HK # 您好，我叫 Sin-ji。我講廣東話。 Ting-Ting zh_CN # 您好，我叫Ting-Ting。我讲中文普通话。 It looks like Ting-Ting is the best match, since I want to render audio with a mainland Chinese accent.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">Github&lt;/th>
&lt;td> &lt;a href="http://github.com/ambuc/pleco-to-anki">http://github.com/ambuc/pleco-to-anki&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;p>Here I discuss how I got audio samples working in Anki (for Mac OS X) and AnkiDroid (for Android).&lt;/p>
&lt;h2 id="using-say">Using &lt;code>say&lt;/code>&lt;/h2>
&lt;p>Mac OS comes with a text-to-speech utility, &lt;code>say&lt;/code>. Say supports a few voices, many of which are tailored to specific languages. I ran:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="963418572" type="checkbox" />
&lt;label for="963418572">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
$ say -v &amp;#34;?&amp;#34; | grep zh_
Mei-Jia zh_TW # 您好，我叫美佳。我說國語。
Sin-ji zh_HK # 您好，我叫 Sin-ji。我講廣東話。
Ting-Ting zh_CN # 您好，我叫Ting-Ting。我讲中文普通话。
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>It looks like &lt;code>Ting-Ting&lt;/code> is the best match, since I want to render audio with a &lt;em>mainland&lt;/em> Chinese accent. So we can render and save an audio sample with:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="621495387" type="checkbox" />
&lt;label for="621495387">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
/usr/bin/say -v Ting-Ting &amp;#34;拼音&amp;#34; -o ~/path/to/output
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>I wanted to avoid the cost of converting the generated audio file, since there would be as many unique files as there are flash cards. I used &lt;code>.flac&lt;/code> since it was the only format which &lt;code>say&lt;/code> can natively render on Mac OS X &lt;em>and&lt;/em> which Android (and, by extension AnkiDroid, running on Android) can open.&lt;/p>
&lt;h2 id="saving-to-anki">Saving to Anki&lt;/h2>
&lt;p>Anki looks for media at a path which &lt;a href="https://docs.ankiweb.net/#/files?id=file-locations">varies per-platform&lt;/a>. Since I&amp;rsquo;m on Mac OS X, I need to save my samples to &lt;code>~/Library/Application Support/Anki2/User 1/media.collection/&amp;lt;file&amp;gt;.flac&lt;/code>.&lt;/p>
&lt;p>In Anki, you can use the special embed syntax &lt;code>[sound:&amp;lt;file&amp;gt;]&lt;/code> which renders a system-native audio player. If the field containing that embed is on the front of a card, it will play when the card first appears; otherwise it will play when the reverse of the card is revealed.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>I ended up writing this behavior directly into &lt;a href="https://github.com/ambuc/pleco-to-anki">pleco-to-anki&lt;/a>, which now generate the audio samples, save them to &lt;code>~/Library/Application Support/Anki2/media.collection&lt;/code>, and generate a &lt;code>.csv&lt;/code> with a column containing embed syntax as specified above. You may need to adapt this workflow for your own platform and card/field format.&lt;/p></content></item><item><title>A Pleco-to-Anki flash card creator</title><link>https://jbuckland.com/blog/language-pleco-to-anki/</link><pubDate>Fri, 27 Nov 2020 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/language-pleco-to-anki/</guid><description>Github http://github.com/ambuc/pleco-to-anki Background When I am learning a new word in Chinese, I usually look it up in Pleco and hit the [+] button to create a flash card. Pleco is extremely useful for generating flash cards, but I prefer to use Anki, which lets me review those flash cards using spaced repetition.
Both Pleco and Anki have Android apps, but there is no native interface between them. Pleco exports a complex XML file with a lot of metadata.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">Github&lt;/th>
&lt;td> &lt;a href="http://github.com/ambuc/pleco-to-anki">http://github.com/ambuc/pleco-to-anki&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;h2 id="background">Background&lt;/h2>
&lt;p>When I am learning a new word in Chinese, I usually look it up in &lt;a href="https://www.pleco.com/products/pleco-for-android/">Pleco&lt;/a> and hit the &lt;code>[+]&lt;/code> button to create a flash card. Pleco is extremely useful for generating flash cards, but I prefer to use &lt;a href="https://apps.ankiweb.net/">Anki&lt;/a>, which lets me review those flash cards using &lt;a href="https://en.wikipedia.org/wiki/Spaced_repetition">spaced repetition&lt;/a>.&lt;/p>
&lt;p>Both Pleco and Anki have Android apps, but there is no native interface between them. Pleco exports a complex XML file with a lot of metadata. Anki expects to import a CSV file which maps to your custom note.&lt;/p>
&lt;h3 id="anki">Anki&lt;/h3>
&lt;p>In Anki terminology, a &lt;strong>note&lt;/strong> is a set of two or more fields:&lt;/p>
&lt;pre tabindex="0">&lt;code># type: Note
French: Bonjour
English: Hello
Page: 12
&lt;/code>&lt;/pre>&lt;p>and a &lt;strong>card&lt;/strong> is a view into that note via a &lt;strong>card type&lt;/strong>:&lt;/p>
&lt;pre tabindex="0">&lt;code># type: Card Type
Q: French
A: English&amp;lt;br&amp;gt;
Page #Page
&lt;/code>&lt;/pre>&lt;pre tabindex="0">&lt;code># type Card
Q: Bonjour
A: Hello
Page #12
&lt;/code>&lt;/pre>&lt;p>It is normal to have a few card types, which represent a few views into that note which you are trying to memorize.&lt;/p>
&lt;h3 id="anki-for-chinese">Anki for Chinese&lt;/h3>
&lt;p>My Chinese-language notecards have three fields:&lt;/p>
&lt;pre tabindex="0">&lt;code># type: Note
characters: 苹果
pinyin: &amp;lt;font color=&amp;#34;green&amp;#34;&amp;gt;píng&amp;lt;/font&amp;gt;&amp;lt;font color=&amp;#34;blue&amp;#34;&amp;gt;guǒ&amp;lt;/font&amp;gt;
meaning: noun apple
&lt;/code>&lt;/pre>&lt;p>where the &lt;code>characters&lt;/code> field contains CJK unicode, and the &lt;code>pinyin&lt;/code> field is HTML. Anki will render these fields correctly on web and mobile.&lt;/p>
&lt;p>I have four card types:&lt;/p>
&lt;pre tabindex="0">&lt;code>Q: characters + meaning
A: pinyin
Q: characters + pinyin
A: meaning
Q: pinyin + meaning
A: characters
Q: characters
A: pinyin + meaning
&lt;/code>&lt;/pre>&lt;p>It doesn&amp;rsquo;t make sense to have a card where &lt;code>Q: pinyin&lt;/code>, since the question is ambiguous &amp;ndash; there are many words and characters with the same pronunciation. I think it also doesn&amp;rsquo;t make sense to have a card where &lt;code>Q: meaning&lt;/code>, since there are many ways to say the same idea in each language.&lt;/p>
&lt;h2 id="problem-statement">Problem statement&lt;/h2>
&lt;p>In Pleco, a flash card looks like this:&lt;/p>
&lt;figure class="center" >
&lt;img src="images/pleco.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>Pleco can export your set of saved flash cards, but it does so as XML:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="142536897" type="checkbox" />
&lt;label for="142536897">
&lt;span class="collapsable-code__language">xml&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-xml" >&lt;code>
&amp;lt;?xml version=&amp;#34;1.0&amp;#34; ?&amp;gt;
&amp;lt;plecoflash formatversion=&amp;#34;2&amp;#34; creator=&amp;#34;Pleco User -1&amp;#34; generator=&amp;#34;Pleco 2.0 Flashcard Exporter&amp;#34; platform=&amp;#34;Android&amp;#34; created=&amp;#34;1605883885&amp;#34;&amp;gt;
&amp;lt;categories/&amp;gt;
&amp;lt;cards&amp;gt;
&amp;lt;card language=&amp;#34;chinese&amp;#34;&amp;gt;
&amp;lt;entry&amp;gt;
&amp;lt;headword charset=&amp;#34;sc&amp;#34;&amp;gt;感冒&amp;lt;/headword&amp;gt;
&amp;lt;headword charset=&amp;#34;tc&amp;#34;&amp;gt;感冒&amp;lt;/headword&amp;gt;
&amp;lt;pron type=&amp;#34;hypy&amp;#34; tones=&amp;#34;numbers&amp;#34;&amp;gt;gan3mao4&amp;lt;/pron&amp;gt;
&amp;lt;defn&amp;gt;noun common cold verb 1 catch cold 2 dialect be interested in; like (usu. used in the negative)&amp;lt;/defn&amp;gt;
&amp;lt;/entry&amp;gt;
&amp;lt;dictref dictid=&amp;#34;PACE&amp;#34; entryid=&amp;#34;21428224&amp;#34;/&amp;gt;
&amp;lt;/card&amp;gt;
&amp;lt;/cards&amp;gt;
&amp;lt;/plecoflash&amp;gt;
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>where each &lt;code>&amp;lt;card&amp;gt;&lt;/code> entry is a rich object with (1) &lt;code>sc&lt;/code> (simplified Chinese) and &lt;code>tc&lt;/code> (traditional Chinese) characters, (2) a pinyin string in which the numeral following a syllable denotes its tone, and (3) a dictionary definition.&lt;/p>
&lt;p>Anki, on the other hand, prefers to import data as a CSV (comma-separated or semicolon-separated is fine), where each columns maps to a field in the destination note. The above example as CSV might look like:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="837254961" type="checkbox" />
&lt;label for="837254961">
&lt;span class="collapsable-code__language">csv&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-csv" >&lt;code>
&amp;lt;span&amp;gt;&amp;lt;font color=&amp;#34;blue&amp;#34;&amp;gt;găn&amp;lt;/font&amp;gt;&amp;lt;/span&amp;gt; &amp;lt;span&amp;gt;&amp;lt;font color=&amp;#34;purple&amp;#34;&amp;gt;mào&amp;lt;/font&amp;gt;&amp;lt;/span&amp;gt;;感冒;noun common cold verb 1 catch cold 2 dialect be interested in.
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>This might render in Anki like so:&lt;/p>
&lt;figure class="center" >
&lt;img src="images/anki.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;h2 id="solution">Solution&lt;/h2>
&lt;p>I wrote &lt;a href="https://github.com/ambuc/pleco-to-anki">pleco-to-anki&lt;/a>, a Python script which converts an XML file with a &lt;code>plecoflash&lt;/code> object to a CSV file suitable for import.&lt;/p>
&lt;h3 id="requirements">Requirements&lt;/h3>
&lt;ul>
&lt;li>I want to test myself on a character&amp;rsquo;s tones, but I don&amp;rsquo;t want to use the style of pinyin in which tones have numbers. I prefer to read the tones as diacritics over vowels, i.e. the pinyin for 苹果 should appear as píngguǒ, not ping2guo3.&lt;/li>
&lt;li>I like the convention where syllables are colored based on their tone. Everyone has their own convention, but for me red=flat, green=rising, blue=u-shaped, purple=falling, and grey=neutral. I want these flash cards to have colored pinyin syllables, but not colored Chinese characters.&lt;/li>
&lt;/ul>
&lt;h3 id="gotchas">Gotchas&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://resources.allsetlearning.com/chinese/pronunciation/The_%22o%22_and_%22u%22_vowels">&lt;code>u&lt;/code>&lt;/a> and &lt;a href="https://resources.allsetlearning.com/chinese/pronunciation/The_%22%C3%BC%22_vowel">&lt;code>ü&lt;/code>&lt;/a> are two different vowels in pinyin and the difference must be preserved. For example, 旅游 can be written as &amp;ldquo;lǚyóu&amp;rdquo;: note the diaeresis (¨) &lt;em>and&lt;/em> háček (ˇ) over the letter &amp;lsquo;u&amp;rsquo;.&lt;/li>
&lt;li>The dictionary definition which Pleco provides often repeats the Chinese characters: it is unsuitable as a question if it contains or hints at the answer.&lt;/li>
&lt;/ul>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Hopefully &lt;a href="https://github.com/ambuc/pleco-to-anki/">pleco-to-anki&lt;/a> can benefit you as well as me. Please leave me a &lt;a href="https://github.com/ambuc/pleco-to-anki/pulls">PR or open an issue&lt;/a> if you catch a bug or encounter difficulty using this tool.&lt;/p></content></item><item><title>Latis, a command-line spreadsheet toy</title><link>https://jbuckland.com/blog/library-latis/</link><pubDate>Fri, 03 Jul 2020 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/library-latis/</guid><description>Github http://github.com/ambuc/latis Background I wrote a command-line spreadsheet toy in C++. It renders as a TUI and implements an Excel-like grammar for defining formulae. The full source is here.
Design In-scope for this project:
Designing an ABNF grammar Writing a system for lexing, parsing, and evaluating user input. Writing a TUI for rendering spreadsheets interactively. Using C++. (Mostly to learn about OSS Bazel, absl, and protobuffers. Out-of-scope:
A fully-featured spreadsheet app which is easy to use or interoperates with other spreadsheet formats.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">Github&lt;/th>
&lt;td> &lt;a href="http://github.com/ambuc/latis">http://github.com/ambuc/latis&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;h2 id="background">Background&lt;/h2>
&lt;p>I wrote a command-line spreadsheet toy in C++. It renders as a &lt;a href="https://en.wikipedia.org/wiki/Text-based_user_interface">TUI&lt;/a> and implements an Excel-like grammar for defining formulae. The full source is &lt;a href="https://github.com/ambuc/latis">here&lt;/a>.&lt;/p>
&lt;h2 id="design">Design&lt;/h2>
&lt;p>In-scope for this project:&lt;/p>
&lt;ul>
&lt;li>Designing an &lt;a href="https://en.wikipedia.org/wiki/Augmented_Backus%E2%80%93Naur_form">ABNF grammar&lt;/a>&lt;/li>
&lt;li>Writing a system for lexing, parsing, and evaluating user input.&lt;/li>
&lt;li>Writing a TUI for rendering spreadsheets interactively.&lt;/li>
&lt;li>Using C++. (Mostly to learn about OSS &lt;a href="https://bazel.build/">Bazel&lt;/a>, &lt;a href="https://abseil.io/docs/cpp/">absl&lt;/a>, and &lt;a href="https://developers.google.com/protocol-buffers">protobuffers&lt;/a>.&lt;/li>
&lt;/ul>
&lt;p>Out-of-scope:&lt;/p>
&lt;ul>
&lt;li>A fully-featured spreadsheet app which is easy to use or interoperates with other spreadsheet formats.&lt;/li>
&lt;/ul>
&lt;h3 id="design-overview">Design Overview&lt;/h3>
&lt;p>A spreadsheet program needs to have individual cells, each of which either has:&lt;/p>
&lt;ul>
&lt;li>a typed value (a string value, a numeric value, or a monetary value, to name a few) or&lt;/li>
&lt;li>a formula which is dependent on zero or more cells and which returns a typed value.&lt;/li>
&lt;/ul>
&lt;p>User input must be &lt;a href="https://en.wikipedia.org/wiki/Lexical_analysis">lexed&lt;/a> (broken up into a series of meaningful tokens), &lt;a href="https://en.wikipedia.org/wiki/Parsing#Parser">parsed&lt;/a>, (turned from a series of tokens into a data structure via a formal grammar), and evaluated (rendered to a typed value). The degenerate case of this lex-parse-evaluate pipeline is a single typed value.&lt;/p>
&lt;p>The program must also maintain a graph of cells and their dependencies on each other. If cell &lt;code>A&lt;/code> depends on cell &lt;code>B&lt;/code>, and cell &lt;code>B&lt;/code> is updated, the rendering of cell &lt;code>A&lt;/code> (but not its underlying formula) must also be updated. Furthermore, cells depending on &lt;code>A&lt;/code> must be updated, and so on and so forth.&lt;/p>
&lt;p>Finally, there should be some decoupling between the spreadsheet data structure and the onscreen contents. This design problem is often solved using the &lt;a href="https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller">MVC&lt;/a> pattern, and was far and away the most complex part of this project, since I foolishly insisted on using C++ and writing myself not only the spreadsheet but the underlying MVC tools and frameworks.&lt;/p>
&lt;h3 id="abnf-grammar">ABNF Grammar&lt;/h3>
&lt;p>As mentioned above, a spreadsheet which can lex, parse, and evaluate user input must attempt to understand that input according to a &lt;a href="https://en.wikipedia.org/wiki/Formal_grammar">grammar&lt;/a>. I chose to design a grammar using &lt;a href="https://en.wikipedia.org/wiki/Augmented_Backus%E2%80%93Naur_form">ABNF&lt;/a>, which is a manner of specifying grammar using some rules. ABNF already defines some bottom-level terminal values (numerics, literals, etc.) which make defining a grammar easier. Here is the grammar I ended up with:&lt;/p>
&lt;h4 id="notes-on-abnf">Notes on ABNF:&lt;/h4>
&lt;ul>
&lt;li>Anything in double quotes is a literal, i.e. &lt;code>-&lt;/code> might mean a range of values, but &lt;code>&amp;quot;-&amp;quot;&lt;/code> means the literal &lt;code>-&lt;/code> character.&lt;/li>
&lt;li>Anything on a line after &lt;code>;&lt;/code> is a comment.&lt;/li>
&lt;li>&lt;code>%c&lt;/code> means a single text character.&lt;/li>
&lt;li>&lt;code>%d&lt;/code> means a decimal digit.&lt;/li>
&lt;li>&lt;code>0-9&lt;/code> means a range of values.&lt;/li>
&lt;li>&lt;code>&amp;lt;a&amp;gt;*&amp;lt;b&amp;gt;e&lt;/code> means between &lt;code>a&lt;/code> and &lt;code>b&lt;/code> instances of &lt;code>e&lt;/code>. If &lt;code>a&lt;/code> is not specified, the default lower bound is 0. If &lt;code>b&lt;/code> is not specified, the default upper bound is infinity. So &lt;code>1*e&lt;/code> means one or more instances of &lt;code>e&lt;/code>.&lt;/li>
&lt;li>&lt;code>&amp;lt;a&amp;gt;e&lt;/code> means &lt;code>e&lt;/code> exactly &lt;code>a&lt;/code> times.&lt;/li>
&lt;li>&lt;code>X / Y&lt;/code> means either &lt;code>X&lt;/code> or &lt;code>Y&lt;/code>.&lt;/li>
&lt;li>&lt;code>[e]&lt;/code> means that &lt;code>e&lt;/code> is optional, i.e. shorthand for &lt;code>0*1e&lt;/code>.&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code>DIGIT = %d0-9
DATE_FULLYEAR = 4DIGIT
DATE_MONTH = 2DIGIT ; 01-12
DATE_MDAY = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on month/year
TIME_HOUR = 2DIGIT ; 00-23
TIME_MINUTE = 2DIGIT ; 00-59
TIME_SECOND = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second rules
TIME_SECFRAC = &amp;#34;.&amp;#34; 1*DIGIT
TIME_NUMOFFSET = (&amp;#34;+&amp;#34; / &amp;#34;-&amp;#34;) TIME_HOUR &amp;#34;:&amp;#34; TIME_MINUTE
TIME_OFFSET = &amp;#34;Z&amp;#34; / time_numoffset
DATE_TIME = DATE_FULLYEAR &amp;#34;-&amp;#34; DATE_MONTH &amp;#34;-&amp;#34; DATE_MDAY &amp;#34;T&amp;#34;
TIME_HOUR &amp;#34;:&amp;#34; TIME_MINUTE &amp;#34;:&amp;#34; TIME_SECOND [TIME_SECFRAC]
TIME_OFFSET
UPPERCASE = %c&amp;#34;A&amp;#34;-&amp;#34;Z&amp;#34;
ALPHANUMERIC = 1*(UPPERCASE / DIGIT)
INT_NUMERIC = 1*DIGIT
DOUBLE_NUMERIC = *DIGIT &amp;#34;.&amp;#34; *DIGIT
NUM_VAL = INT_NUMERIC |
DOUBLE_NUMERIC
CURRENCY_ENUM = &amp;#34;USD&amp;#34; / &amp;#34;SEK&amp;#34; / ...
CURRENCY_VAL = NUM_VAL CURRENCY_ENUM
STR_VAL = &amp;#34;\&amp;#34;&amp;#34; *e &amp;#34;\&amp;#34;&amp;#34;
ROW_INDICATOR = 1*DIGIT
COL_INDICATOR = 1*UPPERCASE
LOCATION_VAL = COL_INDICATOR ROW_INDICATOR ; A1
RANGE_VAL = COL_INDICATOR &amp;#34;:&amp;#34; COL_INDICATOR ; A:B
/ LOCATION_VAL &amp;#34;:&amp;#34; ROW_INDICATOR ; A1:3
/ LOCATION_VAL &amp;#34;:&amp;#34; LOCATION_VAL ; A1:B3
BOOL_VAL = &amp;#34;True&amp;#34; / &amp;#34;False&amp;#34;
VAL = CURRENCY_VAL / DATE_TIME / NUM_VAL / STR_VAL
/ LOCATION_VAL / RANGE_VAL / BOOL_VAL
FN = 1*(ALPHANUMERIC / &amp;#34;_&amp;#34;)
EXPR = VAL ; 4
/ &amp;#34;(&amp;#34; EXPR &amp;#34;)&amp;#34; ; (5)
/ &amp;#34;(&amp;#34; EXPR FN EXPR &amp;#34;)&amp;#34; ; (1+1)
/ FN &amp;#34;(&amp;#34; EXPR *( &amp;#34;,&amp;#34; EXPR) &amp;#34;)&amp;#34; ; POW(2,4,6)
/ EXPR &amp;#34;?&amp;#34; EXPR &amp;#34;:&amp;#34; EXPR ; cond ? val1 : val2
GRAMMAR = &amp;#34;=&amp;#34; EXPR
&lt;/code>&lt;/pre>&lt;h3 id="lexing">Lexing&lt;/h3>
&lt;p>When lexing, we convert a string of user input into a linear sequence of meaningful tokens. Things get very verbose at this stage. We define an enum &lt;code>T&lt;/code> of possible lex types, and store tokens as:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="597361248" type="checkbox" />
&lt;label for="597361248">
&lt;span class="collapsable-code__language">c&amp;#43;&amp;#43;&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-c&amp;#43;&amp;#43;" >&lt;code>
struct Token {
enum T {
equals, // =
period, // .
...
};
T type
std::string_view value;
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>where each &lt;code>Token&lt;/code> holds a reference to the substring of user input it represents.&lt;/p>
&lt;p>For example, lexing might turn a string like:&lt;/p>
&lt;pre tabindex="0">&lt;code>=POW(4.605, 2)
00000000001111
01234567890123
&lt;/code>&lt;/pre>&lt;p>into a sequence of tokens like:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>substring&lt;/th>
&lt;th>type&lt;/th>
&lt;th>start&lt;/th>
&lt;th>length&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>=&lt;/code>&lt;/td>
&lt;td>&lt;code>T::equals&lt;/code>&lt;/td>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>POW&lt;/code>&lt;/td>
&lt;td>&lt;code>T::alpha&lt;/code>&lt;/td>
&lt;td>1&lt;/td>
&lt;td>3&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>(&lt;/code>&lt;/td>
&lt;td>&lt;code>T::lparen&lt;/code>&lt;/td>
&lt;td>4&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>4.605&lt;/code>&lt;/td>
&lt;td>&lt;code>T::numeric&lt;/code>&lt;/td>
&lt;td>5&lt;/td>
&lt;td>5&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>,&lt;/code>&lt;/td>
&lt;td>&lt;code>T::comma&lt;/code>&lt;/td>
&lt;td>10&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>2&lt;/code>&lt;/td>
&lt;td>&lt;code>T::numeric&lt;/code>&lt;/td>
&lt;td>12&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>)&lt;/code>&lt;/td>
&lt;td>&lt;code>T::rparen&lt;/code>&lt;/td>
&lt;td>13&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>By passing around types &lt;em>and&lt;/em> underlying substrings, we can parse the contents of alphanumeric tokens lazily, i.e. only when necessary.&lt;/p>
&lt;h4 id="note-on-stdstring_view">Note on std::string_view&lt;/h4>
&lt;p>&lt;a href="https://en.cppreference.com/w/cpp/header/string_view">&lt;code>std::string_view&lt;/code>&lt;/a> represents a read-only view into an existing character buffer. So long as the underlying string doesn&amp;rsquo;t vanish, the constructed string_view is a small data structure suitable for passing by-value (or mutating in-place) without the overhead of copying an arbitrary-length string.&lt;/p>
&lt;h3 id="parsing">Parsing&lt;/h3>
&lt;p>Parsing is the process of taking a series of lexical tokens (as described above) and generating a useful data structure (read: tree) which can be stored, traversed, evaluated, manipulated, and displayed. This process happens hand-in-hand with the grammar defined above, and some tools can read ABNF grammars or other regular grammars and spit out parsers. I chose to hand-roll my own. This was the most challenging and educational aspect of this project by far.&lt;/p>
&lt;h4 id="parser-combinators">Parser combinators&lt;/h4>
&lt;p>I used the &lt;a href="https://en.wikipedia.org/wiki/Parser_combinator">parser-combinator&lt;/a> style; that is, I wrote a set of low-level parsers (one which parses into a character, one which parses into a number, etc.) and then a set of higher-order parsers, which combine two or more parsers and return a new parser.&lt;/p>
&lt;p>Before we get into how this works, we need some background on &lt;code>absl::Status&lt;/code>, &lt;code>StatusOr&amp;lt;T&amp;gt;&lt;/code>, &lt;code>absl::Span&lt;/code>, &lt;code>absl::variant&lt;/code>, and &lt;code>absl::bind_front&lt;/code>.&lt;/p>
&lt;h5 id="abslstatus">absl::Status&lt;/h5>
&lt;p>&lt;a href="https://github.com/abseil/abseil-cpp/blob/master/absl/status/status.h">&lt;code>absl::Status&lt;/code>&lt;/a> is a C++ error handling class which encapsulates a typed error (&lt;code>kInvalidArgument&lt;/code>, &lt;code>kPermissionDenied&lt;/code>, etc.) and a human-readable string payload.&lt;/p>
&lt;h5 id="statusort">&lt;code>???::StatusOr&amp;lt;T&amp;gt;&lt;/code>&lt;/h5>
&lt;p>&lt;code>::StatusOr&amp;lt;T&amp;gt;&lt;/code> is the &lt;a href="https://en.wikipedia.org/wiki/Tagged_union">sum type&lt;/a> of a value of type &lt;code>T&lt;/code> and an &lt;code>absl::Status&lt;/code>. It doesn&amp;rsquo;t exist (yet!) in &lt;code>absl&lt;/code>, but other OSS libraries &lt;a href="https://googleapis.dev/cpp/google-cloud-common/0.4.0/classgoogle_1_1cloud_1_1v0_1_1StatusOr.html">hint&lt;/a> &lt;a href="http://www.furidamu.org/blog/2017/01/28/error-handling-with-statusor/">at&lt;/a> &lt;a href="https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/stubs/statusor.h">its&lt;/a> &lt;a href="https://asylo.dev/doxygen/classasylo_1_1StatusOr.html">existence&lt;/a>. Since my project already imported &lt;code>&amp;quot;google/protobuf&amp;quot;&lt;/code>, I decided to use &lt;code>&amp;quot;google/protobuf/stubs/statusor.h&amp;quot;&lt;/code>, which exposes &lt;code>::google::protobuf::util::StatusOr&amp;lt;T&amp;gt;&lt;/code>. One day this may be a part of &lt;code>absl&lt;/code>.&lt;/p>
&lt;h5 id="abslspan">&lt;code>absl::Span&lt;/code>&lt;/h5>
&lt;p>&lt;a href="https://github.com/abseil/abseil-cpp/blob/master/absl/types/span.h">&lt;code>absl::Span&lt;/code>&lt;/a> is the same idea as &lt;code>std::string_view&lt;/code>, only it applies to other containers beyond strings. Think of it as a read-only view into a container. Notably, it can be passed by-reference or mutated in-place.&lt;/p>
&lt;h5 id="abslvariant">&lt;code>absl::variant&lt;/code>&lt;/h5>
&lt;p>&lt;a href="https://github.com/abseil/abseil-cpp/blob/master/absl/types/variant.h">&lt;code>absl::variant&lt;/code>&lt;/a> is a union type of one or more typed values. It holds exactly one value of one of its types. So a value of type &lt;code>absl::Variant&amp;lt;int, double&amp;gt; numeric&lt;/code> could hold either an integer or a double.&lt;/p>
&lt;p>One could use &lt;code>std::get&amp;lt;int&amp;gt;(numeric)&lt;/code> to access the underlying integer type, but &lt;code>std::get&lt;/code> excepts (&lt;code>std::bad_variant_access&lt;/code>) if the underlying variant holds a type other than the one specified. Instead it is idiomatic to use the visitor pattern:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="147823596" type="checkbox" />
&lt;label for="147823596">
&lt;span class="collapsable-code__language">c&amp;#43;&amp;#43;&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-c&amp;#43;&amp;#43;" >&lt;code>
struct Visitor {
void operator()(const int&amp;amp; i) const {
std::cout &amp;lt;&amp;lt; &amp;#34;Int: &amp;#34; &amp;lt;&amp;lt; i;
}
void operator()(const double&amp;amp; i) const {
std::cout &amp;lt;&amp;lt; &amp;#34;Double: &amp;#34; &amp;lt;&amp;lt; i;
}
};
absl::variant&amp;lt;int, double&amp;gt; numeric = 123.456;
Visitor visitor;
absl::visit(visitor, numeric); // Prints &amp;#34;double: 123.456&amp;#34;;
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>This is a little complicated, but the benefits outweigh the costs.&lt;/p>
&lt;h5 id="abslbind_front">&lt;code>absl::bind_front&lt;/code>&lt;/h5>
&lt;p>Finally, &lt;code>absl::bind_front&lt;/code> and &lt;code>std::bind_front&lt;/code> both implement partial function application. Here is an example:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="982713564" type="checkbox" />
&lt;label for="982713564">
&lt;span class="collapsable-code__language">c&amp;#43;&amp;#43;&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-c&amp;#43;&amp;#43;" >&lt;code>
// |minus| is a function with two arguments.
int minus(int a, int b) {
return a - b;
}
// But |fifty_minus| is a function with only one argument;
auto fifty_minus = std::bind_front(minus, 50);
int fifty_minus_three = fifty_minus(3); // outputs 47.
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>We can use this to stage partially-evaluated methods which already have their first &lt;code>n&lt;/code> arguments and are waiting on their last &lt;code>m&lt;/code> arguments, and can be thunked later.&lt;/p>
&lt;h4 id="back-to-parser-combinators">Back to Parser Combinators&lt;/h4>
&lt;h4 id="what-is-a-parser">What is a Parser?&lt;/h4>
&lt;p>If a lexer spits out a vector of tokens, define a &lt;code>using TSpan = absl::Span&amp;lt;const Token&amp;gt;&lt;/code>, where a &lt;code>TSpan&lt;/code> is a &lt;em>mutable&lt;/em> span of &lt;em>immutable&lt;/em> tokens. That is, it is a sliding window which can be slid to any starting node and length, but there is no risk of mutating the underlying tokens.&lt;/p>
&lt;p>In Latis, a parser has this type:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="192435867" type="checkbox" />
&lt;label for="192435867">
&lt;span class="collapsable-code__language">c&amp;#43;&amp;#43;&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-c&amp;#43;&amp;#43;" >&lt;code>
template &amp;lt;typename T&amp;gt;
using Prsr = std::function&amp;lt;StatusOr&amp;lt;T&amp;gt;(TSpan *)&amp;gt;;
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>that is, a parser takes a pointer to a &lt;code>TSpan&lt;/code>, &lt;em>may&lt;/em> mutate that &lt;code>TSpan&lt;/code> in-place, and returns either a value of type &lt;code>T&lt;/code> or an &lt;code>absl::Status&lt;/code> explaining why it was unable to parse the input.&lt;/p>
&lt;p>NB: This parser definition attempts to parse the &lt;em>head&lt;/em> of some input, not the entire body. A successful parse will truncate the token span, leaving a shorter span suitable for running through anothe parser.&lt;/p>
&lt;h4 id="what-is-a-parser-combinator">What is a Parser combinator?&lt;/h4>
&lt;p>A parser combinator is a higher-order function; it accepts as arguments one or more function(s) and returns another function.&lt;/p>
&lt;p>Here are some existing parsers:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="724859631" type="checkbox" />
&lt;label for="724859631">
&lt;span class="collapsable-code__language">c&amp;#43;&amp;#43;&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-c&amp;#43;&amp;#43;" >&lt;code>
StatusOr&amp;lt;int&amp;gt; ConsumeInt(TSpan *tspan);
StatusOr&amp;lt;double&amp;gt; ConsumeDouble(TSpan *tspan);
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>And here is a parser combinator:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="352196478" type="checkbox" />
&lt;label for="352196478">
&lt;span class="collapsable-code__language">c&amp;#43;&amp;#43;&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-c&amp;#43;&amp;#43;" >&lt;code>
template &amp;lt;typename... O&amp;gt;
static std::function&amp;lt;StatusOr&amp;lt;absl::variant&amp;lt;O...&amp;gt;&amp;gt;(TSpan *)&amp;gt;
AnyVariant(StatusOr&amp;lt;O&amp;gt;(TSpan *)&amp;gt;... ls);
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>This parser combinator (named &lt;code>AnyVariant&lt;/code>) uses &lt;a href="https://docs.microsoft.com/en-us/cpp/cpp/ellipses-and-variadic-templates?view=vs-2019">variadic templates&lt;/a> to accept a series of any number of parsers (i.e. any number of inputs of type &lt;code>StatusOr&amp;lt;O&amp;gt;(TSpan *)&lt;/code>, and returns a parser whose return type is &lt;code>absl::variant&amp;lt;O...&amp;gt;&lt;/code>.&lt;/p>
&lt;p>We can use a parser combinator like &lt;code>AnyVariant&lt;/code> to write a parser which attempts to parse the input as &lt;em>either&lt;/em> an int &lt;em>or&lt;/em> a double:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="865749132" type="checkbox" />
&lt;label for="865749132">
&lt;span class="collapsable-code__language">c&amp;#43;&amp;#43;&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-c&amp;#43;&amp;#43;" >&lt;code>
StatusOr&amp;lt;absl::Variant&amp;lt;double, int&amp;gt;&amp;gt; ConsumeNumeric(TSpan *tspan) {
return AnyVariant&amp;lt;double, int&amp;gt;(
absl::bind_front(&amp;amp;ConsumeDouble, this),
absl::bind_front(&amp;amp;ConsumeInt, this))(tspan);
}
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>NB: Recall that &lt;code>AnyVariant&amp;lt;O...&amp;gt;(...)&lt;/code> itself returns a parser. So we need to call it like so: &lt;code>AnyVariant&amp;lt;O...&amp;gt;(...)(...)&lt;/code>.&lt;/p>
&lt;h4 id="other-useful-parser-combinators">Other useful parser combinators:&lt;/h4>
&lt;p>In addition to &lt;code>AnyVariant&lt;/code>, I also wrote:&lt;/p>
&lt;ul>
&lt;li>&lt;code>Any&amp;lt;T&amp;gt;&lt;/code>, which takes one or more parsers of type &lt;code>T&lt;/code> and returns one parser returning type &lt;code>T&lt;/code>, which atempts to match the parsers in priority order.&lt;/li>
&lt;li>&lt;code>Maybe&amp;lt;T&amp;gt;&lt;/code>, which takes exactly one parser and returns another lenient parser, which reads from &lt;code>TSpan&lt;/code> but does not fail if it fails to match. This is useful for implementing optional aspects of the grammar, such as &lt;code>TIME_SECFRAC&lt;/code> in the timespec.&lt;/li>
&lt;li>&lt;code>WithRestriction&amp;lt;T&amp;gt;&lt;/code>, which takes exactly one parser and an filtering function, and returns a parser which only returns a value of type &lt;code>T&lt;/code> if it passes that filtering function. This is useful for introducing logic into our parser, such as recognizing a numeric value as representing a number of hours if and only if it is in the range &lt;code>[0-24]&lt;/code>.&lt;/li>
&lt;li>&lt;code>InSequence&amp;lt;std::tuple&amp;lt;O...&amp;gt;&amp;gt;(Prsr&amp;lt;O&amp;gt;...)&lt;/code>, which takes a sequence of parsers and attempts to match them all, in-sequence, returning a &lt;code>std::tuple&amp;lt;&amp;gt;&lt;/code> of their outputs. This is useful for matching all-or-nothing of a subgrammar, such as datetime specifications, which are only valid if they match the entire &lt;code>DATE_FULLYEAR-DATE_MONTH-DATE_MDAY...&lt;/code> grammar. Otherwise this returns &lt;code>Status&lt;/code>, and we do not truncate the &lt;code>TSpan&lt;/code>.&lt;/li>
&lt;li>&lt;code>WithTransformation&lt;/code>, which is like &lt;code>WithRestriction&lt;/code>, except it takes exactly one parser and exactly one transformation function which can convert the parsed value to some other value of some other type.&lt;/li>
&lt;li>&lt;code>WithLookup&lt;/code>, which accepts exactly one parser and exactly one lookup function. It is useful for looking up the returned value as the key in some map and returning its value instead. It can be thought of as a synonym for &lt;code>WithRestriction&lt;/code> where the restriction is being-in-the-map, and &lt;code>WithTransformation&lt;/code> where the transformation is to-the-matching-value-in-the-map.&lt;/li>
&lt;/ul>
&lt;p>The full list of combinators (and their implementations) lives here: &lt;a href="https://github.com/ambuc/latis/blob/master/src/formula/parser_combinators.h">https://github.com/ambuc/latis/blob/master/src/formula/parser_combinators.h&lt;/a>.&lt;/p>
&lt;h4 id="parsing-altogether">Parsing, altogether.&lt;/h4>
&lt;p>In the end we compose many parser combinators and parsers into one big function, which accepts user input and outputs a data structure (actually a &lt;a href="https://github.com/ambuc/latis/blob/master/proto/latis_msg.proto">protobuffer&lt;/a>) which contains nested formulae, expressions, values, string literals, and table lookup locations.&lt;/p>
&lt;h4 id="evaluation">Evaluation&lt;/h4>
&lt;p>Evaluation is comparatively simple: we walk the data structure described above and &amp;ldquo;crunch&amp;rdquo; the component parts. If a node is a table lookup, we perform that lookup (or fail). If a node is a mathematical expression, we evaluate that expression.&lt;/p>
&lt;p>In practice this means translating &lt;code>MINUS(A4, A3)&lt;/code> -&amp;gt; &lt;code>3.4 - 1.2&lt;/code> -&amp;gt; &lt;code>2.2&lt;/code>. This is relatively &lt;a href="https://github.com/ambuc/latis/blob/master/src/formula/evaluator.h">simple&lt;/a>.&lt;/p>
&lt;h2 id="todoambuc-write-about-the-mvc-part">TODO(ambuc): Write about the MVC part.&lt;/h2></content></item><item><title>Rendering the Julia set in Golang</title><link>https://jbuckland.com/blog/math-julia/</link><pubDate>Sun, 17 Nov 2019 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/math-julia/</guid><description>Github https://gist.github.com/ambuc/a36d007d177c7dd90153dc9ab174ee12 Introduction In anticipation of Advent of Code 2019 I decided to brush up on http://golang.org/. I had written a crypto (codebreaking, not currency) library in Go in 2016 but I didn&amp;rsquo;t remember much of it.
The Julia set The Julia set is a fractal. It can be rendered by, for each point $(x,y)$ in a grid (ideally centered around $(0,0)$), taking the complex number $z = x + iy$ and applying $f_c(z) = z^2 + c$ to it repeatedly until it escapes some threshold from the origin.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">Github&lt;/th>
&lt;td> &lt;a href="https://gist.github.com/ambuc/a36d007d177c7dd90153dc9ab174ee12">https://gist.github.com/ambuc/a36d007d177c7dd90153dc9ab174ee12&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>In anticipation of &lt;a href="https://adventofcode.com/">Advent of Code 2019&lt;/a> I decided to brush up on &lt;a href="Golang">http://golang.org/&lt;/a>. I had written a crypto (codebreaking, not currency) library &lt;a href="https://github.com/ambuc/cryptgo">in Go&lt;/a> in 2016 but I didn&amp;rsquo;t remember much of it.&lt;/p>
&lt;h2 id="the-julia-set">The Julia set&lt;/h2>
&lt;p>The &lt;a href="https://en.wikipedia.org/wiki/Julia_set">Julia set&lt;/a> is a fractal. It can be rendered by, for each point $(x,y)$ in a grid (ideally centered around $(0,0)$), taking the complex number $z = x + iy$ and applying $f_c(z) = z^2 + c$ to it repeatedly until it escapes some threshold from the origin. The precise values used are variable and somewhat open to artistic interpretation; in the code below, we use $c = -0.8 + 0.156i$ and a threshold of $1.9$.&lt;/p>
&lt;p>Here is a gif of the lower-right-hand-quadrant of the Julia set where each frame has one more iteration of $f_c(z)$ than the previous one.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/anim.gif" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>And here is the code which produces it:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="589624713" type="checkbox" />
&lt;label for="589624713">
&lt;span class="collapsable-code__language">go&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-go" >&lt;code>
package main
// Renders a series of julia_n.pngs which can be zipped together into a gif.
//
// Example usage:
// go run julia.go
// convert /tmp/julia*.png -loop 0 animation.gif
import (
&amp;#34;fmt&amp;#34;
&amp;#34;github.com/fogleman/gg&amp;#34;
&amp;#34;image/color&amp;#34;
&amp;#34;math&amp;#34;
&amp;#34;math/cmplx&amp;#34;
)
const (
// Variables for the Julia set.
kConst complex128 = -0.8 &amp;#43; 0.156i
kThreshold float64 = 1.9
// The scaling factor between pixels and floating point coordinates. A higher
// number means a more zoomed-in image.
kScaling float64 = 600.0
// The dimensions of the image.
kDim int = 500
// The number of frames to render at the most.
kSteps int = 50
)
// Computes a fractional |mu|-value given the integer number of steps |i| and
// the complex value |z|.
func Mu(i int, z complex128) float64 {
return float64(i) &amp;#43; 1.0 - math.Log(math.Log(cmplx.Abs(z)))/math.Log(2.0)
}
// Computes the shade for each pixel in a 2D array and returns the maximum
// mu-value (shade analogue) encountered during the aforementioned process.
func Compute(pixels *[kDim][kDim]float64, steps int) float64 {
var max_mu float64 = 0.0
for x := 0; x &amp;lt; kDim; x&amp;#43;&amp;#43; {
a := float64(x) / kScaling
for y := 0; y &amp;lt; kDim; y&amp;#43;&amp;#43; {
b := float64(y) / kScaling
z := complex(a, b)
for i := 0; i &amp;lt; steps; i&amp;#43;&amp;#43; {
z = cmplx.Pow(z, 2) &amp;#43; kConst
if cmplx.Abs(z) &amp;gt; kThreshold {
var mu float64 = Mu(i, z)
if mu &amp;gt; max_mu {
max_mu = mu
}
pixels[x][y] = mu
break
}
}
}
}
return max_mu
}
// Maps a floating-point number between 0 and 1 to a grayscale color between 0
// and 255.
func Shade(n float64) color.Color {
return color.Gray{uint8(n * (math.MaxInt8 - 1))}
}
// Renders a 2D array of pixels to a gg.Context object.
func Render(max_mu float64, pixels [kDim][kDim]float64) *gg.Context {
dc := gg.NewContext(kDim, kDim)
for x := 0; x &amp;lt; kDim; x&amp;#43;&amp;#43; {
for y := 0; y &amp;lt; kDim; y&amp;#43;&amp;#43; {
dc.SetColor(Shade(pixels[x][y] / max_mu))
dc.SetPixel(x, y)
}
}
dc.SetRGB(0, 0, 0)
dc.Fill()
return dc
}
func main() {
for f := 0; f &amp;lt; kSteps; f&amp;#43;&amp;#43; {
fmt.Printf(&amp;#34;Step %v\n&amp;#34;, f)
var pixels [kDim][kDim]float64
max_mu := Compute(&amp;amp;pixels, f /*steps*/)
Render(max_mu, pixels).SavePNG(fmt.Sprintf(&amp;#34;/tmp/julia_%v.png&amp;#34;, f))
}
}
&lt;/code>&lt;/pre>
&lt;/div></content></item><item><title>Writing a Quadtree library for Rust</title><link>https://jbuckland.com/blog/library-quadtree/</link><pubDate>Sat, 06 Apr 2019 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/library-quadtree/</guid><description>App https://crates.io/crates/quadtree_rs Github http://github.com/ambuc/quadtree Introduction I wrote and published a quadtree crate for Rust. It is published on crates.io and has docs on docs.rs.
What is a quadtree? A quadtree is a tree data structure in which each internal node has exactly four children. Quadtrees are the two-dimensional analog of octrees and are most often used to partition a two-dimensional space by recursively subdividing it into four quadrants or regions.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">App&lt;/th>
&lt;td> &lt;a href="https://crates.io/crates/quadtree_rs">https://crates.io/crates/quadtree_rs&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;th style="text-align: right;">Github&lt;/th>
&lt;td> &lt;a href="http://github.com/ambuc/quadtree">http://github.com/ambuc/quadtree&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>I wrote and published a &lt;a href="https://en.wikipedia.org/wiki/Quadtree">quadtree&lt;/a> crate for Rust. It is published on &lt;a href="https://crates.io/crates/quadtree_rs">crates.io&lt;/a> and has docs on &lt;a href="https://docs.rs/quadtree_rs/0.1.2/quadtree_rs/">docs.rs&lt;/a>.&lt;/p>
&lt;h3 id="what-is-a-quadtree">What is a quadtree?&lt;/h3>
&lt;blockquote>
&lt;p>A quadtree is a tree data structure in which each internal node has exactly four children. Quadtrees are the two-dimensional analog of octrees and are most often used to partition a two-dimensional space by recursively subdividing it into four quadrants or regions. The data associated with a leaf cell varies by application, but the leaf cell represents a &amp;ldquo;unit of interesting spatial information&amp;rdquo;. - &lt;a href="https://en.wikipedia.org/wiki/Quadtree">Wikipedia&lt;/a>&lt;/p>
&lt;/blockquote>
&lt;figure class="center" >
&lt;img src="https://upload.wikimedia.org/wikipedia/commons/thumb/8/8b/Point_quadtree.svg/240px-Point_quadtree.svg.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;h3 id="what-is-a-quadtree-good-for">What is a quadtree good for?&lt;/h3>
&lt;p>For graphics, visualization, and other applications which require spatial information at a variable density, quadtrees are a good solution to the problem of storing high-density location data without paying the cost of uniformly high-density partitioning. As always, a picture says a thousand words:&lt;/p>
&lt;figure class="center" >
&lt;img src="https://www.researchgate.net/profile/Marco_Sortino/publication/257444716/figure/fig1/AS:297523286691840@1447946487323/Comparison-between-Raster-and-Quadtree-representation-of-a-complex-shape.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;h3 id="why-did-i-write-a-quadtree-library">Why did I write a quadtree library?&lt;/h3>
&lt;p>I found a &lt;a href="https://crates.io/search?q=quadtree">number&lt;/a> of quadtree implementations on &lt;a href="https://crates.io/">crates.io&lt;/a>. Some of them were simply too outdated to work with modern Rust, some of them were too gamedev-specific, and some were beyond my needs.&lt;/p>
&lt;p>Specifically, I wanted to be able to&lt;/p>
&lt;ul>
&lt;li>insert both points and regions,&lt;/li>
&lt;li>insert mutliple overlapping regions in 2d space,&lt;/li>
&lt;li>query by object handle and mutate the value (but not the location) in-place, and&lt;/li>
&lt;li>later query a region and find all intersecting results.&lt;/li>
&lt;/ul>
&lt;p>No library I found supported all these requirements.&lt;/p>
&lt;h2 id="implementation">Implementation&lt;/h2>
&lt;p>The solution implemented in &lt;a href="https://crates.io/crates/quadtree_rs">&lt;code>quadtree_rs&lt;/code>&lt;/a> makes a few compromises.&lt;/p>
&lt;p>First, value/region associations are cheap to insert, cheap to query, and expensive to delete. This is ideal for gamedev, where a 2d map is constructed once and then rendered frequently (such as on a per-frame basis).&lt;/p>
&lt;p>Second, regions are stored directly at the levels which describe them.&lt;/p>
&lt;p>Here is what I mean: in a quadtree with &lt;em>buckets&lt;/em> (such as that illustrated in the &lt;a href="https://en.wikipedia.org/wiki/File:Point_quadtree.svg">Wikipedia article&lt;/a>, points are bucketed, i.e a single node can have up to &lt;code>n&lt;/code> values before it is subdivided. (In the illustration, that bucket size appears to be one.)&lt;/p>
&lt;p>In the &lt;code>quadtree_rs&lt;/code> implementation, a handle (a key in some hashmap which points to the user-inserved value) is inserted at multiple points in the quadtree.&lt;/p>
&lt;h3 id="insert-operations">Insert operations&lt;/h3>
&lt;p>Let&amp;rsquo;s walk through inserting some points and regions to explore the design of &lt;code>quadtree_rs&lt;/code>.&lt;/p>
&lt;h4 id="inserting-a-point">Inserting a point&lt;/h4>
&lt;p>Associating a value with a point, which is represented by a region with dimensions 1x1, means traversing the full height of the quadtree.&lt;/p>
&lt;p>Let&amp;rsquo;s initialize a quadtree which covers the square region between $(0, 0)$ and $(4, 4)$. The left column will be a tree respresentation, the middle column will be a graphical representation, and the right column will be the data store.&lt;/p>
&lt;pre tabindex="0">&lt;code>tree graphical data store
==== ========= ==========
(0,0)-&amp;gt;4x4 +---+---+---+---+
| |
+ +
| |
+ +
| |
+ +
| |
+---+---+---+---+
&lt;/code>&lt;/pre>&lt;p>Now let&amp;rsquo;s insert the value &lt;code>'a'&lt;/code> at the point $(0, 0)$. In &lt;code>quadtree_rs&lt;/code>, points are regions with dimensions &lt;code>1x1&lt;/code>.&lt;/p>
&lt;p>We subdivide the quadtree twice. Inserting a point means traversing the full &lt;em>depth&lt;/em> of the tree.&lt;/p>
&lt;p>If the quadtree were of depth &lt;code>n&lt;/code>, the width and height of the region would be &lt;code>2^n&lt;/code>, and inserting a point would require &lt;code>n&lt;/code> traversal steps.&lt;/p>
&lt;p>We first insert &lt;code>'a'&lt;/code> into the data store, returning the handle &lt;code>001&lt;/code>. We then insert &lt;code>001&lt;/code> at each node in the tree which is wholly enclosed in the region &lt;code>(0, 0)-&amp;gt;1x1&lt;/code>.&lt;/p>
&lt;pre tabindex="0">&lt;code>(0,0)-&amp;gt;4x4 +---+---+---+---+ 001 &amp;lt;=&amp;gt; &amp;#39;a&amp;#39;
(0,0)-&amp;gt;2x2 |001| | |
(0,0)-&amp;gt;1x1 [001] +---+ + +
| | |
+---+---+ +
| |
+ +
| |
+---+---+---+---+
&lt;/code>&lt;/pre>&lt;h4 id="inserting-a-convenient-region">Inserting a convenient region&lt;/h4>
&lt;p>Now let&amp;rsquo;s insert the value &lt;code>'b'&lt;/code> at a rectangular region anchored at $(0, 0)$ with dimensions &lt;code>2x2&lt;/code>.&lt;/p>
&lt;p>We first insert &lt;code>'b'&lt;/code> into the data store, returning the handle &lt;code>002&lt;/code>. We then insert &lt;code>002&lt;/code> at each node in the tree which is wholly enclosed in the region &lt;code>(0, 0)-&amp;gt;2x2&lt;/code>.&lt;/p>
&lt;pre tabindex="0">&lt;code>(0,0)-&amp;gt;4x4 +---+---+---+---+ 001 &amp;lt;=&amp;gt; &amp;#39;a&amp;#39;
(0,0)-&amp;gt;2x2 [002] |001| | | 002 &amp;lt;=&amp;gt; &amp;#39;b&amp;#39;
(0,0)-&amp;gt;1x1 [001] +---+ + +
| 002| |
+---+---+ +
| |
+ +
| |
+---+---+---+---+
&lt;/code>&lt;/pre>&lt;p>Because there is a node which perfectly describes our insertion region, we only insert the handle &lt;code>002&lt;/code> once in the tree.&lt;/p>
&lt;h4 id="inserting-an-_inconvenient_-region">Inserting an &lt;em>inconvenient&lt;/em> region&lt;/h4>
&lt;p>What happens if we insert a region which cannot be wholly described by a leaf node?&lt;/p>
&lt;p>Let&amp;rsquo;s insert the value &lt;code>'c'&lt;/code> at a rectangular region anchored at $(0, 0)$ with dimensions &lt;code>3x3&lt;/code>.&lt;/p>
&lt;pre tabindex="0">&lt;code>(0,0)-&amp;gt;4x4 +---+---+---+---+ 001 &amp;lt;=&amp;gt; &amp;#39;a&amp;#39;
(0,0)-&amp;gt;2x2 [002,003] |001| |003| | 002 &amp;lt;=&amp;gt; &amp;#39;b&amp;#39;
(0,0)-&amp;gt;1x1 [001] +---+ +---+---+ 003 &amp;lt;=&amp;gt; &amp;#39;c&amp;#39;
(0,2)-&amp;gt;2x2 |002,003|003| |
(0,2)-&amp;gt;1x1 [003] +---+---+---+---+
(1,2)-&amp;gt;1x1 [003] |003|003|003| |
(2,0)-&amp;gt;2x2 +---+---+---+---+
(2,0)-&amp;gt;1x1 [003] | | | | |
(2,1)-&amp;gt;1x1 [003] +---+---+---+---+
(2,2)-&amp;gt;2x2
(2,2)-&amp;gt;1x1 [003]
&lt;/code>&lt;/pre>&lt;p>In the upper-left quadrant, the new region is described by the second-level leaf node at &lt;code>(0,0)-&amp;gt;2x2&lt;/code>, so the handle (&lt;code>003&lt;/code>) is inserted there.&lt;/p>
&lt;p>All other quadrants are subdivided until the division in question is either wholly within or without the insertion region.&lt;/p>
&lt;p>The handle type is lightweight, copyable, and can be inserted multiple times. A handle type is used so that the actual value need not be copyable.&lt;/p>
&lt;h3 id="schema-conclusions">Schema Conclusions&lt;/h3>
&lt;p>As a result of our handle-based design, insertion is fast and trivially parallelizable (although that optimization is unimplemented).&lt;/p>
&lt;p>Querying means describing the region, deduping the set of handles, and looking up each handle in the data store.&lt;/p>
&lt;p>Deleting means describing the region, collecting the set of affected handles, and deleting those handles (and their associated values) from the data store.&lt;/p>
&lt;h2 id="caveats">Caveats&lt;/h2>
&lt;p>There are a few problems with this implementation.&lt;/p>
&lt;p>First, areas must have positive, nonzero dimensions. To avoid runtime exceptions, we use the &lt;a href="https://docs.rs/derive_builder/">derive_builder&lt;/a> pattern to derive an &lt;code>AreaBuilder&lt;/code> type, which is somewhat verbose.&lt;/p>
&lt;p>Second, coordinate types are subject to integer overflow. A client of this library using &lt;code>u8&lt;/code> types may experience hard-to-debug saturation effects at the boundaries of their quadtree region.&lt;/p>
&lt;p>Third, the quadtree requires a block of memory and is subject to frequent reallocation. Thus a client of this library might want to describe the majority of their canvas up-front.&lt;/p>
&lt;h2 id="conclusions">Conclusions&lt;/h2>
&lt;p>If you use this library (or want to) but it is unsuited for your application, feel free to leave me a &lt;a href="https://github.com/ambuc/quadtree/issues">github issue&lt;/a>. I&amp;rsquo;m interested in actively maintaining this library.&lt;/p></content></item><item><title>Pipes in Haskell</title><link>https://jbuckland.com/blog/game-pipes/</link><pubDate>Tue, 23 Oct 2018 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/game-pipes/</guid><description>Github http://github.com/ambuc/pipes Introduction This project remakes the game Pipe Mania in Haskell using Brick, a declarative terminal user interface library and box-drawing characters, which are a form of semigraphics for drawing lines and rectangles. Pipe Mania comes in many forms, such as an updated Metro design, a vintage Windows 95 theme, and online flash game. Each variant is slightly different, so I didn&amp;rsquo;t really copy any one game in particular.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">Github&lt;/th>
&lt;td> &lt;a href="http://github.com/ambuc/pipes">http://github.com/ambuc/pipes&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;figure class="center" >
&lt;img src="images/anim.gif" style="border-radius: 8px;" />
&lt;/figure>
&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>This project remakes the game &lt;a href="https://en.wikipedia.org/wiki/Pipe_Mania">&lt;em>Pipe Mania&lt;/em>&lt;/a> in Haskell using &lt;a href="http://hackage.haskell.org/package/brick">Brick, a declarative terminal user interface library&lt;/a> and &lt;a href="http://hackage.haskell.org/package/brick">box-drawing characters&lt;/a>, which are a form of semigraphics for drawing lines and rectangles. &lt;em>Pipe Mania&lt;/em> comes in many forms, such as an updated &lt;a href="https://store-images.s-microsoft.com/image/apps.50223.9007199266530569.85ca83ac-8716-441b-ac8f-274104073aa0.f8d28383-fd79-4aab-a895-d0a3bcc26f0c?w=672&amp;amp;h=378&amp;amp;q=80&amp;amp;mode=letterbox&amp;amp;background=%23FFE4E4E4&amp;amp;format=jpg">Metro design&lt;/a>, a vintage &lt;a href="https://i.ytimg.com/vi/DkV8PqlMwNc/hqdefault.jpg">Windows 95 theme&lt;/a>, and &lt;a href="https://img-hws.pog.com/cloud/y8-thumbs/30894/big.jpg">online flash game&lt;/a>. Each variant is slightly different, so I didn&amp;rsquo;t really copy any one game in particular.&lt;/p>
&lt;figure class="center" >
&lt;img src="https://store-images.s-microsoft.com/image/apps.50223.9007199266530569.85ca83ac-8716-441b-ac8f-274104073aa0.f8d28383-fd79-4aab-a895-d0a3bcc26f0c?w=672&amp;amp;h=378&amp;amp;q=80&amp;amp;mode=letterbox&amp;amp;background=%23FFE4E4E4&amp;amp;format=jpg" style="border-radius: 8px;" />
&lt;/figure>
&lt;figure class="center" >
&lt;img src="https://i.ytimg.com/vi/DkV8PqlMwNc/hqdefault.jpg" style="border-radius: 8px;" />
&lt;/figure>
&lt;figure class="center" >
&lt;img src="https://img-hws.pog.com/cloud/y8-thumbs/30894/big.jpg" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>You can browse the source at &lt;a href="https://github.com/ambuc/pipes">github.com/ambuc/pipes&lt;/a>, but this post will be a short walkthrough of the most interesting parts of its development.&lt;/p>
&lt;h2 id="graphics">Graphics&lt;/h2>
&lt;p>&lt;em>Pipes&lt;/em> runs in the terminal, consumes keyboard events, and redraws a screen full of Unicode characters every frame. &lt;a href="https://en.wikipedia.org/wiki/Box_Drawing">Box Drawing&lt;/a> characters are used to render empty/full, connected/isolated pipes at all angles and thicknesses. These characters have been around since v1.0.0 (&lt;a href="https://www.unicode.org/history/publicationdates.html">October 1991&lt;/a>) and are commonly used for drawing terminal interfaces such as &lt;a href="https://en.wikipedia.org/wiki/Tmux">&lt;code>tmux&lt;/code>&lt;/a>.&lt;/p>
&lt;p>How do we consume a tile and decide which character to draw? In our case, we have more data than which legs are absent or present &amp;ndash; we want to have control over each of the four limbs, and we want to dictate whether that limb is absent, present, or present and bold.&lt;/p>
&lt;h3 id="braille">Braille&lt;/h3>
&lt;p>In Unicode, &lt;a href="https://en.wikipedia.org/wiki/Braille_Patterns">Braille patterns&lt;/a> are encoded in a sensible way. There are typically six dots, but there can be as many as eight. Those dots are indexed like so:&lt;/p>
&lt;pre>&lt;code>Indexing Base 16
======== =========
(1) (4) ( 1) ( 8)
(2) (5) ( 2) (10)
(3) (6) ( 4) (20)
(7) (8) (40) (80)
&lt;/code>&lt;/pre>
&lt;p>So, for example, the Unicode position of the character ⠓ (which has dots 1, 2, and 5 raised) can be computed by taking the sum of the hexidecimal powers of two which correspond with the desired inputs, plus some offset, like so:&lt;/p>
&lt;p>$$[1,2,5] \rightarrow \Sigma [1_{16},2_{16},10_{16}] \rightarrow 13_{16}
\rightarrow (2800_{16} + 13_{16}) \rightarrow \text{U+2813} \rightarrow
\boxed{⠓}$$&lt;/p>
&lt;p>This is only possible because the Unicode characters are ordered lexicographically:&lt;/p>
&lt;pre>&lt;code> Code Point Character Dots Binary
========== ========= ===== ========
U+2801 ⠁ 1 00000001
U+2802 ⠂ 2 00000010
U+2803 ⠃ 1,2 00000011
U+2804 ⠄ 3 00000100
&lt;/code>&lt;/pre>
&lt;h3 id="box-drawing-characters">Box-Drawing Characters&lt;/h3>
&lt;p>This is not the case for box-drawing characters, which are grouped primarily by shape, secondarily by rotation, and tertiarily by thickness.&lt;/p>
&lt;pre>&lt;code> 0 1 2 3 4 5 6 7 8 9 A B C D E F
U+250x ─ ━ │ ┃ ┄ ┅ ┆ ┇ ┈ ┉ ┊ ┋ ┌ ┍ ┎ ┏
U+251x ┐ ┑ ┒ ┓ └ ┕ ┖ ┗ ┘ ┙ ┚ ┛ ├ ┝ ┞ ┟
U+252x ┠ ┡ ┢ ┣ ┤ ┥ ┦ ┧ ┨ ┩ ┪ ┫ ┬ ┭ ┮ ┯
U+253x ┰ ┱ ┲ ┳ ┴ ┵ ┶ ┷ ┸ ┹ ┺ ┻ ┼ ┽ ┾ ┿
U+254x ╀ ╁ ╂ ╃ ╄ ╅ ╆ ╇ ╈ ╉ ╊ ╋ ╌ ╍ ╎ ╏
&lt;/code>&lt;/pre>
&lt;p>Given some input data like &lt;code>{n: bold, e: absent, s: bold, w: regular}&lt;/code>, what is our preferred schema? My answer was this:&lt;/p>
&lt;pre>&lt;code>N 012012012012012012012012012012012012012012012012012012012012012012012012012012012
E 0--1--2--0--1--2--0--1--2--0--1--2--0--1--2--0--1--2--0--1--2--0--1--2--0--1--2--
S 0--------1--------2--------0--------1--------2--------0--------1--------2--------
W 0--------------------------1--------------------------2--------------------------
╵╹╶└┖╺┕┗╷│╿┌├┞┍┝┡╻╽┃┎┟┠┏┢┣╴┘┚─┴┸╼┶┺┐┤┦┬┼╀┮┾╄┒┧┨┰╁╂┲╆╊╸┙┛╾┵┹━┷┻┑┥┩┭┽╃┯┿╇┓┪┫┱╅╉┳╈╋
&lt;/code>&lt;/pre>
&lt;p>So that we can convert our desired weights into an enum on $[0,1,2]$ and index into some hard-coded &lt;code>schema&lt;/code> string, like so:&lt;/p>
&lt;pre>&lt;code> 0 1 2
n: bold ==&amp;gt; . . X ==&amp;gt; 2 * 3^0
e: absent ==&amp;gt; X . . ==&amp;gt; 0 * 3^1
s: bold ==&amp;gt; . . X ==&amp;gt; 2 * 3^2
w: regular ==&amp;gt; . X . ==&amp;gt; + 1 * 3^3
----------
47 -&amp;gt; schema[47] -&amp;gt; ┨
&lt;/code>&lt;/pre>
&lt;p>So that&amp;rsquo;s how &lt;em>Pipes&lt;/em> renders tiles of various orientations/thicknesses/shapes.&lt;/p>
&lt;h2 id="animation">Animation&lt;/h2>
&lt;p>One of the goals of writing this game was that it would not only respond to cursor movement and rotation (the way all &lt;em>Pipes Mania&lt;/em>-style games do) but that the pipes which were connected to the drain, i.e. had water in them, would pulse gently.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/example-anim.gif" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>This animation relies on tweening, which uses heterogeneous characters like &lt;code>╼&lt;/code> and &lt;code>╾&lt;/code> in-between homogeneous characters like &lt;code>─&lt;/code> and &lt;code>━&lt;/code> to give the appearance of finer-resolution motion. Here is an example:&lt;/p>
&lt;pre>&lt;code>Horizontal Vertical
Locomotion Locomotion
========== =================
0 ─── 0 ½ 1 ½ 2 ½ 3 ½ 4
1 ╾── │ ╿ ┃ ╽ │ │ │ │ │
2 ━── │ │ │ ╿ ┃ ╽ │ │ │
3 ╼╾─ │ │ │ │ │ ╿ ┃ ╽ │
4 ─━─
5 ─╼╾
&lt;/code>&lt;/pre>
&lt;p>When we explore the connected graph of pipes, we mark each limb as being an inlet or an outlet and then generate the appropriate tile given both the distance from the tap and the current time. This lets us stagger the animation across the tile so that the water appears to flow in from the limb nearer to the tap and flow out from the others.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>&lt;a href="http://hackage.haskell.org/package/brick">Brick&lt;/a> is the perfect framework for a project like this. If you&amp;rsquo;re interested in playing with &lt;a href="https://github.com/ambuc/pipes">the code&lt;/a> (or even just playing the game), you can get started by installing &lt;a href="https://docs.haskellstack.org/en/stable/README/#how-to-install">Stack, the Haskell Tool Stack&lt;/a> and running:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="425138769" type="checkbox" />
&lt;label for="425138769">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
$ git@github.com:ambuc/pipes.git
$ cd pipes
$ stack build
$ stack exec pipes-exe
&lt;/code>&lt;/pre>
&lt;/div></content></item><item><title>OSC-Responsive 3D Graphics in Rust</title><link>https://jbuckland.com/blog/graphics-voxel/</link><pubDate>Fri, 02 Feb 2018 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/graphics-voxel/</guid><description>Github https://github.com/ambuc/voxel-preview Introduction As preparation for an upcoming hardware-intensive project involving an 8x8x8 LED cube display, We want a way to preview the behavior of the display remotely. Enter voxel-preview: like the intended physical display, the rendered cube listens over UDP for OSC packets and updates the voxels accordingly.
Requirements Rust and Cargo, ideally by way of Rustup send_osc for testing Installation voxel-preview can be downloaded, built, and run with cargo.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">Github&lt;/th>
&lt;td> &lt;a href="https://github.com/ambuc/voxel-preview">https://github.com/ambuc/voxel-preview&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>As preparation for an upcoming hardware-intensive project involving an &lt;a href="https://www.aliexpress.com/item/DIY-3D8-multicolor-mini-LED-light-display-Excellent-animation-3D-8-8x8x8-Electronic-Kits-Junior/32700909987.html">8x8x8 LED cube display&lt;/a>, We want a way to preview the behavior of the display remotely. Enter &lt;code>voxel-preview&lt;/code>: like the intended physical display, the rendered cube listens over UDP for &lt;a href="http://opensoundcontrol.org/">OSC packets&lt;/a> and updates the voxels accordingly.&lt;/p>
&lt;h3 id="requirements">Requirements&lt;/h3>
&lt;ul>
&lt;li>Rust and Cargo, ideally by way of &lt;a href="https://www.rustup.rs/">Rustup&lt;/a>&lt;/li>
&lt;li>&lt;a href="manpages.ubuntu.com/manpages/xenial/man1/send_osc.1.html">&lt;code>send_osc&lt;/code>&lt;/a> for testing&lt;/li>
&lt;/ul>
&lt;h3 id="installation">Installation&lt;/h3>
&lt;p>&lt;code>voxel-preview&lt;/code> can be downloaded, built, and run with &lt;code>cargo&lt;/code>.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="637519824" type="checkbox" />
&lt;label for="637519824">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
$ git clone https://github.com/ambuc/voxel-preview.git
$ cd voxel-preview
$ cargo build
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h3 id="usage">Usage&lt;/h3>
&lt;div class="collapsable-code">
&lt;input id="482396571" type="checkbox" />
&lt;label for="482396571">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
$ cargo run
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>At this point, an OpenGL window will pop up and show a slowly-revolving rainbow cube; the default dimensions are 8x8x8.&lt;/p>
&lt;figure class="center" >
&lt;img src="https://raw.githubusercontent.com/ambuc/voxel-preview/master/render.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>You can send individual packets with &lt;a href="http://manpages.ubuntu.com/manpages/xenial/man1/send_osc.1.html">&lt;code>send_osc&lt;/code>&lt;/a>. &lt;code>voxel-preview&lt;/code> listens on &lt;code>127.0.0.1:1234&lt;/code> by default.&lt;/p>
&lt;h3 id="schema">Schema&lt;/h3>
&lt;p>The schema is:&lt;/p>
&lt;pre>&lt;code>// port x y z r g b
send_osc 1234 /0/2/4 0.0 0.5 1.0
&lt;/code>&lt;/pre>
&lt;p>where &lt;code>x&lt;/code>, &lt;code>y&lt;/code>, and &lt;code>z&lt;/code> are the xyz coordinates of the voxel to color, and &lt;code>r&lt;/code>, &lt;code>g&lt;/code>, and &lt;code>b&lt;/code> are the rgb coordinates thereof.&lt;/p>
&lt;p>&lt;code>voxel-preview&lt;/code> will drop malformed packets within reason (&lt;code>x&lt;/code> &amp;gt; &lt;code>CUBE_WIDTH&lt;/code>, or &lt;code>r&lt;/code> &amp;gt; 1.0, for example), but it&amp;rsquo;s not &lt;em>that&lt;/em> smart.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>This isn&amp;rsquo;t so much a project as a tool. We aim to write a controller which interfaces nicely with something like &lt;a href="https://itunes.apple.com/us/app/touchosc/id288120394?mt=8">TouchOSC&lt;/a>, which will ultimately serve as a sort of platform for a series of abstract 3D games / toys / visualizations over the coming weeks or months.&lt;/p></content></item><item><title>Writing a Solitaire TUI with Lenses and Brick</title><link>https://jbuckland.com/blog/game-solitaire/</link><pubDate>Sat, 02 Dec 2017 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/game-solitaire/</guid><description>Github http://github.com/ambuc/solitaire ┌───────────── Solitaire ──────────────┐ │┌──┐│┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐│┌ ┐│ Score: 0 ││λ=││┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐│7♠││ │ │└──┘│┌──┐┌──┐┌──┐┌──┐┌──┐│K♥│└──┘│└ ┘│ Moves: 0 │┌──┐│┌──┐┌──┐┌──┐┌──┐│J♣│└──┘ │┌ ┐│ ││3♠││┌──┐┌──┐┌──┐│6♦│└──┘ │ │ [New] │┌──┐│┌──┐┌──┐│9♣│└──┘ │└ ┘│ ││3♥││┌──┐│Q♠│└──┘ │┌ ┐│ [Undo] │┌──┐││4♠│└──┘ │ │ ││7♦││└──┘ │└ ┘│ │└──┘│ │┌ ┐│ │ │ │ │ │ │ │└ ┘│ │ │ │ │ └──────────────────────────────────────┘ I&amp;rsquo;d wanted to write an implementation of Solitaire a.k.a. Patience, Klondike, etc in Haskell ever since I learned about brick, a library for programming terminal user interfaces (TUIs).</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">Github&lt;/th>
&lt;td> &lt;a href="http://github.com/ambuc/solitaire">http://github.com/ambuc/solitaire&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;pre>&lt;code>┌───────────── Solitaire ──────────────┐
│┌──┐│┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐│┌ ┐│ Score: 0
││λ=││┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐│7♠││ │
│└──┘│┌──┐┌──┐┌──┐┌──┐┌──┐│K♥│└──┘│└ ┘│ Moves: 0
│┌──┐│┌──┐┌──┐┌──┐┌──┐│J♣│└──┘ │┌ ┐│
││3♠││┌──┐┌──┐┌──┐│6♦│└──┘ │ │ [New]
│┌──┐│┌──┐┌──┐│9♣│└──┘ │└ ┘│
││3♥││┌──┐│Q♠│└──┘ │┌ ┐│ [Undo]
│┌──┐││4♠│└──┘ │ │
││7♦││└──┘ │└ ┘│
│└──┘│ │┌ ┐│
│ │ │ │
│ │ │└ ┘│
│ │ │ │
└──────────────────────────────────────┘
&lt;/code>&lt;/pre>
&lt;p>I&amp;rsquo;d wanted to write an implementation of &lt;a href="https://en.wikipedia.org/wiki/Patience_(game)">Solitaire&lt;/a> a.k.a. Patience, Klondike, etc in Haskell ever since I learned about &lt;a href="https://hackage.haskell.org/package/brick">brick&lt;/a>, a library for programming terminal user interfaces (TUIs). I liked it because, as &lt;a href="https://github.com/jtdaugherty/brick/blob/master/README.md">the docs&lt;/a> say, it&lt;/p>
&lt;blockquote>
&lt;p>&amp;hellip;exposes a declarative API. Unlike most GUI toolkits which require you to write a long and tedious sequence of &amp;ldquo;create a widget, now bind an event handler&amp;rdquo;, &lt;code>brick&lt;/code> just requires you to describe your interface using a set of declarative combinators. Then you provide a function to transform your application state when input or other kinds of events arrive.&lt;/p>
&lt;/blockquote>
&lt;p>The other component of this project involved learning about &lt;a href="https://hackage.haskell.org/package/lens">lenses&lt;/a>. Lenses are a &lt;a href="https://wiki.haskell.org/Template_Haskell">Template Haskell&lt;/a> solution to &lt;a href="https://ghc.haskell.org/trac/ghc/wiki/Records">the record problem&lt;/a>, which concerns the difficulty of reading from, writing to, and editing in-place deeply-nested record variables. Although Haskell is an immutable language, sometimes in-place modification is simply too convenient to abandon. Lenses are an elegant set of combinators for working around this.&lt;/p>
&lt;h2 id="application-overview">Application Overview&lt;/h2>
&lt;p>This essay will be a high-level architecture of the game, but the code itself is decently commented, and only spans &lt;a href="https://github.com/ambuc/solitaire/blob/master/app/Main.hs">one &lt;code>Main.hs&lt;/code>&lt;/a> and &lt;a href="https://github.com/ambuc/solitaire/blob/master/src/CardTypes.hs">four&lt;/a> &lt;a href="https://github.com/ambuc/solitaire/blob/master/src/Movement.hs">small&lt;/a> &lt;a href="https://github.com/ambuc/solitaire/blob/master/src/Render.hs">helper&lt;/a> &lt;a href="https://github.com/ambuc/solitaire/blob/master/src/Utils.hs">libaries&lt;/a>.&lt;/p>
&lt;h3 id="brick">Brick&lt;/h3>
&lt;p>As discussed above, &lt;code>brick&lt;/code> lets us define&lt;/p>
&lt;ul>
&lt;li>an &lt;code>app :: App State Event ()&lt;/code> application state object, and&lt;/li>
&lt;li>an &lt;code>appEvent :: State -&amp;gt; Event e -&amp;gt; EventM () (Next State)&lt;/code> event handler
and that&amp;rsquo;s almost entirely it. There&amp;rsquo;s a bit more business for styling and click region detection, but the core of the game takex place in the event loop within &lt;code>appEvent&lt;/code>.&lt;/li>
&lt;/ul>
&lt;div class="collapsable-code">
&lt;input id="765438192" type="checkbox" />
&lt;label for="765438192">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
appEvent :: GSt -&amp;gt; BrickEvent Ext e -&amp;gt; EventM Ext (Next GSt)
appEvent s (VtyEvent e) = case e of
Vty.EvKey Vty.KEsc [] -&amp;gt; halt s
Vty.EvKey (Vty.KChar &amp;#39;q&amp;#39;) [] -&amp;gt; halt s
Vty.EvMouseDown col row _ _ -&amp;gt; do
extents &amp;lt;- map extentName &amp;lt;$&amp;gt; findClickedExtents (col, row)
case extents of
[ActionX New] -&amp;gt; continue $ newGame s
[ActionX Undo] -&amp;gt; continue $ undoMove s
_ -&amp;gt; if hasWon s
then continue s
else continue $ doMove s extents
_ -&amp;gt; continue s
appEvent s _ = continue s
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>In the above, some keys &lt;code>halt&lt;/code> the game, but most of them &lt;code>continue&lt;/code> the game either&lt;/p>
&lt;ul>
&lt;li>with the state &lt;code>s&lt;/code> as-is, or&lt;/li>
&lt;li>with the state &lt;code>s&lt;/code> modified by some function (&lt;code>newGame&lt;/code>, &lt;code>undoMove&lt;/code>, or
&lt;code>doMove&lt;/code>).&lt;/li>
&lt;/ul>
&lt;h3 id="rules-of-solitaire">Rules of Solitaire&lt;/h3>
&lt;p>Before we continue let&amp;rsquo;s just speak briefly about Solitaire.&lt;/p>
&lt;pre>&lt;code>+-------+----------------------+
| Stock | | |
+-------+ Tableau | Foundation |
| Waste | | |
+-------+----------------------+
&lt;/code>&lt;/pre>
&lt;ul>
&lt;li>Cards start either facedown in the &lt;code>stock&lt;/code> or in seven piles of lengths 1, 2,.. in the &lt;code>tableau&lt;/code>.&lt;/li>
&lt;li>The stock is always facedown, but can be dealt three at a time to the&lt;/li>
&lt;li>The piles in the &lt;code>tableau&lt;/code> are splayed downwards, and start with only their top card visible.&lt;/li>
&lt;li>Nothing starts in the &lt;code>foundation&lt;/code>, but cards can accumulate there face-up.&lt;/li>
&lt;/ul>
&lt;p>Cards can be moved like so:&lt;/p>
&lt;pre>&lt;code>+-------+----------------------+
| Stock | | |
| ^ | &amp;lt;-- |
+-- | --+ Tableau | Foundation |
| v | --&amp;gt; |
| Waste -&amp;gt; | |
+-------+----------------------+
&lt;/code>&lt;/pre>
&lt;p>Some more rules:&lt;/p>
&lt;ul>
&lt;li>in the &lt;code>tableau&lt;/code> only a King can go on an empty pile, but any card can go on any other card as long as it has a different color and is of exactly one rank less.&lt;/li>
&lt;li>in the &lt;code>foundation&lt;/code> only an Ace can go on an empty pile, and any card can go on a foundation pile as long as it matches the base suit and is of exactly one rank more.&lt;/li>
&lt;/ul>
&lt;p>I&amp;rsquo;m not sure Solitaire is a very interesting game to play, but abstracting the core ideas of cards, displaycards, piles, lists of piles, and operations between them was a lot of fun.&lt;/p>
&lt;h2 id="custom-types">Custom Types&lt;/h2>
&lt;p>I think Haskell is fairly readable, so it might be best to just &lt;a href="https://github.com/ambuc/solitaire/blob/master/src/CardTypes.hs">look at the &lt;code>CardTypes.hs&lt;/code> source&lt;/a>. But just as a quick overview, we define:&lt;/p>
&lt;ul>
&lt;li>a &lt;code>Card&lt;/code> (rank and suit),&lt;/li>
&lt;li>a &lt;code>DCard&lt;/code>, a display-card which wraps a &lt;code>Card&lt;/code> and contains a preference for being displayed face-up or face-down&lt;/li>
&lt;li>a &lt;code>Pile&lt;/code>, which is a list of &lt;code>DCard&lt;/code>s with an opinion on what sort of card can go at its base (for example, only a King, or only an Ace) as well as a preference for its cards being displayed stacked or splayed out.&lt;/li>
&lt;li>a &lt;code>GSt&lt;/code>, a game state which wraps the stock, waste, tableau, and foundation, as well as containing the current score, the elapsed move count, a random seed, and a history of prior fields and scores.&lt;/li>
&lt;/ul>
&lt;h3 id="show-instances">&lt;code>Show&lt;/code> instances&lt;/h3>
&lt;p>We can make our own type instance of a few of the above custom typeclasses by defining what it means to &lt;code>Show&lt;/code> a &lt;code>Rank&lt;/code> or &lt;code>Suit&lt;/code>.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="128396574" type="checkbox" />
&lt;label for="128396574">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
instance Show Rank where
show RA = &amp;#34;A&amp;#34;;
show R2 = &amp;#34;2&amp;#34;; show R3 = &amp;#34;3&amp;#34;; show R4 = &amp;#34;4&amp;#34;; show R5 = &amp;#34;5&amp;#34;;
show R6 = &amp;#34;6&amp;#34;; show R7 = &amp;#34;7&amp;#34;; show R8 = &amp;#34;8&amp;#34;; show R9 = &amp;#34;9&amp;#34;;
show R10 = [toEnum 0x2491] :: String; -- unicode ligature for one-char width
show RJ = &amp;#34;J&amp;#34;; show RQ = &amp;#34;Q&amp;#34;; show RK = &amp;#34;K&amp;#34;;
instance Show Suit where
show Spade = [toEnum 0x2660] :: String -- unicode characters for suits
show Heart = [toEnum 0x2665] :: String
show Diamond = [toEnum 0x2666] :: String
show Club = [toEnum 0x2663] :: String
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h3 id="lenses-101">Lenses 101&lt;/h3>
&lt;p>We want to define our record fields with underscores like so:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="827913645" type="checkbox" />
&lt;label for="827913645">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
data DCard = DCard { _card :: Card
, _facedir :: FaceDir }
deriving (Eq, Show, Ord)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>So that the &lt;code>Lens&lt;/code> library can, at compile time, create functions like &lt;code>card&lt;/code> or &lt;code>facedir&lt;/code> which can be called on &lt;code>DCard&lt;/code> objects, like so:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="872631495" type="checkbox" />
&lt;label for="872631495">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
&amp;gt; let dc = DCard { _card = Card RA Club
, _facedir = FaceDown
}
&amp;gt; dc
DCard { _card = Card A ♣, _facedir = FaceDown }
&amp;gt; dc ^. card
Card A ♣
&amp;gt; dc &amp;amp; facedir .~ FaceUp
DCard { _card = Card A ♣, _facedir = FaceUp }
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>where &lt;code>(^.)&lt;/code> is a getter and &lt;code>.~&lt;/code> is a setter (sorta). For more read the &lt;a href="https://hackage.haskell.org/package/lens-tutorial-1.0.3/docs/Control-Lens-Tutorial.html">lens tutorial&lt;/a>.&lt;/p>
&lt;p>By the same convention, a deeply-nested object could be accessed with&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="438517926" type="checkbox" />
&lt;label for="438517926">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
obj &amp;amp; fieldOuter . fieldInner . fieldVeryInner %~ mutationFn
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>which makes it super easy for us to just pass around the &lt;code>Field&lt;/code> or the &lt;code>GSt&lt;/code> gamestate and modify it at any level. Thanks, Lenses!&lt;/p>
&lt;h2 id="output">Output&lt;/h2>
&lt;p>Just as we wrote a set of abstract data types above which can be composed into flexible &lt;code>Pile&lt;/code>s, etc., we want to write a set of abstract render functions which can be composed to draw a &lt;code>Pile&lt;/code>, or a &lt;code>DCard&lt;/code>, or whatever. Brick wants us to define our &lt;code>app&lt;/code> like so:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="615839247" type="checkbox" />
&lt;label for="615839247">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
app = App { appDraw = drawUI
, ...
}
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>where &lt;code>drawUI :: GSt -&amp;gt; [Widget ()]&lt;/code> handles every part of the program, from the field to the score counters. It is a pure function of the game state and doesn&amp;rsquo;t need callbacks or promises or event handlers at all, &lt;em>except&lt;/em> that we can name certain regions so that, when clicked, Brick handles a &lt;code>BrickEvent Ext (Vty.EvMouseDown col row _ _)&lt;/code> where &lt;code>extents = map extentName $ findClickedExtents (col,row)&lt;/code> lets us interpet the observed column and row and get a list of clicked extents. We can report a named extent by wrapping it in &lt;code>reporteExtent ExtentName&lt;/code>.&lt;/p>
&lt;p>Brick provides some primitive combinators for stacking widgets (rectangles) next to (&lt;code>&amp;lt;+&amp;gt;&lt;/code>) or above (&lt;code>&amp;lt;=&amp;gt;&lt;/code>) each other, as well as some primitive widgets for displaying text (&lt;code>str :: String -&amp;gt; Widget ()&lt;/code>), wrapping widgets in styled borders, ( &lt;code>withBorderStyle unicodeRounded $ borderWithLabel (str &amp;quot;title&amp;quot;) $ myWidget&lt;/code>), etc. As before, the code is &lt;a href="https://github.com/ambuc/solitaire/blob/master/src/Render.hs">fairly readable&lt;/a>, so I&amp;rsquo;ll just cover some interesting mechanics here briefly before moving on.&lt;/p>
&lt;h3 id="custom-borderstyles">Custom Borderstyles&lt;/h3>
&lt;p>A typical card looks like this: a string &lt;code>7♦&lt;/code> wrapped in a &lt;code>unicodeRounded&lt;/code> border:&lt;/p>
&lt;pre>&lt;code>┌──┐
│7♦│
└──┘
&lt;/code>&lt;/pre>
&lt;p>but we want to be able to draw custom border too, in the case of our empty piles:&lt;/p>
&lt;pre>&lt;code>┌ ┐
└ ┘
&lt;/code>&lt;/pre>
&lt;p>Brick lets us define custom borderstyles like so:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="238469571" type="checkbox" />
&lt;label for="238469571">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
rrGhost :: Widget Ext -- renders a &amp;#39;ghost&amp;#39; card with no content
rrGhost = withBorderStyle ghostRounded $ border $ str &amp;#34; &amp;#34;
where ghostRounded = BorderStyle
{ bsIntersectFull = toEnum 0x253C
, bsCornerTL = toEnum 0x256D , bsCornerTR = toEnum 0x256E
, bsCornerBR = toEnum 0x256F , bsCornerBL = toEnum 0x2570
, bsIntersectL = toEnum 0x251C , bsIntersectR = toEnum 0x2524
, bsIntersectT = toEnum 0x252C , bsIntersectB = toEnum 0x2534
, bsHorizontal = &amp;#39; &amp;#39; , bsVertical = &amp;#39; &amp;#39;
}
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Where those unicode &lt;code>0x....&lt;/code> codes are just various box-drawing characters, and the &lt;code>bsVertical&lt;/code> and &lt;code>bsHorizontal&lt;/code> codes are (intentionally) spaces.&lt;/p>
&lt;h3 id="piles">Piles&lt;/h3>
&lt;p>Once we have a &lt;code>drawCard&lt;/code> function, we can stack the cards by cropping their bottom or right borders as necessary, with more of the card cropped if it is meant to be face-down than if it is meant to be face-up. For example,&lt;/p>
&lt;pre>&lt;code>stacked face-up | stacked face-down
┌──┐ │┌──┐
│3♥│ │┌──┐
┌──┐ ││4♠│
│7♦│ │└──┘
└──┘ │
&lt;/code>&lt;/pre>
&lt;p>Otherwise, &lt;code>Render.hs&lt;/code> is mostly composing existing Brick primitives in easy ways.&lt;/p>
&lt;h2 id="input">Input&lt;/h2>
&lt;p>OK, here&amp;rsquo;s where the complexity of the game begins to shine through.&lt;/p>
&lt;p>In computer solitaire, we typically expect to be able to click on a card and it will, it possible, move to the next open position. Thus, whenever a card gets clicked on, we should be able to figure out the next valid pile it could be moved to and move it there.&lt;/p>
&lt;p>We can do so with lenses &amp;ndash; and we can define our own lenses with independent getters and setters to make doing so easier.&lt;/p>
&lt;p>For example, here&amp;rsquo;s a &lt;code>lens&lt;/code> which writes to or reads from the stock. Its type is &lt;code>stockL :: Lens' Field [DCard]&lt;/code>, and it either reads and returns a list of &lt;code>DCard&lt;/code>s or accepts a list of &lt;code>DCard&lt;/code>s and writes them to the stock. The syntax is&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="845172936" type="checkbox" />
&lt;label for="845172936">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
fooLens = Lens (anonymous getter) (anonymous setter)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;div class="collapsable-code">
&lt;input id="235864971" type="checkbox" />
&lt;label for="235864971">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
-- creates a lens from the field to the stock
-- operates on lists of displaycards
stockL :: Lens&amp;#39; Field [DCard] --all
stockL = lens (\f -&amp;gt; f ^. stock.cards)
(\f dcs -&amp;gt; f &amp;amp; stock.cards .~ dcs)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Actually, let&amp;rsquo;s use this opportunity to flip the cards at read/write-time. We can use &lt;code>each&lt;/code> to iterate over each of the returned or processed objects and apply some transformation with &lt;code>(.~)&lt;/code>.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="592867431" type="checkbox" />
&lt;label for="592867431">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
stockL = lens (\f -&amp;gt; f ^. stock.cards &amp;amp; each.facedir .~ FaceUp)
(\f dcs -&amp;gt; f &amp;amp; stock.cards .~ (dcs &amp;amp; each.facedir .~ FaceDown))
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Perfect. In &lt;a href="https://github.com/ambuc/solitaire/blob/master/src/Movement.hs">&lt;code>Movement.hs&lt;/code>&lt;/a> you can see custom lenses for the stock, the waste, as well as two lens generators for the tableau and foundation which instead &lt;em>a)&lt;/em> return Piles instead of &lt;code>[DCard]&lt;/code>s, and &lt;em>b)&lt;/em> accept an integer index for which tableau pile / foundation pile to return. They are of type &lt;code>tableLN :: Int -&amp;gt; Lens' Field Pile&lt;/code>, where &lt;code>N&lt;/code> is a convention for indexed generators.&lt;/p>
&lt;h3 id="domove">&lt;code>doMove&lt;/code>&lt;/h3>
&lt;p>Eventually we want to be able to, upon reading a list of &lt;code>extents&lt;/code> from a clicked region, continue with our game by calling&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="138265947" type="checkbox" />
&lt;label for="138265947">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
appEvent :: GSt -&amp;gt; BrickEvent Ext e -&amp;gt; EventM Ext (Next GSt)
appEvent s (VtyEvent e) = case e of
Vty.EvMouseDown col row _ _ -&amp;gt; do
extents &amp;lt;- map extentName &amp;lt;$&amp;gt; findClickedExtents (col, row)
case extents of
_ -&amp;gt; if hasWon s
then continue s
else continue $ doMove s extents
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>We&amp;rsquo;ll write &lt;code>hasWon :: GSt -&amp;gt; Bool&lt;/code> later, but for now let&amp;rsquo;s write &lt;code>doMove :: GSt -&amp;gt; [Ext] -&amp;gt; GSt&lt;/code>, which tries to move the clicked card and, if successful, returns a changed &lt;code>GSt&lt;/code> with incremented &lt;code>moves&lt;/code> ticker, mutated &lt;code>score&lt;/code>, and augmented &lt;code>history&lt;/code>.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="974213568" type="checkbox" />
&lt;label for="974213568">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
doMove :: GSt -&amp;gt; [Ext] -&amp;gt; GSt
doMove s exs = if wasChange
then s &amp;amp; field .~ newField
&amp;amp; history %~ ((oldField, oldScore):)
&amp;amp; score %~ scoreFn
&amp;amp; moves %~ succ
else s
where
wasChange = oldField /= newField
oldField = s ^. field
oldScore = s ^. score
(newField, scoreFn) = tryMove exs oldField
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Here we can see chained&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="324768915" type="checkbox" />
&lt;label for="324768915">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
foo &amp;amp; fieldA .~ newFieldA
&amp;amp; fieldB .~ newFieldb
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>operator chaining for the first time, which is pretty snazzy. Here, &lt;code>doMove&lt;/code> expects a &lt;code>tryMove&lt;/code> function which will return not just the new field, but a score mutator (+5, -10, &lt;code>id&lt;/code>, etc.).&lt;/p>
&lt;h3 id="extents">Extents&lt;/h3>
&lt;div class="collapsable-code">
&lt;input id="815673942" type="checkbox" />
&lt;label for="815673942">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
data Ext = StockX | WasteX | TableX | FoundX
| IdX Int | DCX DCard | ActionX Action
deriving (Eq, Show, Ord)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Before we write &lt;code>tryMove&lt;/code>, we should talk about Extents and what they look like in practice. They end up being lists of clicked extents where the innermost extents are first. We can wrap our stock, waste, etc. in &lt;code>StockX&lt;/code>, &lt;code>WasteX&lt;/code>, etc. extent labels, and we can report the &lt;code>DCard&lt;/code> directly with a &lt;code>DCX&lt;/code> wrapper. Later we&amp;rsquo;ll use the &lt;code>ActionX&lt;/code> wrapper to report something of type &lt;code>Action&lt;/code>.&lt;/p>
&lt;p>Finally, we can use &lt;code>IdX Int&lt;/code> as a wrapper for a row/col index.&lt;/p>
&lt;p>For now let&amp;rsquo;s write &lt;code>tryMove&lt;/code> by pattern-matching on the reported extents. Each region will have a different shape so we should be able to striate our regions fairly easily.&lt;/p>
&lt;ul>
&lt;li>&lt;code>[StockX]&lt;/code>: reporting just an empty &lt;code>StockX&lt;/code> region means there are no cards, so we should refresh it from the waste.&lt;/li>
&lt;li>&lt;code>[_, StockX]&lt;/code>: reporting a non-empty stock means we want to take three cards from the stock and move them to the waste.&lt;/li>
&lt;li>&lt;code>[DCX dc, IdX 0, WasteX]&lt;/code>: reporting from the top of the waste stack means we should try to move it. We don&amp;rsquo;t pattern-match on just any index in the waste since none of the others are actionable.&lt;/li>
&lt;li>&lt;code>[DCX dc, Idx row, FoundX]&lt;/code>: reporting from any row in the foundation means we should try and move its topmost card.&lt;/li>
&lt;li>&lt;code>[DCX dc, Idx row, Id col, TableX]&lt;/code>: reporting from the tableau means we expect a row and column index, which tells us where in the tableau structure to try and read from. Uniquely, we can read a card or a stack of cards at a time from the tableau and move them all as a unit, as long as the stack of cards doesn&amp;rsquo;t leave the tableau.&lt;/li>
&lt;/ul>
&lt;p>One more thing to do before we can write &lt;code>tryMove&lt;/code>:&lt;/p>
&lt;h3 id="movement-lenses">Movement Lenses&lt;/h3>
&lt;p>Eventually, we want to be able to write (pseudocode below):&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="179536842" type="checkbox" />
&lt;label for="179536842">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
process state =
if (canMove?)
then state &amp;amp; newLocation %~ (card:) -- add one or more cards
&amp;amp; oldLocation %~ (drop n) -- drop n cards
else state
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>these &lt;code>newLocation&lt;/code> and &lt;code>oldLocation&lt;/code> lenses will have to be deduced from context.&lt;/p>
&lt;p>Let&amp;rsquo;s get a list of our tableau and foundation lenses:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="543978216" type="checkbox" />
&lt;label for="543978216">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
inTableau :: Functor f0 =&amp;gt; [(Pile -&amp;gt; f0 Pile) -&amp;gt; Field -&amp;gt; f0 Field]
inTableau = map tableLN [0..6]
inFoundation :: Functor f0 =&amp;gt; [(Pile -&amp;gt; f0 Pile) -&amp;gt; Field -&amp;gt; f0 Field]
inFoundation = map foundLN [0..3]
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>These are &lt;em>almost&lt;/em> of type &lt;code>Lens' Field Pile&lt;/code>, but not quite. We define them differently here because they aren&amp;rsquo;t setters or getters yet. If we defined them as &lt;code>Lens'&lt;/code> types, they&amp;rsquo;d be polymorphic, and the process of evaluating them thru a &lt;code>filter cond ls&lt;/code> mechanism would solidify them as setters, when ideally we want to be able to later turn around and use them as getters.&lt;/p>
&lt;p>Each location can provide its own set of candidate lenses (usually either &lt;code>inFoundation++inTableau&lt;/code> or &lt;code>inTableau&lt;/code>, but it will depend) and evaluate them through &lt;code>findSpot&lt;/code>, which takes a list of lenses and a card and a field and returns the index of the first matching lens, if possible.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="172359486" type="checkbox" />
&lt;label for="172359486">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
findSpot :: [Getting Pile s Pile] -&amp;gt; Card -&amp;gt; s -&amp;gt; Maybe Int
findSpot pLenses c f = findIndex (\pL -&amp;gt; canPlace c (f ^. pL)) pLenses
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>&lt;code>canPlace&lt;/code> is a manual bit of pattern-matching which lives in &lt;a href="https://github.com/ambuc/solitaire/blob/master/src/Utils.hs#L35-L65">`Utils.hs&lt;/a> and runs through the types of piles and types of cards to provide a true/false.&lt;/p>
&lt;p>In practice it is convenient to provide two helpers to &lt;code>findSpot&lt;/code>:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="461925783" type="checkbox" />
&lt;label for="461925783">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
isSpot pLs c f = isJust $ findSpot pLs c f
mkSpot pLs c f = fromJust $ findSpot pLs c f
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>We can use the first in &lt;code>canMove&lt;/code>, and the second in &lt;code>mkMove&lt;/code>. &lt;code>canMove&lt;/code> tells us whether there is a spot for a card to go elsewhere in the field, and &lt;code>mkMoveL&lt;/code> will, assuming there is a spot, return both a lens to that spot and the piletype of the spot the card can go to.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="938617542" type="checkbox" />
&lt;label for="938617542">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
canMove :: Int -&amp;gt; DCard -&amp;gt; Field -&amp;gt; Bool
canMove _ DCard{_facedir=FaceDown} _ = False
canMove 0 DCard{_card=c} f = isSpot (inFoundation &amp;#43;&amp;#43; inTableau) c f
canMove _ DCard{_card=c} f = isSpot inTableau c f
mkMoveL :: Functor f =&amp;gt; Int -&amp;gt; Card -&amp;gt; Field
-&amp;gt; ( (Pile -&amp;gt; f Pile) -&amp;gt; Field -&amp;gt; f Field, PileType)
mkMoveL 0 c f = if idx &amp;lt;= 3
then (foundLN idx , FoundP)
else (tableLN (idx - 4) , TableP)
where idx = mkSpot (inFoundation &amp;#43;&amp;#43; inTableau) c f
mkMoveL _ c f = (tableLN $ mkSpot inTableau c f , TableP)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>OK, let&amp;rsquo;s write &lt;code>tryMove&lt;/code>. If you don&amp;rsquo;t like lenses, the above was the worst of it.&lt;/p>
&lt;h3 id="trymove">&lt;code>tryMove&lt;/code>&lt;/h3>
&lt;p>We expect &lt;code>tryMove&lt;/code> to have the form:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="531782964" type="checkbox" />
&lt;label for="531782964">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
tryMove :: [Ext] -&amp;gt; Field -&amp;gt; (Field, Int-&amp;gt;Int)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h4 id="trymove-stockx-moving-from-the-stock-i">&lt;code>tryMove [StockX]&lt;/code> Moving from the Stock (i)&lt;/h4>
&lt;p>Let&amp;rsquo;s write the &lt;code>tryMove [StockX]&lt;/code> function first, since it is the simplest:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="489217563" type="checkbox" />
&lt;label for="489217563">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
tryMove [StockX] f = (f&amp;#39;,id)
where f&amp;#39; = f &amp;amp; stockL %~ (reverse load &amp;#43;&amp;#43;)
&amp;amp; wasteL .~ []
load = f ^. wasteL
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Even for Haskell, this is pretty esoteric. we read a load from the waste using the &lt;code>wasteL&lt;/code> custom lens, reverse it, and prepend it (++) to the stock by using the in-place mutation operator (&lt;code>%~&lt;/code>), while overwriting the waste with an empty list (&lt;code>.~&lt;/code>). We return that new field &lt;code>f'&lt;/code> and a &lt;code>scoreFn&lt;/code> &lt;code>id&lt;/code>, which keeps the score as-is.&lt;/p>
&lt;h4 id="trymove-_-stockx-moving-from-the-stock-ii">&lt;code>tryMove [_, StockX]&lt;/code> Moving from the Stock (ii)&lt;/h4>
&lt;p>This is pretty similar to the last function, except that we are reading from the stock and writing to the waste instead. We drop 3 and take 3 at a time. Remember that the need to flip our cards over is handled innately in the &lt;code>stockL&lt;/code> and &lt;code>wasteL&lt;/code> lenses!&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="849631752" type="checkbox" />
&lt;label for="849631752">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
tryMove [_, StockX] f = (f&amp;#39;,id)
where f&amp;#39; = f &amp;amp; stockL %~ drop 3 --drop 3 from stock
&amp;amp; wasteL %~ (reverse load &amp;#43;&amp;#43;) --add 3 to waste
load = f ^. stockL &amp;amp; take 3 --get 3 from stock
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h4 id="trymove-dcx-dc-idx-0-wastex-moving-from-the-waste">&lt;code>tryMove [DCX dc, IdX 0, WasteX]&lt;/code> Moving from the Waste&lt;/h4>
&lt;p>We&amp;rsquo;ve already solved the hardest subproblem, that if determining whether or not a card can move anywhere else in the field. So we can just use &lt;code>canMove rowIndex displaycard field&lt;/code> to decide whether or not to try to evaluate &lt;code>f'&lt;/code>. If we do, it lazily evaluates &lt;code>mkMoveL&lt;/code>, which returns the &lt;code>moveL&lt;/code> lens which we can use to write one card to the location in question. We also use the computed &lt;code>PileType&lt;/code> to inform our scoring mutator.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="963874521" type="checkbox" />
&lt;label for="963874521">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
tryMove [DCX dc, IdX 0, WasteX] f
| canMove 0 dc f = (f&amp;#39;, scoreFn)
| otherwise = (f , id)
where (moveL, pType) = mkMoveL 0 (dc ^. card) f
f&amp;#39; = f &amp;amp; moveL . cards %~ (dc:) --write 1 to _
&amp;amp; wasteL %~ drop 1 --drop 1 from waste
scoreFn
| pType == FoundP = (&amp;#43;10)
| otherwise = (&amp;#43;5)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h4 id="trymove-dcx-dc-idx-row-foundx-moving-from-the-foundation">&lt;code>tryMove [DCX dc, IdX row, FoundX]&lt;/code> Moving from the Foundation&lt;/h4>
&lt;div class="collapsable-code">
&lt;input id="572914836" type="checkbox" />
&lt;label for="572914836">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
tryMove [DCX dc, IdX row, FoundX] f
| canMove row dc f = (f&amp;#39;, scoreFn)
| otherwise = (f , id)
where (moveL, pType) = mkMoveL row (dc ^. card) f
f&amp;#39; = f &amp;amp; moveL . cards %~ (dc:) --write 1 to _
&amp;amp; foundLN row . cards %~ drop 1 --drop 1 from found.
scoreFn i = i - 15
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>This is starting to feel familiar; we know how to move one card at a time. The only difficulty comes when it&amp;rsquo;s time to move from the tableau; we could be moving one card or more at a time.&lt;/p>
&lt;h4 id="trymove-dcx-dc-idx-row-idx-col-tablex-moving-from-the-tableau">&lt;code>tryMove [DCX dc, IdX row, Idx col, TableX]&lt;/code> Moving from the Tableau&lt;/h4>
&lt;div class="collapsable-code">
&lt;input id="453261879" type="checkbox" />
&lt;label for="453261879">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
tryMove [DCX dc, IdX row, IdX col, TableX] f
| canMove row dc f = (f&amp;#39;, scoreFn)
| otherwise = (f , id)
where load = f ^. tableLN col . cards &amp;amp; take (succ row)
(moveL, pType) = mkMoveL row (dc ^. card) f
f&amp;#39; = f &amp;amp; moveL . cards %~ (load&amp;#43;&amp;#43;) --write n to _
&amp;amp; tableLN col . cards
%~ drop (succ row) --drop n from tableau
&amp;amp; tableLN col . cards . _head . facedir
.~ FaceUp --flip underlying card
scoreFn
| pType == FoundP = (&amp;#43;15)
| otherwise = (&amp;#43;5)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>OK, this was definitely the most difficult one, But we&amp;rsquo;ve built ourselves a nice set of primitives, so that flipping the underlying card, or dropping &lt;code>n&lt;/code> cards from the tableau row in question become pretty readable.&lt;/p>
&lt;p>This is great! A well-formed &lt;code>tryMove&lt;/code> mean we can write &lt;code>doMove&lt;/code>, and the core of our game is finished.&lt;/p>
&lt;h2 id="final-touches">Final Touches&lt;/h2>
&lt;p>Now that we know how to use lenses, a lot of the remaining functions are pretty simple:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="927538146" type="checkbox" />
&lt;label for="927538146">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
-- if a game is won, all 52 cards are in the foundation
hasWon :: GSt -&amp;gt; Bool
hasWon s = length (s ^. field . found . traverse . cards) == 52
&lt;/code>&lt;/pre>
&lt;/div>
&lt;div class="collapsable-code">
&lt;input id="762948351" type="checkbox" />
&lt;label for="762948351">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
-- undoing a move means rolling back the field, the history, the score, and
-- the moves counter
undoMove :: GSt -&amp;gt; GSt
undoMove s = if hasHistory
then s &amp;amp; field .~ oldField
&amp;amp; history %~ drop 1
&amp;amp; score .~ oldScore
&amp;amp; moves %~ pred
else s
where (oldField, oldScore) = s ^. history ^?! _head -- assured if called
hasHistory = not $ null $ s ^. history
&lt;/code>&lt;/pre>
&lt;/div>
&lt;div class="collapsable-code">
&lt;input id="576238491" type="checkbox" />
&lt;label for="576238491">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
-- given a game with a seed, get a new seed and use it to spawn a new game
newGame :: GSt -&amp;gt; GSt
newGame s = let seed&amp;#39; = snd $ R.next $ s ^. seed
in mkInitS seed&amp;#39;
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Now we can finally finish &lt;code>appEvent&lt;/code> and our app is done!&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="324165978" type="checkbox" />
&lt;label for="324165978">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
appEvent :: GSt -&amp;gt; BrickEvent Ext e -&amp;gt; EventM Ext (Next GSt)
appEvent s (VtyEvent e) = case e of
Vty.EvKey Vty.KEsc [] -&amp;gt; halt s
Vty.EvKey (Vty.KChar &amp;#39;q&amp;#39;) [] -&amp;gt; halt s
Vty.EvMouseDown col row _ _ -&amp;gt; do
extents &amp;lt;- map extentName &amp;lt;$&amp;gt; findClickedExtents (col, row)
case extents of
[ActionX New] -&amp;gt; continue $ newGame s
[ActionX Undo] -&amp;gt; continue $ undoMove s
_ -&amp;gt; if hasWon s
then continue s
else continue $ doMove s extents
_ -&amp;gt; continue s
appEvent s _ = continue s
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h2 id="conclusions">Conclusions&lt;/h2>
&lt;p>I think a good UI framework is one where the bulk of the difficulty of writing the app is the internal logic of the underlying app itself, not fighting with the framework. Brick fit right into my functional understanding of frameworks like React or Angular, and although there was some learning curve for lenses, the final product is, like all Haskell, surprisingly short and readable.&lt;/p>
&lt;p>If you&amp;rsquo;ve never played with Haskell before, I think Brick is an excellent place to start. I encourage you to &lt;a href="https://github.com/ambuc/solitaire#playing-solitaire">download and play Solitiare&lt;/a> if you&amp;rsquo;re interested in getting a feel for the ecosystem, or just &lt;a href="https://github.com/ambuc/solitaire/blob/master/src/CardTypes.hs">reading through some of the code&lt;/a> if you&amp;rsquo;re interested in how a comparatively large app feels at a low level.&lt;/p></content></item><item><title>Multiprocessing Conway's Game of Life in Erlang</title><link>https://jbuckland.com/blog/graphics-conway/</link><pubDate>Thu, 12 Oct 2017 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/graphics-conway/</guid><description>Github http://github.com/ambuc/conway This is an implementation of Conway&amp;rsquo;s Game of Life written in Erlang, a general-purpose, concurrent, functional programming language. A lot of this essay will be an attempt to document not just the novel syntax but the different problem-solving and architecting ideas that go into developing in Erlang.
The Game Conway&amp;rsquo;s Game of Life is a cellular automaton, which means it&amp;rsquo;s a set of cells which turn themselves on and off frame-by-frame according to some set of rules.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">Github&lt;/th>
&lt;td> &lt;a href="http://github.com/ambuc/conway">http://github.com/ambuc/conway&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;p>This is an implementation of &lt;a href="https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life">Conway&amp;rsquo;s Game of Life&lt;/a> written in Erlang, a &lt;a href="https://www.erlang.org/">general-purpose, concurrent, functional programming language&lt;/a>. A lot of this essay will be an attempt to document not just the novel syntax but the different problem-solving and
architecting ideas that go into developing in Erlang.&lt;/p>
&lt;h2 id="the-game">The Game&lt;/h2>
&lt;figure class="center" >
&lt;img src="images/random.gif" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>Conway&amp;rsquo;s Game of Life is a &lt;a href="https://en.wikipedia.org/wiki/Cellular_automaton">cellular
automaton&lt;/a>, which means it&amp;rsquo;s a
set of cells which turn themselves on and off frame-by-frame according to some
set of rules. Specifically, Conway&amp;rsquo;s GOL takes place on an infinite 2D grid of
squares, each of which cares about its &lt;a href="https://en.wikipedia.org/wiki/Moore_neighborhood">eight cardinal
neighbors&lt;/a>. The entire game
consists of&lt;/p>
&lt;ul>
&lt;li>&lt;em>a)&lt;/em> a starting configuration, and&lt;/li>
&lt;li>&lt;em>b)&lt;/em> a set of rules by which to evaluate if a cell lives or dies. In this
case, Conway relies on rules which can be written as &lt;code>B3/S23&lt;/code>, which means a
cell is born if it has three living neighbors, and survives if it has either
two or three living neighbors. (There are &lt;a href="https://en.wikipedia.org/wiki/Life-like_cellular_automaton">lots of
variants&lt;/a>.)&lt;/li>
&lt;/ul>
&lt;p>Conway&amp;rsquo;s GOL has become famous for the shockling high complexity and variety of
its emergent patterns, as you can see in the 100x100 randomly-seeded grid above.&lt;/p>
&lt;h2 id="computing-the-game">Computing The Game&lt;/h2>
&lt;p>In practice, the complexity of computing Conway&amp;rsquo;s Game of Life depends on the
size of the grid in question and the length of the simulation. It would be
fairly easy to render the world as a matrix of binary 1s and 0s, or Trues and
Falses; each tick in the world could be a matrix and each next tick could read
off the states of the previous tick.&lt;/p>
&lt;p>But I think the problem generalizes nicely to Erlang in the sense that it fits
the &lt;a href="https://en.wikipedia.org/wiki/Actor_model">Actor model&lt;/a>, in which each cell
might be its own process living on some VM, and each update / neighbor-check
might be a message or series of messages passed between the elements of the
grid.&lt;/p>
&lt;h2 id="erlang">Erlang&lt;/h2>
&lt;p>Erlang is a little esoteric but not much more so than Haskell or any functional
language. The basics for us go something like this:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="517643928" type="checkbox" />
&lt;label for="517643928">
&lt;span class="collapsable-code__language">erlang&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-erlang" >&lt;code>
Ref = spawn(module, function, [arg1, arg2..])
Msg = {foo, bar}
Ref ! Msg
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>&lt;code>spawn&lt;/code> spawns a process, the function &lt;code>function&lt;/code> from module &lt;code>module&lt;/code> with args
&lt;code>[arg1, arg2...]&lt;/code>; &lt;code>Ref&lt;/code> captures the PID of that process, and &lt;code>!&lt;/code> lets us
pass messages to a unique instance of a process. This is the whole mechanism
for Erlang; the VM aggressively cleans up processes which are done processing
things (or waiting to process things).&lt;/p>
&lt;h2 id="semaphores">Semaphores&lt;/h2>
&lt;p>Before we get into the specifics of simulating Conway&amp;rsquo;s Game of Life, let&amp;rsquo;s
discuss two small abstractions which make message passing between objects
slightly easier.&lt;/p>
&lt;h3 id="latch">Latch&lt;/h3>
&lt;p>One is a latch, which is basically a counter; you give it a spring-loaded
message, and it only sends that message once it&amp;rsquo;s reached a certain number of
recieved messages.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="746582193" type="checkbox" />
&lt;label for="746582193">
&lt;span class="collapsable-code__language">erlang&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-erlang" >&lt;code>
-module(latch).
-compile(export_all).
% Starts a latch reporting to Ref, waiting for N pings.
init(Ref, N) -&amp;gt; spawn(?MODULE, loop, [Ref, N]).
% Ends a latch reporting to Ref.
loop(Ref, 0) -&amp;gt; Ref ! pong;
% Recieves a ping and returns a latch waiting for N-1 pings.
loop(Ref, X) -&amp;gt;
receive ping -&amp;gt; loop(Ref, X-1)
after 1000 -&amp;gt; erlang:error(latch_timeout)
end.
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h3 id="collector">Collector&lt;/h3>
&lt;p>The other is a collector, which waits for a certain number of bits of
information, and then sends them all in aggregate somewhere.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="326948175" type="checkbox" />
&lt;label for="326948175">
&lt;span class="collapsable-code__language">erlang&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-erlang" >&lt;code>
-module(collector).
-compile(export_all).
% Starts a collector reporting to Ref, waiting for N reports.
% Optionally bundled with Metadata of any type, as a secondary payload.
init(Ref, Meta, N) -&amp;gt; spawn(?MODULE, loop, [Ref, Meta, [], N]).
% Ends a collector reporting to Ref.
% Sends a payload like {data, Meta, Data}
loop(Ref, Meta, Data, 0) -&amp;gt; Ref ! {data, Meta, Data};
% Recieves a payload and returns a collector waiting for N-1 more payloads.
loop(Ref, Meta, Data, X) -&amp;gt;
receive D -&amp;gt; loop(Ref, Meta, [ D | Data ], X-1)
after 1000 -&amp;gt; erlang:error(collector_timeout)
end.
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>I defined the collector here with the ability to carry some bit of metadata over
its lifetime, from initialization to final payload. This becomes useful in
preserving the relative statelessness of each of our cells.&lt;/p>
&lt;h2 id="writing-a-game-of-life">Writing A Game Of Life&lt;/h2>
&lt;p>Now that we have a sense of how the game works and language and tools in which
to write it, let&amp;rsquo;s try and design how the &lt;code>board&lt;/code> and &lt;code>cell&lt;/code> objects look and
interact.&lt;/p>
&lt;h3 id="the-board">The &lt;code>board&lt;/code>&lt;/h3>
&lt;p>We want to initialize the board with dimensions, a starting pattern, and a
lifespan for which to run. It should create, control, and eventually destroy the
cells. It should also collate the current state of each board and output that
state in some useful way.&lt;/p>
&lt;p>Each &lt;code>tick&lt;/code> of the board should consist of three stages:&lt;/p>
&lt;ul>
&lt;li>the board should ask each cell to &lt;code>report&lt;/code> its current state, and then the
board should output that aggregate state.&lt;/li>
&lt;li>then the board should ask each cell to &lt;code>check_ns&lt;/code> check its neighbors and
decide which state it would like to assume next. Once the cells are done,
they should report back, so that the board knows it&amp;rsquo;s alright to move on to
the next phase,&lt;/li>
&lt;li>when the board asks each cell to &lt;code>alter_st&lt;/code> alter its state. Once the cells
are done, they should report back&amp;hellip;&lt;/li>
&lt;/ul>
&lt;p>so that the board knows it&amp;rsquo;s alright to loop these three stages over and over
until the lifespan of the game is run out.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="619543827" type="checkbox" />
&lt;label for="619543827">
&lt;span class="collapsable-code__language">erlang&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-erlang" >&lt;code>
% Starts a GOL with Frames (an integer) frames left to live.
% Its initial state is a list of 1s or 0s, and it has dimensions X by Y.
init(Initial_State, Frames, {X, Y}) -&amp;gt;
io:format(&amp;#34;Drawing ~w frames on an ~w x ~w grid...~n&amp;#34;,[Frames, X, Y]),
out:cleanOutputFolder(),
Size = X * Y,
Is = lists:seq(1,Size),
io:format(&amp;#34;Initializing ~w cells...~n&amp;#34;,[Size]),
Cs = [ cell:init(I,L) || {I,L} &amp;lt;- lists:zip(Is,Initial_State) ],
S = #state{frames=Frames, stage=report, size=Size, cells=Cs, x=X, y=Y},
% Once we have created the cells, we need to broadcast out a list of their
% neighbor&amp;#39;s PIDs to them.
io:format(&amp;#34;Linking ~w cells...~n&amp;#34;,[Size]),
[ C ! {moore, neighbors:getNPids(I,X,Y,Cs)} || {I,C} &amp;lt;- lists:zip(Is,Cs) ],
io:format(&amp;#34;Linked ~w cells...~n&amp;#34;,[Size]),
action(S).
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>We have a base case: when a board has no frames left to run, it dies.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="361495872" type="checkbox" />
&lt;label for="361495872">
&lt;span class="collapsable-code__language">erlang&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-erlang" >&lt;code>
action(#state{frames=0}) -&amp;gt;
io:format(&amp;#34;Writing out to /tmp/conway/gif.gif.~n&amp;#34;)
, os:cmd(&amp;#34;convert -delay 20 -loop 0 $(ls /tmp/conway/frame_* | tac ) /tmp/conway/gif.gif&amp;#34;)
, io:format(&amp;#34;Wrote out to /tmp/conway/gif.gif.~n&amp;#34;)
;
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>And some not-base cases. Sometimes a board has an action it needs to take.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="923147568" type="checkbox" />
&lt;label for="923147568">
&lt;span class="collapsable-code__language">erlang&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-erlang" >&lt;code>
action(S) -&amp;gt;
case S#state.stage of
% If we need to collect the states of all the cells, we create a Collector
% which knows to report back to the Board when it&amp;#39;s done. We tell the
% Collector it needs to collect X*Y data points.
%
% Then we broadcast out {CollectorPid, report} to each of the cells, to tell
% them to self-report their {index, life} to the Collector.
%
% Then we switch over to waiting for the Collector to ping us back.
report -&amp;gt; C_Ref = collector:init(self(), nil, S#state.size),
[ C ! {C_Ref, report } || C &amp;lt;- S#state.cells],
waiting(S);
% Similarly, if we need to tell all the cells to check their neighbors, we
% create a Latch which knows to report back to the Board when it&amp;#39;s done. We
% tell the Latch it needs to collect X*Y pings.
%
% Then we broadcast out {LatchPid, check_ns} to each of the cells, to tell
% them to check their neighbors and ping the Latch when they&amp;#39;re done.
%
% Then we switch over to waiting for the Latch to ping us back.
check_ns -&amp;gt; L_Ref = latch:init(self(), S#state.size),
[ C ! {L_Ref, check_ns} || C &amp;lt;- S#state.cells],
waiting(S);
% FInally, if we need to tell all the cells to alter their states, we create
% a Latch and broadcast our message just as we did above.
alter_st -&amp;gt; L_Ref = latch:init(self(), S#state.size),
[ C ! {L_Ref, alter_st} || C &amp;lt;- S#state.cells],
waiting(S);
_ -&amp;gt; erlang:error(weird_stage)
end.
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>When our board is waiting for a report, it expects a payload like
&lt;code>{data, Meta, Data}&lt;/code>. It collects the Data, uses it to draw the gameboard at
that frame, and then calls &lt;code>action()&lt;/code> at the next stage, check_ns, and with one
fewer frame left to go.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="845317269" type="checkbox" />
&lt;label for="845317269">
&lt;span class="collapsable-code__language">erlang&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-erlang" >&lt;code>
waiting(S = #state{stage=report}) -&amp;gt;
receive {data, _, Data} -&amp;gt; out:writeData(S#state.frames, S#state.x, Data),
io:format(&amp;#34;~w frames left.~n&amp;#34;,[S#state.frames]),
action(S#state{ stage=check_ns
, frames=(S#state.frames-1)
})
after 1000 -&amp;gt; erlang:error(board_timeout)
end;
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>When our board is waiting for anything else, it expects a simple pong.
Depending on the current stage, it will know to switch over to the next stage
in the cycle.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="431796825" type="checkbox" />
&lt;label for="431796825">
&lt;span class="collapsable-code__language">erlang&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-erlang" >&lt;code>
waiting(S) -&amp;gt;
receive pong -&amp;gt;
case S#state.stage of check_ns -&amp;gt; action(S#state{stage=alter_st});
alter_st -&amp;gt; action(S#state{stage=report })
end
after 1000 -&amp;gt; erlang:error(board_timeout)
end.
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h3 id="the-cells">The &lt;code>cell&lt;/code>s&lt;/h3>
&lt;p>Each &lt;code>cell&lt;/code> of the board should know its own index and current state. When
asked, they should be able to report one or both of those bits of information.
Additionally, they should know the PIDs of their neighbors so that, if asked,
they can query those neighbors (through a Collector) to discover their states
and determine its next state.&lt;/p>
&lt;p>This neighbor determination actually occurs in the &lt;code>board.erl&lt;/code> initialization
process, and the neighbor determination algorithm is abstracted away into
&lt;code>neighbors.erl&lt;/code>.&lt;/p>
&lt;p>We have an initialization function &lt;code>init&lt;/code>, which starts a cell with knowledge of
its own index and starting state.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="194852376" type="checkbox" />
&lt;label for="194852376">
&lt;span class="collapsable-code__language">erlang&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-erlang" >&lt;code>
init(Index, Life) -&amp;gt; spawn(?MODULE, loop, [ #state{index=Index, life=Life} ]).
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>A cell always is waiting to receive a signal. When the game is over, the
&lt;code>main/1&lt;/code> function in &lt;code>conway.erl&lt;/code> ends and all these other processes die
automatically.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="764281953" type="checkbox" />
&lt;label for="764281953">
&lt;span class="collapsable-code__language">erlang&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-erlang" >&lt;code>
% we are waiting to receive...
loop(S) -&amp;gt;
receive
% ... a moore neighbor space,
{moore, NPids} -&amp;gt; loop(S#state{npids=NPids});
% ... a request to check their neighbors&amp;#39; states, decide their desired
% state, and ping back to a latch L when done,
{L, check_ns} -&amp;gt; Ref = collector:init(self(),L,length(S#state.npids)),
[ NPid ! {Ref, alive} || NPid &amp;lt;- S#state.npids ],
loop(S);
% ... a payload from the above collector, allowing them to decide their
% desired state, and ping a latch on receipt,
{data, L, NData} -&amp;gt; L ! ping,
loop(S#state{desire=getDesire(S#state.life,NData)});
% ... a request from the board to ping a latch and flip to their new state,
{L, alter_st} -&amp;gt; L ! ping,
loop(S#state{life=(S#state.desire)});
% ... a request to report their {index,life},
{C, report} -&amp;gt; C ! {S#state.index, S#state.life},
loop(S);
% ... a request to report their life,
{Ref, alive} -&amp;gt; Ref ! S#state.life,
loop(S);
% ... or a request to die.
die -&amp;gt; ok;
_ -&amp;gt; erlang:error(weird_input)
after 10000 -&amp;gt; erlang:error(cell_timeout)
end.
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>The actual math of Conway&amp;rsquo;s GOL can be written quite simply:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="617428539" type="checkbox" />
&lt;label for="617428539">
&lt;span class="collapsable-code__language">erlang&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-erlang" >&lt;code>
% Takes a current state and data about the states of their neighbors, and
% decides whether to live or die.
getDesire(Curr, NData) -&amp;gt;
decide(Curr, length(lists:filter(fun(X) -&amp;gt; X =:= 1 end, NData))).
% Encodes the GOL rules.
% - Any live cell with fewer than two live neighbours dies, as if caused by
% underpopulation.
% - Any live cell with two or three live neighbours lives on to the next
% generation.
% - Any live cell with more than three live neighbours dies, as if by
% overpopulation.
% - Any dead cell with exactly three live neighbours becomes a live cell, as if
% by reproduction.
% Formally, this might be encoded as &amp;#34;B3/S23&amp;#34;.
decide(0,3) -&amp;gt; 1;
decide(0,_) -&amp;gt; 0;
decide(1,2) -&amp;gt; 1;
decide(1,3) -&amp;gt; 1;
decide(1,_) -&amp;gt; 0.
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h2 id="output">Output&lt;/h2>
&lt;p>The &lt;code>board&lt;/code> object collects a sorted list of all nodes and states, like&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="982561437" type="checkbox" />
&lt;label for="982561437">
&lt;span class="collapsable-code__language">erlang&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-erlang" >&lt;code>
[(0,0),(1,0),(2,1),(3,0)... (&amp;lt;index&amp;gt;,&amp;lt;life&amp;gt;)]
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>We need a way to translate that into a still frame (a &lt;code>.png&lt;/code>) of the gif we want
to write eventually. Sadly, this task is best abstracted into a language with
better graphics rendering capabilities. We write out this data into plaintext
files and then run &lt;code>renderFrame/render.hs&lt;/code>, which looks in &lt;code>/tmp&lt;/code> for the file
in question and writes it in-place to a &lt;code>.png&lt;/code>.&lt;/p>
&lt;p>The render script (&lt;a href="https://github.com/ambuc/conway/blob/master/renderFrame/render.hs">&lt;code>render.hs&lt;/code>&lt;/a>)
is really just a wrapper around
&lt;a href="https://hackage.haskell.org/package/Rasterific/docs/Graphics-Rasterific.html">&lt;code>Graphics.Rasterific&lt;/code>&lt;/a>:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="765893241" type="checkbox" />
&lt;label for="765893241">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
size = 5
white = PixelRGBA8 255 255 255 255
black = PixelRGBA8 0 0 0 255
-- is called once per frame, like
-- $ render.hs /tmp/conway/data0001 /tmp/conway/frame0001.png
main = do
[inPath, outPath] &amp;lt;- getArgs
grid &amp;lt;- fmap words $ readFile inPath
let height = length $ grid
let width = length $ head $ grid
let datum = id
$ map snd
$ filter ((==&amp;#39;1&amp;#39;).fst)
$ zip (concat $ grid)
[(x,y) | y &amp;lt;- [0..height-1], x &amp;lt;- [0..width-1]]
let img = renderDrawing (width*size) (height*size) white
$ mapM_ (uncurry pixelAt) datum
writePng outPath img
pixelAt :: Int -&amp;gt; Int -&amp;gt; Drawing PixelRGBA8 ()
pixelAt x y = withTexture (uniformTexture $ PixelRGBA8 0 0 0 255)
$ fill
$ rectangle (V2 (fromIntegral $ x*size) (fromIntegral $ y*size))
(fromIntegral size)
(fromIntegral size)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h2 id="tying-it-all-together">Tying it all together&lt;/h2>
&lt;p>We just need one central &lt;code>conway.erl&lt;/code> which exports &lt;code>main/1&lt;/code>, which lets us call
this from the command line.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="963875421" type="checkbox" />
&lt;label for="963875421">
&lt;span class="collapsable-code__language">erlang&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-erlang" >&lt;code>
-module(conway).
%% API exports
-export([main/1]).
% Modify this file to generate your own.
main([Pattern]) -&amp;gt;
case Pattern of
&amp;#34;random&amp;#34; -&amp;gt; board:init( fog:getFog(100,100), 100, {100,100} );
&amp;#34;blinker&amp;#34; -&amp;gt; board:init( [0,0,0,0,0
,0,0,1,0,0
,0,0,1,0,0
,0,0,1,0,0
,0,0,0,0,0], 2, {5,5});
% board:init(&amp;lt;initial_state&amp;gt;, &amp;lt;runtime&amp;gt;, {&amp;lt;width&amp;gt;,&amp;lt;height&amp;gt;})
...
end.
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>I wrote a teeny &lt;code>fog.erl&lt;/code> module which wraps the builtin random number
generator, which is how we achieve the main gif at the top of the page, where
the starting state is a random haze of pixels. For the other patterns, we
manually define the starting pattern with a list (not array) of ones and zeros.&lt;/p>
&lt;h2 id="performance">Performance&lt;/h2>
&lt;p>It took about 30s to render the 100x100 grid above for 100 frames, which is not
too bad. One optimization that could have helped would have been to decouple the
time it takes to write out the frame from the main board tick loop by spawning a
process and not waiting for it to complete before dying. There might be a
resource constraint there where it&amp;rsquo;s not great to have more than, say, 10 copies
of &lt;code>render.hs&lt;/code> running at once; a mutex might be a useful resource for solving
that issue in a concurrent way.&lt;/p>
&lt;p>Additionally, it would have been very cool to learn how to write bitmaps in raw
binary. Erlang isn&amp;rsquo;t so good at image rendering, but it&amp;rsquo;s quite good at
interfacing with the machine at a lower level; writing out raw bitmaps with
individual pixels colored in (or not) might have been an elegant solution here.&lt;/p>
&lt;h2 id="results">Results&lt;/h2>
&lt;p>OK, here are some cool oscillators I stole from Wikipedia. You can call them by
name as the first argument to the &lt;code>conway&lt;/code> executable under &lt;strong>Run&lt;/strong>. They are:&lt;/p>
&lt;h3 id="blinker">&lt;code>blinker&lt;/code>&lt;/h3>
&lt;figure class="center" >
&lt;img src="images/blinker.gif" style="border-radius: 8px;" />
&lt;/figure>
&lt;h3 id="toad">&lt;code>toad&lt;/code>&lt;/h3>
&lt;figure class="center" >
&lt;img src="images/toad.gif" style="border-radius: 8px;" />
&lt;/figure>
&lt;h3 id="beacon">&lt;code>beacon&lt;/code>&lt;/h3>
&lt;figure class="center" >
&lt;img src="images/beacon.gif" style="border-radius: 8px;" />
&lt;/figure>
&lt;h3 id="pulsar">&lt;code>pulsar&lt;/code>&lt;/h3>
&lt;figure class="center" >
&lt;img src="images/pulsar.gif" style="border-radius: 8px;" />
&lt;/figure>
&lt;h3 id="pentadecathlon">&lt;code>pentadecathlon&lt;/code>&lt;/h3>
&lt;figure class="center" >
&lt;img src="images/pentadecathlon.gif" style="border-radius: 8px;" />
&lt;/figure>
&lt;h3 id="random-seen-at-the-top-of-the-page">&lt;code>random&lt;/code> (seen at the top of the page)&lt;/h3>
&lt;figure class="center" >
&lt;img src="images/random.gif" style="border-radius: 8px;" />
&lt;/figure></content></item><item><title>Puzzle Pong III - Irreversible Cube II</title><link>https://jbuckland.com/blog/puzzle-irreversible-ii/</link><pubDate>Sat, 02 Sep 2017 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/puzzle-irreversible-ii/</guid><description>Github https://gist.github.com/ambuc/84fca250c25f72b46a16dd28eb653708 The Puzzle This is a continuation of Puzzle Pong - Irreversible Cube I. In the first part, Josh Mermelstein posed me a series of questions about the Irreversible Cube:
Oskar van Deventer {1} presents an Irreversible Cube in his 2013 video {2} which is an ordinary 2x2x2 Rubix Cube where you are only allowed to turn any face clockwise, and only once in a row.
By the end of part 1), I had a fairly poor approximation of an isometric projection, which was able to take a set of points and turn them into tilted tiles along one of three skew planes.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">Github&lt;/th>
&lt;td> &lt;a href="https://gist.github.com/ambuc/84fca250c25f72b46a16dd28eb653708">https://gist.github.com/ambuc/84fca250c25f72b46a16dd28eb653708&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;h2 id="the-puzzle">The Puzzle&lt;/h2>
&lt;p>This is a continuation of &lt;a href="http://jbuckland.com/irreversible/">Puzzle Pong - Irreversible Cube I&lt;/a>. In the first part, &lt;a href="http://joshmermelstein.com/">Josh Mermelstein&lt;/a> posed me a series of questions about the Irreversible Cube:&lt;/p>
&lt;blockquote>
&lt;p>Oskar van Deventer &lt;a href="https://www.youtube.com/user/OskarPuzzle/">{1}&lt;/a> presents an Irreversible Cube in his 2013 video &lt;a href="https://www.youtube.com/watch?v=eMGeKdog8Ws">{2}&lt;/a> which is an ordinary 2x2x2 Rubix Cube where you are only allowed to turn any face clockwise, and only once in a row.&lt;/p>
&lt;/blockquote>
&lt;figure class="center" >
&lt;img src="../irreversible/images/cube1.jpg" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>By the &lt;a href="http://jbuckland.com/irreversible/#conclusions">end of part 1)&lt;/a>, I had a fairly poor approximation of an isometric projection, which was able to take a set of points and turn them into tilted tiles along one of three skew planes. This was pretty good for seeing what state the cube might be in at any given stage, but it was not a true 3D rendering, meaning that my higher goals of animating a real turn were next-to-impossible.&lt;/p>
&lt;p>This inspired a separate Haskell library, &lt;a href="https://github.com/ambuc/cornea">Cornea&lt;/a> for isometric 3D graphing and rendering, which I discuss &lt;a href="http://jbuckland.com/isometric/">here&lt;/a>. Cornea has the benefit of being easily mapped to write out either a single image or a series of images, which can be stitched together into &lt;a href="http://jbuckland.com/isometric/#animation">animations&lt;/a>.&lt;/p>
&lt;p>In this post, I&amp;rsquo;ll discuss wrapping the Irreversible Cube solver from part I into something which can render animations of the cube in the process of being solved.&lt;/p>
&lt;h2 id="changes">Changes&lt;/h2>
&lt;p>Recall our custom type definitions:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="156793482" type="checkbox" />
&lt;label for="156793482">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
data Side = F | B | U | D | L | R deriving (Eq, Bounded, Show, Enum, Ord)
data Axis = X | Y | Z deriving (Eq, Show)
data Cardinality = Pos | Neg deriving (Eq, Show)
type Rotation = (Coord -&amp;gt; Coord)
type Coord = [Int]
type Tile = Coord
type Cube = [Tile]
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>The biggest change we must make from the old way of doing things is to rewrite &lt;code>mkRot :: Cardinality -&amp;gt; Axis -&amp;gt; Rotation&lt;/code> to rely on a much more general &lt;code>rotate :: Float -&amp;gt; Axis -&amp;gt; [x,y,z] -&amp;gt; [x',y',z']&lt;/code> under the hood. That way, we can still do things like&lt;/p>
&lt;pre>&lt;code>given a turn on face L
-&amp;gt; run through sideToCardinalityAndAxis s2CA(L) = (Neg, Y)
-&amp;gt; get cardinality Neg and axis Y
-&amp;gt; mkRot Neg Y -&amp;gt; (\[x,y,z] -&amp;gt; rotate (-90) Y [x,y,z])
&lt;/code>&lt;/pre>
&lt;p>Or, when we need to start calculating in-between frames (tweens) for our side rotation animation, we can do things like&lt;/p>
&lt;pre>&lt;code>rotateFace :: Float -&amp;gt; Axis -&amp;gt; Obj -&amp;gt; Obj
rotateFace &amp;lt;angle&amp;gt; &amp;lt;axis&amp;gt; (Face pts) = Face (map (rotate &amp;lt;angle&amp;gt; &amp;lt;axis&amp;gt;) pts)
&lt;/code>&lt;/pre>
&lt;p>Here&amp;rsquo;s the code that makes that happen.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="857634921" type="checkbox" />
&lt;label for="857634921">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
mkRot :: Cardinality -&amp;gt; Axis -&amp;gt; Rotation
mkRot c a = map (\x -&amp;gt; round x :: Int) . rotate (toAngle c) a . map fromIntegral
s2Rot :: Side -&amp;gt; Rotation
s2Rot = uncurry mkRot . s2CA
s2CA :: Side -&amp;gt; (Cardinality, Axis)
s2CA F = (Pos, X); s2CA R = (Pos, Y); s2CA U = (Pos, Z);
s2CA B = (Neg, X); s2CA L = (Neg, Y); s2CA D = (Neg, Z);
rotate :: Float -&amp;gt; Axis -&amp;gt; [Float] -&amp;gt; [Float]
rotate t X [x,y,z] = [ x , cos t * y - sin t * z , sin t * y &amp;#43; cos t * z]
rotate t Y [x,y,z] = [ cos t * x &amp;#43; sin t * z , y , -sin t * x &amp;#43; cos t * z]
rotate t Z [x,y,z] = [ cos t * x - sin t * y , sin t * x &amp;#43; cos t * y , z ]
rotateFace :: Float -&amp;gt; Axis -&amp;gt; Obj -&amp;gt; Obj
rotateFace t a (Face pts) = Face (map (rotate t a) pts)
toAngle :: Cardinality -&amp;gt; Float
toAngle Pos = pi/2; toAngle Neg = -pi/2
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Now we can just write &lt;code>twist&lt;/code> as&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="829564173" type="checkbox" />
&lt;label for="829564173">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
twist :: Side -&amp;gt; Cube -&amp;gt; Cube
twist side = map (\x -&amp;gt; if side `sees` x then s2Rot side x else x)
sees :: Side -&amp;gt; (Coord -&amp;gt; Bool)
(sees) F = (&amp;gt;0) . (!!0); (sees) R = (&amp;gt;0) . (!!1); (sees) U = (&amp;gt;0) . (!!2);
(sees) B = (&amp;lt;0) . (!!0); (sees) L = (&amp;lt;0) . (!!1); (sees) D = (&amp;lt;0) . (!!2);
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h2 id="seqeuence-of-moves">Seqeuence of Moves&lt;/h2>
&lt;p>As for the algorithm, &lt;code>seed&lt;/code> and &lt;code>kids&lt;/code> stay the same; we end up producing the sequence of moves the same way with the same call.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="193246785" type="checkbox" />
&lt;label for="193246785">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
moves = reverse $ snd $ head $ filter (solved.fst) $ concat
$ iterate (concatMap kids) seed
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h2 id="rendering-a-cube">Rendering a Cube&lt;/h2>
&lt;p>Things have changed a little bit; instead of projecting squares and skewing/translating them, we have to actually describe the four corner coordinates of a tile in able to render it in 3d space.&lt;/p>
&lt;p>Recall that we store &lt;code>Tile&lt;/code>s as 3-tuples describing the center of the tile in question; if a tile is on the top face of the cube, it will have coordinates $(\pm 1, \pm 1, +2)$, etc. Tiles don&amp;rsquo;t come bundled with normal vectors describing their face orientation, so there&amp;rsquo;s nothing to it here but to hard-code this logic.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="861274359" type="checkbox" />
&lt;label for="861274359">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
toTile :: [Int] -&amp;gt; Obj
toTile [x,y,z] = p2t&amp;#39; [fromIntegral x, fromIntegral y, fromIntegral z]
where p2t&amp;#39; [ x, y, 2] = Face [ [x&amp;#43;a, y&amp;#43;b, n] | (a,b) &amp;lt;- rg ]
p2t&amp;#39; [ x, y,-2] = Face [ [x&amp;#43;a, y&amp;#43;b, -n] | (a,b) &amp;lt;- rg ]
p2t&amp;#39; [ x, 2, z] = Face [ [x&amp;#43;a, n, z&amp;#43;b] | (a,b) &amp;lt;- rg ]
p2t&amp;#39; [ x,-2, z] = Face [ [x&amp;#43;a, -n, z&amp;#43;b] | (a,b) &amp;lt;- rg ]
p2t&amp;#39; [ 2, y, z] = Face [ [ n, y&amp;#43;a, z&amp;#43;b] | (a,b) &amp;lt;- rg ]
p2t&amp;#39; [-2, y, z] = Face [ [-n, y&amp;#43;a, z&amp;#43;b] | (a,b) &amp;lt;- rg ]
rg :: Num a =&amp;gt; [(a,a)]
rg = [(1,1),(1,-1),(-1,-1),(-1,1)] --range
n = 5 --offset
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>We use &lt;code>n&lt;/code> as a way to control how &amp;ldquo;exploded&amp;rdquo; the cube is; at the beginning I rendered cubes as &lt;code>n=2&lt;/code> to draw the cube realistically; later we can tune it up to explode the cube more and more.&lt;/p>
&lt;h2 id="animation">Animation&lt;/h2>
&lt;p>We have a couple of options here; let&amp;rsquo;s start simple and work our way up in complexity.&lt;/p>
&lt;p>We can &lt;code>scanr&lt;/code> and sequentially apply &lt;code>moves&lt;/code> over a &lt;code>solvedCube&lt;/code> to get a sequence of cubes in various staged of solved-ness.&lt;/p>
&lt;p>If we write &lt;code>toImage &amp;lt;cube&amp;gt; &amp;lt;viewpoint&amp;gt;&lt;/code> to return an &lt;code>Image px&lt;/code> of the cube (zipped with our custom &lt;code>kolors&lt;/code> color-sequence),&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="524781639" type="checkbox" />
&lt;label for="524781639">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
toImage :: [Obj] -&amp;gt; (Float, Float) -&amp;gt; Image PixelRGBA8
toImage cs v = render 500 500 40 $ world `seenFrom` v
where world = map (second solid) $ zip cs kolors
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Then we can write an &lt;code>animate&lt;/code> function. In this case, because we want to do our styling in &lt;code>toImage&lt;/code>, we only need an array of &lt;code>[Obj]&lt;/code>s as our input. &lt;code>animate&lt;/code> accepts a series of cubes, a series of viewpoints, and a series of filenames:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="274138569" type="checkbox" />
&lt;label for="274138569">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
animate :: [[Obj]] -&amp;gt; [(Float,Float)] -&amp;gt; [String] -&amp;gt; IO ()
animate cs vs fs = mapM_ (\(c,v,f) -&amp;gt; writePng f $ toImage c v) $ zip3 cs vs fs
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>This is great. Let&amp;rsquo;s call something like:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="371826945" type="checkbox" />
&lt;label for="371826945">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
main = do
let frames = map (map toTile) $ reverse $ scanr twist solvedCube moves
let views = repeat isometric
let filenames = map (\i -&amp;gt; &amp;#34;/tmp/frame&amp;#34; &amp;#43;&amp;#43; show i &amp;#43;&amp;#43; &amp;#34;.png&amp;#34;) [100000..]
animate frames views filenames
&lt;/code>&lt;/pre>
&lt;/div>
&lt;figure class="center" >
&lt;img src="images/simple.gif" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>We can see the turns a bit more clearly if we explode the cube by tuning up &lt;code>n&lt;/code>, as noted above:&lt;/p>
&lt;figure class="center" >
&lt;img src="images/simple-exploded.gif" style="border-radius: 8px;" />
&lt;/figure>
&lt;h2 id="animating-motion">Animating Motion&lt;/h2>
&lt;p>This is pretty good, but the whole point of a real isometric projection library is the ability to draw tiles in places other than along x-y, x-z, or y-z planes. Let&amp;rsquo;s try tweening frames.&lt;/p>
&lt;p>Tweening is a concept from animation wherein, given a start and end position and a desired number of inbetween frames, we can generate those inbetween frames. This is a little like interpolation, but there are lots of &lt;a href="http://easings.net/">different easing curves&lt;/a> out there. For now we&amp;rsquo;ll stick with linear motion, though.&lt;/p>
&lt;p>We want &lt;code>tween&lt;/code> to take not a beginning- and end-state cube, but instead a beginning-state cube and a &lt;code>Side&lt;/code> to turn (and a number of frames across which to turn it.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="382451679" type="checkbox" />
&lt;label for="382451679">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
tween :: Int -&amp;gt; Side -&amp;gt; Cube -&amp;gt; [[Obj]]
tween num side cube = map (`swivel` cube) angles
where (cardinality, axis) = s2CA side
angles = map (* toAngle cardinality) $ init
$ map (/ fromIntegral num) [0..(fromIntegral num)]
swivel :: Float -&amp;gt; Cube -&amp;gt; [Obj]
swivel ang = map (\t -&amp;gt; if side `sees` t
then rotateFace ang axis $ toTile t
else toTile t
)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>&lt;code>swivel&lt;/code> is just a local implementation of &lt;code>twist&lt;/code> above, with an inline renderer (toTile) which returns &lt;code>[Obj]&lt;/code>s instead of &lt;code>Cube&lt;/code>s. We generate a sequence of angles by using &lt;code>toAngle&lt;/code> from above, and then simply return a series of cubes with more and more of the desired twist angle applied to it.&lt;/p>
&lt;p>Now our &lt;code>frames&lt;/code> looks like:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="981475263" type="checkbox" />
&lt;label for="981475263">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
let frames r = concatMap (uncurry $ tween r)
$ zip (reverse moves) (reverse $ scanr twist solvedCube moves)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>where &lt;code>r&lt;/code> the framerate, sort of - the number of frames across each turn should occur.&lt;/p>
&lt;p>If we write something like&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="594783261" type="checkbox" />
&lt;label for="594783261">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
main = do
let frames = concatMap (uncurry $ tween 20)
$ zip (reverse moves) (reverse $ scanr twist solvedCube moves)
let views = repeat isometric
let filenames = map (\i -&amp;gt; &amp;#34;/tmp/frame&amp;#34; &amp;#43;&amp;#43; show i &amp;#43;&amp;#43; &amp;#34;.png&amp;#34;) [100000..]
animate frames views filenames
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>We might get something like&lt;/p>
&lt;figure class="center" >
&lt;img src="images/motion.gif" style="border-radius: 8px;" />
&lt;/figure>
&lt;h2 id="roving-camera">Roving Camera&lt;/h2>
&lt;p>Just for fun, let&amp;rsquo;s create a series of &lt;code>views&lt;/code> which, knowing the length of the frameset, does a complete yaw rotation. (Also we&amp;rsquo;re retuning &lt;code>n=3&lt;/code> here for a more exploded view.)&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="748153269" type="checkbox" />
&lt;label for="748153269">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
mkViews :: Int -&amp;gt; [(Float,Float)]
mkViews n = zip ps ys
where ys = take n [0, (360 / fromIntegral n).. ]
ps = [35,35..]
&lt;/code>&lt;/pre>
&lt;/div>
&lt;div class="collapsable-code">
&lt;input id="192875346" type="checkbox" />
&lt;label for="192875346">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
main = do
...
let views = mkViews (length frames)
animate frames views filenames
&lt;/code>&lt;/pre>
&lt;/div>
&lt;figure class="center" >
&lt;img src="images/motion-2.gif" style="border-radius: 8px;" />
&lt;/figure>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>These are all pretty fast to render, with the bulk of the difficulty being from running &lt;code>convert&lt;/code> after a run to stitch the produced &lt;code>.png&lt;/code>s together into a gif. Presumably &lt;code>convert&lt;/code> holds the frames in-memory, which gets expensive for longer animations; I found that using &lt;code>Codec.Picture&lt;/code> to do the same thing in-Haskell was equally or more expensive.&lt;/p></content></item><item><title>Cornea, an Isometric 3D Graphing Module for Haskell</title><link>https://jbuckland.com/blog/library-cornea/</link><pubDate>Wed, 30 Aug 2017 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/library-cornea/</guid><description>Github http://github.com/ambuc/cornea While working on the rendering my solution for the Irreversible Pocket Cube, I ended up writing a faux isometric rendering engine which could turn my specific Rubix Cube datastructure into something like an isometric projection of that cube. This my attempt to do it the right way.
The full code lives at ambuc/Cornea.
Mathematics To quote Wikipedia,
$$ \begin{bmatrix} b_x \\ b_y \\ 0 \end{bmatrix} = \begin{bmatrix} 1 &amp;amp; 0 &amp;amp; 0 \\ 0 &amp;amp; 1 &amp;amp; 0 \\ 0 &amp;amp; 0 &amp;amp; 0 \end{bmatrix} \begin{bmatrix} 1 &amp;amp; 0 &amp;amp; 0 \\ 0 &amp;amp; \cos\alpha &amp;amp; \sin\alpha \\ 0 &amp;amp; -\sin\alpha &amp;amp; \cos\alpha \end{bmatrix} \begin{bmatrix} \cos\beta &amp;amp; 0 &amp;amp; -\sin\beta \\ 0 &amp;amp; 1 &amp;amp; 0 \\ \sin\beta &amp;amp; 0 &amp;amp; \cos\beta \end{bmatrix} \begin{bmatrix} a_x \\ a_y \\ a_z \end{bmatrix} $$</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">Github&lt;/th>
&lt;td> &lt;a href="http://github.com/ambuc/cornea">http://github.com/ambuc/cornea&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;p>While working on the rendering my solution for the &lt;a href="http://jbuckland.com/irreversible/">Irreversible Pocket Cube&lt;/a>, I ended up writing a faux isometric rendering engine which could turn my specific Rubix Cube datastructure into something like an isometric projection of that cube. This my attempt to do it the right way.&lt;/p>
&lt;p>The full code lives at &lt;a href="https://github.com/ambuc/cornea">ambuc/Cornea&lt;/a>.&lt;/p>
&lt;h2 id="mathematics">Mathematics&lt;/h2>
&lt;p>To quote &lt;a href="https://en.wikipedia.org/wiki/Isometric_projection">Wikipedia&lt;/a>,&lt;/p>
&lt;p>$$
\begin{bmatrix} b_x \\ b_y \\ 0 \end{bmatrix} =
\begin{bmatrix} 1 &amp;amp; 0 &amp;amp; 0 \\ 0 &amp;amp; 1 &amp;amp; 0 \\ 0 &amp;amp; 0 &amp;amp; 0 \end{bmatrix}
\begin{bmatrix} 1 &amp;amp; 0 &amp;amp; 0 \\ 0 &amp;amp; \cos\alpha &amp;amp; \sin\alpha \\ 0 &amp;amp; -\sin\alpha &amp;amp;
\cos\alpha \end{bmatrix}
\begin{bmatrix} \cos\beta &amp;amp; 0 &amp;amp; -\sin\beta \\ 0 &amp;amp; 1 &amp;amp; 0 \\ \sin\beta &amp;amp; 0 &amp;amp;
\cos\beta \end{bmatrix}
\begin{bmatrix} a_x \\ a_y \\ a_z \end{bmatrix}
$$&lt;/p>
&lt;p>where $\alpha$ and $\beta$ are the pitch and yaw of the camera angle; that is, how far from the equator and meridian in spherical coordinates our viewpoint resides, if the center of the scene is at the origin. This projection matrix works for any 3d point $ \begin{bmatrix} a_x &amp;amp; a_y &amp;amp; a_z \end{bmatrix}^\intercal $ and results in 2d coordinates $ \begin{bmatrix} b_x &amp;amp; b_y &amp;amp; 0 \end{bmatrix}^\intercal $.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="238965471" type="checkbox" />
&lt;label for="238965471">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
metric :: (Float, Float) -&amp;gt; Matrix Float
metric (p, w) = m1 * m2
where m1 = fromList 3 3 [1, 0, 0, 0, 1, 0, 0, 0, 0]
m2 = fromList 3 3 [1, 0, 0, 0, cos a, sin a, 0, -sin a, -cos a]
* fromList 3 3 [cos b, 0, -sin b, 0, 1, 0, sin b, 0, cos b]
where a = p * pi / 180; b = w * pi * 2 / 360
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>That&amp;rsquo;s really all the projection math we need. The name &lt;code>metric&lt;/code> is sort of a play on &lt;em>isometric&lt;/em>, except the function is extensible and can return a transformation matrix for any viewing angle, not just the default $(35.264,45)$ isometric viewing angle.&lt;/p>
&lt;h2 id="describing-the-world">Describing the World&lt;/h2>
&lt;div class="collapsable-code">
&lt;input id="537619248" type="checkbox" />
&lt;label for="537619248">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
data Obj = Cord [Float] | Edge [[Float]] | Face [[Float]] deriving (Eq, Show)
type Style = ([Primitive] -&amp;gt; Drawing PixelRGBA8 ())
type World = [(Obj, Style)]
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Let&amp;rsquo;s define an &lt;code>Obj&lt;/code> object as either&lt;/p>
&lt;ul>
&lt;li>a single point, a &lt;code>[Float]&lt;/code> list of three floating point numbers describing its $(x,y,z)$ coordinates, or&lt;/li>
&lt;li>a list of points describing a line or polyline, or&lt;/li>
&lt;li>a list of points describing a face&lt;/li>
&lt;/ul>
&lt;h3 id="styling-the-world">Styling the World&lt;/h3>
&lt;p>We want to be able to &lt;code>style&lt;/code> each of these objects. Because I like &lt;code>Graphics.Rasterific&lt;/code>, we&amp;rsquo;ll use their types and define &lt;code>style&lt;/code> as a mapping between a &lt;code>[Primitive]&lt;/code> and a final &lt;code>Drawing px ()&lt;/code>. In practice we would use the builtin &lt;code>withTexture (uniformTexture &amp;lt;color&amp;gt;) . fill&lt;/code> for a solid fill, or &lt;code>withTexture (uniformTexture &amp;lt;color&amp;gt;) . stroke &amp;lt;width&amp;gt; JoinRound (CapRound, CapRound)&lt;/code> for a stroke (used for a line, polyline, or point). In practice it would be nice to provide synonyms in the form of &lt;code>solid k&lt;/code> and &lt;code>mark k n&lt;/code>.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="762513849" type="checkbox" />
&lt;label for="762513849">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
solid :: Geometry geom =&amp;gt; PixelRGBA8 -&amp;gt; geom -&amp;gt; Drawing PixelRGBA8 ()
solid k = withTexture (uniformTexture k) . fill
mark :: Geometry geom =&amp;gt; PixelRGBA8 -&amp;gt; Float -&amp;gt; geom -&amp;gt; Drawing PixelRGBA8 ()
mark k n = withTexture (uniformTexture k)
. stroke n JoinRound (CapRound, CapRound)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h2 id="projecting-an-object-onto-a-drawing">Projecting an Object onto a Drawing&lt;/h2>
&lt;p>Let&amp;rsquo;s define a convenient type synonym &lt;code>View&lt;/code> for our pitch/yaw tuple:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="698754321" type="checkbox" />
&lt;label for="698754321">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
type View = (Float,Float)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>and write a &lt;code>proj v &amp;lt;obj&amp;gt;&lt;/code> function which can take a coordinate and project it into the plane.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="916823547" type="checkbox" />
&lt;label for="916823547">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
proj :: (Float,Float) -&amp;gt; Obj -&amp;gt; V2 Float
proj v (Cord [x,y,z]) = (\[x,y] -&amp;gt; V2 x y) $ take 2 $ toList
$ metric v * Data.Matrix.transpose (fromList 1 3 [y,-z,x])
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Great. Now we can plot a coordinate by simply projecting it into the plane. We should be able to plot a line or polyline by projecting each of its coordinates, and a face (or polygon) the same way.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="165234978" type="checkbox" />
&lt;label for="165234978">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
drawFrom :: (Obj, [Primitive] -&amp;gt; t) -&amp;gt; (Float, Float) -&amp;gt; t
(Cord coord, sty) `drawFrom` v = sty $ circle (proj v $ Cord coord) 0.5
(Edge pts , sty) `drawFrom` v = sty $ polyline $ map (proj v . Cord) pts
(Face pts , sty) `drawFrom` v = sty $ polygon $ map (proj v . Cord) pts
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Very cool. Let&amp;rsquo;s construct a sample &lt;code>world&lt;/code> and try writing it to a &lt;code>.png&lt;/code>.&lt;/p>
&lt;h2 id="writing-to-png">Writing to &lt;code>.png&lt;/code>&lt;/h2>
&lt;p>Here&amp;rsquo;s our calibration world:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="143586729" type="checkbox" />
&lt;label for="143586729">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
calibWorld :: World
calibWorld = [ ( Edge [[0,0,0], [15, 0, 0]] , mark (PixelRGBA8 255 100 100 255) 1)
, ( Edge [[0,0,0], [ 0,15, 0]] , mark (PixelRGBA8 100 255 100 255) 2)
, ( Edge [[0,0,0], [ 0, 0,15]] , mark (PixelRGBA8 100 100 255 255) 3)
]
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Now we need a function which can turn a list of these &lt;code>(Obj,Style)&lt;/code> tuples into a single &lt;code>Drawing px ()&lt;/code> item. We really want to map &lt;code>drawFrom&lt;/code> over each of them, and then &lt;code>sequence_ []&lt;/code> the list; we can write that as &lt;code>mapM_&lt;/code> instead, which maps a monad over a list of inputs, discarding the intermediate results along the way.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="541398276" type="checkbox" />
&lt;label for="541398276">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
seenFrom :: World -&amp;gt; (Float,Float) -&amp;gt; Drawing PixelRGBA8 ()
world `seenFrom` v = mapM_ (`drawFrom` v) world
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>The very final piece of our puzzle is something which takes a &lt;code>Drawing px ()&lt;/code> and actually turns it into an &lt;code>Image px&lt;/code>, which can be written to &lt;code>.png&lt;/code> with Rasterific&amp;rsquo;s &lt;code>writePng&lt;/code>.&lt;/p>
&lt;p>We&amp;rsquo;ll need to supply &lt;code>render&lt;/code> with a width/height/scale&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="637294581" type="checkbox" />
&lt;label for="637294581">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
render :: Int -&amp;gt; Int -&amp;gt; Float -&amp;gt; Drawing PixelRGBA8 () -&amp;gt; Image PixelRGBA8
render x y s d = renderDrawing x y (PixelRGBA8 255 255 255 255)
$ withTransformation ( translate (V2 (fromIntegral x / 2)
(fromIntegral y / 2)
) &amp;lt;&amp;gt; scale s s
) d
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Great. Let&amp;rsquo;s write this &lt;code>Drawing px ()&lt;/code> out to an &lt;code>Image px ()&lt;/code> and eventually to a file &lt;code>IO ()&lt;/code>.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="463719825" type="checkbox" />
&lt;label for="463719825">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
main = writePng &amp;#34;canvas.png&amp;#34; $ render 500 500 12 $ calibWorld `seenFrom` (35,45)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;figure class="center" >
&lt;img src="images/calibWorld.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>That&amp;rsquo;s super! Let&amp;rsquo;s try something more complicated: define &lt;code>myWorld&lt;/code> to be a bunch of intersecting squares along the x-y and y-z planes:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="128745396" type="checkbox" />
&lt;label for="128745396">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
myWorld :: World
myWorld = [ ( Face [[0,0,0], [0, 10,0], [ 10, 10,0], [ 10,0,0] ] , solid $ PixelRGBA8 255 100 0 255)
, ( Face [[0,0,0], [0,-10,0], [ 10,-10,0], [ 10,0,0] ] , solid $ PixelRGBA8 255 120 0 255)
, ( Face [[0,0,0], [0,-10,0], [-10,-10,0], [-10,0,0] ] , solid $ PixelRGBA8 255 140 0 255)
, ( Face [[0,0,0], [0, 10,0], [-10, 10,0], [-10,0,0] ] , solid $ PixelRGBA8 255 160 0 255)
, ( Face [[0,0,0], [ 10,0,0], [ 10,0, 10], [0,0, 10] ] , solid $ PixelRGBA8 255 180 0 255)
, ( Face [[0,0,0], [-10,0,0], [-10,0, 10], [0,0, 10] ] , solid $ PixelRGBA8 255 200 0 255)
, ( Face [[0,0,0], [-10,0,0], [-10,0,-10], [0,0,-10] ] , solid $ PixelRGBA8 255 220 0 255)
, ( Face [[0,0,0], [ 10,0,0], [ 10,0,-10], [0,0,-10] ] , solid $ PixelRGBA8 255 240 0 255)
]
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>And render it as before:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="291538647" type="checkbox" />
&lt;label for="291538647">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
main = writePng &amp;#34;canvas.png&amp;#34; $ render 500 500 12
$ (calibWorld&amp;#43;&amp;#43;myWorld) `seenFrom` (35,45)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;figure class="center" >
&lt;img src="images/calibWorld2.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>Uh-oh. We&amp;rsquo;re running into &lt;a href="https://en.wikipedia.org/wiki/Visibility_(geometry)">the visibility problem&lt;/a>, wherein we don&amp;rsquo;t know which order to render the faces in so that the things nearer the camera would be visible over the things farther from the camera.&lt;/p>
&lt;h2 id="the-visibility-problem">The Visibility Problem&lt;/h2>
&lt;p>There are a &lt;em>bunch&lt;/em> of ways to solve this problem, but I ended up computing a vector from the origin to the centroid of each object, defining a vector from the origin to the position of the camera, and ranking the objects by the &lt;a href="https://en.wikipedia.org/wiki/Scalar_projection">scalar projection&lt;/a> of each object&amp;rsquo;s centroid-vector onto the camera-vector.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="523719684" type="checkbox" />
&lt;label for="523719684">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
scalarProject u v = dot v (unit u)
where dot a b = sum $ zipWith (*) a b
unit n = map (/ norm n) n
norm = sqrt . sum . map (^2)
avgPts :: [[Float]] -&amp;gt; [Float]
avgPts = map (\xs -&amp;gt; realToFrac (sum xs) / genericLength xs)
. Data.List.transpose
centroid :: Obj -&amp;gt; [Float]
centroid (Cord coord) = avgPts [coord]
centroid (Edge pts ) = avgPts pts
centroid (Face pts ) = avgPts pts
closeness :: (Float,Float) -&amp;gt; [Float] -&amp;gt; Float
closeness (p,w) [x, y, z] = scalarProject (toBaseline p w) [x,y,z]
where toBaseline :: Float -&amp;gt; Float -&amp;gt; [Float] -- pitch, yaw in degrees
toBaseline p w = [cos theta * sin phi, sin theta * sin phi, cos phi]
where theta = w * c; phi = (90-p) * c; c = pi / 180;
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Now, instead of just &lt;code>mapM_ (`drawFrom` v) world&lt;/code> we can write:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="968234715" type="checkbox" />
&lt;label for="968234715">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
seenFrom :: World -&amp;gt; (Float,Float) -&amp;gt; Drawing PixelRGBA8 ()
world `seenFrom` v = mapM_ (`drawFrom` v)
$ sortBy (comparing $ closeness v . centroid . fst) world
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Which uses a &lt;code>Data.List&lt;/code> builtin &lt;code>sortBy (comparing &amp;lt;comparison_fn&amp;gt;) list&lt;/code> to reorder a list according a custom &lt;code>comparison_fn&lt;/code>. You could write one like &lt;code>(\a b -&amp;gt; if a &amp;gt; b then a else b)&lt;/code>, or we could use &lt;code>comparing&lt;/code> to rank by an implicit function of each item.&lt;/p>
&lt;p>In this case, that implicit function is &lt;code>closeness v . centroid . fst&lt;/code>, which takes each &lt;code>(Obj, Style)&lt;/code> tuple, extracts the &lt;code>Obj&lt;/code>, finds its centroid, and finds the closeness by calculating the scalar product of that centroid-origin vector with the viewpoint-origin vector.&lt;/p>
&lt;p>This gets us something a little more reasonable:&lt;/p>
&lt;figure class="center" >
&lt;img src="images/calibWorld3.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;h2 id="animation">Animation&lt;/h2>
&lt;p>This is pretty cool, but what if we want to animate our world?&lt;/p>
&lt;p>Let&amp;rsquo;s define a series of pitches and yaws to make a cool wobbling sweep around our world. Again, &lt;code>mapM_&lt;/code> lets us map our &lt;code>writePng&lt;/code> function over a list of 3-tuples containing the pitch, yaw, and frame number. We can write them out to &lt;code>/tmp/canvas*.png&lt;/code>&amp;hellip;&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="175964283" type="checkbox" />
&lt;label for="175964283">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
pitches = ( map (\x -&amp;gt; 20 * (sin (x * pi / 12) &amp;#43; 1.5)) [0,1..] )
yaws = [0,5..355]
main = do
mapM_ (\(p,w,i) -&amp;gt; writePng (&amp;#34;/tmp/canvas&amp;#34; &amp;#43;&amp;#43; show i &amp;#43;&amp;#43; &amp;#34;.png&amp;#34;)
$ render 500 500 $ (calibWorld&amp;#43;&amp;#43;myWorld) `seenFrom` (p,w)
) $ zip3 pitches yaws [10..]
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>&amp;hellip;and compose them together into a &lt;code>.gif&lt;/code> later with &lt;code>convert -loop 0 /tmp/canvas* \&amp;lt;filename\&amp;gt;&lt;/code>.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/wobbly1.gif" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>Here&amp;rsquo;s that contrasted with an animation from before we solved the visibility problem:&lt;/p>
&lt;figure class="center" >
&lt;img src="images/wobbly2.gif" style="border-radius: 8px;" />
&lt;/figure>
&lt;h2 id="animating-with-codecpicturegif">Animating with &lt;code>Codec.Picture.Gif&lt;/code>&lt;/h2>
&lt;p>&lt;code>Codec.Picture&lt;/code> supports assembling &lt;code>Image&lt;/code>s into gifs in Haskell, without having to write the frames out to a file and assembling them with &lt;code>convert&lt;/code>. It&amp;rsquo;s a lot slower, though. I&amp;rsquo;ll present it here just out of interest, but in practice I found it anywhere from 8x-10x slower.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="723915846" type="checkbox" />
&lt;label for="723915846">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
import Codec.Picture
import Codec.Picture.Gif
import Codec.Picture.Types
import Cornea -- this is our module! If you check out the repo
-- at the very top of the page, you can see how we wrap it into
-- an importable module.
-- same calibWorld and myWorld as above
options :: PaletteOptions
options = PaletteOptions { paletteCreationMethod=MedianMeanCut, enableImageDithering=True, paletteColorCount=256 }
main = head $ rights
$ [ writeGifImages &amp;#34;animation.gif&amp;#34; LoopingForever
$ map ( (\(i,p) -&amp;gt; (p,2,i)) --arbitrary framerate
. (\(p,w) -&amp;gt; palettize options
$ dropAlphaLayer
$ render 500 500 12 $ (calibWorld&amp;#43;&amp;#43;myWorld) `seenFrom` (p,w)
)
) $ zip ( map (\x -&amp;gt; 20 * (sin (x * pi / 120) &amp;#43; 1.5)) [0,1..] )
[0,1..359]
]
&lt;/code>&lt;/pre>
&lt;/div>
&lt;figure class="center" >
&lt;img src="images/codec-animated.gif" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>This took ~3min and I see a bit of flickering &amp;ndash; probably because the command-line &lt;code>convert&lt;/code> utility understands color and framerate better than I do.&lt;/p>
&lt;h2 id="conclusions">Conclusions&lt;/h2>
&lt;p>This is a neat little library, just powerful enough to handle the matrix multiplication necessary to take a scene depicted in raw coordinates and render it in a useful way. I have no idea what its performance will be like for a larger scene, but I&amp;rsquo;ll continue to use it in the future and we&amp;rsquo;ll see what sorts of optimizations can be made.&lt;/p>
&lt;p>I&amp;rsquo;ve put it on Github here: &lt;a href="https://github.com/ambuc/cornea">ambuc/cornea&lt;/a> in the hopes that someone else might get a kick out of it.&lt;/p></content></item><item><title>Puzzle Pong III - Irreversible Cube I</title><link>https://jbuckland.com/blog/puzzle-irreversible/</link><pubDate>Tue, 22 Aug 2017 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/puzzle-irreversible/</guid><description>Github https://gist.github.com/ambuc/84fca250c25f72b46a16dd28eb653708 The Puzzle Josh Mermelstein and I have decided to begin challenging each other to a series of a math/programming puzzles. Here was his most recent puzzle:
Oskar van Deventer presents an Irreversible Cube in his 2013 video which is an ordinary 2x2x2 Rubix Cube where you are only allowed to turn any face clockwise, and only once in a row.
(1) Give this cube a single twist from solved position.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">Github&lt;/th>
&lt;td> &lt;a href="https://gist.github.com/ambuc/84fca250c25f72b46a16dd28eb653708">https://gist.github.com/ambuc/84fca250c25f72b46a16dd28eb653708&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;h2 id="the-puzzle">The Puzzle&lt;/h2>
&lt;p>&lt;a href="http://joshmermelstein.com/">Josh Mermelstein&lt;/a> and I have decided to begin
challenging each other to a series of a math/programming puzzles. Here was his
most recent puzzle:&lt;/p>
&lt;blockquote>
&lt;p>&lt;a href="https://www.youtube.com/user/OskarPuzzle/">Oskar van Deventer&lt;/a> presents an Irreversible Cube in his &lt;a href="https://www.youtube.com/watch?v=eMGeKdog8Ws">2013 video&lt;/a> which is an ordinary 2x2x2 Rubix Cube where you are only allowed to turn any face clockwise, and only once in a row.&lt;/p>
&lt;p>(1) Give this cube a single twist from solved position. How many twists should
it take, at minimum, to return to a solved position?&lt;/p>
&lt;p>(2) For this cube, what is &lt;a href="https://en.wikipedia.org/wiki/God%27s_algorithm">God&amp;rsquo;s Number?&lt;/a>&lt;/p>
&lt;p>I&amp;rsquo;ll address &lt;em>1)&lt;/em> in this post and &lt;em>2)&lt;/em> in another.&lt;/p>
&lt;/blockquote>
&lt;figure class="center" >
&lt;img src="images/cube1.jpg" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>I didn&amp;rsquo;t think that the math involved in representing the cube, performing turns on it, or validating its solved-ness should be very difficult. What concerned me was difficulty in debugging these operations in the first place. Without a visualizer, it would be hard to know that my &lt;code>turn&lt;/code> was working as intended. I wanted to build an engine which could take my representation of a cube and draw it, preferably in a way close to its actual 3d appearance.&lt;/p>
&lt;h2 id="representing-a-cube">Representing a Cube&lt;/h2>
&lt;p>We represent a cube as an ordered list of points in 3d space. Implicitly, the first four are red, the second four are yellow, etc. This makes it easy to perform twists on the cube. Half the list will be on one side or another of the x, y, or z-plane, and they can be rotated about some axis while remaining put in the list.&lt;/p>
&lt;p>A solved cube looks like this:&lt;/p>
&lt;pre>&lt;code>[ [ 1, 1, 2], [ 1,-1, 2], [-1, 1, 2], [-1,-1, 2]
, [ 1, 1,-2], [ 1,-1,-2], [-1, 1,-2], [-1,-1,-2]
, [ 1, 2, 1], [ 1, 2,-1], [-1, 2, 1], [-1, 2,-1]
, [ 1,-2, 1], [ 1,-2,-1], [-1,-2, 1], [-1,-2,-1]
, [ 2, 1, 1], [ 2, 1,-1], [ 2,-1, 1], [ 2,-1,-1]
, [-2, 1, 1], [-2, 1,-1], [-2,-1, 1], [-2,-1,-1]
]
&lt;/code>&lt;/pre>
&lt;p>Our coordinate axis is such that the very center of the cube is $(0,0,0)$, and each tile is of dimensions $2\times 2\times 0$, such that the cube is in total $4\times 4\times 4$ in dimension. This places the centers of the tiles on the faces of the subcubes at points like $(1,1,2)$; all four tiles on a the top face would have centers $(\pm 1, \pm 1, 2)$, and the four tiles on the bottom face would have centers $(\pm 1, \pm 1, -2)$.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="486532179" type="checkbox" />
&lt;label for="486532179">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
type Coord = [Int]
type Tile = Coord -- convenient synonym
type Cube = [Tile]
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h2 id="drawing-a-cube">Drawing a Cube&lt;/h2>
&lt;p>Because the &lt;code>cube&lt;/code> type carries no color information with it, the way we draw the cube is up to us. For ease of visualizing which unique tiles are which (telling apart two otherwise-identical red tiles, for example) I&amp;rsquo;ve chosen to draw each tile at a slightly different hue, generally grouped by the uniform colors a real cube might have.&lt;/p>
&lt;p>I&amp;rsquo;m using the excellent &lt;a href="https://hackage.haskell.org/package/Rasterific-0.7.2.1/docs/Graphics-Rasterific.html">&lt;code>Graphics.Rasterific&lt;/code>&lt;/a> package, which I&amp;rsquo;ve used &lt;a href="https://gist.github.com/ambuc/a901bf18fb034a5078a46f7cfe0738b5">before&lt;/a>.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="613987542" type="checkbox" />
&lt;label for="613987542">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
import Codec.Picture
( PixelRGBA8( .. ), writePng, Image)
import Graphics.Rasterific
import Graphics.Rasterific.Texture
(uniformTexture)
import Graphics.Rasterific.Transformations
(translate, skewX, skewY, rotate, scale)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>I define some &lt;code>kolors&lt;/code> which are cute little palettes of similar hues.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="384125769" type="checkbox" />
&lt;label for="384125769">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
kolors :: [PixelRGBA8]
kolors = [ PixelRGBA8 255 x 0 255 | x &amp;lt;- [000,050,100,150] ] -- reds
&amp;#43;&amp;#43; [ PixelRGBA8 x 0 128 255 | x &amp;lt;- [000,050,100,128] ] -- purples
&amp;#43;&amp;#43; [ PixelRGBA8 0 x 255 255 | x &amp;lt;- [000,050,100,128] ] -- cyans
&amp;#43;&amp;#43; [ PixelRGBA8 x 255 0 255 | x &amp;lt;- [150,170,190,210] ] -- greens
&amp;#43;&amp;#43; [ PixelRGBA8 255 0 x 255 | x &amp;lt;- [255,200,150,100] ] -- pinks
&amp;#43;&amp;#43; [ PixelRGBA8 x x x 255 | x &amp;lt;- [100,125,150,175] ] -- greys
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>And of course the &lt;code>solvedCube&lt;/code> itself:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="326519847" type="checkbox" />
&lt;label for="326519847">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
solvedCube :: Cube
solvedCube = [ [ a, b, 2] | a &amp;lt;- [1,-1], b &amp;lt;- [1,-1] ] -- F
&amp;#43;&amp;#43; [ [ a, b,-2] | a &amp;lt;- [1,-1], b &amp;lt;- [1,-1] ] -- B
&amp;#43;&amp;#43; [ [ a, 2, b] | a &amp;lt;- [1,-1], b &amp;lt;- [1,-1] ] -- R
&amp;#43;&amp;#43; [ [ a,-2, b] | a &amp;lt;- [1,-1], b &amp;lt;- [1,-1] ] -- L
&amp;#43;&amp;#43; [ [ 2, a, b] | a &amp;lt;- [1,-1], b &amp;lt;- [1,-1] ] -- U
&amp;#43;&amp;#43; [ [-2, a, b] | a &amp;lt;- [1,-1], b &amp;lt;- [1,-1] ] -- D
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Here&amp;rsquo;s the meat and potatoes.&lt;/p>
&lt;p>Our drawing style is inspired by the following exploded isometric drawing, which I accidentally stole from a tutorial for &lt;a href="https://msdn.microsoft.com/en-us/library/windows/desktop/ff476906">Direct3D 11&lt;/a>&lt;/p>
&lt;figure class="center" >
&lt;img src="images/mipmap.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>This is roughly isometric, which means we&amp;rsquo;ll need to concern ourselves with which tile to draw first, so that nearer tiles are layered over (drawn later than) further tiles.&lt;/p>
&lt;p>To draw a cube (which is just an ordered list of 3d points, the centers of the 24 tiles), we zip it with the list of kolors, and then re-order the entire list by z-index, which we approximate by the sum of the three coordinates themselves. (the &lt;code>sortBy (comparing $ sum . fst)&lt;/code> bit.). Then we &lt;code>uncurry drawTile&lt;/code>, which takes each element of the &lt;code>[(coordinate, color)]&lt;/code> list of tuples, calls &lt;code>drawTile crd clr&lt;/code>, and then sweeps thru the list calling &lt;code>mapM_&lt;/code>, which composes the monadic &lt;code>Drawing PixelRGBA8 ()&lt;/code> I/O type nicely.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="826957413" type="checkbox" />
&lt;label for="826957413">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
:t renderDrawing Int -&amp;gt; Int -&amp;gt; px -&amp;gt; Drawing px () -&amp;gt; Image px
:t writePng FilePath -&amp;gt; Image px -&amp;gt; IO ()
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>We get a final &lt;code>Drawing * ()&lt;/code> which we can &lt;code>renderDrawing &amp;lt;drawing&amp;gt;&lt;/code> to turn into an &lt;code>Image *&lt;/code>, and finally &lt;code>writePng &amp;lt;filepath&amp;gt; &amp;lt;image&amp;gt;&lt;/code>. That&amp;rsquo;s just how &lt;code>Rasterific&lt;/code> handles things.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="472613598" type="checkbox" />
&lt;label for="472613598">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
drawCube :: Cube -&amp;gt; Image PixelRGBA8
drawCube c = renderDrawing 600 600 (PixelRGBA8 255 255 255 255)
$ mapM_ (uncurry drawTile)
$ sortBy (comparing $ sum . fst)
$ zip c kolors
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>OK, here&amp;rsquo;s the crazy bit. We take a coordinate &lt;code>crd&lt;/code> (and a color &lt;code>clr&lt;/code> to shade it, draw that as a $100x100$ pixel square in the upper-left-hand corner of the canvas, and then use a composition of several &lt;code>translate&lt;/code> and &lt;code>scale&lt;/code> and &lt;code>rotate&lt;/code> and &lt;code>skewX&lt;/code> / &lt;code>skewY&lt;/code> linear matrix transformations to move the tile into the position we want it.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/animation-manip.gif" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>We compose these transformations by calling functions &lt;code>resize&lt;/code>, &lt;code>move0&lt;/code>, &lt;code>turn&lt;/code>, &lt;code>skew&lt;/code>, &lt;code>move1&lt;/code> as functions of &lt;code>[x,y,z]&lt;/code>, which know by &lt;em>how much&lt;/em> to resize, move, turn, skew, etc. each individual tile based on its desired eventual location, angle, what have you.&lt;/p>
&lt;p>It would be cool to in the future write a simple projection library which can take a 3d tuple and an assumed camera position and generate this transformation automatically. I did a bit of hand-tuning.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="938761524" type="checkbox" />
&lt;label for="938761524">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
drawTile :: Tile -&amp;gt; PixelRGBA8 -&amp;gt; Drawing PixelRGBA8 ()
drawTile crd clr = withTransformation (center&amp;lt;&amp;gt;resize&amp;lt;&amp;gt;move0&amp;lt;&amp;gt;turn&amp;lt;&amp;gt;skew&amp;lt;&amp;gt;move1)
$ withTexture (uniformTexture clr)
$ fill $ rectangle (V2 0 0) 100 100
where [x ,y ,z ] = crd
[x&amp;#39;,y&amp;#39;,z&amp;#39;] = [fromIntegral x, fromIntegral y, fromIntegral z]
center = translate (V2 300 300)
resize | abs z == 2 = scale 1.0 1.0
| otherwise = scale 0.9 0.9
move0 | x == 2 = translate (V2 (-250) 50 ) --F
| x == -2 = translate (V2 150 (-200)) --B
| y == 2 = translate (V2 150 120 ) --R
| y == -2 = translate (V2 (-250) (-140)) --L
| z == 2 = translate (V2 0 (-230)) --U
| z == -2 = translate (V2 0 120 ) --D
| otherwise = translate (V2 0 0 )
turn | abs z == 2 = rotate 0.7853
| otherwise = rotate 0
skew | abs z == 2 = skewX (-0.2) &amp;lt;&amp;gt; skewY (-0.2)
| abs y == 2 = skewY (-0.6)
| abs x == 2 = skewY 0.6
| otherwise = skewX 0
move1 | abs z == 2 = translate (V2 ( 50*y&amp;#39;) ( 50*x&amp;#39;))
| abs y == 2 = translate (V2 (-50*x&amp;#39;) (-50*z&amp;#39;))
| abs x == 2 = translate (V2 ( 50*y&amp;#39;) (-50*z&amp;#39;))
| otherwise = translate (V2 0 0 )
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>To print an image we can simply write:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="253847961" type="checkbox" />
&lt;label for="253847961">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
main = do
writePng &amp;#34;solved.png&amp;#34; $ drawCube solvedCube
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>And here&amp;rsquo;s what our solved cube looks like, finally.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/solved.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;h2 id="manipulation">Manipulation&lt;/h2>
&lt;p>There are basically two things you can do with a $2\times 2\times 2$ cube &amp;ndash; either turn the entire thing about an axis or twist exactly half of it about an axis.&lt;/p>
&lt;p>Let&amp;rsquo;s define some new data types to make thinking about this easier.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="316798452" type="checkbox" />
&lt;label for="316798452">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
data Side = F | B | U | D | L | R deriving (Eq, Bounded, Show, Enum, Ord)
data Axis = X | Y | Z
data Cardinality = Pos | Neg
type Rotation = (Coord -&amp;gt; Coord)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>At the most basic level, we want the ability to rotate a point clockwise or counter-clockwise exactly a quarter-turn (that is, $90^\circ$ or $\pi/2 \text{rad}$) about a line.&lt;/p>
&lt;h2 id="pivoting">Pivoting&lt;/h2>
&lt;p>There is a pretty general form for &lt;a href="https://en.wikipedia.org/wiki/Rotation_matrix#Basic_rotations">rotating a vector&lt;/a> any turn about any line. But rather than deal with $\sin$s and $\cos$s, let&amp;rsquo;s see if we can&amp;rsquo;t limit that transformation to only quarter-turns. Using a robotics convention, we will write $\sin\theta$ as $s\theta$ and $\cos\theta$ as $c\theta$ for brevity.&lt;/p>
&lt;p>$$
R_x(\theta) = \begin{bmatrix} 1&amp;amp;0&amp;amp;0 \\ 0&amp;amp;c\theta&amp;amp;-s\theta \\ 0&amp;amp;s\theta&amp;amp;c\theta\end{bmatrix}
\qquad
R_y(\theta) = \begin{bmatrix} c\theta&amp;amp;0&amp;amp;s\theta \\ 0&amp;amp;1&amp;amp;0 \\ -s\theta&amp;amp;0&amp;amp;c\theta \end{bmatrix}
\qquad
R_z(\theta) = \begin{bmatrix} c\theta&amp;amp;-s\theta&amp;amp;0 \\ s\theta&amp;amp;c\theta&amp;amp;0 \\ 0&amp;amp;0&amp;amp;1 \end{bmatrix}
$$&lt;/p>
&lt;p>We can apply this rotation matrix to a vector with basic matrix multiplication, like so:&lt;/p>
&lt;p>$$
\begin{bmatrix}a&amp;amp;b&amp;amp;c \\ d&amp;amp;e&amp;amp;f \\ g&amp;amp;h&amp;amp;i\end{bmatrix}
\begin{bmatrix}x \\ y \\ z\end{bmatrix} =
\begin{bmatrix}ax+by+cz \\ dx+ey+fz \\ gx+hy+iz\end{bmatrix}
$$&lt;/p>
&lt;p>So what if $\theta=\pm90^\circ$? We get much simpler forms for $\pm R_x$, $\pm R_y$, and $\pm R_z$:&lt;/p>
&lt;p>$$ &lt;br>
\begin{align*}
R_x (+90^\circ) &amp;amp;= \begin{bmatrix}1&amp;amp;0&amp;amp;0 \\ 0&amp;amp;0&amp;amp;-1 \\ 0&amp;amp;1&amp;amp;0\end{bmatrix} &amp;amp;
\qquad R_x (-90^\circ) &amp;amp;= \begin{bmatrix}1&amp;amp;0&amp;amp;0 \\ 0&amp;amp;0&amp;amp;1 \\ 0&amp;amp;-1&amp;amp;0\end{bmatrix} \\
R_y (+90^\circ) &amp;amp;= \begin{bmatrix}0&amp;amp;0&amp;amp;1 \\ 0&amp;amp;1&amp;amp;0 \\ -1&amp;amp;0&amp;amp;0\end{bmatrix} &amp;amp;
\qquad R_y (-90^\circ) &amp;amp;= \begin{bmatrix}0&amp;amp;0&amp;amp;-1 \\ 0&amp;amp;1&amp;amp;0 \\ 1&amp;amp;0&amp;amp;0\end{bmatrix} \\
R_z (+90^\circ) &amp;amp;= \begin{bmatrix}0&amp;amp;-1&amp;amp;0 \\ 1&amp;amp;0&amp;amp;0 \\ 0&amp;amp;0&amp;amp;1\end{bmatrix} &amp;amp;
\qquad R_z (-90^\circ) &amp;amp;= \begin{bmatrix}0&amp;amp;1&amp;amp;0 \\ -1&amp;amp;0&amp;amp;0 \\ 0&amp;amp;0&amp;amp;1\end{bmatrix}
\end{align*}
$$&lt;/p>
&lt;p>If we apply these simplified transformation matrices to $$\begin{bmatrix}x&amp;amp;y&amp;amp;z\end{bmatrix}^\intercal$$, we get extremely efficient, simple vector transformation anonymous functions:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="819653247" type="checkbox" />
&lt;label for="819653247">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
mkRot :: Cardinality -&amp;gt; Axis -&amp;gt; Rotation
mkRot Pos X = \[x,y,z] -&amp;gt; [x,-z,y]; mkRot Neg X = \[x,y,z] -&amp;gt; [x,z,-y];
mkRot Pos Y = \[x,y,z] -&amp;gt; [z,y,-x]; mkRot Neg Y = \[x,y,z] -&amp;gt; [-z,y,x];
mkRot Pos Z = \[x,y,z] -&amp;gt; [-y,x,z]; mkRot Neg Z = \[x,y,z] -&amp;gt; [y,-x,z];
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Writing the &lt;code>pivot&lt;/code> function, which looks like &lt;code>newCube = pivot &amp;lt;cardinality&amp;gt; &amp;lt;axis&amp;gt; oldCube&lt;/code>, for example, is really just a map:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="569421837" type="checkbox" />
&lt;label for="569421837">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
pivot :: Cardinality -&amp;gt; Axis -&amp;gt; Cube -&amp;gt; Cube
pivot r a = map (mkRot r a)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h2 id="twisting">Twisting&lt;/h2>
&lt;p>Let&amp;rsquo;s look into the future just a little bit.&lt;/p>
&lt;blockquote>
&lt;p>It&amp;rsquo;s the future. We&amp;rsquo;ve written our pivot/twist functions and we start exploring the space of possible cubes. Oh no! We have two cubes and we want to know if they&amp;rsquo;re the same. It&amp;rsquo;s possible that they are the same colors in the same configuration, but one is the pivoted version of another. If we just compare the points one by one, they won&amp;rsquo;t match up. And we can&amp;rsquo;t possibly generate all 24 possible pivots of $\text{cube}_A$ just to see if any of them matches $\text{cube}_B$.&lt;/p>
&lt;/blockquote>
&lt;p>What we really want is a &lt;em>default position&lt;/em> for any cube. &lt;code>resolve&lt;/code> takes a cube and returns that same cube, rotated according to a regular set of rules. In this case, we want to rotate the cube so that the center of the very first tile in it (the first red tile, or what have you) is located at point $(1,1,2)$.&lt;/p>
&lt;p>If it&amp;rsquo;s not, but it&amp;rsquo;s located at $(?,?,2)$, we give the whole thing a clockwise Z-axis pivot and check again. It&amp;rsquo;ll get there eventually. If it&amp;rsquo;s located at $(?,?,-2)$, we flip it and try again. (Etc, etc.)&lt;/p>
&lt;p>This iterative algorithm is on average quite a bit faster than checking all 24 possible pivots of a cube.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="176893542" type="checkbox" />
&lt;label for="176893542">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
resolve :: Cube -&amp;gt; Cube
resolve c = resolve&amp;#39; $ head c
where resolve&amp;#39; [ 1, 1, 2] = c
resolve&amp;#39; [ _, _, 2] = resolve $ pivot Pos Z c
resolve&amp;#39; [ _, _,-2] = resolve $ pivot Pos X $ pivot Pos X c
resolve&amp;#39; [ _, 2, _] = resolve $ pivot Pos X c
resolve&amp;#39; [ _,-2, _] = resolve $ pivot Neg X c
resolve&amp;#39; [ 2, _, _] = resolve $ pivot Neg Y c
resolve&amp;#39; [-2, _, _] = resolve $ pivot Pos Y c
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>OK, now we can write our &lt;code>twist&lt;/code> function.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="486591273" type="checkbox" />
&lt;label for="486591273">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
twist :: Side -&amp;gt; Cube -&amp;gt; Cube
twist side = resolve . map (\x -&amp;gt; if isOn side x then s2Rot side x else x)
where
isOn :: Side -&amp;gt; (Coord -&amp;gt; Bool)
isOn F = (&amp;gt;0) . (!!0); isOn R = (&amp;gt;0) . (!!1); isOn U = (&amp;gt;0) . (!!2);
isOn B = (&amp;lt;0) . (!!0); isOn L = (&amp;lt;0) . (!!1); isOn D = (&amp;lt;0) . (!!2);
s2Rot :: Side -&amp;gt; Rotation
s2Rot F = mkRot Pos X; s2Rot B = mkRot Neg X;
s2Rot R = mkRot Pos Y; s2Rot L = mkRot Neg Y;
s2Rot U = mkRot Pos Z; s2Rot D = mkRot Neg Z;
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>We need two helper functions, &lt;code>isOn&lt;/code> and &lt;code>s2Rot&lt;/code>, which only need to be scoped here. &lt;code>isOn&lt;/code> takes a side and returns a function which, given a coordinate, evaluates whether or not that coordinate is on that side or not. &lt;code>s2Rot&lt;/code> takes a side and returns a rotation function, so that &lt;code>s2Rot &amp;lt;side&amp;gt; &amp;lt;coord&amp;gt;&lt;/code> does what you&amp;rsquo;d expect.&lt;/p>
&lt;h2 id="solving-a-cube">Solving a Cube&lt;/h2>
&lt;p>To solve a cube we need to define what a solved cube looks like. Luckily we already know what a solved cube looks like &amp;ndash; because tiles are indexed and we expect to always pass them around in their resolved position, we can simply write:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="643952718" type="checkbox" />
&lt;label for="643952718">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
solved :: Cube -&amp;gt; Bool
solved = (== solvedCube)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>That&amp;rsquo;s a great first step.&lt;/p>
&lt;h2 id="solving-the-_return-from-a-single-offset_-problem">Solving the &lt;em>Return from a Single Offset&lt;/em> Problem&lt;/h2>
&lt;p>To recall the video, our current problem is to find the minimum number of moves it might take to, given a solved cube with a single twist in any direction, return to a solved position.&lt;/p>
&lt;p>How do we explore the space of cubes? Well, for a 2x2 cube there&amp;rsquo;s actually only:&lt;/p>
&lt;p>$$ \dfrac{ 8! \times 3^7 }{ 24 } = 7! \times 3^6 = 3,764,160$$&lt;/p>
&lt;p>possible turns. Even trying all of thes positions just isn&amp;rsquo;t that bad. As we end up doing more complex things, we&amp;rsquo;ll spend more time optimizing this, but for now let&amp;rsquo;s just &lt;em>a)&lt;/em> take our starter cube, &lt;em>b)&lt;/em> give it all possible twists from that position, &lt;em>c)&lt;/em> take all possible twists from those positions, etc. until we find a cube which is &lt;code>solved&lt;/code>.&lt;/p>
&lt;p>Here&amp;rsquo;s the caveat: because this cube is &lt;em>irreversible&lt;/em>, we have to carry a history of the last turn. For convenience, we might as well carry a history of all turns, to make it easier to animate this solution later.&lt;/p>
&lt;p>Let&amp;rsquo;s make our &lt;code>seed&lt;/code> cube, which is a tuple; the first item is a twisted cube (doesn&amp;rsquo;t matter what side is twisted), and the second item is a list of performed moves so far.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="653978124" type="checkbox" />
&lt;label for="653978124">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
seed :: [(Cube, [Side])]
seed = [ (twist U solvedCube, [U]) ]
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Now let&amp;rsquo;s make a function to explore the space of possible &amp;ldquo;children&amp;rdquo; of a given cube. We&amp;rsquo;ll inspect the most recent turn, disallow it, and try the other two.&lt;/p>
&lt;p>Even though a cube has six sides, we can only turn any given side clockwise, and turning the top face clockwise is actually the same as turning the bottom face clockwise. So we can really only operate on &lt;code>[R,F,U]&lt;/code>, not &lt;code>[R,F,U,L,B,D]&lt;/code>.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="734928156" type="checkbox" />
&lt;label for="734928156">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
kids :: (Cube, [Side]) -&amp;gt; [(Cube, [Side])]
kids (c, h:hs) = [ (twist dir c, dir:h:hs) | dir &amp;lt;- delete h [R,F,U] ]
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Ok, great! Let&amp;rsquo;s use &lt;a href="http://hackage.haskell.org/package/base-4.10.0.0/docs/Prelude.html#v:iterate">iterate&lt;/a> to explore the space; we can &lt;code>concatMap&lt;/code> to apply the &lt;code>kids&lt;/code> function to a list of input cubes and get out a flat list of output cubes. &lt;code>iterate&lt;/code> applies the function to its own output over and over and returns a list of outputs; &lt;code>concat&lt;/code> flattens the stream, and we can &lt;code>filter&lt;/code> by which cubes are solved and print just the &lt;code>head&lt;/code> (the &lt;code>snd&lt;/code> of which is the winning sequence itself).&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="914678523" type="checkbox" />
&lt;label for="914678523">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
main = do
print $ snd $ head $ filter (solved.fst)
$ concat $ iterate (concatMap kids) seed
&lt;/code>&lt;/pre>
&lt;/div>
&lt;div class="collapsable-code">
&lt;input id="257193468" type="checkbox" />
&lt;label for="257193468">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
j@mes $ ghc -O2 l.hs &amp;amp;&amp;amp; time ./l
[1 of 1] Compiling Main ( l.hs, l.o )
Linking l ...
[F,R,F,R,F,R,U,F,R,F,R,F,R,U]
real 0m0.551s
user 0m0.547s
sys 0m0.003s
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>This is super fast, so I don&amp;rsquo;t really care yet about optimizing. But I do want to see what it looks like.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="973842651" type="checkbox" />
&lt;label for="973842651">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
main = do
let seq = snd $ head $ filter (solved.fst)
$ concat $ iterate (concatMap kids) seed
mapM_ ( \(n,c) -&amp;gt; writePng (&amp;#34;frame&amp;#34; &amp;#43;&amp;#43; show n &amp;#43;&amp;#43; &amp;#34;.png&amp;#34;)
$ drawCube $ resolve c
) $ zip [10..] $ scanr twist solvedCube seq
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>We can use &lt;code>scanr&lt;/code> to gradually scan the list of turns across the &lt;code>solvedCube&lt;/code> base by applying &lt;code>twist&lt;/code> zero times, one time, two times, etc. We end up with a list of altered cubes, which we can zip with a list of indices. We can use &lt;code>mapM_&lt;/code> (which is a synonym for &lt;code>sequence_ . map&lt;/code>) to write each cube out to a &lt;code>.png&lt;/code> using &lt;code>writePng &amp;lt;filename&amp;gt; drawCube &amp;lt;cube&amp;gt;&lt;/code>. Then we can run &lt;code>convert -delay 10 -loop 0 frame* animation.gif&lt;/code> (bash, not Haskell) and get:&lt;/p>
&lt;figure class="center" >
&lt;img src="images/animation.gif" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>And that&amp;rsquo;s the 13-turn sequence it takes, at minimum, to return to a solved position from a single initial offset from a solved irreversible cube.&lt;/p>
&lt;h2 id="conclusions">Conclusions&lt;/h2>
&lt;p>The next entry will deal with finding God&amp;rsquo;s Number.&lt;/p></content></item><item><title>Puzzle Pong II - Least Nonconstructable Positive Integer</title><link>https://jbuckland.com/blog/puzzle-pong-ii/</link><pubDate>Sun, 25 Jun 2017 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/puzzle-pong-ii/</guid><description>Github https://gist.github.com/ambuc/731f2d9b789a5e4e32bdafbd60bf7ff8 The Puzzle Josh Mermelstein and I have decided to begin challenging each other to a series of a math/programming puzzles. Here was his most recent puzzle:
Given a) the list of numbers $[1, 2, 3, 4, 5]$, b) unlimited parentheses, and c) the operations $+$, $-$, $\times$, $/$, $\wedge$, and $!$, it is possible to construct many integers. For example,
$1 = 1 + 2 - 3 - 4 + 5$, and $2 = 1 + (((2 + 3) * 4!</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">Github&lt;/th>
&lt;td> &lt;a href="https://gist.github.com/ambuc/731f2d9b789a5e4e32bdafbd60bf7ff8">https://gist.github.com/ambuc/731f2d9b789a5e4e32bdafbd60bf7ff8&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;h2 id="the-puzzle">The Puzzle&lt;/h2>
&lt;p>&lt;a href="http://joshmermelstein.com/">Josh Mermelstein&lt;/a> and I have decided to begin challenging each other to a series of a math/programming puzzles. Here was his most recent puzzle:&lt;/p>
&lt;blockquote>
&lt;p>Given &lt;em>a)&lt;/em> the list of numbers $[1, 2, 3, 4, 5]$, &lt;em>b)&lt;/em> unlimited parentheses,
and &lt;em>c)&lt;/em> the operations $+$, $-$, $\times$, $/$, $\wedge$, and $!$, it is
possible to construct many integers. For example,&lt;/p>
&lt;ul>
&lt;li>$1 = 1 + 2 - 3 - 4 + 5$, and&lt;/li>
&lt;li>$2 = 1 + (((2 + 3) * 4!) / 5!)$, etc.&lt;/li>
&lt;/ul>
&lt;p>What is the least positive integer that cannot be written this way?&lt;/p>
&lt;ul>
&lt;li>Numbers must appear in order, each exactly once.&lt;/li>
&lt;li>You may not take the factorial of a factorial (i.e. $(3!)! = 720$).&lt;/li>
&lt;li>Concatination is forbidden (i.e. $1 + 23 + 4 + 5$).&lt;/li>
&lt;li>Unary negative is forbidden (i.e. $13 = (-1) + 2 + 3 + 4 + 5$)&lt;/li>
&lt;/ul>
&lt;p>For some inspiration, please enjoy:
&lt;a href="https://www.youtube.com/watch?v=ukUkVaOyI0o">{1}&lt;/a>
&lt;a href="https://www.youtube.com/watch?v=-ruC5A9EzzE">{2}&lt;/a>&lt;/p>
&lt;/blockquote>
&lt;p>Let&amp;rsquo;s define this function a little more. I&amp;rsquo;d like to call the output the &lt;em>least nonconstructable positive integer&lt;/em>, or $\text{LNPI}$ of an ordered list. In this case, the ordered list is $[1,2,3,4,5]$.&lt;/p>
&lt;p>OK, how do we start?&lt;/p>
&lt;h2 id="abstract-syntax-trees">Abstract Syntax Trees&lt;/h2>
&lt;p>Let&amp;rsquo;s represent an expression like $(1!+2)-3$ as an &lt;a href="https://en.wikipedia.org/wiki/Abstract_syntax_tree">abstract syntax tree&lt;/a>; terminal nodes are integers, and all the other nodes are operators.&lt;/p>
&lt;p>$$(1! + 2) - 3$$&lt;/p>
&lt;figure class="center" >
&lt;img src="images/ast-one.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>It makes sense to have two types of operators; functions of one argument, like the factorial, and combining functions of two arguments, like all the rest.&lt;/p>
&lt;p>Haskell makes it easy to create abstract data types and start playing with them.&lt;/p>
&lt;h3 id="ast-implementation">AST Implementation&lt;/h3>
&lt;p>We can represent our values &lt;code>Val&lt;/code> as unrounded ratios of long ints, or &lt;code>Integer&lt;/code>s. This means we never lose precision to rounding / near-zero numbers.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="891253476" type="checkbox" />
&lt;label for="891253476">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
type Val = Ratio Integer
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>As we discovered before, there are two things we can do to &lt;code>Val&lt;/code>s:&lt;/p>
&lt;ul>
&lt;li>take a &lt;em>function&lt;/em> of one of them: $y = f(x)$&lt;/li>
&lt;li>perform an &lt;em>operation&lt;/em> on two of them: $y = f(x_1, x_2)$.&lt;/li>
&lt;/ul>
&lt;div class="collapsable-code">
&lt;input id="134786295" type="checkbox" />
&lt;label for="134786295">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
data Fn = Id | Fact deriving (Bounded, Enum)
data Op = Plus | Sub | Mult | Div | Exp deriving (Bounded, Enum)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Here &lt;code>Id&lt;/code> is the identity function $y(x) = x$. The rest should be self-explanatory.&lt;/p>
&lt;p>Finally we need an &lt;em>expression&lt;/em> type &lt;code>Expr&lt;/code>, which can be either:&lt;/p>
&lt;ol>
&lt;li>a raw &lt;code>Val&lt;/code>: &lt;code>V (Ratio Integer)&lt;/code>,&lt;/li>
&lt;li>an expression &lt;code>E1&lt;/code> of 1 argument, which takes a function &lt;code>Fn&lt;/code> and an
expression to operate on, or&lt;/li>
&lt;li>an expression &lt;code>E2&lt;/code> of 2 arguments, which takes an operation &lt;code>Op&lt;/code> and a tuple
of two expressions to operate on.&lt;/li>
&lt;/ol>
&lt;div class="collapsable-code">
&lt;input id="164952783" type="checkbox" />
&lt;label for="164952783">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
data Expr = V Val | E1 Fn Expr | E2 Op (Expr,Expr)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Let&amp;rsquo;s even derive custom &lt;code>Show&lt;/code> instances so we can pretty-print them, for debugging purposes.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="246978513" type="checkbox" />
&lt;label for="246978513">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
instance Show Op where
show Plus = &amp;#34;&amp;#43;&amp;#34;
show Sub = &amp;#34;-&amp;#34;
show Mult = &amp;#34;*&amp;#34;
show Div = &amp;#34;/&amp;#34;
show Exp = &amp;#34;^&amp;#34;
instance Show Expr where
show (V a) = show $ round a
show (E1 Id e) = show e
show (E1 Fact e) = show e &amp;#43;&amp;#43; &amp;#34;!&amp;#34;
show (E2 o (e1,e2)) = &amp;#34;(&amp;#34; &amp;#43;&amp;#43; show e1 &amp;#43;&amp;#43; show o &amp;#43;&amp;#43; show e2 &amp;#43;&amp;#43; &amp;#34;)&amp;#34;
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>If we play with this in a &lt;code>ghci&lt;/code> shell, we see:&lt;/p>
&lt;pre>&lt;code>&amp;gt; E2 Sub ( E2 Plus ( E1 Fact ( V 1 ), V 2 ), V 3 )
((1!+2)-3)
&lt;/code>&lt;/pre>
&lt;p>This is good. Let&amp;rsquo;s figure out how to evaluate these expressions.&lt;/p>
&lt;h2 id="evaluation">Evaluation&lt;/h2>
&lt;p>The type signature of &lt;code>eval&lt;/code> should be &lt;code>eval :: Expr -&amp;gt; Maybe Val&lt;/code>, since not all expressions return values. (Imagine taking the factorial of a non-integer, or dividing by zero). To accomplish this in an elegant way we make use of monad stuff like &lt;code>=&amp;lt;&amp;lt;&lt;/code>, &lt;code>&amp;lt;$&amp;gt;&lt;/code>, and &lt;code>&amp;lt;*&amp;gt;&lt;/code>, which is worth exploring in some detail in a moment.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="491325876" type="checkbox" />
&lt;label for="491325876">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
eval :: Expr -&amp;gt; Maybe Val
eval (V a) = Just a
eval (E1 f e) = calc1 f =&amp;lt;&amp;lt; eval e
eval (E2 o (e1, e2)) = calc2 o =&amp;lt;&amp;lt; (,) &amp;lt;$&amp;gt; eval e1 &amp;lt;*&amp;gt; eval e2
calc1 :: Fn -&amp;gt; Val -&amp;gt; Maybe Val
calc1 Id a = Just a
calc1 Fact a = guard (isInt a &amp;amp;&amp;amp; a&amp;lt;100 &amp;amp;&amp;amp; a&amp;gt;0) &amp;gt;&amp;gt; Just f
where f = facts !! (pred . fromIntegral . numerator) a
calc2 :: Op -&amp;gt; (Val, Val) -&amp;gt; Maybe Val
calc2 Plus (a,b) = Just $ a &amp;#43; b
calc2 Sub (a,b) = Just $ a - b
calc2 Mult (a,b) = Just $ a * b
calc2 Div (a,b) = guard (b/=0) &amp;gt;&amp;gt; Just (a/b)
calc2 Exp (a,b) = makeExp
where makeExp
| a == 0 = Just 0
| a == 1 = Just 1
| not (isInt b) = Nothing
| abs b &amp;gt; 1023 = Nothing
| otherwise = Just $ a ^^ numerator b
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Obviously not all computation is valid. We refuse to:&lt;/p>
&lt;ul>
&lt;li>take the factorial of any negative number,&lt;/li>
&lt;li>take the factorial of any non-integer, or&lt;/li>
&lt;li>divide by zero.&lt;/li>
&lt;/ul>
&lt;p>One hurdle of this problem was the discovery that it is possible to quickly generate numbers too large for the computer to handle: for example,&lt;/p>
&lt;p>$$\texttt{1+(2^(3^(4^5)))} = 1 + 2^{3^{4^5}} \approx 10^{10^{488}}$$&lt;/p>
&lt;p>So we end up doing a fair bit of bounds checking. We also refuse to:&lt;/p>
&lt;ul>
&lt;li>compute the factorial of any integer greater than a hundred,&lt;/li>
&lt;li>take an exponent to the power of any non-integer,&lt;/li>
&lt;li>take an exponent to the power of any number outside $[-1023..1023]$,&lt;/li>
&lt;/ul>
&lt;p>Additionally, we also precompute the factorial of all numbers for which the factorial is allowed, $[0..100]$, and store them in &lt;code>facts&lt;/code>; thus &lt;code>calc1 Fact a&lt;/code> is just a lookup with guards.&lt;/p>
&lt;p>Finally, we avoid some computation by noticing that $a^1 = a$ and $0^b = 0$.&lt;/p>
&lt;p>OK, let&amp;rsquo;s try evaluating the above expression!&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="846759123" type="checkbox" />
&lt;label for="846759123">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
input output
&amp;gt; eval $ E2 Sub (E2 Plus (E1 Fact (V 1), V 2), V 3) Just (0 % 1)
&amp;gt; eval $ E2 Sub (E2 Plus (E1 Fact (V 1), V 2), V 4) Just ((-1) % 1)
&amp;gt; eval $ E2 Div (V 1, V 0) Nothing
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Perfect: when an expression is valid, we compute the &lt;code>Val&lt;/code> as a &lt;code>Just (Ratio Integer)&lt;/code>; when an expression is invalid, we return Nothing.&lt;/p>
&lt;h2 id="maybes-and-monads">Maybes and Monads&lt;/h2>
&lt;p>Let&amp;rsquo;s take a moment and explore some of the above Haskell in detail. I&amp;rsquo;m going to try and explain how I accidentally invented the Monad while trying to solve a subproblem, as was predicted in &lt;a href="http://blog.sigfpe.com/2006/08/you-could-have-invented-monads-and.html">You Could Have Invented Monads! (And Maybe You Already Have.)&lt;/a>, a wonderful article about this exact phenomenon.&lt;/p>
&lt;p>Imagine a function like &lt;code>calc1 :: Fn -&amp;gt; ? -&amp;gt; ?&lt;/code>. How should we write this?&lt;/p>
&lt;h3 id="first-try">First Try&lt;/h3>
&lt;p>Writing it like &lt;code>calc1 :: Fn -&amp;gt; Maybe Val -&amp;gt; Maybe Val&lt;/code> essentially requires us to write two cases:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="973528146" type="checkbox" />
&lt;label for="973528146">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
calc1 :: Fn -&amp;gt; Maybe Val -&amp;gt; Maybe Val
calc1 _ Nothing = Nothing
calc1 Id (Just a) = Just a
calc1 Fact (Just a) = if (canFactorial a) then Just (factorial a) else Nothing
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>I&amp;rsquo;m not sure that will carry over well to &lt;code>calc2&lt;/code>:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="198547632" type="checkbox" />
&lt;label for="198547632">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
calc2 :: Fn -&amp;gt; (Maybe Val, Maybe Val) -&amp;gt; Maybe Val
calc2 _ (Nothing, _ ) = Nothing
calc2 _ (_, Nothing) = Nothing
calc2 Plus (Just a, Just b ) = Just $ a &amp;#43; b
calc2 Minus ...
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>This is OK, but let&amp;rsquo;s find a better way &amp;ndash; maybe by assuming we won&amp;rsquo;t pass invalid expressions in the first place.&lt;/p>
&lt;h3 id="second-try">Second Try&lt;/h3>
&lt;p>Writing it like &lt;code>calc1 :: Fn -&amp;gt; Val -&amp;gt; Maybe Val&lt;/code> assumes a valid expression, so we can write:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="536871492" type="checkbox" />
&lt;label for="536871492">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
calc1 :: Fn -&amp;gt; Val -&amp;gt; Maybe Val
calc1 Id a = Just a
calc1 Fact a = if (canFactorial a) then Just (factorial a) else Nothing
calc2 :: Fn -&amp;gt; (Val, Val) -&amp;gt; Maybe Val
calc2 Plus (a,b) = Just $ a &amp;#43; b
calc2 Minus ...
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>This is much better. So if our type signatures look like&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="834216975" type="checkbox" />
&lt;label for="834216975">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
calc1 :: Fn -&amp;gt; Val -&amp;gt; Maybe Val
calc2 :: Fn -&amp;gt; (Val, Val) -&amp;gt; Maybe Val
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>then what does &lt;code>eval&lt;/code> look like? It would need to do something like&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="986521437" type="checkbox" />
&lt;label for="986521437">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
eval :: Expr -&amp;gt; Maybe Val
eval (V a) = Just a
eval (E1 f e) = calc1 f (???)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>And here&amp;rsquo;s where our trouble begins. Remember that we want to evaluate &lt;code>e&lt;/code> before passing it into &lt;code>calc1&lt;/code>. So we&amp;rsquo;d have to check if it was valid first.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="895624137" type="checkbox" />
&lt;label for="895624137">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
eval (E1 f e) = if (isJust $ eval e)
then (calc1 f (fromJust $ eval e))
else Nothing
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>This is nuts. Luckily this type of implicit &lt;code>Maybe&lt;/code> checking is easy, since the
&lt;code>Maybe&lt;/code> type &lt;a href="https://hackage.haskell.org/package/base-4.9.1.0/docs/Prelude.html#t:Maybe">is a
monad&lt;/a>,
and implements the Monad, Functor, and Applicative types.&lt;/p>
&lt;h4 id="functors-really-quick">Functors, really quick&lt;/h4>
&lt;p>Quick functor recap:&lt;/p>
&lt;ul>
&lt;li>&lt;code>map&lt;/code> lets us apply a function over a list: &lt;code>map fn [x,y] = [fn x, fn y]&lt;/code>&lt;/li>
&lt;li>&lt;code>fmap&lt;/code> lets us apply a function inside a container type: &lt;code>fmap f (m a) = m (f a)&lt;/code>.&lt;/li>
&lt;/ul>
&lt;p>Note also that &lt;code>&amp;lt;$&amp;gt;&lt;/code> is an infix synonym for &lt;code>fmap&lt;/code>. Confused? See the following examples:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="632914587" type="checkbox" />
&lt;label for="632914587">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
&amp;gt; map (&amp;#43;1) [1,2] [2,3]
&amp;gt; fmap (&amp;#43;1) (Just 1) Just 2
&amp;gt; (&amp;#43;1) &amp;lt;$&amp;gt; Just 1 Just 2
&amp;gt; (&amp;#43;1) &amp;lt;$&amp;gt; Nothing Nothing
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>One benefit of &lt;code>fmap&lt;/code>ping over the &lt;code>Maybe&lt;/code> monad is that if we pass it &lt;code>Nothing&lt;/code>, it doesn&amp;rsquo;t need to unwrap and apply; it&amp;rsquo;ll just pass &lt;code>Nothing&lt;/code> through unscathed.&lt;/p>
&lt;h4 id="fmap-really-quick">&lt;code>fmap&lt;/code>, really quick&lt;/h4>
&lt;p>So remember the above problem:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="263154789" type="checkbox" />
&lt;label for="263154789">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
&amp;gt; calc1 Fact (4%1) Just (24%1)
&amp;gt; calc1 Fact Nothing &amp;lt;error&amp;gt;
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>OK, that&amp;rsquo;s expected. Let&amp;rsquo;s try using &lt;code>&amp;lt;$&amp;gt;&lt;/code>&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="437526981" type="checkbox" />
&lt;label for="437526981">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
&amp;gt; calc1 Fact &amp;lt;$&amp;gt; Just (4%1) Just (Just (24%1))
&amp;gt; calc1 Fact &amp;lt;$&amp;gt; Nothing Nothing
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>So, good news and bad news. We can unwrap &lt;code>Just a&lt;/code> and apply the function to the interior, but we pass it back re-wrapped, since that&amp;rsquo;s what &lt;code>&amp;lt;$&amp;gt;&lt;/code> does. Sadly, that&amp;rsquo;s also what &lt;code>calc1&lt;/code> does. Here&amp;rsquo;s a clue:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="543876129" type="checkbox" />
&lt;label for="543876129">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
&amp;gt; :type (&amp;lt;$&amp;gt;) (&amp;lt;$&amp;gt;) :: Functor f =&amp;gt; (a -&amp;gt; b) -&amp;gt; f a -&amp;gt; f b
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>This makes sense: &lt;code>&amp;lt;$&amp;gt;&lt;/code> expects a function like &lt;code>(a -&amp;gt; b)&lt;/code> which doesn&amp;rsquo;t have an opinion on how to re-wrap &lt;code>b&lt;/code> itself. So maybe &lt;code>&amp;lt;$&amp;gt;&lt;/code> isn&amp;rsquo;t the function we want?&lt;/p>
&lt;h4 id="monads-really-quick">Monads, really quick&lt;/h4>
&lt;p>At this point I searched &lt;a href="https://www.haskell.org/hoogle/?hoogle=">Hoogle&lt;/a> for &lt;code>(a -&amp;gt; f b) -&amp;gt; f a -&amp;gt; f b&lt;/code> and &lt;a href="https://www.haskell.org/hoogle/?hoogle=%28a+-%3E+f+b%29+-%3E+f+a+-%3E+f+b">got, of course&lt;/a>&amp;hellip; the Monad.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="759186342" type="checkbox" />
&lt;label for="759186342">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
&amp;gt; :type (=&amp;lt;&amp;lt;) (=&amp;lt;&amp;lt;) :: Monad m =&amp;gt; (a -&amp;gt; m b) -&amp;gt; m a -&amp;gt; m b
&amp;gt; :type (&amp;gt;&amp;gt;=) (&amp;gt;&amp;gt;=) :: Monad m =&amp;gt; m a -&amp;gt; (a -&amp;gt; m b) -&amp;gt; m b
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>There&amp;rsquo;s lots more to the monad, but for now we can use it as a way to take a value in context, apply a context-aware function, and return some output in context.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="781936452" type="checkbox" />
&lt;label for="781936452">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
&amp;gt; calc1 Fact =&amp;lt;&amp;lt; Just (4%1) Just (24%1)
&amp;gt; calc1 Fact =&amp;lt;&amp;lt; Nothing Nothing
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>What about &lt;code>calc2&lt;/code>? It needs to take a tuple of &lt;code>Expr&lt;/code>s, and neither of them can be &lt;code>Nothing&lt;/code>. Turns out we can use the fmap infix &lt;code>&amp;lt;$&amp;gt;&lt;/code> and their friend the sequential application infix &lt;code>&amp;lt;*&amp;gt;&lt;/code>. Here&amp;rsquo;s a set of three trials:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="824937615" type="checkbox" />
&lt;label for="824937615">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
(&amp;#43;) &amp;lt;$&amp;gt; Just 1 &amp;lt;*&amp;gt; Just 2 Just 3
(&amp;#43;) &amp;lt;$&amp;gt; Just 1 &amp;lt;*&amp;gt; Nothing Nothing
(&amp;#43;) &amp;lt;$&amp;gt; Nothing &amp;lt;*&amp;gt; Just 2 Nothing
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>So finally we can simply write &lt;code>calc2 o =&amp;lt;&amp;lt; (,) &amp;lt;$&amp;gt; eval e1 &amp;lt;*&amp;gt; eval e2&lt;/code>. Thus,&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="624893751" type="checkbox" />
&lt;label for="624893751">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
eval :: Expr -&amp;gt; Maybe Val
eval (V a) = Just a
eval (E1 f e) = calc1 f =&amp;lt;&amp;lt; eval e
eval (E2 o (e1, e2)) = calc2 o =&amp;lt;&amp;lt; (,) &amp;lt;$&amp;gt; eval e1 &amp;lt;*&amp;gt; eval e2
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h2 id="solving-the-puzzle">Solving the Puzzle&lt;/h2>
&lt;p>Technically this is all we need to start solving the puzzle. Now that we have a way to compose and evaluate expressions, we just need to generate and evaluate all of them.&lt;/p>
&lt;h3 id="generating-all-expressions">Generating all expressions&lt;/h3>
&lt;p>Let&amp;rsquo;s talk about partitioning a strictly ordered list.&lt;/p>
&lt;p>One interesting aspect of this problem is that because our numbers have to be &lt;em>in order&lt;/em>, the nodes containing the values (for example) $[1..3]$ will appear in order, from left to right, as the terminal nodes of our &lt;em>AST&lt;/em>. This means that we can generate all possible ASTs by taking our root node and splitting the numbers $[1..3]$ at some point, throwing the lesser half on the left, and the greater half on the right.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/part.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>It&amp;rsquo;s easy to see how we can apply this recursively to generate all tree shapes. Then we can insert all possible iterations at each &lt;code>op&lt;/code> node, and all possible functions &lt;em>at&lt;/em> each function. There are only two functions, and one is &lt;code>Id&lt;/code> which leaves the value untouched.&lt;/p>
&lt;p>We implement the partition algorithm as:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="124573896" type="checkbox" />
&lt;label for="124573896">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
mkPart :: [a] -&amp;gt; [([a],[a])]
mkPart xs = tail $ init $ zip (inits xs) (tails xs)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>such that&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="689274153" type="checkbox" />
&lt;label for="689274153">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
&amp;gt; mkPartitions [1,2,3] [([1],[2,3]),([1,2],[3])]
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h3 id="valuesfrom-in-theory">&lt;code>valuesFrom&lt;/code> in Theory&lt;/h3>
&lt;p>This is excellent. Let&amp;rsquo;s write the function &lt;code>valuesFrom&lt;/code>, which takes an ordered list and returns all possible values which can be generated from combining it as described above. Note:&lt;/p>
&lt;ul>
&lt;li>&lt;code>valuesFrom 1&lt;/code> will just be &lt;code>[1, 1!] = [1,1]&lt;/code>&lt;/li>
&lt;li>&lt;code>valuesFrom 3&lt;/code> will just be &lt;code>[3, 3!] = [3,6]&lt;/code>&lt;/li>
&lt;/ul>
&lt;div class="collapsable-code">
&lt;input id="674382195" type="checkbox" />
&lt;label for="674382195">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
valuesFrom :: [Val] -&amp;gt; [Val]
valuesFrom [x] = mapMaybe eval [ E1 f (V x) | f &amp;lt;- functions ]
valuesFrom xs = mapMaybe eval [ E1 f $ E2 o (V va, V vb)
| f &amp;lt;- functions
, o &amp;lt;- operations
, (as, bs) &amp;lt;- mkPartitions xs
, va &amp;lt;- valuesFrom as
, vb &amp;lt;- valuesFrom bs
]
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>We use &lt;code>mapMaybe&lt;/code> to map &lt;code>eval&lt;/code> over a list and discard the &lt;code>Nothing&lt;/code>s (and &lt;code>fromJust&lt;/code> the &lt;code>Just a&lt;/code>s). This is great! It doesn&amp;rsquo;t check for duplicates or anything, but it&amp;rsquo;s a good start.&lt;/p>
&lt;h3 id="caching">Caching&lt;/h3>
&lt;p>In practice, we would like to cache &lt;code>valuesFrom&lt;/code>.&lt;/p>
&lt;p>Here&amp;rsquo;s why: in calling &lt;code>valuesFrom [1,2,3,4,5]&lt;/code>, we end up evaluating &lt;code>valuesFrom [2,3,4]&lt;/code> twice: once as part of &lt;code>[1,2,3,4]&lt;/code> and once as part of &lt;code>[2,3,4,5]&lt;/code>. See the above tree, which is an expansion of the earlier one for a larger ordered list.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/part_big.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>You can see we end up evaluating &lt;code>f([2,3])&lt;/code> twice, &lt;code>f([1,2])&lt;/code> twice, &lt;code>f([3,4])&lt;/code> twice, etc.&lt;/p>
&lt;p>You can imagine how this inefficiency bares its teeth for higher-length ordered lists; evaluating &lt;code>f([1,2,3,4,5])&lt;/code> will evaluate &lt;code>[2,3]&lt;/code> four times; as a part of &lt;code>[1,2,3]&lt;/code>,&lt;code>[1,2,3,4]&lt;/code>,&lt;code>[2,3,4]&lt;/code>, and &lt;code>[2,3,4,5]&lt;/code>.&lt;/p>
&lt;p>So let&amp;rsquo;s generate a big &lt;code>Map&lt;/code>. On that note, &lt;code>Map&lt;/code> is Haskell&amp;rsquo;s version of a key/value pairing, what would be a dict in Python. Here&amp;rsquo;s what it will contain:&lt;/p>
&lt;ul>
&lt;li>the keys in this map should be consecutive sequences like &lt;code>[1], [1,2], [2,3,4], [4,5]&lt;/code>, etc., and&lt;/li>
&lt;li>the values should be sets of values &lt;code>S.Set Val&lt;/code> which can be created from combining those numbers in the key.&lt;/li>
&lt;/ul>
&lt;p>To generate this recursively from the bottom up, we can first generate a map of all keys of length 1, and then use it to quickly generate a map of all keys of length 2, and so on and so on and so on.&lt;/p>
&lt;p>Let&amp;rsquo;s do it.&lt;/p>
&lt;h3 id="valuesfrom-in-practice">&lt;code>valuesFrom&lt;/code> in Practice&lt;/h3>
&lt;p>Here&amp;rsquo;s how it all looks together: &lt;code>valuesFrom range&lt;/code> does a &lt;code>M.findWithDefault (S.empty) range&lt;/code> on a map, which is the union of two smaller maps:&lt;/p>
&lt;p>$$ \text{Map}([1..n]) = \text{Map}(n) \ \bigcup \ \text{Map}([1..(n-1)]) $$&lt;/p>
&lt;p>where &lt;code>prevMap&lt;/code> is the &lt;code>mkMap&lt;/code> of a smaller subset, and &lt;code>thisMap&lt;/code> is the set of the values of the expressions generated in &lt;code>exprsFrom&lt;/code>, which uses &lt;code>valsFrom&lt;/code> the partitioned right- and left-available values. Check it out:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="417295683" type="checkbox" />
&lt;label for="417295683">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
valuesFrom :: [Val] -&amp;gt; S.Set Val
valuesFrom range = M.findWithDefault S.empty range
$ mkMap (length range)
where
mkMap :: Int -&amp;gt; M.Map [Val] (S.Set Val)
mkMap 0 = M.empty
mkMap n = M.union prevMap thisMap
where
prevMap = mkMap $ pred n
thisMap = foldr (\sq -&amp;gt; M.insert sq $ mkSet sq) M.empty $ subsequences n
mkSet = S.fromList . mapMaybe eval . exprsFrom prevMap
exprsFrom :: M.Map [Val] (S.Set Val) -&amp;gt; [Val] -&amp;gt; [Expr]
exprsFrom prevMap [x] = [ E1 f (V x) | f &amp;lt;- functions ]
exprsFrom prevMap xs = [ E1 f $ E2 o (V va, V vb)
| f &amp;lt;- functions
, o &amp;lt;- operations
, (as, bs) &amp;lt;- mkPartitions xs
, va &amp;lt;- valsFrom as
, vb &amp;lt;- valsFrom bs
]
where valsFrom xs = S.toList $ M.findWithDefault S.empty xs prevMap
subsequences n = filter ((== n) . length) $ map (take n) $ tails range
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>So, with caching, here&amp;rsquo;s what each &lt;code>valuesFrom&lt;/code> function call ends up looking like:&lt;/p>
&lt;figure class="center" >
&lt;img src="images/part_cached.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;h2 id="putting-it-all-together">Putting it all together&lt;/h2>
&lt;p>We&amp;rsquo;re so close to the end here.&lt;/p>
&lt;p>We can get a set of all possible numbers from a range &lt;code>xs&lt;/code> with &lt;code>valuesFrom xs&lt;/code>. This returns a set we can filter for just positive integers; then we want to turn that into a list and find its first gap; where&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="739641258" type="checkbox" />
&lt;label for="739641258">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
-- &amp;gt; firstGap [1,2,3,5,6] = 4
firstGap :: (Num a, Eq a) =&amp;gt; [a] -&amp;gt; a
firstGap ls = 1 &amp;#43; ls !! (head . findIndices (/=1) . gaps) ls
where gaps :: Num t =&amp;gt; [t] -&amp;gt; [t]
gaps [x] = []
gaps (x:xs) = (head xs - x) : gaps xs
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Finally, we can define
&lt;div class="collapsable-code">
&lt;input id="839527461" type="checkbox" />
&lt;label for="839527461">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
lnpi :: [Val] -&amp;gt; Integer
lnpi = numerator . firstGap
. S.toList . S.filter (&amp;gt;0)
. S.filter isInt. valuesFrom
&lt;/code>&lt;/pre>
&lt;/div>
&lt;/p>
&lt;p>So, let&amp;rsquo;s try it!
&lt;div class="collapsable-code">
&lt;input id="328419567" type="checkbox" />
&lt;label for="328419567">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
&amp;gt; lnpi [1..5] 159
&lt;/code>&lt;/pre>
&lt;/div>
&lt;/p>
&lt;p>and that&amp;rsquo;s our final answer; 159 is the smallest integer we can&amp;rsquo;t construct from the numbers $[1..5]$.&lt;/p>
&lt;p>For the complete code, please see &lt;a href="https://gist.github.com/ambuc/731f2d9b789a5e4e32bdafbd60bf7ff8">this gist&lt;/a>.&lt;/p>
&lt;h2 id="runtime">Runtime&lt;/h2>
&lt;p>Finally, the runtime&amp;hellip;&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="723485691" type="checkbox" />
&lt;label for="723485691">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
j@mes $ ghc -O2 lnpi.hs &amp;amp;&amp;amp; time ./lnpi
[1 of 1] Compiling Main ( lnpi.hs, lnpi.o )
Linking lnpi ...
159
real 0m0.332s
user 0m0.327s
sys 0m0.003s
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>This is phenomenal. Early drafts of this took anywhere from one to ten minutes. The secret to this low runtime is evaluating the values early for re-use in other expressions, and nubbing down lists to sets as early as possible, for fewer iterations.&lt;/p>
&lt;h2 id="the-puzzles">The Puzzles&lt;/h2>
&lt;p>So Josh actually posed me a set of puzzles. Once we discussed how the performance was and how easy it was to play around with different ideas in this space, we settled on a set of bonus problems; provided here are their questions, answers, runtimes, and some comments on performance.&lt;/p>
&lt;h3 id="puzzle-one">Puzzle One&lt;/h3>
&lt;p>&lt;strong>Find the least nonconstructable integer from the ordered list &lt;code>[1,2,3,4,5]&lt;/code>&lt;/strong>:&lt;/p>
&lt;p>This was the original problem. The solution &lt;code>01.hs&lt;/code> was trivial:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="724968531" type="checkbox" />
&lt;label for="724968531">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
$ ghc -O2 operationsLibrary.hs 01.hs &amp;amp;&amp;amp; time ./01
159
0m0.333s
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h3 id="puzzle-two">Puzzle Two&lt;/h3>
&lt;p>&lt;strong>Find the least nonconstructable integer from any length five, strictly
increasing sublist of &lt;code>[0..9]&lt;/code>&lt;/strong>:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="841365729" type="checkbox" />
&lt;label for="841365729">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
main = print $ first (map numerator)
$ minimumBy (compare `on` snd) $ map (id &amp;amp;&amp;amp;&amp;amp; lnpi) $ sets [0..9] 5
&lt;/code>&lt;/pre>
&lt;/div>
&lt;div class="collapsable-code">
&lt;input id="685321749" type="checkbox" />
&lt;label for="685321749">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
$ ghc -O2 operationsLibrary.hs 02.hs &amp;amp;&amp;amp; time ./02
([0,2,6,8,9],2)
0m39.127s
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>So I actually played a lot with trying to parallelize this one. A first attempt to compute one large cached map of all sublists of &lt;code>[0..9]&lt;/code> took a very large amount of time, and was too large to fit entirely in memory; the process of swapping bits of it in and out was expensive. It turned out to be faster to compute &lt;code>lnpi([])&lt;/code> from scratch each time, at anywhere from &lt;code>0.03s&lt;/code> to &lt;code>3s&lt;/code> each, depending on how many possible products were exponentially large and could be dropped immediately.&lt;/p>
&lt;p>I did play with the &lt;a href="hackage.haskell.org/package/parallel-3.2.1.1/docs/Control-Parallel.html">Control.Parallel&lt;/a> library for a bit, and ended up using &lt;code>pseq&lt;/code> and &lt;code>par&lt;/code> to parallelize this independent computation of &lt;code>lnpi([])&lt;/code> across four cores, for a slightly better runtime:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="637218594" type="checkbox" />
&lt;label for="637218594">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
main = print
$ first (map numerator)
$ a `par` b `par` c `par` d `pseq` reduceFunc [a,b,c,d]
where [a,b,c,d] = map (reduceFunc . map mapFunc)
$ segmentInto (sets [0..9] 5) 4
reduceFunc = minimumBy (compare `on` snd)
mapFunc = id &amp;amp;&amp;amp;&amp;amp; lnpi
segmentInto xs n = map (\x -&amp;gt; take y $ drop (y*x) xs) [0..pred n]
where y = succ $ div (length xs) n
&lt;/code>&lt;/pre>
&lt;/div>
&lt;div class="collapsable-code">
&lt;input id="713254986" type="checkbox" />
&lt;label for="713254986">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
$ ghc -j -O2 -threaded -rtsopts operationsLibrary.hs 02b.hs --make -fforce-recomp &amp;amp;&amp;amp; time ./02b &amp;#43;RTS -N4
([0,2,6,8,9],2)
0m23.407s
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h3 id="puzzle-three-ab">Puzzle Three (a,b)&lt;/h3>
&lt;p>&lt;strong>What lists can&amp;rsquo;t make one?&lt;/strong>&lt;/p>
&lt;p>Here we end up using &lt;code>valuesFrom&lt;/code> directly; we don&amp;rsquo;t care about the &lt;code>lnpi&lt;/code> at all. We can use &lt;code>map (f &amp;amp;&amp;amp;&amp;amp; g)&lt;/code> which takes an input &lt;code>x&lt;/code> and returns a tuple &lt;code>(f x, g x)&lt;/code>. We use list function &lt;code>elem&lt;/code> to see that &lt;code>1&lt;/code> isn&amp;rsquo;t a possible value, and print the (empty) list.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="416395872" type="checkbox" />
&lt;label for="416395872">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
main = print
$ filter (not.snd)
$ map ( id &amp;amp;&amp;amp;&amp;amp; elem 1 . valuesFrom ) $ sets [0..9] 5
&lt;/code>&lt;/pre>
&lt;/div>
&lt;div class="collapsable-code">
&lt;input id="275468319" type="checkbox" />
&lt;label for="275468319">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
$ ghc -O2 operationsLibrary.hs 03.hs &amp;amp;&amp;amp; time ./03
[]
0m36.4s
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>This performance was a little lacking, but we can reoptimize by fetching the list of &lt;code>expressionsFrom&lt;/code> and evaluating one-by-one to see if they are &lt;code>Just (1%1)&lt;/code>; we skip the process of mapping &lt;code>id&lt;/code>s and the cost of holding all the values in memory instead of printing an empty list lazily.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="265418379" type="checkbox" />
&lt;label for="265418379">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
main = print
$ map (map numerator)
$ filter (isNothing . find1)
$ sets [0..9] 5
where find1 xs = if null ls then Nothing else Just (head ls)
where ls = filter (\e -&amp;gt; eval e == Just (1%1)) $ expressionsFrom xs
&lt;/code>&lt;/pre>
&lt;/div>
&lt;div class="collapsable-code">
&lt;input id="546923817" type="checkbox" />
&lt;label for="546923817">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
$ ghc -O2 operationsLibrary.hs 03b.hs &amp;amp;&amp;amp; time ./03b
[]
0m7.087s
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h4 id="whats-expressionsfrom">What&amp;rsquo;s &lt;code>expressionsFrom&lt;/code>?&lt;/h4>
&lt;p>It&amp;rsquo;s a lot like &lt;code>valuesFrom&lt;/code>, except we don&amp;rsquo;t evaluate the expressions at every possible step. To write both &lt;code>valuesFrom&lt;/code> and &lt;code>expressionsFrom&lt;/code> I was able to extract a lot of shared abstractions. Here is the code for them both, all in one place. This looks different from the form of &lt;code>valuesFrom&lt;/code> above, but it has the same performance and interface.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="857426139" type="checkbox" />
&lt;label for="857426139">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
makeMap :: Ord a =&amp;gt; [a] -&amp;gt; ((M.Map [a] b, [a]) -&amp;gt; b) -&amp;gt; M.Map [a] b
makeMap range insertFn = mkMap (length range)
where mkMap 0 = M.empty
mkMap n = M.union prev this
where prev = mkMap (n-1)
this = foldr f z ls
where f sq = M.insert sq $ insertFn (prev, sq)
z = M.empty
ls = filter ((==n).length) $ map (take n) $ tails range
valuesFrom :: [Val] -&amp;gt; [Val]
valuesFrom range = S.toList $ M.findWithDefault S.empty range $ makeMap range insFn
where insFn = S.fromList . mapMaybe eval . exprsFrom
exprsFrom :: (M.Map [Val] (S.Set Val), [Val]) -&amp;gt; [Expr]
exprsFrom (prevMap, [x] ) = [ E1 f (V x) | f &amp;lt;- functions ]
exprsFrom (prevMap, range) = [ E1 f $ E2 o (V va, V vb)
| f &amp;lt;- functions
, o &amp;lt;- operations
, (as, bs) &amp;lt;- mkPart range
, va &amp;lt;- valsFrom as
, vb &amp;lt;- valsFrom bs
]
where valsFrom range = S.toList $ M.findWithDefault S.empty range prevMap
expressionsFrom :: [Val] -&amp;gt; [Expr]
expressionsFrom range = M.findWithDefault [] range $ makeMap range exprsFrom
where exprsFrom :: (M.Map [Val] [Expr], [Val]) -&amp;gt; [Expr]
exprsFrom (prevMap, [x] ) = [ E1 f (V x) | f &amp;lt;- functions ]
exprsFrom (prevMap, range) = [ E1 f $ E2 o (ea, eb)
| f &amp;lt;- functions
, o &amp;lt;- operations
, (as, bs) &amp;lt;- mkPart range
, ea &amp;lt;- exprsFrom (prevMap, as)
, eb &amp;lt;- exprsFrom (prevMap, bs)
]
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h3 id="puzzle-three-c">Puzzle Three (c)&lt;/h3>
&lt;p>&lt;strong>Print how each list &lt;em>can&lt;/em> make one.&lt;/strong>&lt;/p>
&lt;p>By using &lt;code>expressionsFrom&lt;/code> we can find the first expression that evaluates to $1$, and avoid evaluating the entire possible tree.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="985632741" type="checkbox" />
&lt;label for="985632741">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
main = mapM_ print $ map (map numerator &amp;amp;&amp;amp;&amp;amp; find1) $ sets [0..9] 5
where find1 xs = if null ls then Nothing else Just (head ls)
where ls = filter (\e -&amp;gt; eval e == Just (1%1)) $ expressionsFrom xs
&lt;/code>&lt;/pre>
&lt;/div>
&lt;div class="collapsable-code">
&lt;input id="759628143" type="checkbox" />
&lt;label for="759628143">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
$ ghc -O2 operationsLibrary.hs 03c.hs &amp;amp;&amp;amp; time ./03c
([0,1,2,3,4],Just (0&amp;#43;(1&amp;#43;((2-3!)&amp;#43;4))))
([0,1,2,3,5],Just (0&amp;#43;(1&amp;#43;(2&amp;#43;(3-5)))))
([0,1,2,4,5],Just (0&amp;#43;((1*2)&amp;#43;(4-5))))
([0,1,3,4,5],Just (0&amp;#43;(((1&amp;#43;3)!/4)-5)))
([0,2,3,4,5],Just (0&amp;#43;(2&amp;#43;((3-4)^5))))
([1,2,3,4,5],Just (1&amp;#43;((2-(3&amp;#43;4))&amp;#43;5)))
([0,1,2,3,6],Just (0&amp;#43;(1&amp;#43;((2*3)-6))))
([0,1,2,4,6],Just (0&amp;#43;(1&amp;#43;(2&amp;#43;(4-6)))))
([0,1,3,4,6],Just (0&amp;#43;((1*3)&amp;#43;(4-6))))
([0,2,3,4,6],Just (0&amp;#43;(2&amp;#43;(3-(4!/6)))))
...
0m7.085s
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h2 id="return-volley">Return Volley&lt;/h2>
&lt;p>Josh, here is my puzzle:&lt;/p>
&lt;blockquote>
&lt;p>I saw a &lt;a href="http://socks-studio.com/2016/06/15/irrational-thoughts-should-be-followed-absolutely-and-logically-sol-lewitts-variations-of-incomplete-open-cubes-1974/">Sol Lewitt exhibition&lt;/a> at a museum a few years ago which consisted of a &lt;a href="http://socks-studio.com/img/blog/le-witt-incomplete-open-cubes-02.jpg">series of variations of incomplete cubes&lt;/a>. There were 122 of them, and each was a contiguous subset of lines which traced the twelve edges of the skeleton of a cube. The trick was that these subsets were unique across rotation but not reflection.&lt;/p>
&lt;/blockquote>
&lt;p>Create a script / renderer to generate the &lt;a href="http://socks-studio.com/img/blog/le-witt-incomplete-open-cubes-01.jpg">table of Varations of Incomplete Open Cubes&lt;/a>, and then apply it to the skeleton of each platonic solid.&lt;/p>
&lt;p>Some light reading: &lt;a href="http://krex.k-state.edu/dspace/bitstream/handle/2097/15809/MichaelReb2013.pdf">Analysis of Variations of Incomplete Open Cubes by Sol Lewitt&lt;/a>, a paper by Michael Allan Reb (2011).&lt;/p></content></item><item><title>Puzzle Pong I - Generating All Possible 4x4 Crosswords</title><link>https://jbuckland.com/blog/puzzle-pong-i/</link><pubDate>Sat, 27 May 2017 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/puzzle-pong-i/</guid><description>Github https://gist.github.com/ambuc/ac4ed787e1b9bb3eba08bb02c9b25c49 The Puzzle Josh Mermelstein and I have decided to begin challenging each other to a series of a math/programming puzzles. He asked me to generate all possible valid English-language four-by-four crossword grids; that is, a grid of sixteen letters where every row and column is a real English word. One caveat was that no grid could have the same word twice.
An example grid:
bash ACTS --&amp;gt; (across) acts, lore, idea, teem LORE ( down ) alit, code, tree, seam IDEA TEEM The Difficulty It&amp;rsquo;s easy to think of a two really bad ways to solve this problem:</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">Github&lt;/th>
&lt;td> &lt;a href="https://gist.github.com/ambuc/ac4ed787e1b9bb3eba08bb02c9b25c49">https://gist.github.com/ambuc/ac4ed787e1b9bb3eba08bb02c9b25c49&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;h2 id="the-puzzle">The Puzzle&lt;/h2>
&lt;p>&lt;a href="http://joshmermelstein.com/">Josh Mermelstein&lt;/a> and I have decided to begin challenging each other to a series of a math/programming puzzles. He asked me to generate all possible valid English-language four-by-four crossword grids; that is, a grid of sixteen letters where every row and column is a real English word. One caveat was that no grid could have the same word twice.&lt;/p>
&lt;p>An example grid:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="348725916" type="checkbox" />
&lt;label for="348725916">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
ACTS --&amp;gt; (across) acts, lore, idea, teem
LORE ( down ) alit, code, tree, seam
IDEA
TEEM
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h3 id="the-difficulty">The Difficulty&lt;/h3>
&lt;p>It&amp;rsquo;s easy to think of a two really bad ways to solve this problem:&lt;/p>
&lt;ul>
&lt;li>You could try all $26^{16} = 4.36\times10^{22}$ grids and filter by validating their component words, or&lt;/li>
&lt;li>you could find real four-letter english words (there are a little over 2000 of them, so $ 2354 \text{ choose } 8 = 2.31\times10^{22}$ possible grids ) and try and fit them, eight at a time, on a grid.&lt;/li>
&lt;/ul>
&lt;h2 id="the-solution">The Solution&lt;/h2>
&lt;p>The best way I found was to:&lt;/p>
&lt;ul>
&lt;li>precompute a dictionary (called &lt;code>paths&lt;/code> here) with
&lt;ul>
&lt;li>keys like &lt;code>&amp;quot;a&amp;quot;&lt;/code>, &lt;code>&amp;quot;ab&amp;quot;&lt;/code>, &lt;code>&amp;quot;abc&amp;quot;&lt;/code>, and&lt;/li>
&lt;li>values corresponding to the lists of letters which, if put after their keys, would lead to real four-letter words.&lt;/li>
&lt;li>(For example, &lt;code>paths[&amp;quot;wok&amp;quot;] = ['e','s']&lt;/code>, or (perhaps less obviously), &lt;code>paths[&amp;quot;z&amp;quot;] = [('a','e','i','o']&lt;/code>.)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>lay out all possible &lt;em>starting grids&lt;/em>, where
&lt;ul>
&lt;li>the top row and leftmost column were filled out&lt;/li>
&lt;li>with two real four-letter words&lt;/li>
&lt;li>whose first letters were the same&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>for each grid (node, really),
&lt;ul>
&lt;li>find the next blank,
&lt;ul>
&lt;li>find the partial word above it,&lt;/li>
&lt;li>find the partial word to the left of it,&lt;/li>
&lt;li>look them both up in &lt;code>paths&lt;/code>,&lt;/li>
&lt;li>take the intersection of the resultant lists&lt;/li>
&lt;li>for each character in this intersection,
&lt;ul>
&lt;li>create a list of child nodes with the blank square filled in.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>In this way, we guarantee that any placement will always lead to a real word in that row and column.&lt;/p>
&lt;h3 id="annotated-code">Annotated Code&lt;/h3>
&lt;p>I&amp;rsquo;ll present the code annotated below, or in its &lt;a href="https://gist.github.com/ambuc/ac4ed787e1b9bb3eba08bb02c9b25c49#file-crossword-hs">entirety in the attached gist&lt;/a>.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="927586314" type="checkbox" />
&lt;label for="927586314">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
import qualified Control.Monad as N (forM)
import qualified Data.Maybe as B (isJust, isNothing, catMaybes)
import qualified Data.Char as C (isAsciiLower)
import qualified Data.Function as F (on)
import qualified Data.List as L (all, nub, groupBy, intercalate)
import qualified Data.List.Split as L (chunksOf)
import qualified Data.Map.Strict as M (Map, insert, empty, unions, findWithDefault)
import qualified Language.Words as W (allStringWords)
-- A box can either have a single character or nothing. Using the Maybe monad
-- here turned out to be a really useful decision, with functions like isJust
-- and catMaybes doing the heavy lifting for the most part.
type Box = Maybe Char
-- A grid is just a list of boxes. This was originally implemented as a
-- `Data.Matrix Maybe Char`, but it turned out to be very slow compared to a
-- stupid list. That said, I ended up having to rewrite the getters and setters,
-- as seen below.
type Grid = [Box]
-- One issue I ran into a lot was confusing row and col indices. Defining a
-- custom datatype akin to Either helped solve this -- it&amp;#39;s a lot harder to pass
-- a Row index as a Column if the compiler catches it ;)
data Idx a = Row a | Col a deriving (Eq, Ord, Show)
-- This is the precomputed dictionary discussed above -- it maps Strings (like
-- &amp;#34;a&amp;#34; or &amp;#34;abc&amp;#34;) to list of chars (printed as `[&amp;#39;a&amp;#39;, &amp;#39;e&amp;#39;, &amp;#39;i&amp;#39;...]`)
type Paths = M.Map String [Char]
-- This is the width of the grid. Can be changed, but it would be a hassle to
-- extricate into a command-line argument or even a main-block variable.
n = 4
-- Here begins the set of getters and setters I mentioned above. You can see
-- that while gridSet takes an element to insert, a tuple of the row and column
-- at which to insert it, the original grid, and returns the resultant grid. The
-- _type_ of `Row r` and/or `Col c` are `Idx Int`, since they are of type `Idx`,
-- which is just a wrappeer for the `Int` index itself. By naming their types on
-- the left-hand side of the definition, we can use `r` and `c` in the
-- computation without unwrapping.
gridSet :: Num a =&amp;gt; Char -&amp;gt; (Idx Int, Idx Int) -&amp;gt; Grid -&amp;gt; Grid
gridSet el (Row r, Col c) g = take i g &amp;#43;&amp;#43; [Just el] &amp;#43;&amp;#43; drop (i&amp;#43;1) g
where i = (r-1)*n &amp;#43; (c-1)
-- Another trick with `Idx` -- we can pattern-match on the type, returning one
-- computation for the nth row of a grid, and another for the mth column.
-- This uses Data.List.chunksOf, which I&amp;#39;, a big fan of.
gridGet :: Grid -&amp;gt; Idx Int -&amp;gt; [Box]
gridGet g (Col x) = map head $ L.chunksOf n $ drop (x-1) g
gridGet g (Row x) = take n $ drop (n*(x-1)) g
-- This is a pretty-printer for a grid -- it unwraps a list of [Maybe Char]s,
-- replacing Nothings with _; then it segments that into chunks of 4 and
-- recombines them with &amp;#34;\n&amp;#34;.
gridPrint :: Grid -&amp;gt; String
gridPrint xs = L.intercalate &amp;#34;\n&amp;#34; $ L.chunksOf n $ unwrap xs
where unwrap [] = &amp;#34;&amp;#34;
unwrap (Just x : xs) = x : unwrap xs
unwrap (Nothing: xs) = &amp;#39;_&amp;#39; : unwrap xs
-- This is a foldr implementing `gridSet` across a list of elements and a list
-- of (Row, Col) pairs. By zipping the chars and locs and `uncurry`ing them, we
-- avoid having to write something like
-- gridWrite [] _ g = g
-- gridWrite (c:cs) (l:ls) g = gridSet c l $ gridWrite cs ls g
gridWrite :: String -&amp;gt; [(Idx Int, Idx Int)] -&amp;gt; Grid -&amp;gt; Grid
gridWrite cs ls g = foldr (uncurry gridSet) g $ zip cs ls
-- All the words we care about. There are 98k words in
-- Language.Words.allStringWords, probably taken from usr/share/dict/words. Of
-- those, 64 are lowercase ascii, and 2.3k are four lettersr long.
allWords :: [String]
allWords = filter (\x -&amp;gt; length x == n)
$ filter (L.all C.isAsciiLower) W.allStringWords
-- Here&amp;#39;s where the magic happens. dictMake i creates a Paths, where:
-- type Paths = Map String [Char]
-- but only where the keys are of length `i`. We call it a few types and
-- `M.unions` them together later on to commbine them into one Map in memory.
dictMake :: Int -&amp;gt; Paths
dictMake len = foldr (\xs -&amp;gt; M.insert (key xs) (val xs)) M.empty nglyphs
where key = take len . head
val = S.fromList . map (head . drop len)
nglyphs = L.groupBy ((==) `F.on` take len) $ map (take $ len&amp;#43;1) allWords
-- Working backwards, assuming len = 2 for this example:
-- allWords ~ [&amp;#34;abed&amp;#34;, &amp;#34;abet&amp;#34;, &amp;#34;able&amp;#34;, &amp;#34;ably&amp;#34;, &amp;#34;abut&amp;#34;...]
-- map (take $ len&amp;#43;1) allWords ~ [&amp;#34;abe&amp;#34;, &amp;#34;abe&amp;#34;, &amp;#34;abl&amp;#34;, &amp;#34;abl&amp;#34;, &amp;#34;abu&amp;#34;...]
-- L.groupBy ((==) `F.on` take len) $ map (take $ len&amp;#43;1) allWords
-- ~ [[&amp;#34;abe&amp;#34;, &amp;#34;abl&amp;#34;..], [&amp;#34;ace&amp;#34;, &amp;#34;ace&amp;#34;, &amp;#34;ach&amp;#34;]..]
-- from this list, we can map `key`, which gets just the first `len` letters
-- from the first item in each list, and
-- `val`, which gets the list of remaining letters.
-- ~ Map [&amp;#34;ab&amp;#34;] -&amp;gt; [&amp;#39;e&amp;#39;,&amp;#39;l&amp;#39;..]
-- [&amp;#34;ac&amp;#34;] -&amp;gt; [&amp;#39;e&amp;#39;,&amp;#39;h&amp;#39;..]
-- Then we fold this `(\xs -&amp;gt; M.insert (key xs) (val xs))`
-- over `nglyphs`, with
-- base `M.empty`.
-- If the data being intersected is complex and needs to be sorted and nubbed,
-- treating the items as Sets is often efficient. That&amp;#39;s why the following
-- function, `children`, used to do a Set intersection, and `paths` used to
-- contain values of `S.Set Char`.
--
-- But it turns out that the values in `Paths` comes pre-sorted and pre-nubbed
-- by virtue of its origins in `W.allStringWords`. So we don&amp;#39;t want the overhead
-- of creating, comparing, and toList-ing `Data.Set`s. We don&amp;#39;t even want the
-- overhead of `Data.List.intersection`, which sorts the arrays before taking
-- their intersection. For that reason, we implement `intersect` ourselves,
-- below.
intersect :: [Char] -&amp;gt; [Char] -&amp;gt; [Char]
intersect [] _ = []
intersect _ [] = []
intersect (a:as) (b:bs)
| a == b = a : intersect as bs
| a &amp;lt; b = intersect as (b:bs)
| a &amp;gt; b = intersect (a:as) bs
-- `children` accepts a `paths` dictionary to hold inline, which end up being
-- fairly efficient -- I believe the compiler notices it is unchanged between
-- calls of `children` and makes it something like a global.
--
-- Anyway, `children` accepts a `paths` dictionary and a grid and returns the
-- possible child grids, as decribed above. This is just a list comprehension
-- where the location of the next blank is described by `(r,c)`, which zips the
-- grid with the locations of its squares and drops filled squares until we get
-- the location of the first blank.
--
-- Additionally, we find the `poss`ible next letters by taking the intersection
-- of the two `setValid` valid lists along the `c` column and `r` row indices,
-- where `setValid` utilized `Data.Maybe.catMaybes` to strip the `Nothing`s from
-- a list of `[Maybe Char]`s, and uses a macro&amp;#39;d `Data.Map.findWithDefault`
-- titled `nextIn`. to find it in the `paths` map.
children :: Paths -&amp;gt; Grid -&amp;gt; [Grid]
children p g = [ gridSet l (r,c) g | l &amp;lt;- poss g ]
where (r,c) = snd $ head $ filter (B.isNothing . fst) $ zip g indices
indices = [ (Row i, Col j) | i&amp;lt;-[1..n], j&amp;lt;-[1..n] ]
nextIn s = M.findWithDefault [] s p
poss g = intersect (setValid c) (setValid r)
where setValid = nextIn . B.catMaybes . gridGet g
-- We want to start with grids with filled top rows and leftmost columns. We use
-- `gridWrite` from before to write full words along lists of locations,
-- described by `firstRow` and `firstColumn`. We write these into a `blank` grid
-- of sixteen `Nothing`s. This, too, is a list comprehension, which is
-- convenient for applying the restrictions which make our grids unique.
--
-- Specifically, we want the top and left words to be different, have the same
-- first letter, and not appear again in the opposite configuration later on.
-- Luckily, we can compare `wa &amp;lt; wb` to make sure this holds.
seeds :: [Grid]
seeds = [ gridWrite wa firstRow $ gridWrite wb firstCol blank
| wa &amp;lt;- allWords , wb &amp;lt;- allWords , wa &amp;lt; wb, wa /= wb, head wa == head wb
]
where firstRow = [ (Row 1, Col x) | x &amp;lt;- [1..n] ]
firstCol = [ (Row x, Col 1) | x &amp;lt;- [1..n] ]
blank = replicate (n^2) Nothing
-- As usual, we utilize the `until cond fn seed` pattern to apply `fn` to `seed`
-- over and over ([x, f(x), f(f(x))..]) until one of the elements fulfills
-- `cond`. In this case, we define `paths` right here, inline, as the union of
-- the `dictMake` dicts for a range of integers from 1 til one less the side
-- length.
--
-- Additionally, we `filter` our answer to make sure none of the grids have
-- repeating words. Eliminating these grids with duplicate words at the very end
-- is more efficient than whittling down the `paths` dictionary recursively. We
-- build `noRepeats` with `wordsIn`, which gets each row and column with
-- `gridGet`, builds a list, nubs it, and inspects its length.
grids = filter noRepeats
$ until (B.isJust . last . head) (concatMap $ children paths) seeds
where paths = M.unions $ map dictMake [1..(n-1)]
noRepeats g = 2*n == length (wordsIn g)
wordsIn g = L.nub
$ map (gridGet g)
$ concatMap (\x -&amp;gt; [Row x, Col x])
[1..n]
-- And that&amp;#39;s it! Initially, we just want to print the number of grids, but
-- we may do more interesting things with `grids` later.
main = print $ length $ grids
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>With comments, this is ~160 lines; without, this is ~70.&lt;/p>
&lt;h3 id="final-answer">Final Answer&lt;/h3>
&lt;p>OK, what you came for. There are $686739$ distinct four-by-four crossword grids in English with no repeats and no diagonal symmetry. The script runs in just over a minute and uses far short of 100% of my memory (unlike several intermediate versions).&lt;/p>
&lt;h2 id="runtime">Runtime&lt;/h2>
&lt;p>Run normally, this script is fairly fast:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="562984713" type="checkbox" />
&lt;label for="562984713">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
j@mes $ ghc -O2 words.hs
[1 of 1] Compiling Main ( words.hs, words.o )
Linking words ...
j@mes $ time ./words
686739
real 1m1.138s
user 1m0.810s
sys 0m0.303s
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>But it was not always so. During development I made extensive use of the built-in GHC profiler:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="485719632" type="checkbox" />
&lt;label for="485719632">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
j@mes $ ghc -prof -fprof-auto -rtsopts -O2 words.hs
[1 of 1] Compiling Main ( words.hs, words.o )
Linking words ...
j@mes $ time ./words &amp;#43;RTS -p
686739
real 2m8.759s
user 2m8.277s
sys 0m0.470s
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>This writes out to &lt;code>words.prof&lt;/code> and looks something like: (see &lt;a href="https://gist.github.com/ambuc/ac4ed787e1b9bb3eba08bb02c9b25c49#file-crossword-prof">the full words.prof profiler output on Gist.&lt;/a>)&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="683514972" type="checkbox" />
&lt;label for="683514972">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
Sun May 28 00:13 2017 Time and Allocation Profiling Report (Final)
words &amp;#43;RTS -p -RTS
total time = 107.52 secs (107523 ticks @ 1000 us, 1 processor)
total alloc = 134,700,416,432 bytes (excludes profiling overheads)
COST CENTRE MODULE %time %alloc
children.nextIn Main 20.8 0.0
children.(...) Main 13.1 14.1
gridGet Main 11.0 16.8
children Main 10.0 6.1
gridSet Main 9.4 20.8
build Data.List.Split.Internals 7.7 9.9
chunksOf Data.List.Split.Internals 7.1 18.8
grids Main 5.7 2.6
children.poss.setValid Main 5.5 6.3
intersect Main 5.3 1.8
grids.wordsIn Main 1.9 1.2
children.poss Main 1.4 1.4
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Which gives a bit of a hint where things might be taking up a lot of time. In this case, &lt;code>nextIn&lt;/code> is taking up 20% of our time, and it&amp;rsquo;s an $O(\log n)$ pre-optimized Map lookup function. Might be time to stop optimizing, with a near-one-minute runtime.&lt;/p>
&lt;h2 id="more-fun-with-our-solver">More Fun with our Solver&lt;/h2>
&lt;p>What else can we do with our program now that we have a pretty speedy crossword solver? Well, because Haskell is lazy it&amp;rsquo;s not that hard to see &lt;em>if&lt;/em> there exists a 5x5 or 6x6 grid. By doing &lt;code>main = putStrLn $ gridPrint $ head grids&lt;/code> we end up executing incredibly fast:&lt;/p>
&lt;p>Here are some grids I found:&lt;/p>
&lt;pre tabindex="0">&lt;code>0.062s 0.164s 54.0s
ABED ABACI ABBESS
BLUR BOWED SEESAW
EERY AXIAL CATTLE
TWOS SENSE ERRATA
EDGED NEATER
DRYERS
&lt;/code>&lt;/pre>&lt;h3 id="palindromes">Palindromes&lt;/h3>
&lt;p>Additionally, we can find &amp;ldquo;palindromic&amp;rdquo; crosswords, where the words are valid even if the grid is rotated 90, 180, or 270 degrees:&lt;/p>
&lt;p>In 6.627s it finds the following palindromic 4x4:&lt;/p>
&lt;pre>&lt;code>DRAB WARD DRAW BARD
RAGA AJAR RAJA AGAR
AJAR RAGA AGAR RAJA
WARD DRAB BARD DRAW
&lt;/code>&lt;/pre>
&lt;p>There are 10 such palindromic 4x4s; eight of them rely on the quartet &lt;code>raga/raja/ajar/agar&lt;/code> at their centers; the other two rely on &lt;code>time/tide/emit/edit&lt;/code>.&lt;/p>
&lt;pre>&lt;code>DRAB DRAB DRAB DRAB DRAW DRAW TRAM TRAM STEP STEP
RAGA RAJA RAGA RAJA RAGA RAJA RAGA RAJA TIDE TIME
AJAR AGAR AJAR AGAR AJAR AGAR AJAR AGAR EMIT EDIT
WARD WARD YARD YARD YARD YARD PART PART WETS WETS
&lt;/code>&lt;/pre>
&lt;h3 id="word-frequency">Word Frequency&lt;/h3>
&lt;p>By running&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="315876429" type="checkbox" />
&lt;label for="315876429">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
main = Control.Monad.forM grids
$ \g -&amp;gt; appendFile &amp;#34;grids.txt&amp;#34; $ gridPrint g &amp;#43;&amp;#43; &amp;#34;\n&amp;#34;
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>We can write a list of all grids to a file &lt;code>grids.txt&lt;/code> and perform some rudimentary Bash-level analysis on what sorts of words appear in the rows most commonly:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="532694187" type="checkbox" />
&lt;label for="532694187">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
j@mes $ cat grids.txt | sort | uniq -c | sort -h | tail
14642 west
15424 oboe
15516 oral
16678 pest
17451 test
19541 psst
22165 aria
26541 oleo
28531 area
85689 urea
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>These don&amp;rsquo;t quite look like the letter distributions we&amp;rsquo;re used to in full-fledged English &amp;ndash; let&amp;rsquo;s look at letter frequencies and see how different it is for four-letter words, and which letters are more likely to appear in crosswords.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="695324781" type="checkbox" />
&lt;label for="695324781">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
j@mes $ awk -vFS=&amp;#34;&amp;#34; &amp;#39;{for(i=1;i&amp;lt;=NF;i&amp;#43;&amp;#43;)w[$i]&amp;#43;&amp;#43;}END{for(i in w) print w[i],i}&amp;#39; grids.txt | sort -hr
1673174 e
1428194 a
1307329 s
781441 t
722476 o
709668 l
693442 r
450942 p
448701 n
424290 i
324295 d
296590 m
246367 w
236242 c
221680 u
204857 h
199943 b
185596 g
114375 k
106834 v
104330 y
80840 f
17625 x
4530 j
4030 z
33 q
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Looks like &lt;code>q&lt;/code> only appears 33 times; only ever in the context of &lt;code>quay&lt;/code>, &lt;code>aqua&lt;/code>, &lt;code>quip&lt;/code>, &lt;code>quad&lt;/code>, &lt;code>quit&lt;/code>, or &lt;code>quid&lt;/code>.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>I have challenged &lt;a href="http://joshmermelstein.com/">Josh Mermelstein&lt;/a> to solve the following puzzle:&lt;/p>
&lt;blockquote>
&lt;p>For a set of &lt;code>n&lt;/code> letters &lt;code>('a','b','d')&lt;/code> you can make &lt;code>m&lt;/code> real English words &lt;code>(&amp;quot;bad&amp;quot;, &amp;quot;dab&amp;quot;, etc...)&lt;/code>. Find the subset of all 26 letters with the highest ratio of words to letters; in other words, the most bang for your buck.&lt;/p>
&lt;/blockquote></content></item><item><title>Solving One Tough Puzzle</title><link>https://jbuckland.com/blog/puzzle-one-tough-puzzle-ii/</link><pubDate>Sat, 29 Apr 2017 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/puzzle-one-tough-puzzle-ii/</guid><description>Github https://gist.github.com/ambuc/b8ce48c034a1843d7ab1def052654d15 This is a puzzle from 538&amp;rsquo;s Puzzler. The puzzle is as follows:
You play a game with four balls: One ball is red, one is blue, one is green and one is yellow. They are placed in a box. You draw a ball out of the box at random and note its color. Without replacing the first ball, you draw a second ball and then paint it to match the color of the first.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">Github&lt;/th>
&lt;td> &lt;a href="https://gist.github.com/ambuc/b8ce48c034a1843d7ab1def052654d15">https://gist.github.com/ambuc/b8ce48c034a1843d7ab1def052654d15&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;p>This is a puzzle from 538&amp;rsquo;s &lt;a href="https://fivethirtyeight.com/features/can-you-solve-these-colorful-puzzles/">Puzzler&lt;/a>. The puzzle is as follows:&lt;/p>
&lt;blockquote>
&lt;p>You play a game with four balls: One ball is red, one is blue, one is green and one is yellow. They are placed in a box. You draw a ball out of the box at random and note its color. Without replacing the first ball, you draw a second ball and then paint it to match the color of the first. Replace both balls, and repeat the process. The game ends when all four balls have become the same color. What is the expected number of turns to finish the game?&lt;/p>
&lt;/blockquote>
&lt;h2 id="the-solution">The Solution&lt;/h2>
&lt;p>We can simplify this problem down from (all possible combinations of colors) to (all possible combinations of the number of balls of each color).&lt;/p>
&lt;p>The initial state (red, yellow, green, blue) can be written as &lt;code>(1,1,1,1)&lt;/code>; the final state (with all balls of the same color) can be written as &lt;code>(4)&lt;/code>.&lt;/p>
&lt;p>It turns out there are only five possible states: &lt;code>[(1,1,1,1),(2,1,1),(3,1),(2,2),(4)]&lt;/code>.&lt;/p>
&lt;p>So the most important thing will be figuring out how often state &lt;em>i&lt;/em> goes to state &lt;em>j&lt;/em>. Once we get a complete list of those states and probabilities, it turns out we can draw a Markov chain, turn its edges into cells in a stochastic matrix, and then find the expected number of turns to finish. The expected number of turns to finish is also known as the &lt;em>expected hitting time&lt;/em>. We will get into all that a bit later.&lt;/p>
&lt;h2 id="the-code">The Code&lt;/h2>
&lt;h3 id="writing-pdist">Writing &lt;code>pDist&lt;/code>&lt;/h3>
&lt;p>First, we want to write a function which can take a state and figure out which other states it can go to and how likely it is to do that. Our function &lt;code>pDist&lt;/code> does that. Since the states will soon be nodes in a graph, we refer to them as &lt;code>node&lt;/code> inline.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="731596842" type="checkbox" />
&lt;label for="731596842">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
import Control.Arrow (second, (&amp;amp;&amp;amp;&amp;amp;))
import Data.Either (rights)
import Data.List (sort, group, permutations, delete)
import Data.Map.Strict as Map (Map, empty, fromList, keys, elems, insert,
findWithDefault)
import Data.Matrix as Matrix (Matrix, fromList, multStd, inverse, identity,
submatrix, nrows)
import Data.Ratio (Ratio, (%))
import Data.Set as Set (Set, toList, difference, fromList)
import Prelude as P
import System.Environment (getArgs)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Here&amp;rsquo;s our spec: &lt;code>pDist i&lt;/code> will return a map where the keys are the nodes &lt;code>j&lt;/code> it is possible to reach from &lt;code>i&lt;/code>, and the values are the probability of reaching &lt;code>j&lt;/code> from &lt;code>i&lt;/code>.&lt;/p>
&lt;p>First we turn the node (&lt;code>[1,1,1,1]&lt;/code>) into an actual list of different &amp;ldquo;colors&amp;rdquo;. We call this &lt;code>toUniqueRepresentation&lt;/code>. It takes a node like &lt;code>[1,1,1,1]&lt;/code> and returns a list like &lt;code>[1,2,3,4]&lt;/code>.&lt;/p>
&lt;p>Then we call a custom &lt;code>allPairings&lt;/code> on that unique representation. This is a reduced form of &lt;code>Data.List.permutations&lt;/code> which generates all possible pairings we actually care about. For example, &lt;code>[1,2,3,4]&lt;/code> and &lt;code>[1,2,4,3]&lt;/code> are different and might both be generated by &lt;code>permutations&lt;/code>, but in practice we really only care about the order of the first two and the contents (but not order) of the rest of the list. This is done with &lt;code>delete&lt;/code>, which searches through a list in $O(n)$ time to delete the first instance of a passed argument. This returns a list of potential boxes.&lt;/p>
&lt;p>Then we perform the &lt;code>paint&lt;/code> operation on each potential box. This involves taking two balls, painting them both the color of the first, and returning them to the box.&lt;/p>
&lt;p>This &lt;code>paint&lt;/code> function is done inside a larger &lt;code>decorate&lt;/code> decorator, which paints, sorts, groups, replaces each group with its length, and sorts those numbers. This is computationally the bottleneck of the solution, but it is the bit of logic which attempts to resolve [A,B,B,C] and [A,A,B,C] to the same underlying distribution [2,1,1].&lt;/p>
&lt;p>After &lt;code>decorate&lt;/code>ing each potential box, we probably end up with a long list of resultant states with a lot of repetition. We don&amp;rsquo;t want the whole list of resultant states; we want a histogram! We run it thru &lt;code>freqMap&lt;/code>, which does something like:&lt;/p>
&lt;p>&lt;code>freqMap [1,1,2] -&amp;gt; [(1, 2%3), (2, 1%3)]&lt;/code>, where the keys of the resultant map are the possible end states, and the values are the probabilities (as fractions).&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="398154672" type="checkbox" />
&lt;label for="398154672">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
pDist :: [Int] -&amp;gt; Map [Int] (Ratio Int)
pDist = freqMap . P.map decorate . allPairings . toUniqueRepresentation
where toUniqueRepresentation :: [Int] -&amp;gt; [Int]
toUniqueRepresentation node = concat $ zipWith replicate node [1..]
allPairings :: [Int] -&amp;gt; [[Int]]
allPairings ns = [ x : y : delete y (delete x ns)
| x &amp;lt;- ns, y &amp;lt;- delete x ns
]
decorate :: [Int] -&amp;gt; [Int]
decorate = sort . P.map length . group . sort . paint
paint :: [Int] -&amp;gt; [Int]
paint (x:y:xs) = x:x:xs
freqMap :: [[Int]] -&amp;gt; Map [Int] (Ratio Int)
freqMap xs = Map.fromList
$ P.map ( second (% length xs) . (head &amp;amp;&amp;amp;&amp;amp; length) )
$ group $ sort xs
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Phew. Now that we have a function like &lt;code>pDist&lt;/code>, let&amp;rsquo;s see how it behaves. Inside &lt;code>ghci&lt;/code>:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="925184367" type="checkbox" />
&lt;label for="925184367">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
*Main&amp;gt; pDist [1,1,1,1]
fromList [([1,1,2],1 % 1)]
*Main&amp;gt; pDist [1,1,2]
fromList [([1,1,2],1 % 2),([1,3],1 % 3),([2,2],1 % 6)]
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Very nice. It looks like &lt;code>[1,1,1,1]&lt;/code> always goes to &lt;code>[1,1,2]&lt;/code>, with a ratio of &lt;code>1%1&lt;/code> or 100%. It also looks like there are lots of places &lt;code>[1,1,2]&lt;/code> could go; it loops back in on itself half the time.&lt;/p>
&lt;h3 id="writing-makearrows">Writing &lt;code>makeArrows&lt;/code>&lt;/h3>
&lt;p>Eventually we want to get a list of all reachable states in the puzzle, and turn those states into a set of nodes. Once we have the nodes, we can create a graph with arrows from node to node, and think about our puzzle like that. This is shaping up to be a &lt;a href="https://en.wikipedia.org/wiki/Markov_chain">Markov chain&lt;/a>, so we might as well embrace it and prepare for the &lt;a href="https://en.wikipedia.org/wiki/Stochastic_matrix">stochastic matrix&lt;/a> which is to come.&lt;/p>
&lt;p>Our function &lt;code>makeArrows&lt;/code> is another classic haskell &lt;code>until condition function seed&lt;/code>, which applies function to seed over and over until some condition is met. We will use this to build a 2D Map, where the keys are states and the values are also maps from states to probabilities. Think about it like this:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="762341958" type="checkbox" />
&lt;label for="762341958">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
Map key = state i
value = Map (key = state j )
(value = prob. of transition i -&amp;gt; j)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>We don&amp;rsquo;t know &lt;em>any&lt;/em> of this at the beginining, so we &lt;code>seed&lt;/code> our function with the initial state &lt;code>[1,1,1,1]&lt;/code> and let it build itself lazily. The initial 2D map is just &lt;code>Map.insert seed (pDist seed) Map.empty&lt;/code>, which inserts: &lt;code>map[seed] = (pDist seed)&lt;/code> into an empty map.&lt;/p>
&lt;p>How do we know we&amp;rsquo;re done making this 2D map? There are lots of states in this space which are not reachable; we only care about states which either have arrows coming from or going to them. We&amp;rsquo;ll be done when every node which is the endpoint of an arrow is also the beginning of an arrow. Said another way, we&amp;rsquo;ll be done when every node &lt;code>j&lt;/code> in the list of &lt;code>state j&lt;/code>s is also in the list of &lt;code>state i&lt;/code>s.&lt;/p>
&lt;p>We&amp;rsquo;ll write some helper functions, &lt;code>is&lt;/code> and &lt;code>js&lt;/code>, which return the set of all states with arrows leaving and entering them, respectively. &lt;code>diff&lt;/code> does a set difference, so we&amp;rsquo;re &lt;code>finished&lt;/code> when that &lt;code>diff&lt;/code> is &lt;code>null&lt;/code>!&lt;/p>
&lt;p>If we&amp;rsquo;re not finished, we want to take a &lt;code>step&lt;/code> forwards in finishing this 2D map. We find the &lt;code>diff&lt;/code>, take the first &lt;code>node&lt;/code>, and perform &lt;code>Map.insert node (pDist node)&lt;/code> on the old &lt;code>arrows&lt;/code> 2D map.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="761934528" type="checkbox" />
&lt;label for="761934528">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
makeArrows :: [Int] -&amp;gt; Map [Int] (Map [Int] (Ratio Int))
makeArrows seed = until finished step $ Map.insert seed (pDist seed) Map.empty
where finished :: Map [Int] (Map [Int] (Ratio Int)) -&amp;gt; Bool
finished = P.null . diff
step :: Map [Int] (Map [Int] (Ratio Int)) -&amp;gt; Map [Int] (Map [Int] (Ratio Int))
step arrows = Map.insert node (pDist node) arrows
where node :: [Int]
node = head $ Set.toList $ diff arrows
diff :: Map [Int] (Map [Int] (Ratio Int)) -&amp;gt; Set [Int]
diff arrows = Set.difference (js arrows) (is arrows)
is :: Map [Int] (Map [Int] (Ratio Int)) -&amp;gt; Set [Int]
is = Set.fromList . Map.keys
js :: Map [Int] (Map [Int] (Ratio Int)) -&amp;gt; Set [Int]
js = Set.fromList . concatMap Map.keys . Map.elems
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Trying it, we find:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="921387654" type="checkbox" />
&lt;label for="921387654">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
*Main&amp;gt; makeArrows [1,1,1,1]
fromList [([1,1,1,1],fromList [([1,1,2],1 % 1)]),([1,1,2],fromList [([1,1,2],1 % 2),([1,3],1 % 3),([2,2],1 % 6)]),([1,3],fromList [([1,3],1 % 2),([2,2],1 % 4),([4],1 % 4)]),([2,2],fromList [([1,3],2 % 3),([2,2],1 % 3)]),([4],fromList [([4],1 % 1)])]
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>I&amp;rsquo;ll clean this up for you.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="314678925" type="checkbox" />
&lt;label for="314678925">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
*Main&amp;gt; makeArrows [1,1,1,1]
fromList [ ([1,1,1,1],fromList [([1,1,2],1 % 1)])
, ([1,1,2], fromList [([1,1,2],1 % 2)
,([1,3], 1 % 3)
,([2,2], 1 % 6)])
, ([1,3], fromList [([1,3], 1 % 2)
,([2,2], 1 % 4)
,([4], 1 % 4)])
, ([2,2], fromList [([1,3], 2 % 3)
,([2,2], 1 % 3)])
, ([4], fromList [([4], 1 % 1)])
]
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>This is great! We can look up a key for state &lt;code>i&lt;/code>, look up another key for state &lt;code>j&lt;/code>, and find a probability &lt;code>P_{i,j}&lt;/code>. If it doesn&amp;rsquo;t exist, we know you can&amp;rsquo;t reach &lt;code>j&lt;/code> from &lt;code>i&lt;/code>. But we don&amp;rsquo;t need a 2D map, we need a matrix to do matrix arithmetic on.&lt;/p>
&lt;h3 id="writing-makematrix">Writing &lt;code>makeMatrix&lt;/code>&lt;/h3>
&lt;p>&lt;code>Data.Matrix&lt;/code> has a utility &lt;code>fromList&lt;/code> to take a list and turn it into an &lt;code>n&lt;/code> by &lt;code>m&lt;/code> matrix; we can simply turn &lt;code>makeArrows _&lt;/code> into a list with &lt;code>0&lt;/code>s in the appropriate places.&lt;/p>
&lt;p>If we get a list of all possible nodes, we can do a list comprehension like &lt;code>[probability (or zero) from i to j | i &amp;lt;- nodes, j &amp;lt;- nodes]&lt;/code>. We use &lt;code>nodes = Map.keys arrows&lt;/code> to represent all the states we care about, and use a doubly-nested &lt;code>Map.findWithDefault&lt;/code> to perform a lookup with two defaults.&lt;/p>
&lt;p>If &lt;code>i&lt;/code> isn&amp;rsquo;t in the map, we return an empty map to perform our second lookup on; if &lt;code>j&lt;/code> isn&amp;rsquo;t in that second map, we return the zero ratio &lt;code>(0%1)&lt;/code>. This looks like &lt;code>[ Map.findWithDefault (0%1) j $ Map.findWithDefault Map.empty i arrows | i &amp;lt;- nodes, j &amp;lt;- nodes ]&lt;/code>.&lt;/p>
&lt;p>Writing this with &lt;code>nodes&lt;/code> and some dimension &lt;code>n&lt;/code> defined inline, we get:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="148625793" type="checkbox" />
&lt;label for="148625793">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
makeMatrix :: Map [Int] (Map [Int] (Ratio Int)) -&amp;gt; Matrix (Ratio Int)
makeMatrix arrows = Matrix.fromList n n list
where list :: [Ratio Int]
list = [ Map.findWithDefault (0%1) j $ Map.findWithDefault Map.empty i arrows
| i &amp;lt;- nodes, j &amp;lt;- nodes
]
n :: Int
n = length nodes
nodes :: [[Int]]
nodes = Map.keys arrows
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Let&amp;rsquo;s try it:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="691387425" type="checkbox" />
&lt;label for="691387425">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
*Main&amp;gt; makeMatrix $ makeArrows [1,1,1,1]
( 0 % 1 1 % 1 0 % 1 0 % 1 0 % 1 )
( 0 % 1 1 % 2 1 % 3 1 % 6 0 % 1 )
( 0 % 1 0 % 1 1 % 2 1 % 4 1 % 4 )
( 0 % 1 0 % 1 2 % 3 1 % 3 0 % 1 )
( 0 % 1 0 % 1 0 % 1 0 % 1 1 % 1 )
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>This looks exactly right to me. Just for fun, let&amp;rsquo;s use &lt;code>dot&lt;/code>/&lt;code>graphviz&lt;/code> to draw this chain with its proper nodes and edges, for comparison to the stochastic matrix which represents it.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/colorful-graph.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;h3 id="writing-expectedvalue">Writing &lt;code>expectedValue&lt;/code>&lt;/h3>
&lt;p>The final step will be to tease an average lifetime from this matrix. Our problem is actually asking for the &lt;em>expected hitting time&lt;/em> of the Markov chain. This is the part which actually requires some math.&lt;/p>
&lt;p>If we write our stochastic matrix $\textbf{P}$, we can remove the bottom row and rightmost column to remove the influence of our end state on the average lifetime. We write this reduced matrix as $\textbf{T}$, a &lt;code>submatrix 1 n 1 n p&lt;/code>.&lt;/p>
&lt;p>$$ \textbf{P} = \begin{bmatrix} 0 &amp;amp; 1 &amp;amp; 0 &amp;amp; 0 &amp;amp; 0\\0 &amp;amp; \frac{1}{2} &amp;amp; \frac{1}{3} &amp;amp; \frac{1}{6} &amp;amp; 0\\0 &amp;amp; 0 &amp;amp; \frac{1}{2} &amp;amp; \frac{1}{4} &amp;amp; \frac{1}{4}\\0 &amp;amp; 0 &amp;amp; \frac{2}{3} &amp;amp; \frac{1}{3} &amp;amp; 0\\0 &amp;amp; 0 &amp;amp; 0 &amp;amp; 0 &amp;amp; 1 \end{bmatrix} $$&lt;/p>
&lt;p>$$ \textbf{T} = \begin{bmatrix} 0 &amp;amp; 1 &amp;amp; 0 &amp;amp; 0\\0 &amp;amp; \frac{1}{2} &amp;amp; \frac{1}{3} &amp;amp; \frac{1}{6}\\0 &amp;amp; 0 &amp;amp; \frac{1}{2} &amp;amp; \frac{1}{4}\\0 &amp;amp; 0 &amp;amp; \frac{2}{3} &amp;amp; \frac{1}{3} \end{bmatrix} $$&lt;/p>
&lt;p>Here I begin quoting the Wikipedia &lt;a href="https://en.wikipedia.org/wiki/Stochastic_matrix">Stochastic matrix&lt;/a> page&lt;/p>
&lt;ul>
&lt;li>$E[k] = \tau(I + \textbf{T} + \textbf{T}^2 + &amp;hellip;)\textbf{1}$&lt;/li>
&lt;li>$\phantom{E[k]} = \tau(I-\textbf{T})^{-1}\textbf{1}$ is the &lt;em>expected
hitting time&lt;/em> of the final state, where&lt;/li>
&lt;li>$\textbf{T}$ is the truncated matrix above,&lt;/li>
&lt;li>$\tau = \begin{bmatrix} 1 &amp;amp; 0 &amp;amp; 0 &amp;amp; 0 \end{bmatrix}$ is an array as long as the number of relevant states $n = 4$ with the initial state marked (in this case, state 1),&lt;/li>
&lt;li>$I$ is a 4x4 identity matrix, and&lt;/li>
&lt;li>$\textbf{1} = \begin{bmatrix} 1 &amp;amp; 1 &amp;amp; 1 &amp;amp; 1 \end{bmatrix}^{T}$ is a column vector of ones, as high as the number of relevant states $n$.&lt;/li>
&lt;/ul>
&lt;p>Luckily Haskell has some nice &lt;a href="https://hackage.haskell.org/package/matrix/docs/Data-Matrix.html">Matrix handling faculties&lt;/a>.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="975184632" type="checkbox" />
&lt;label for="975184632">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
expectedValue :: Matrix (Ratio Int) -&amp;gt; Matrix (Ratio Int)
expectedValue p = P.foldr1 multStd [tau, inv, one]
where tau = Matrix.fromList 1 n (1:[0,0..]) -- [1,0,0..]
one = Matrix.fromList n 1 [1,1..] -- [[1],[1],...]
inv = head $ rights $ (:[])
$ inverse (identity n - t) -- (I - T)^(-1)
t = submatrix 1 n 1 n p -- P, but w/o last row / last col
n = nrows p - 1
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h2 id="tying-it-all-together">Tying it all together&lt;/h2>
&lt;p>We wrap this in a &lt;code>main&lt;/code> function which allows a &lt;code>seed&lt;/code> to be passed in as a CLI argument, and finally call &lt;code>(exectedValue . makeMatrix . makeArrows) seed&lt;/code>.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="583719624" type="checkbox" />
&lt;label for="583719624">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
main = do
args &amp;lt;- getArgs
let seed = read (head args) :: [Int]
print $ expectedValue $ makeMatrix $ makeArrows seed
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>That&amp;rsquo;s all! If we call &lt;code>ghc&lt;/code> to compile it first, it takes 0.006s.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="519437628" type="checkbox" />
&lt;label for="519437628">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
j@mes ~/dev/math-problems/four-color-balls $ ghc colorful.lhs -O2
j@mes ~/dev/math-problems/four-color-balls $ ./colorful [1,1,1,1]
( 9 % 1 )
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Thus, the expected number of turns until the game is over is $9$.&lt;/p>
&lt;p>You can see the complete (unannotated) haskell code on &lt;a href="https://gist.github.com/ambuc/b8ce48c034a1843d7ab1def052654d15">gist&lt;/a>.&lt;/p></content></item><item><title>Writing an Aaronson Oracle</title><link>https://jbuckland.com/blog/math-oracle/</link><pubDate>Sun, 23 Apr 2017 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/math-oracle/</guid><description>Github https://gist.github.com/ambuc/01187518b73c21029e8ef427cc9137be The Aaronson Oracle is a game where you type f and d at random and the Oracle tries to guess which you&amp;rsquo;re gonna type next. It looks at your input history and tries to find patterns.
haskell import Data.Map as M import Data.Maybe import Numeric We use a Map [Bool] Double structure (called a brain here for no good reason) to store sequences and how often they occur.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">Github&lt;/th>
&lt;td> &lt;a href="https://gist.github.com/ambuc/01187518b73c21029e8ef427cc9137be">https://gist.github.com/ambuc/01187518b73c21029e8ef427cc9137be&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;p>The Aaronson Oracle is a game where you type &lt;code>f&lt;/code> and &lt;code>d&lt;/code> at random and the
Oracle tries to guess which you&amp;rsquo;re gonna type next. It looks at your input
history and tries to find patterns.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="782563941" type="checkbox" />
&lt;label for="782563941">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
import Data.Map as M
import Data.Maybe
import Numeric
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>We use a &lt;code>Map [Bool] Double&lt;/code> structure (called a brain here for no good reason)
to store sequences and how often they occur. Instead of storing &lt;code>f&lt;/code> and &lt;code>d&lt;/code>, we
store Booleans True and False &lt;code>(True = &amp;quot;f&amp;quot;)&lt;/code>.&lt;/p>
&lt;p>We improve the brain with &lt;code>learn&lt;/code>, which takes the history of sequences and uses
it to increment subsequence occurrence counts. We store sequences between three
and five characters long, and simultaneously create and/or update them with:&lt;/p>
&lt;ul>
&lt;li>&lt;code>Map.insertwithKey f key newValue&lt;/code>, where&lt;/li>
&lt;li>&lt;code>f key oldValue newValue = oldValue + newValue&lt;/code>, which can be written&lt;/li>
&lt;li>&lt;code>f _ = (+)&lt;/code> or&lt;/li>
&lt;li>&lt;code>f = const (+)&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>We fold this &lt;code>learn'&lt;/code> operation across the brain a few times and return the new
brain.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="623197485" type="checkbox" />
&lt;label for="623197485">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
learn :: [Bool] -&amp;gt; Map [Bool] Double -&amp;gt; Map [Bool] Double
learn hist brain = Prelude.foldr learn&amp;#39; brain [3..5]
where learn&amp;#39; n = M.insertWithKey (const (&amp;#43;)) (take n hist) 1.0
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>We want to use the brain to &amp;ldquo;guess&amp;rdquo; how likely an &lt;code>f&lt;/code> or &lt;code>d&lt;/code> will be. This will
be a weighted average, where the value is how likely a given lookback period
would indicate, and the weight is how many datapoints we have for a given
lookback period.&lt;/p>
&lt;p>For example, take a brain with key/value pairs:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="864273195" type="checkbox" />
&lt;label for="864273195">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
[ T T ] -&amp;gt; 2
[ F T ] -&amp;gt; 3
[ T T F ] -&amp;gt; 3
[ F T F ] -&amp;gt; 1
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Imagine we&amp;rsquo;ve just typed &lt;code>F&lt;/code> and then &lt;code>T&lt;/code>. Looking back only one character (just
at the &lt;code>T&lt;/code>) would indicate that F will follow 3 out of 5 times, but looking back
two characters would indicate that &lt;code>T&lt;/code> will follow 3 out of 4 times. We can
weight those values by certainty to get a pretty good prediction.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="395761824" type="checkbox" />
&lt;label for="395761824">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
guess :: Map [Bool] Double -&amp;gt; [Bool] -&amp;gt; Bool
guess brain hist = wAvg (Prelude.map guess&amp;#39; [2..4]) &amp;gt;= 0.5
where wAvg xs = sum (Prelude.map (uncurry (*)) xs) / sum (Prelude.map snd xs)
guess&amp;#39; n = (occ True / (occ True &amp;#43; occ False), occ True &amp;#43; occ False)
where occ val = fromMaybe 0.0 $ M.lookup (val : take n hist) brain
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>&lt;code>M.lookup key map&lt;/code> returns a &lt;code>Maybe val&lt;/code>, so we wrap it in a &lt;code>fromMaybe default&lt;/code>
to ensure &lt;em>some&lt;/em> real number gets returned.&lt;/p>
&lt;p>Then it&amp;rsquo;s time to play a turn! Each turn needs to be aware of what turn number
it is (turn), how many games were won and held before it (wons, total), the
prior history (hist), and the prior brain (brain).&lt;/p>
&lt;p>We get the keypress, judge it against the &lt;code>guess brain hist&lt;/code>, and print a
message to the user.&lt;/p>
&lt;p>Then we kick off a new round with an incremented turn number, potential
incremented win number, incremented total games number, augmented history, and
new and improved brain.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="259417386" type="checkbox" />
&lt;label for="259417386">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
play :: Int -&amp;gt; (Double, Double) -&amp;gt; [Bool] -&amp;gt; Map [Bool] Double -&amp;gt; IO a
play turn (wons, total) hist brain = do
press &amp;lt;- getLine
let key = press == &amp;#34;f&amp;#34;
let right = key == guess brain hist
let wins = (if right then succ else id) wons
print $ &amp;#34;I guessed &amp;#34; &amp;#43;&amp;#43; (if right then &amp;#34;RIGHT!&amp;#34; else &amp;#34;wrong.&amp;#34;)
&amp;#43;&amp;#43; &amp;#34; My avg: &amp;#34; &amp;#43;&amp;#43; showFFloat (Just 2) (wins / succ total) &amp;#34;&amp;#34;
play (succ turn) (wins, succ total) (key:hist) (learn (key:hist) brain)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>The main block prints instructions and kicks off the game at round zero, with
zero wins for zero total games, an empty history array, and an empty brain.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="456239781" type="checkbox" />
&lt;label for="456239781">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
main = do
print &amp;#34;aaronson oracle | Press &amp;#39;f&amp;#39; or &amp;#39;d&amp;#39; over and over (followed by enter)&amp;#34;
print &amp;#34; | and we&amp;#39;ll try to predict which you&amp;#39;ll press next.&amp;#34;
play 0 (0.0, 0.0) [] M.empty
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>You can see the full code &lt;a href="https://gist.github.com/ambuc/01187518b73c21029e8ef427cc9137be">here, in a
gist&lt;/a>.&lt;/p></content></item><item><title>Solving One Tough Puzzle</title><link>https://jbuckland.com/blog/puzzle-one-tough-puzzle/</link><pubDate>Sun, 22 Jan 2017 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/puzzle-one-tough-puzzle/</guid><description>Github https://gist.github.com/ambuc/ec5d72fcc6d931afa745e3c8ac100edb The Puzzle One Tough Puzzle is a physical jigsaw puzzle made up of nine square pieces. Each side of the square as either a tab or a blank in the shape of one of the four card suits. The goal is to put them all together so they fit, like any jigsaw puzzle.
I will use this post as a tour of some cool Haskell features I&amp;rsquo;ve been enjoying for the past few months doing Project Euler problems.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">Github&lt;/th>
&lt;td> &lt;a href="https://gist.github.com/ambuc/ec5d72fcc6d931afa745e3c8ac100edb">https://gist.github.com/ambuc/ec5d72fcc6d931afa745e3c8ac100edb&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;h2 id="the-puzzle">The Puzzle&lt;/h2>
&lt;p>
&lt;picture>
&lt;source srcset="https://jbuckland.com/blog/puzzle-one-tough-puzzle/images/tough_hu48fbc04b6937a8de41c253b266db803a_803803_2804x1864_resize_q75_h2_box.webp" type="image/webp" />
&lt;img
class="img-fluid"
src="https://jbuckland.com/blog/puzzle-one-tough-puzzle/images/tough.80d92eb6dd54ac0cc007db62486ef9f0.jpg"
alt="Photocredit to https://spabettie.files.wordpress.com"
loading="lazy"
height="1864"
width="2804"
/>
&lt;/picture>
&lt;/p>
&lt;p>&lt;strong>One Tough Puzzle&lt;/strong> is a &lt;a href="https://www.amazon.com/Ideal-0X120-One-Tough-Puzzle/dp/B000A32O2E">physical jigsaw puzzle&lt;/a> made up of nine square pieces. Each side of the square as either a tab or a blank in the shape of one of the four card suits. The goal is to put them all together so they fit, like any jigsaw puzzle.&lt;/p>
&lt;p>I will use this post as a tour of some cool Haskell features I&amp;rsquo;ve been enjoying for the past few months doing Project Euler problems.&lt;/p>
&lt;h3 id="the-problem">The Problem&lt;/h3>
&lt;p>We number the spaces like so:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;td>2&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3&lt;/td>
&lt;td>4&lt;/td>
&lt;td>5&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>6&lt;/td>
&lt;td>7&lt;/td>
&lt;td>8&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Nine pieces in nine slots gives us $9! = 362880$ positional combinations, and nine pieces which can be rotated any of four ways gives us $4^9 = 262144$ rotational combinations, for a total of just over $23\times 10^9$ combinations (accounting for rotational symmetry). Too many to brute force.&lt;/p>
&lt;h2 id="the-solution">The Solution&lt;/h2>
&lt;p>We can instead try to solve the problem a little bit at a time. Our algorithm will be as follows:&lt;/p>
&lt;ul>
&lt;li>For position &lt;em>n&lt;/em>:
&lt;ul>
&lt;li>generate all possible options for position &lt;em>n&lt;/em>&lt;/li>
&lt;li>validate position &lt;em>n&lt;/em> with regard to the pieces around it&lt;/li>
&lt;li>repeat for position &lt;em>n+1&lt;/em> until done&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>This has the advantage of trimming the search space as quickly as possible, to avoid having to validate all twenty-three billion possible grids. We will check in at the end and see how many grids or fractions thereof we actually did have to validate.&lt;/p>
&lt;h2 id="using-haskell">Using Haskell&lt;/h2>
&lt;p>My language of choice for this is Haskell, mostly because I&amp;rsquo;ve been doing Project Euler problems in it recently and I really like the custom data types and flexibility.&lt;/p>
&lt;h3 id="data">&lt;code>data&lt;/code>&lt;/h3>
&lt;p>Haskell has the ability to define custom data types, and compose and define them in interesting ways. In this puzzle we want to be able to inspect and compare tabs and blanks, puzzle pieces, etc.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="456123798" type="checkbox" />
&lt;label for="456123798">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
data Suit = Club | Heart | Diamond | Spade deriving (Show, Read, Eq)
data Sex = Out | In deriving (Show, Read, Eq)
data Side = Side { suit :: Suit , sex :: Sex} deriving (Show, Read)
data Piece = Piece { north :: Side , east :: Side
, south :: Side , west :: Side } deriving (Show, Read, Eq)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>&lt;code>deriving&lt;/code> after a custom data type tells Haskell to infer how to print (&lt;code>show&lt;/code>), &lt;code>read&lt;/code>, or compare variables of that type. Comparisons can be&lt;/p>
&lt;ul>
&lt;li>&lt;code>Eq&lt;/code>, which lets us tell whether or not two things are equal.&lt;/li>
&lt;li>&lt;code>Ord&lt;/code>, which lets us tell whether one thing is bigger than another.&lt;/li>
&lt;/ul>
&lt;p>In our case, we don&amp;rsquo;t really have a way to decide whether one &lt;code>Suit&lt;/code> or &lt;code>Sex&lt;/code> is &amp;ldquo;bigger&amp;rdquo; than another, but we do want to check equality.&lt;/p>
&lt;p>We define &lt;code>Side&lt;/code> and &lt;code>Piece&lt;/code> with &lt;em>named fields&lt;/em>, which lets us extract those fields with generated getters:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="723954861" type="checkbox" />
&lt;label for="723954861">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
λ&amp;gt; let foo = Side {suit=Club, sex=Out}
λ&amp;gt; suit foo
Club
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>For two puzzle pieces to be able to meet at an edge, they need to have the same &lt;code>Suit&lt;/code> and opposite &lt;code>Sex&lt;/code> es. This makes it convenient to define a custom instance of &lt;code>Eq&lt;/code> for &lt;code>Side&lt;/code> variables. This lets us redefine &lt;code>a == b&lt;/code> to check whether two sides are &lt;em>compatible&lt;/em>, rather than &lt;em>equal&lt;/em>.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="576218394" type="checkbox" />
&lt;label for="576218394">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
instance Eq Side where x == y = (suit x == suit y) &amp;amp;&amp;amp; (sex x /= sex y)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h3 id="input">Input&lt;/h3>
&lt;p>I defined the nine pieces in plaintext as follows:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="421678359" type="checkbox" />
&lt;label for="421678359">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
[ &amp;#34;HDdh&amp;#34;, &amp;#34;CHsh&amp;#34;, &amp;#34;DCcd&amp;#34;, &amp;#34;SDsh&amp;#34;, &amp;#34;SDhd&amp;#34;, &amp;#34;SShc&amp;#34;, &amp;#34;CHdc&amp;#34;, &amp;#34;HDcc&amp;#34;, &amp;#34;HSsc&amp;#34;]
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>where each character is the tab or blank on the North, East, South, and West faces of a piece. Each letter stands for Heart, Diamond, Spade, or Club, and is uppercase if the side sticks out rather than in.&lt;/p>
&lt;p>We have to write a bit of parsing code to turn this a list of &lt;code>Piece&lt;/code> objects.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="361974825" type="checkbox" />
&lt;label for="361974825">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
parsePiece [n,e,s,w] = Piece { north = parseSide n, east = parseSide e
, south = parseSide s, west = parseSide w }
where parseSide c = Side { suit = parseSuit c, sex = parseSex c }
parseSuit c | toLower c == &amp;#39;c&amp;#39; = Club
| toLower c == &amp;#39;d&amp;#39; = Diamond
| toLower c == &amp;#39;s&amp;#39; = Spade
| otherwise = Heart
parseSex c = if isLower c then In else Out
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Later, we can call&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="586239147" type="checkbox" />
&lt;label for="586239147">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
let allPieces = map parsePiece $ [ &amp;#34;HDdh&amp;#34;, &amp;#34;CHsh&amp;#34;, ... ]
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h3 id="explore">&lt;code>explore&lt;/code>&lt;/h3>
&lt;h3 id="theory">Theory&lt;/h3>
&lt;p>At the core of this solution is a function I named &lt;code>explore&lt;/code>, which takes a known grid and a list of candidates and generates a list of potential grids and their remaining candidates. We represent a grid as a list of pieces &lt;code>[Piece]&lt;/code>.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="796852431" type="checkbox" />
&lt;label for="796852431">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
explore :: ([Piece], [Piece]) -&amp;gt; [([Piece], [Piece])]
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Here is an example of &lt;code>explore&lt;/code> in action.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="417896352" type="checkbox" />
&lt;label for="417896352">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
-- known grid
-- | candidates
-- | |
-- v v
λ&amp;gt; explore ([1, 2], [3, 4, 5])
[ ([1, 2, 3], [4, 5]),
([1, 2, 4], [3, 5]),
([1, 2, 5], [3, 4]) ]
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>One caveat here is that we don&amp;rsquo;t just append all the items in the candidates pool to the known grid; because these are puzzle pieces, we need to append all possible rotations of each candidate. So a closer approximation with lower- and upper-case letters might be:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="529763481" type="checkbox" />
&lt;label for="529763481">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
λ&amp;gt; explore ([], [&amp;#39;c&amp;#39;, &amp;#39;d&amp;#39;, &amp;#39;e&amp;#39;])
[ ([&amp;#39;c&amp;#39;], [&amp;#39;d&amp;#39;, &amp;#39;e&amp;#39;]),
([&amp;#39;C&amp;#39;], [&amp;#39;d&amp;#39;, &amp;#39;e&amp;#39;]),
([&amp;#39;d&amp;#39;], [&amp;#39;c&amp;#39;, &amp;#39;e&amp;#39;]),
([&amp;#39;D&amp;#39;], [&amp;#39;c&amp;#39;, &amp;#39;e&amp;#39;]),
... ]
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Once we have this &lt;code>explore&lt;/code> function, we can validate the list it generates, and then call &lt;code>explore&lt;/code> on every remaining grid again. We can repeat this until we are left with complete, valid grids.&lt;/p>
&lt;h3 id="practice">Practice&lt;/h3>
&lt;p>Here&amp;rsquo;s what the real &lt;code>explore&lt;/code> function looks like:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="372598146" type="checkbox" />
&lt;label for="372598146">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
explore :: ([Piece], [Piece]) -&amp;gt; [([Piece], [Piece])]
explore (list, pool) = concatMap pluck [0..(length pool - 1)]
where pluck i = [ (list &amp;#43;&amp;#43; [c], excise i pool)
| c &amp;lt;- take 4 $ iterate rotate (pool!!i) ]
excise i xs = take i xs &amp;#43;&amp;#43; drop (i&amp;#43;1) xs
rotate piece = Piece { north = east piece, east = south piece
, south = west piece, west = north piece }
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>We define a few helper functions which are pretty useful.&lt;/p>
&lt;ul>
&lt;li>&lt;code>pluck i&lt;/code> implicitly takes the list &lt;code>pool&lt;/code>, and returns a list of tuples of the form(newList, newPool), where
&lt;ul>
&lt;li>the newList is the old list plus the selected candidate (at index &lt;code>i&lt;/code>), and&lt;/li>
&lt;li>the newPool is the old pool minus the selected candidate.
The whole thing is wrapped in a list comprehension so that rather than returning a tuple, &lt;code>pluck&lt;/code> actually returns a list of all four rotations for a selected rotation. &lt;code>concatMap&lt;/code> applied &lt;code>pluck&lt;/code> to every item in the pool.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;code>excise i xs&lt;/code> returns the &lt;code>xs&lt;/code> minus the element at the &lt;code>i&lt;/code>th index.&lt;/li>
&lt;li>&lt;code>rotate&lt;/code> takes a piece and rotates it 90 degrees. &lt;code>take 4 $ iterate rotate&lt;/code> returns a list of the piece at (xs!!i), that piece rotated once, that piece rotate twice&amp;hellip; etc, four times. This serves the list comprehension, and then &lt;code>concatMap&lt;/code> flattens it into a normal list.&lt;/li>
&lt;/ul>
&lt;h3 id="validate">&lt;code>validate&lt;/code>&lt;/h3>
&lt;p>We need to validate each grid or portion of a grid which is generated in an efficient way. If we add pieces in slots zero through nine in order, then each new piece only needs to be compared against the pieces above or to the left of it (if they exist). Thus:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="295876413" type="checkbox" />
&lt;label for="295876413">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
validate :: Int -&amp;gt; [Piece] -&amp;gt; Bool
validate n xs = (not hasAbove || matchAbove) &amp;amp;&amp;amp; (not hasLeft || matchLeft)
where hasLeft = n `mod` 3 /= 0
hasAbove = n &amp;gt;= 3
matchLeft = west (xs!!n) == east (xs!!(n-1))
matchAbove = north (xs!!n) == south (xs!!(n-3))
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>&lt;code>matchLeft&lt;/code> and &lt;code>matchAbove&lt;/code> both take advantage of our custom instance of &lt;code>Eq&lt;/code> for &lt;code>Side&lt;/code>s. The getters &lt;code>north&lt;/code>, &lt;code>east&lt;/code>, &lt;code>south&lt;/code>, and &lt;code>west&lt;/code> were also generated implicitly from the &lt;em>named fields&lt;/em> above.&lt;/p>
&lt;p>OK. Let&amp;rsquo;s put it together.&lt;/p>
&lt;h2 id="finding-the-solution">Finding the solution&lt;/h2>
&lt;div class="collapsable-code">
&lt;input id="285437961" type="checkbox" />
&lt;label for="285437961">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
step 0 xs = explore ([], xs)
step n xs = filter (\(xs,_) -&amp;gt; validate n xs)
$ concatMap explore $ step (pred n) xs
main = do
let allPieces = map parsePiece
$ [ &amp;#34;HDdh&amp;#34;, &amp;#34;CHsh&amp;#34;, &amp;#34;DCcd&amp;#34;,
&amp;#34;SDsh&amp;#34;, &amp;#34;SDhd&amp;#34;, &amp;#34;SShc&amp;#34;,
&amp;#34;CHdc&amp;#34;, &amp;#34;HDcc&amp;#34;, &amp;#34;HSsc&amp;#34; ]
let sol = fst $ head $ step 8 allPieces
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Cool. We can get our list of potential position &lt;strong>0&lt;/strong> s easily with &lt;code>step 0 allPieces&lt;/code>.&lt;/p>
&lt;p>We can then get our list of potential position &lt;strong>1&lt;/strong> s with:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="614983752" type="checkbox" />
&lt;label for="614983752">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
step 1 allPieces
-- which turns into
filter (\(xs, _) -&amp;gt; validate 1 xs)) $ concatMap explore $ step 0 xs
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>This recursion means we validate a list of potential grids before returning it. So, &lt;code>step 8 allPieces&lt;/code> generates the list of all potential 8th-poisition grids, which requires Haskell to generate the list of all potential 7th-position grids first, and so on and so on.&lt;/p>
&lt;h3 id="filtering">Filtering&lt;/h3>
&lt;p>How much does this save us? Let&amp;rsquo;s look at the numbers.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="782396154" type="checkbox" />
&lt;label for="782396154">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
λ&amp;gt; print $ length $ step 0 allPieces
36
λ&amp;gt; print $ length $ step 1 allPieces
138
λ&amp;gt; print $ length $ step 2 allPieces
470
λ&amp;gt; print $ length $ step 3 allPieces
1350
λ&amp;gt; print $ length $ step 4 allPieces
474
λ&amp;gt; print $ length $ step 5 allPieces
144
λ&amp;gt; print $ length $ step 6 allPieces
175
λ&amp;gt; print $ length $ step 7 allPieces
28
λ&amp;gt; print $ length $ step 8 allPieces
4
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>One note &amp;ndash; some of these numbers will be off by a factor of four, since a solved grid is the same across any rotation. That&amp;rsquo;s why there are $4$ valid solutions in the last row &amp;ndash; it&amp;rsquo;s all the same solution across four rotations.&lt;/p>
&lt;p>This means there were 36 valid pieces in position &lt;strong>0&lt;/strong> (makes sense, since $9\times4$), but only 138 valid pairings for slots &lt;strong>0&lt;/strong> and &lt;strong>1&lt;/strong> adjacent, far less than the 1152 potentials. We peak at 1350 potential three-in-a-row combinations, and then filter down steadily.&lt;/p>
&lt;p>Summing across these numbers, we see that &lt;code>validate&lt;/code> must have been called something like 2800 times. Much, much better than validating all 23 billion grids.&lt;/p>
&lt;h3 id="printing">Printing&lt;/h3>
&lt;p>Finally, we define a solution grid like so:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="759318264" type="checkbox" />
&lt;label for="759318264">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
let sol = fst $ head $ step 8 allPieces
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Unfortunately this gives us an output like this:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="591326874" type="checkbox" />
&lt;label for="591326874">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
λ&amp;gt; main --solves the puzzle, returns the first valid grid.
[Piece {north = Side {suit = Heart, sex = In}, east = Side {suit = ...
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Hard to read and inspect visually. I wrote a prettyprinter which turns that into this:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="386147529" type="checkbox" />
&lt;label for="386147529">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
λ&amp;gt; main
.h. | .c. | .d.
s S | s H | h S
.D. | .S. | .D.
----|-----|----
.d. | .s. | .d.
D h | H h | H c
.H. | .C. | .C.
----|-----|----
.h. | .c. | .c.
S c | C d | D c
.S. | .D. | .H.
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Which is much easier to inspect and validate. Here&amp;rsquo;s the prettyprinter:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="415372896" type="checkbox" />
&lt;label for="415372896">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
renderGrid :: [Piece] -&amp;gt; [String]
renderGrid = intercalate [&amp;#34;----|-----|----&amp;#34;] . map renderRow . chunksOf 3
where renderRow xs = [ intercalate &amp;#34; | &amp;#34; $ map (\x -&amp;gt; renderSq x!!n) xs
| n &amp;lt;- [0..2] ]
renderSq p = [ &amp;#34;.&amp;#34; &amp;#43;&amp;#43; [unParse $ north p] &amp;#43;&amp;#43; &amp;#34;.&amp;#34;
, [unParse $ west p] &amp;#43;&amp;#43; &amp;#34; &amp;#34; &amp;#43;&amp;#43; [unParse $ east p]
, &amp;#34;.&amp;#34; &amp;#43;&amp;#43; [unParse $ south p] &amp;#43;&amp;#43; &amp;#34;.&amp;#34;
]
unParse s
| suit s == Club = f &amp;#39;c&amp;#39;
| suit s == Spade = f &amp;#39;s&amp;#39;
| suit s == Diamond = f &amp;#39;d&amp;#39;
| otherwise = f &amp;#39;h&amp;#39;
where f = if sex s == Out then toUpper else id
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Understanding this particular function is left as an exercise to the reader.&lt;/p>
&lt;h2 id="full-code">Full Code&lt;/h2>
&lt;p>You can see the full code &lt;a href="https://gist.github.com/ambuc/ec5d72fcc6d931afa745e3c8ac100edb">here, in a gist&lt;/a>.&lt;/p></content></item><item><title>Writing a TextTwist Implementation</title><link>https://jbuckland.com/blog/game-text-twist/</link><pubDate>Sat, 03 Dec 2016 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/game-text-twist/</guid><description>Github http://github.com/ambuc/text-twist bash $ runhaskell text-twist.hs ┌ T E X T T W I S T ├ Please enter a seed word: domino ┏━━━━━━━━━━━━━━━━━━━━━┓ ┃ ~~~ ~~~ ~~~~ ~~~~ ┃ ┃ ~~~ ~~~ ~~~~ domino ┃ ┃ ~~~ ~~~ ~~~~ ┃ ┃ ~~~ ~~~ ~~~~ ┃ ┗┯━━━━━━━━━━━━━━━━━━━━┛ ├ Please enter a guess: mind ┏━━━━━━━━━━━━━━━━━━━━━┓ ┃ ~~~ ~~~ ~~~~ ~~~~ ┃ ┃ ~~~ ~~~ mind domino ┃ ┃ ~~~ ~~~ ~~~~ ┃ ┃ ~~~ ~~~ ~~~~ ┃ ┗┯━━━━━━━━━━━━━━━━━━━━┛ ├ Please enter a guess: mood ┏━━━━━━━━━━━━━━━━━━━━━┓ ┃ ~~~ ~~~ ~~~~ ~~~~ ┃ ┃ ~~~ ~~~ mind domino ┃ ┃ ~~~ ~~~ ~~~~ ┃ ┃ ~~~ ~~~ mood ┃ ┗━━━━━━━━━━━━━━━━━━━━━┛ Text Twist is a classic old internet flash game in which the objective is to, given a starting word, find all the unique words which can be made from its letters.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">Github&lt;/th>
&lt;td> &lt;a href="http://github.com/ambuc/text-twist">http://github.com/ambuc/text-twist&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;div class="collapsable-code">
&lt;input id="743152968" type="checkbox" />
&lt;label for="743152968">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
$ runhaskell text-twist.hs
┌ T E X T T W I S T
├ Please enter a seed word:
domino
┏━━━━━━━━━━━━━━━━━━━━━┓
┃ ~~~ ~~~ ~~~~ ~~~~ ┃
┃ ~~~ ~~~ ~~~~ domino ┃
┃ ~~~ ~~~ ~~~~ ┃
┃ ~~~ ~~~ ~~~~ ┃
┗┯━━━━━━━━━━━━━━━━━━━━┛
├ Please enter a guess:
mind
┏━━━━━━━━━━━━━━━━━━━━━┓
┃ ~~~ ~~~ ~~~~ ~~~~ ┃
┃ ~~~ ~~~ mind domino ┃
┃ ~~~ ~~~ ~~~~ ┃
┃ ~~~ ~~~ ~~~~ ┃
┗┯━━━━━━━━━━━━━━━━━━━━┛
├ Please enter a guess:
mood
┏━━━━━━━━━━━━━━━━━━━━━┓
┃ ~~~ ~~~ ~~~~ ~~~~ ┃
┃ ~~~ ~~~ mind domino ┃
┃ ~~~ ~~~ ~~~~ ┃
┃ ~~~ ~~~ mood ┃
┗━━━━━━━━━━━━━━━━━━━━━┛
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Text Twist is a &lt;a href="http://zone.msn.com/gameplayer/gameplayer.aspx?game=texttwist">classic old internet flash game&lt;/a> in which the objective is to, given a starting word, find all the unique words which can be made from its letters.&lt;/p>
&lt;p>This is a implementation in Haskell, playable from the shell. I hope the code is fairly readable.&lt;/p>
&lt;p>You can just play the game:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="814253697" type="checkbox" />
&lt;label for="814253697">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
$ runhaskell text-twist.hs
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>or, compile it first if you like:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="486725193" type="checkbox" />
&lt;label for="486725193">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
$ ghc text-twist.hs
[1 of 1] Compiling Main ( text-twist.hs, text-twist.o )
Linking text-twist ...
$ ./text-twist
┌ T E X T T W I S T
│ Please enter a seed word:
...
&lt;/code>&lt;/pre>
&lt;/div></content></item><item><title>Solving the Pairs of Percentages Puzzle</title><link>https://jbuckland.com/blog/puzzle-pairs-of-percentages/</link><pubDate>Sat, 29 Oct 2016 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/puzzle-pairs-of-percentages/</guid><description>Here&amp;rsquo;s a math puzzle inspired by the election. I noticed a while back that the two candidates were at 81.5% and 18.5%, respectively. The numbers 185 and 815 are anagrams, and add to 1000 ($10^3$). How many such pairs of positive integers add to $10^3$?
I&amp;rsquo;ve been doing a bit of Haskell recently, so that&amp;rsquo;s my language of choice.
Exploring the space The first step is to define a function which takes an integer pair and decides if the pair is valid (that is, if the two integers are anagrams or not.</description><content>&lt;p>Here&amp;rsquo;s a math puzzle inspired by the election. I noticed a while back that the two candidates were at 81.5% and 18.5%, respectively. The numbers 185 and 815 are anagrams, and add to 1000 ($10^3$). How many such pairs of positive integers add to $10^3$?&lt;/p>
&lt;p>I&amp;rsquo;ve been doing a bit of Haskell recently, so that&amp;rsquo;s my language of choice.&lt;/p>
&lt;h2 id="exploring-the-space">Exploring the space&lt;/h2>
&lt;p>The first step is to define a function which takes an integer pair and decides if the pair is valid (that is, if the two integers are anagrams or not.)&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="825937614" type="checkbox" />
&lt;label for="825937614">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
import Data.List
valid :: Int -&amp;gt; Int -&amp;gt; Bool
valid a b = list a == list b
where list = sort . filter (/=0) . digits
digits n = map (\x -&amp;gt; read [x] :: Int) (show n)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Let&amp;rsquo;s use this &lt;code>valid&lt;/code> function to check a range of numbers &lt;code>(a,b)&lt;/code> where
$a+b=1000$, and &lt;code>valid a b == True&lt;/code>.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="196278543" type="checkbox" />
&lt;label for="196278543">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
import Data.List
λ&amp;gt; [(a,b) | a &amp;lt;- [1..500], let b = 1000 - a, valid a b]
[(95,905),(185,815),(275,725),(365,635),(455,545),(500,500)]
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>This is pretty cool. Let&amp;rsquo;s fit this into a function $f(n)$, where&lt;/p>
&lt;ul>
&lt;li>$f(n)$ is the power $10^n$ we&amp;rsquo;re matching against,&lt;/li>
&lt;li>&lt;code>pairs n&lt;/code> returns the list of potential pairs,&lt;/li>
&lt;li>&lt;code>validPairs n = filter(\(a,b) -&amp;gt; valid a b) $ pairs n&lt;/code>, and&lt;/li>
&lt;li>&lt;code>f n = length $ validPairs n&lt;/code>, which is what we&amp;rsquo;re ultimately trying to find an
expression for.&lt;/li>
&lt;/ul>
&lt;div class="collapsable-code">
&lt;input id="815237496" type="checkbox" />
&lt;label for="815237496">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
pairs :: Int -&amp;gt; [(Int, Int)]
pairs pow = [(a, (10^pow)-a) | a &amp;lt;- candidates]
where candidates pow = [0,5..(div (10^pow) 2)]
validPairs = filter(\(a,b) -&amp;gt; valid a b) . pairs
f = length . validPairs
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>If we run &lt;code>validPairs 4&lt;/code>, we see a cool pattern.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="692835174" type="checkbox" />
&lt;label for="692835174">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
λ&amp;gt; validPairs 4
[(995,99005), (1895,98105), (1985,98015), ... (49055,50945), (49505,50495), (50000,50000)]
λ&amp;gt; f 4
6
λ&amp;gt; f 5
141
λ&amp;gt; f 6
...
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Before we go further, a few notes on this: for performance reasons, we only compute pairs &lt;code>(a,b)&lt;/code>, not &lt;code>(b,a)&lt;/code>. Because the last pair computed is always something like &lt;code>(50,50)&lt;/code>, &lt;code>(500,500)&lt;/code>, etc., the total number of pairs is actually $2f(n) - 1$. We&amp;rsquo;ll keep this in mind for later.&lt;/p>
&lt;p>Here&amp;rsquo;s what we have so far:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;code>n&lt;/code>&lt;/th>
&lt;th>1&lt;/th>
&lt;th>2&lt;/th>
&lt;th>3&lt;/th>
&lt;th>4&lt;/th>
&lt;th>5&lt;/th>
&lt;th>6&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>f(n)&lt;/code>&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>6&lt;/td>
&lt;td>6&lt;/td>
&lt;td>141&lt;/td>
&lt;td>141&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>2*f(n)-1&lt;/code>&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>11&lt;/td>
&lt;td>11&lt;/td>
&lt;td>281&lt;/td>
&lt;td>281&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Computing $f(6)$ took a really long time, so let&amp;rsquo;s eventually wrap this in a &lt;code>.hs&lt;/code> file, compile, and execute it with timing.&lt;/p>
&lt;p>But before we go further, let&amp;rsquo;s check the &lt;a href="http://oeis.org">Online Encyclopedia of Integer Sequences&lt;/a> to see if any of the sequences &lt;code>(1,1,6,6,141,141)&lt;/code>, &lt;code>(1,6,141)&lt;/code>, or &lt;code>(1,11,281)&lt;/code> appear at all. It turns out &lt;code>(1,6,141)&lt;/code> are the first few digits of &lt;a href="http://oeis.org/A241015">A241015&lt;/a>, which is the&lt;/p>
&lt;blockquote>
&lt;p>Number of pairs of endofunctions $f$, $g$ on $[n]$ satisfying $g(g(g(f(i)))) = f(i)$ for all $i$ in $[n]$.&lt;/p>
&lt;/blockquote>
&lt;p>&amp;hellip;whatever that means.&lt;/p>
&lt;p>If our pattern matches theirs, we might expect the next number in the sequence to be is 6184. We&amp;rsquo;ll find out in a bit, but first let&amp;rsquo;s bring runtime down a bit.&lt;/p>
&lt;h2 id="profiling">Profiling&lt;/h2>
&lt;p>OK, it&amp;rsquo;s time to wrap this in a file, compile, and execute it with timing.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="375612984" type="checkbox" />
&lt;label for="375612984">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
--puzzle.hs &amp;lt;num&amp;gt; (L | _)
import System.Environment
import Data.List
import Data.Char
valid :: Int -&amp;gt; Int -&amp;gt; Bool
valid a b = list a == list b
where list = sort . filter (/=0) . digits
digits n = map (\x -&amp;gt; read [x] :: Int) (show n)
pairs :: Int -&amp;gt; [(Int, Int)]
pairs pow = [(a, (10^pow)-a) | a &amp;lt;- candidates pow]
where candidates pow = [0,5..(div (10^pow) 2)]
validPairs = filter(\(a,b) -&amp;gt; valid a b) . pairs
f = length . validPairs
main :: IO ()
main = do
[num, action] &amp;lt;- getArgs
let n = digitToInt (head num)
let shouldList = (head action == &amp;#39;L&amp;#39;)
if shouldList
then print $ validPairs n
else print $ f n
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Just for kicks, &lt;code>./puzzle&lt;/code> takes two arguments; the first is the number $n$, and the second is either &lt;code>L&lt;/code>, to print the full list of matches, or &lt;code>_&lt;/code>, to print the value $f(n)$.&lt;/p>
&lt;p>We have a few ways to run this. One is to open up a &lt;code>ghci&lt;/code> shell, import the file, and run &lt;code>validPairs &amp;lt;num&amp;gt;&lt;/code> or &lt;code>f &amp;lt;num&amp;gt;&lt;/code> right there in the shell:&lt;/p>
&lt;h3 id="with-ghci">with &lt;code>ghci&lt;/code>&lt;/h3>
&lt;div class="collapsable-code">
&lt;input id="831974526" type="checkbox" />
&lt;label for="831974526">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
$ ghci
GHCi, version 7.10.3: http://www.haskell.org/ghc/ :? for help
λ&amp;gt; :l puzzle
[1 of 1] Compiling Main ( puzzle.hs, interpreted )
Ok, modules loaded: Main.
λ&amp;gt; validPairs 3
[(95,905),(185,815),(275,725),(365,635),(455,545),(500,500)]
λ&amp;gt; f 3
6
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>This is great for testing, but we don&amp;rsquo;t get too many stats. Let&amp;rsquo;s use a real-time thing like &lt;code>runhaskell&lt;/code>.&lt;/p>
&lt;h3 id="with-runhaskell">with &lt;code>runhaskell&lt;/code>&lt;/h3>
&lt;div class="collapsable-code">
&lt;input id="284567193" type="checkbox" />
&lt;label for="284567193">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
$ runhaskell puzzle.hs 3 L
[(95,905),(185,815),(275,725),(365,635),(455,545),(500,500)]
$ runhaskell puzzle.hs 3 _
6
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>To add stats, we use the &lt;code>+RTS&lt;/code> runtime system flag with the &lt;code>-s&lt;/code> summary flag.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="493178562" type="checkbox" />
&lt;label for="493178562">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
$ runhaskell puzzle.hs 3 _ &amp;#43;RTS -s
6
101,696 bytes allocated in the heap
3,464 bytes copied during GC
68,912 bytes maximum residency (1 sample(s))
13,008 bytes maximum slop
1 MB total memory in use (0 MB lost due to fragmentation)
Tot time (elapsed) Avg pause Max pause
Gen 0 0 colls, 0 par 0.000s 0.000s 0.0000s 0.0000s
Gen 1 1 colls, 0 par 0.000s 0.000s 0.0001s 0.0001s
INIT time 0.000s ( 0.000s elapsed)
MUT time 0.001s ( 0.189s elapsed)
GC time 0.000s ( 0.000s elapsed)
EXIT time 0.000s ( 0.000s elapsed)
Total time 0.006s ( 0.189s elapsed)
%GC time 2.5% (0.1% elapsed)
Alloc rate 178,562,837 bytes per MUT second
Productivity 96.1% of total user, 2.9% of total elapsed
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Very cool. As a final step, we&amp;rsquo;ll properly compile and optimize it with &lt;code>ghc&lt;/code>.&lt;/p>
&lt;h3 id="with-ghc">with &lt;code>ghc&lt;/code>&lt;/h3>
&lt;div class="collapsable-code">
&lt;input id="519236478" type="checkbox" />
&lt;label for="519236478">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
$ ghc -Odph puzzle.hs -rtsopts
[1 of 1] Compiling Main ( puzzle.hs, puzzle.o )
Linking puzzle ...
$ ./puzzle 3 _
6
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>We can run this with &lt;code>+RTS -s&lt;/code> too. &lt;code>$ ./puzzle 3 _ +RTS -s&lt;/code> generates the following table for &lt;code>f(1)&lt;/code> through &lt;code>f(7)&lt;/code>:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>n&lt;/th>
&lt;th>1&lt;/th>
&lt;th>2&lt;/th>
&lt;th>3&lt;/th>
&lt;th>4&lt;/th>
&lt;th>5&lt;/th>
&lt;th>6&lt;/th>
&lt;th>7&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>f(n)&lt;/code>&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>6&lt;/td>
&lt;td>6&lt;/td>
&lt;td>141&lt;/td>
&lt;td>141&lt;/td>
&lt;td>5591&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>time&lt;/td>
&lt;td>0.001s&lt;/td>
&lt;td>0.001s&lt;/td>
&lt;td>0.003s&lt;/td>
&lt;td>0.017s&lt;/td>
&lt;td>0.190s&lt;/td>
&lt;td>2.301s&lt;/td>
&lt;td>26.711s&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Let&amp;rsquo;s see if this matches our sequence from earlier. We now have the next value, putting our sequence at (1,6,141,5591). &lt;a href="http://oeis.org/search?q=1%2C6%2C141%2C5591">This is not in the OEIS.&lt;/a>. So we&amp;rsquo;ll have to find an expression ourselves, and check with the scripts.&lt;/p>
&lt;p>Unfortunately, our timing statisics are not optimistic. At this rate, $f(8)$ will take around five minutes to calculate. It would be a good idea to parallelize.&lt;/p>
&lt;h2 id="parallelization">Parallelization&lt;/h2>
&lt;p>We can use the &lt;code>Control.Parallel&lt;/code> library, (see &lt;a href="https://wiki.haskell.org/Haskell_in_5_steps#Write_your_first_parallel_Haskell_program">the docs&lt;/a> for more) which gives us the handy &lt;code>par&lt;/code> and &lt;code>pseq&lt;/code> functions, which force the compiler to split evaluations into separate threads and then wait to recombine. Much of the code has to be modified slightly. I run this on a four-core processor, so I rewrote this to split up the list of candidates into four roughly equal parts, evaluate the validity of each element in each of them, compute the length of that valid sublist, and recombine afterwards.&lt;/p>
&lt;p>We&amp;rsquo;ll call this new file &lt;code>puzzle-parallel.hs&lt;/code>. You&amp;rsquo;ll notice we drop support for printing the full list &amp;ndash; constructing the sum was easier to parallelize.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="246319578" type="checkbox" />
&lt;label for="246319578">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
-- ghc -O2 --make puzzle-parallel.hs -threaded -rtsopts
-- ./puzzle-parallel &amp;lt;n&amp;gt; &amp;#43;RTS -N4 -s
--
import Control.Parallel
import Data.List.Split
... -- all the same imports as before
valid :: Int -&amp;gt; Int -&amp;gt; Bool -- as before
pairs :: Int -&amp;gt; Int -&amp;gt; [(Int, Int)]
pairs pow core = [(x, (10^pow)-x) | x &amp;lt;- candidates]
where candidates = [a&amp;#43;5,a&amp;#43;10..b] -- not as before!
a = (core - 1) * (div (10^pow) 8)
b = (core - 0) * (div (10^pow) 8)
-- validPairs can no longer be point-free, since it takes
-- two arguments: pow and core. There&amp;#39;s probably a nice currying fix, though.
validPairs pow core = filter(\(a,b) -&amp;gt; valid a b) $ pairs pow core
f n = s1 `par` s2 `par` s3 `par` s4 `pseq` (s1 &amp;#43; s2 &amp;#43; s3 &amp;#43; s4)
where
s1 = length $ validPairs n 1
s2 = length $ validPairs n 2
s3 = length $ validPairs n 3
s4 = length $ validPairs n 4
main :: IO ()
main = do
[num] &amp;lt;- getArgs
-- we drop support for `./puzzle &amp;lt;num&amp;gt; L`, since computing these
-- large lists, sorting, and printing them is sort of out of
-- scope for the moment.
let n = digitToInt (head num)
print $ f n
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>The file &lt;code>puzzle-parallel.hs&lt;/code> can now be compiled to be multithreaded with &lt;code>ghc&lt;/code>:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="962438571" type="checkbox" />
&lt;label for="962438571">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
$ ghc -O2 --make puzzle-parallel.hs -threaded -rtsopts
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>and executed on four cores with the &lt;code>+RTS&lt;/code> flag &lt;code>-N4&lt;/code> (or &lt;code>-N2&lt;/code>, or however many cores you have. The above code is written for four cores, but it&amp;rsquo;s not hard to modify for any other number of cores.)&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="518947326" type="checkbox" />
&lt;label for="518947326">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
$ ./puzzle-parallel &amp;lt;n&amp;gt; #unparallelized
$ ./puzzle-parallel &amp;lt;n&amp;gt; &amp;#43;RTS -N4 #force 4 cores
$ ./puzzle-parallel &amp;lt;n&amp;gt; &amp;#43;RTS -N4 -s #with stats
&lt;/code>&lt;/pre>
&lt;/div>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>n&lt;/th>
&lt;th>1&lt;/th>
&lt;th>2&lt;/th>
&lt;th>3&lt;/th>
&lt;th>4&lt;/th>
&lt;th>5&lt;/th>
&lt;th>6&lt;/th>
&lt;th>7&lt;/th>
&lt;th>8&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>f(n)&lt;/code>&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>6&lt;/td>
&lt;td>6&lt;/td>
&lt;td>141&lt;/td>
&lt;td>141&lt;/td>
&lt;td>5591&lt;/td>
&lt;td>5591&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>time w/o parallelization&lt;/td>
&lt;td>0.001s&lt;/td>
&lt;td>0.001s&lt;/td>
&lt;td>0.003s&lt;/td>
&lt;td>0.017s&lt;/td>
&lt;td>0.190s&lt;/td>
&lt;td>2.301s&lt;/td>
&lt;td>26.711s&lt;/td>
&lt;td>501.863s&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>time w/ parallelization&lt;/td>
&lt;td>0.001s&lt;/td>
&lt;td>0.001s&lt;/td>
&lt;td>0.003s&lt;/td>
&lt;td>0.021s&lt;/td>
&lt;td>0.133s&lt;/td>
&lt;td>1.567s&lt;/td>
&lt;td>18.489s&lt;/td>
&lt;td>208.285s&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>We see around a 2x speedup from this &amp;ndash; we expect a 4x from threading, but there is some loss to overhead. This speedup helps more the higher $n$ grows. This is better performance across the board. Now calculating $f(9)$ isn&amp;rsquo;t quite as impossible.&lt;/p>
&lt;h2 id="expression">Expression&lt;/h2>
&lt;p>Rather than delve deeper into optimization, let&amp;rsquo;s focus on trying to find a closed-form expression for this sequence. We do this first by visual inspection of the types of pairs generated.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="284951736" type="checkbox" />
&lt;label for="284951736">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
$ ./puzzle 1 L
[(5,5)]
$ ./puzzle 2 L
[(50,50)]
$ ./puzzle 3 L
[(95,905),(185,815),(275,725),(365,635),(455,545),(500,500)]
$ ./puzzle 4 L
[(950,9050),(1850,8150),(2750,7250),(3650,6350),(4550,5450),(5000,5000)]
$ ./puzzle 5 L
[(995,99005),(1895,98105),(1985,98015),(2795,97205),(2975,97025),(3695,96305),
(3965,96035),(4595,95405),(4955,95045),(5495,94505),(5945,94055),(6395,93605),
(6935,93065),(7295,92705),(7925,92075),(8195,91805),(8915,91085),(9095,90905),
...
(45815,54185),(45905,54095),(46355,53645),(46535,53465),(47255,52745),
(47525,52475),(48155,51845),(48515,51485),(49055,50945),(49505,50495),
(50000,50000)]
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Let&amp;rsquo;s introduce some nomenclature here to turn this from a programming puzzle into a math puzzle. For some $n$, we define $f(n)$ to be the number of possible pairs, and $S(n)$ to be the set of pairs itself. Thus, &lt;code>f(n) = length S(n)&lt;/code>.&lt;/p>
&lt;p>Some observations:&lt;/p>
&lt;ul>
&lt;li>For all even values $n$, we can see &lt;code>S(n) = [10*i for i in S(n-1)]&lt;/code>; that is, every element of &lt;code>S(even n)&lt;/code> is an element of &lt;code>S(odd n)&lt;/code> multiplied by ten.&lt;/li>
&lt;li>Thus, $f(n) = f(n-1)$ for even n.&lt;/li>
&lt;/ul>
&lt;h3 id="mapping-to-a-simpler-domain">Mapping to a simpler domain&lt;/h3>
&lt;p>We can see from the examples above that for sets generated by, say, $n=3$ and $n=4$, only the first two digits really fluctuate. We suspect the problem reduces to a combinatorics problem focused on strings of that length, which we call $m$. So, let&amp;rsquo;s map the domain of possible $n$ values to a simpler domain:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="613478529" type="checkbox" />
&lt;label for="613478529">
&lt;span class="collapsable-code__language">bash&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-bash" >&lt;code>
m(n)
^
6 | o o
5 |
4 | o o
3 |
2 | o o
1 &amp;#43;-&amp;#43;-&amp;#43;-&amp;#43;-&amp;#43;-&amp;#43;-&amp;#43;-&amp;#43;--&amp;gt; n
1 2 3 4 5 6 7 8
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>We&amp;rsquo;ll switch to Python here for readability, and also because our algorithm will be imperative in a moment:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="715643928" type="checkbox" />
&lt;label for="715643928">
&lt;span class="collapsable-code__language">python&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-python" >&lt;code>
def M(n):
return ((n-1)//2)*2
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>This alongside future memoizations will help us avoid having to calculate $f(n)$ when $f(n-1)$ is known.&lt;/p>
&lt;h3 id="the-9-pair">The 9-pair&lt;/h3>
&lt;p>We first define the concept of a 9-pair. A 9-pair is a set of two positive numbers which add to 9. 9-pairs are: &lt;code>(0,9)&lt;/code>, &lt;code>(1,8)&lt;/code>, &lt;code>(2,7)&lt;/code>, &lt;code>(3,6)&lt;/code>, and &lt;code>(4,5)&lt;/code>.&lt;/p>
&lt;h3 id="numbers-and-9-pairs">Numbers and 9-pairs&lt;/h3>
&lt;p>Here is an example pair from $S(n=5)$: &lt;code>(48155,51845)&lt;/code>. We can reduce this to its tuple of interest: &lt;code>(4815, 5184)&lt;/code>. It seems &lt;code>(1,8)&lt;/code> and &lt;code>(4,5)&lt;/code> are the 9-pairs which compose the alphabet from which this tuple was generated.&lt;/p>
&lt;p>Since &lt;code>1&lt;/code> and &lt;code>8&lt;/code> are a 9-pair, if &lt;code>1&lt;/code> appears in the first number of the tuple, then we know for sure that:&lt;/p>
&lt;ul>
&lt;li>&lt;code>1&lt;/code> appears in the second item of the tuple,&lt;/li>
&lt;li>&lt;code>8&lt;/code> appears in the first item of the tuple,&lt;/li>
&lt;li>&lt;code>8&lt;/code> appears in the second item of the tuple.&lt;/li>
&lt;/ul>
&lt;h2 id="generalization-elements-and-element-pairs">Generalization: elements and element pairs&lt;/h2>
&lt;p>Let&amp;rsquo;s generalize this away from digits and number and into elements, sets, and permutations.&lt;/p>
&lt;p>We will call &lt;code>(a,b)&lt;/code> a tuple, &lt;code>a&lt;/code> and &lt;code>b&lt;/code> strings in that tuple, and the characters in the strings &lt;code>a&lt;/code> and &lt;code>b&lt;/code> characters.&lt;/p>
&lt;p>For a given string &lt;code>a&lt;/code> or &lt;code>b&lt;/code>, any character in &lt;code>a&lt;/code> or &lt;code>b&lt;/code> uniquely defines its counterpart within an element-pair (&lt;code>i&lt;/code>,&lt;code>j&lt;/code>): if &lt;code>i in a&lt;/code>, then &lt;code>j in b&lt;/code>. Since the strings &lt;code>a&lt;/code> and &lt;code>b&lt;/code> are by definition anagrams, this means &lt;code>j in a&lt;/code> as well. Thus &lt;code>a&lt;/code> is a unique ordering of pairs of characters, selected from a known alphabet of character pairs.&lt;/p>
&lt;h3 id="combinatorics-on-that-domain">Combinatorics on that domain&lt;/h3>
&lt;p>We can reduce this problem to finding the number of unique orderings of a list composed of selecting $x$ element-pairs from a list of $p$ element-pairs. We call this function $G(x,p)$. Because our real problem lies on the domain of digits, there will only eve be five element-pairs (the five 9-pairs), so $p=5$, always.&lt;/p>
&lt;p>This is simple (if inefficient) in Python:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="263157849" type="checkbox" />
&lt;label for="263157849">
&lt;span class="collapsable-code__language">python&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-python" >&lt;code>
def G(x,p):
result = 0
# since p=5 always, this domain is always [&amp;#39;1&amp;#39;,&amp;#39;2&amp;#39;,&amp;#39;3&amp;#39;,&amp;#39;4&amp;#39;,&amp;#39;5&amp;#39;]
domain = (str(i) for i in range(p))
itr = itertools.combinations_with_replacement(domain,x)
for i0 in itr:
# turns [&amp;#39;1&amp;#39;,&amp;#39;2&amp;#39;] into [&amp;#39;1&amp;#39;,&amp;#39;1~&amp;#39;,&amp;#39;2&amp;#39;,&amp;#39;2~&amp;#39;] for proper element-pairs
alphabet = list(i0) &amp;#43; list( map((lambda x: str(x)&amp;#43;&amp;#34;~&amp;#34;), i0) )
#the number of unique permutations in that alphabet of length 2x
result &amp;#43;= len(set(list(itertools.permutations(alphabet,2*x))))
return result
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>To get our desired $f(n)$, we can just write:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="136489257" type="checkbox" />
&lt;label for="136489257">
&lt;span class="collapsable-code__language">python&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-python" >&lt;code>
def f(n):
# the same as M(n) above
m = ((n-1)//2)*2
# the answer is recursive -- G(x,5) alone will only return pairs of length x.
# we must also consider pairs of length y&amp;lt;x, right-padded by zeroes.
def inner_f(n):
return (G(n,5) &amp;#43; inner_f(n-1)) if (n &amp;gt; 0) else 0
# divided by two because (185, 815), (815, 185) are the same pair.
# plus one because (500, 500) won&amp;#39;t be found by G5(x)
return inner_f(m)/2 &amp;#43; 1
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h3 id="unmemoized-timing-t0">Unmemoized timing &lt;code>t0&lt;/code>&lt;/h3>
&lt;p>We can run this with timing:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="962438715" type="checkbox" />
&lt;label for="962438715">
&lt;span class="collapsable-code__language">python&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-python" >&lt;code>
for n in range(1,12):
start = time.time()
localF = f(n)
end = time.time()
print &amp;#34;f({0})\t{1}\t{2}&amp;#34;.format(n, localF, end-start)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>n&lt;/th>
&lt;th>1&lt;/th>
&lt;th>2&lt;/th>
&lt;th>3&lt;/th>
&lt;th>4&lt;/th>
&lt;th>5&lt;/th>
&lt;th>6&lt;/th>
&lt;th>7&lt;/th>
&lt;th>8&lt;/th>
&lt;th>9&lt;/th>
&lt;th>10&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>f(n)&lt;/code>&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>6&lt;/td>
&lt;td>6&lt;/td>
&lt;td>141&lt;/td>
&lt;td>141&lt;/td>
&lt;td>5591&lt;/td>
&lt;td>5591&lt;/td>
&lt;td>281566&lt;/td>
&lt;td>281566&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>t0&lt;/code>&lt;/td>
&lt;td>5.9e-6s&lt;/td>
&lt;td>1.3e-5s&lt;/td>
&lt;td>4.2e-5s&lt;/td>
&lt;td>2.2e-5s&lt;/td>
&lt;td>1.5e-4s&lt;/td>
&lt;td>1.4e-4s&lt;/td>
&lt;td>4.3e-3s&lt;/td>
&lt;td>4.0e-3s&lt;/td>
&lt;td>0.94s&lt;/td>
&lt;td>0.94s&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="memoization-t1">Memoization &lt;code>t1&lt;/code>&lt;/h3>
&lt;p>We can improve performance a lot through memoization. Functions like $G(n,5)$ get called repeatedly for the same value of $n$, for example.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="624318957" type="checkbox" />
&lt;label for="624318957">
&lt;span class="collapsable-code__language">python&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-python" >&lt;code>
G5memo = {}
def G5(x):
if x not in G5memo:
G5memo[x] = G(x,5)
return G5memo[x]
&lt;/code>&lt;/pre>
&lt;/div>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>n&lt;/th>
&lt;th>1&lt;/th>
&lt;th>2&lt;/th>
&lt;th>3&lt;/th>
&lt;th>4&lt;/th>
&lt;th>5&lt;/th>
&lt;th>6&lt;/th>
&lt;th>7&lt;/th>
&lt;th>8&lt;/th>
&lt;th>9&lt;/th>
&lt;th>10&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>t1&lt;/code>&lt;/td>
&lt;td>9.1e-6s&lt;/td>
&lt;td>3.1e-6s&lt;/td>
&lt;td>5.5e-5s&lt;/td>
&lt;td>3.1e-6s&lt;/td>
&lt;td>1.6e-4s&lt;/td>
&lt;td>2.9e-6s&lt;/td>
&lt;td>5.5e-3s&lt;/td>
&lt;td>4.1e-6s&lt;/td>
&lt;td>0.91s&lt;/td>
&lt;td>1.6e-5s&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The most obvious boost is that $f(n)$ where $n$ is even now takes almost no time to compute, since $f(n) = f(n-1)$ for even $n$.&lt;/p>
&lt;h3 id="memoization-t2">Memoization &lt;code>t2&lt;/code>&lt;/h3>
&lt;p>We can memoize further. The &lt;code>alphabet&lt;/code> of characters fed into &lt;code>len(list(set(itr(alphabet))))&lt;/code> is usually something like &lt;code>['a','A','a','A']&lt;/code> or &lt;code>['b','B','b','B']&lt;/code>. However, it seems obvious to us that the number of unique pairs from the first of these two alphabets will be the same as that of the second. It would be useful to create a non-unique &lt;em>hash&lt;/em> of some kind, representing the pair distribution within an alphabet, and check whether &lt;code>len(list(set(itr(alphabet))))&lt;/code> for an equivalent alphabet has already been calculated.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="915246378" type="checkbox" />
&lt;label for="915246378">
&lt;span class="collapsable-code__language">python&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-python" >&lt;code>
# takes an alphabet like [&amp;#39;1&amp;#39;, &amp;#39;1&amp;#39;, &amp;#39;2&amp;#39;, &amp;#39;3&amp;#39;, &amp;#39;1~&amp;#39;, &amp;#39;1~&amp;#39;, &amp;#39;2~&amp;#39;, &amp;#39;3~&amp;#39;] and returns
# \ / | |
# \ / | |
# (2) (1) (1) --&amp;gt; returns [1,1,2] as a list-hash
def makeListHash(alphabet):
d = {}
for i in alphabet[0:len(alphabet)/2]:
try:
d[i] &amp;#43;= 1 #if i in d
except:
d[i] = 1
return list(str(x) for x in sorted(d.values()))
# just a wrapper for makeListHash which returns a string (&amp;#34;1,2,2&amp;#34;) instead.
def makeStringHash(alphabet):
return &amp;#34;,&amp;#34;.join(makeListHash(alphabet))
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>This lets us rewrite $G(x,p)$ as:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="792468513" type="checkbox" />
&lt;label for="792468513">
&lt;span class="collapsable-code__language">python&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-python" >&lt;code>
def G(x,p):
result = 0
alphabet = (str(i) for i in range(p))
itr = itertools.combinations_with_replacement(alphabet,x)
lMemo = {} #inline since memoization is only useful across a single value of x
for i0 in itr:
alphabet = list(i0) &amp;#43; list( map((lambda x: str(x)&amp;#43;&amp;#34;~&amp;#34;), i0) )
key = makeStringHash(alphabet)
if not key in lMemo:
lMemo[key] = len(set(list(itertools.permutations(alphabet,2*x))))
result &amp;#43;= lMemo[key]
return result
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>This memoization brings down computation time even further, and lets us calculate $f(11)=f(12)$ for the first time ever.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>$n$&lt;/th>
&lt;th>1&lt;/th>
&lt;th>2&lt;/th>
&lt;th>3&lt;/th>
&lt;th>4&lt;/th>
&lt;th>5&lt;/th>
&lt;th>6&lt;/th>
&lt;th>7&lt;/th>
&lt;th>8&lt;/th>
&lt;th>9&lt;/th>
&lt;th>10&lt;/th>
&lt;th>11&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>$f(n)$&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>6&lt;/td>
&lt;td>6&lt;/td>
&lt;td>141&lt;/td>
&lt;td>141&lt;/td>
&lt;td>5591&lt;/td>
&lt;td>5591&lt;/td>
&lt;td>281566&lt;/td>
&lt;td>281566&lt;/td>
&lt;td>16397596&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>t2&lt;/code>&lt;/td>
&lt;td>6.9e-6&lt;/td>
&lt;td>3.1e-6&lt;/td>
&lt;td>6.8e-5&lt;/td>
&lt;td>1.3e-5&lt;/td>
&lt;td>1.4e-4&lt;/td>
&lt;td>3.1e-6&lt;/td>
&lt;td>6.9e-4&lt;/td>
&lt;td>5.0e-6&lt;/td>
&lt;td>6.8e-2&lt;/td>
&lt;td>7.2e-6&lt;/td>
&lt;td>10.5&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="conclusions">Conclusions&lt;/h2>
&lt;p>That&amp;rsquo;s all! I wasn&amp;rsquo;t able to further optimize the problem, and I&amp;rsquo;m not convinced a closed-form solution necessarily exists. That said, we found the number of anagram pairs which sum to $100,000,000,000$, and that&amp;rsquo;s pretty cool. I also learned a lot about parallelization in Haskell and using the built-in optimization flags in &lt;code>ghc&lt;/code>, and a bit of memoization in Python.&lt;/p>
&lt;h2 id="update-11112016">Update (11/11/2016)&lt;/h2>
&lt;p>It&amp;rsquo;s been around a week since last I touched this puzzle. My buddy &lt;a href="http://joshmermelstein.com/">Josh&lt;/a> bounced the problem to his boss Piotr, who came up with this phenomenal closed-form solution for $G(x,p=5)$. It&amp;rsquo;s brilliant and involves reducing the combinatorics problem, searching for and finding the elusive closed-form solution in the literature, and writing a small haskell script to implement it. In short:&lt;/p>
&lt;p>The combinatorics section above describes a&lt;/p>
&lt;blockquote>
&lt;p>sequence $a_n^N := \Sigma \left( \dfrac{n!}{p_1! p_2! &amp;hellip; p_N!}\right)^2$, where the sum runs over sets of non-negative integers $p_1,..p_N$ summing to $n$.&lt;/p>
&lt;/blockquote>
&lt;p>This sequence can be found by a recurrence relation, and was performed in &lt;a href="https://arxiv.org/pdf/math/0407327v1.pdf">&lt;em>Sums of squares of binomial coefficients, with applications to Picard-Fuchs equations&lt;/em>&lt;/a> by H. A. Verrill in 2008.&lt;/p>
&lt;p>&lt;em>Table 1&lt;/em> in the above paper describes recurrence relations for $a_n^N$, and we care about the case where $N=5$: the fourth equation is the object of our desire. It reads:&lt;/p>
&lt;p>$$ 0 = n^4 a_n^5 - (35n^4 - 70n^2 + 63n^2 - 28n + 5)a^5_{n-1} + (n-1)^2(259(n-1)^2 + 26)a^5_{n-2} - (3 \cdot 5)^2(n-1)^2 a^5_{n-3} $$&lt;/p>
&lt;p>Piotr implemented this as so: (I&amp;rsquo;ve changed it the tiniest bit to better perform testing on it.)&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="348591762" type="checkbox" />
&lt;label for="348591762">
&lt;span class="collapsable-code__language">haskell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-haskell" >&lt;code>
import System.Environment
import Data.List
import Data.Char
next :: Integer -&amp;gt; Integer -&amp;gt; Integer -&amp;gt; Integer -&amp;gt; Integer
next n prev1 prev2 prev3 =
((35*n*n*n*n - 70*n*n*n &amp;#43; 63*n*n - 28*n &amp;#43; 5) * prev1 -
((n-1)*(n-1)*(259*(n-1)*(n-1) &amp;#43; 26)) * prev2 &amp;#43;
15*15*(n-1)*(n-1)*(n-2)*(n-2) * prev3)
`div` (n*n*n*n)
vals :: [Integer]
vals = 1 : 5 : 45 : (zipWith4 next [3..] (drop 2 vals) (drop 1 vals) vals)
choose :: Integer -&amp;gt; Integer -&amp;gt; Integer
choose n 0 = 1
choose 0 k = 0
choose n k = choose (n-1) (k-1) * n `div` k
finalvals = zipWith (\v n -&amp;gt; v * (choose (n*2) n)) vals [0..]
main :: IO ()
main = do
[num] &amp;lt;- getArgs
let n = read num :: Int
print $ (finalvals !! n)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>and called with &lt;code>runhaskell math.hs &amp;lt;num&amp;gt; +RTS -s&lt;/code> (assuming you save it as &lt;code>math.hs&lt;/code>.)&lt;/p>
&lt;p>As expected, this has a phenomenal runtime.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>$n$&lt;/th>
&lt;th>1&lt;/th>
&lt;th>10&lt;/th>
&lt;th>1e2&lt;/th>
&lt;th>1e3&lt;/th>
&lt;th>1e4&lt;/th>
&lt;th>2e4&lt;/th>
&lt;th>3e4&lt;/th>
&lt;th>4e4&lt;/th>
&lt;th>5e4&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>runtime&lt;/td>
&lt;td>0.183s&lt;/td>
&lt;td>0.183s&lt;/td>
&lt;td>0.180s&lt;/td>
&lt;td>0.198s&lt;/td>
&lt;td>0.542s&lt;/td>
&lt;td>1.232s&lt;/td>
&lt;td>2.032s&lt;/td>
&lt;td>3.203s&lt;/td>
&lt;td>4.643s&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>I think that&amp;rsquo;s about as far as this problem goes. Thanks to Josh and Piotr for doing the actual difficult mathematics.&lt;/p></content></item><item><title>Building a Siteswap Visualizer in d3.js</title><link>https://jbuckland.com/blog/graphics-juggling-graph/</link><pubDate>Fri, 25 Dec 2015 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/graphics-juggling-graph/</guid><description>App http://jbuckland.com/juggling-graph Github http://github.com/ambuc/juggling-graph Siteswap is a juggling notation used to describe or represent juggling patterns. It encodes the number of beats of each throw, which is related to their height, and the hand to which the throw is to be made: &amp;ldquo;The idea behind siteswap is to keep track of the order that balls are thrown and caught, and only that.&amp;quot;[1] It is an invaluable tool in determining which combinations of throws yield valid juggling patterns for a given number of objects, and has led to previously unknown patterns (such as 441).</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">App&lt;/th>
&lt;td> &lt;a href="http://jbuckland.com/juggling-graph">http://jbuckland.com/juggling-graph&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;th style="text-align: right;">Github&lt;/th>
&lt;td> &lt;a href="http://github.com/ambuc/juggling-graph">http://github.com/ambuc/juggling-graph&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;figure class="center" >
&lt;img src="images/example.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;blockquote>
&lt;p>Siteswap is a juggling notation used to describe or represent juggling patterns. It encodes the number of beats of each throw, which is related to their height, and the hand to which the throw is to be made: &amp;ldquo;The idea behind siteswap is to keep track of the order that balls are thrown and caught, and &lt;em>only&lt;/em> that.&amp;quot;[1] It is an invaluable tool in determining which combinations of throws yield valid juggling patterns for a given number of objects, and has led to previously unknown patterns (such as 441). However, it does not describe body movements such as behind-the-back and under-the-leg.&lt;/p>
&lt;p>(from &lt;a href="https://en.wikipedia.org/wiki/Siteswap">Wikipedia&lt;/a>.)&lt;/p>
&lt;/blockquote>
&lt;p>This is a visualization engine for Siteswaps, a notation system used in juggling to represent patterns in space. It accepts input in the form of numbers &lt;code>0-9&lt;/code>, letters &lt;code>a-z&lt;/code>, and brackets &lt;code>[ ]&lt;/code>, which denote multiplexes, i.e. synchronous events. &lt;code>juggling-graph&lt;/code> draws arrows from each valid throwable position to each valid catch position. Multiplexes throw from their contents, but recieve at their opening bracket.&lt;/p>
&lt;p>This project was my first venture into exclusively-compiled web development, utilizing &lt;a href="http://coffeescript.org">Coffeescript&lt;/a> and &lt;a href="http://jade-lang.com/">Jade&lt;/a>. Both were a joy to use.&lt;/p></content></item><item><title>Design and Construction of a Walking Alarm Clock</title><link>https://jbuckland.com/blog/manufacturing-walking-alarm-clock/</link><pubDate>Sun, 15 Nov 2015 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/manufacturing-walking-alarm-clock/</guid><description>Paper ME270 Team Project Final Report App https://goo.gl/photos/BAEoTaT7WMQ4G2Av5 Github http://github.com/ambuc/watchdog The WatchDog is an electromechanical walking alarm clock built by Chris Olsen, Andrew Mott, David Thunga, Mike Bloom, and myself for the Design for Manufacturability (ME270) course at the University of Illinois in Fall 2015.
From the product description:
The WatchDog is an interactive novelty alarm clock that deters the user from overusing the snooze button. After the alarm has been snoozed once, the WatchDog is programmed to begin barking and running at the end of the snooze cycle to ensure the user must get out of bed to turn off the alarm.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">Paper&lt;/th>
&lt;td> &lt;a href="http://jbuckland.com/assets/pdfs/watchdog.pdf">ME270 Team Project Final Report&lt;/a> &lt;/td>
&lt;/tr>
&lt;tr>
&lt;th style="text-align: right;">App&lt;/th>
&lt;td> &lt;a href="https://goo.gl/photos/BAEoTaT7WMQ4G2Av5">https://goo.gl/photos/BAEoTaT7WMQ4G2Av5&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;th style="text-align: right;">Github&lt;/th>
&lt;td> &lt;a href="http://github.com/ambuc/watchdog">http://github.com/ambuc/watchdog&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;p>The &lt;strong>WatchDog&lt;/strong> is an electromechanical walking alarm clock built by Chris Olsen, Andrew Mott, David Thunga, Mike Bloom, and myself for the Design for Manufacturability (ME270) course at the University of Illinois in Fall 2015.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/watchdog.jpg" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>From the product description:&lt;/p>
&lt;blockquote>
&lt;p>The WatchDog is an interactive novelty alarm clock that deters the user from overusing the snooze button. After the alarm has been snoozed once, the WatchDog is programmed to begin barking and running at the end of the snooze cycle to ensure the user must get out of bed to turn off the alarm. The WatchDog is a complex electro-mechanical system in which an Arduino controls both the alarm clock functionality and the rotation of two small servo motors. These servos were attached to eight-bar linkages (Jansen mechanisms) that were created using laser cut acrylic and held together with dowel pins and retaining rings. These linkages allow the WatchDog to move with fluid, canine-like motion thus adding to the aesthetic value of the design. The Arduino circuit and servos are housed within a segmented, 3D-printed torso that is held together with snap fits. The two endcaps of the torso are attached to a dog-like head and tail to add to the friendliness of the design.&lt;/p>
&lt;/blockquote>
&lt;p>The majority of my contributions to the project were towards its circuit design and Arduino programming, as detailed in the paper.&lt;/p></content></item><item><title>Scraping Wikipedia to Build a Historical Trivia Game</title><link>https://jbuckland.com/blog/game-test-of-time/</link><pubDate>Tue, 11 Aug 2015 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/game-test-of-time/</guid><description>App http://jbuckland.com/test-of-time Github http://github.com/ambuc/test-of-time The Test of Time is a historical trivia game where you order events chronologically, guessing which events came before and after each other. As the round goes on, it becomes harder to pinpoint exactly when obscure events happened. That&amp;rsquo;s pretty much all there is to it.
Julian and I decided to build this after thinking about how much excellent data was locked up inside Wikipedia as unformatted plaintext.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">App&lt;/th>
&lt;td> &lt;a href="http://jbuckland.com/test-of-time">http://jbuckland.com/test-of-time&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;th style="text-align: right;">Github&lt;/th>
&lt;td> &lt;a href="http://github.com/ambuc/test-of-time">http://github.com/ambuc/test-of-time&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;p>&lt;strong>&lt;a href="http://jbuckland.com/test-of-time">The Test of Time&lt;/a>&lt;/strong> is a historical trivia game where you order events chronologically, guessing which events came before and after each other. As the round goes on, it becomes harder to pinpoint exactly when obscure events happened. That&amp;rsquo;s pretty much all there is to it.&lt;/p>
&lt;p>&lt;a href="http://julianrosenblum.com">Julian&lt;/a> and I decided to build this after thinking about how much excellent data was locked up inside Wikipedia as unformatted plaintext. We found a fantastic corpus created by &lt;a href="http://www.gesis.org/das-institut/mitarbeiterverzeichnis">Dr. Daniel Heinert&lt;/a> for &lt;a href="http://vizgr.org/historical-events/">extracting chronological events&lt;/a>, downloaded a bunch of events, and reformatted them into JSON. The interface uses the &lt;a href="http://vizgr.org/historical-events/">Materialize&lt;/a> CSS library and Google&amp;rsquo;s &lt;a href="www.google.com/design/icons/">Material Icons&lt;/a>.&lt;/p></content></item><item><title>Generating Fractals with Lindenmayer Systems</title><link>https://jbuckland.com/blog/graphics-lindenmayer/</link><pubDate>Thu, 06 Aug 2015 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/graphics-lindenmayer/</guid><description>App http://jbuckland.com/lindenmayer Github http://github.com/ambuc/lindenmayer About Lindenmayer Systems L-systems are formal grammar structures used in the study of both botany and mathematics, usually to simulate iterative or recursive structures, such as algae or fractals. In this case, we employ them to render fractals.
As noted, in the Wikipedia article on L-systems (from which much of this math is cribbed), an L-system can be represented by the tuple $$\v G = (V, \omega, P)$$</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">App&lt;/th>
&lt;td> &lt;a href="http://jbuckland.com/lindenmayer">http://jbuckland.com/lindenmayer&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;th style="text-align: right;">Github&lt;/th>
&lt;td> &lt;a href="http://github.com/ambuc/lindenmayer">http://github.com/ambuc/lindenmayer&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;figure class="center" >
&lt;img src="images/thumbnail.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;h2 id="about-lindenmayer-systems">About Lindenmayer Systems&lt;/h2>
&lt;p>&lt;strong>L-systems&lt;/strong> are formal grammar structures used in the study of both botany and mathematics, usually to simulate iterative or recursive structures, such as algae or fractals. In this case, we employ them to render fractals.&lt;/p>
&lt;p>As noted, in the &lt;a href="https://en.wikipedia.org/wiki/L-system">Wikipedia article&lt;/a> on L-systems (from which much of this math is cribbed), an L-system can be represented by the tuple $$\v G = (V, \omega, P)$$&lt;/p>
&lt;ul>
&lt;li>where $\v G$ is the &lt;em>alphabet&lt;/em>,&lt;/li>
&lt;li>$\omega$ is the &lt;em>seed&lt;/em>, the first string, and&lt;/li>
&lt;li>$P$ is the set of &lt;em>production rules&lt;/em>, by which the current string is replaced
by definitions from the alphabet.&lt;/li>
&lt;/ul>
&lt;h3 id="dragon-curve">Dragon Curve&lt;/h3>
&lt;p>Let&amp;rsquo;s take the famous Dragon Curve for example. Here,&lt;/p>
&lt;ul>
&lt;li>$\v G = [X, Y]$,&lt;/li>
&lt;li>$\omega = FX$, and&lt;/li>
&lt;li>$P = (X \longrightarrow X+YF+,\quad Y \longrightarrow -FX-Y)$.&lt;/li>
&lt;/ul>
&lt;p>In this example, $X$ and $Y$ are variables, which get replaced iteratively with the definitions in $P$; $+$ and $-$ are &lt;em>right&lt;/em> and &lt;em>left&lt;/em> turns, respectively, and $F$ means &lt;em>go forwards&lt;/em>.&lt;/p>
&lt;ul>
&lt;li>
&lt;p>At a depth of 0, we have only the initial string $\omega$:&lt;/p>
&lt;ul>
&lt;li>$FX$&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>At a depth of 1, we get&lt;/p>
&lt;ul>
&lt;li>$FX \longrightarrow F[X]$&lt;/li>
&lt;li>$ \phantom{FX} \longrightarrow F[X+YF+] $&lt;/li>
&lt;li>$ \phantom{FX} \longrightarrow FX+YF+ $&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>At a depth of 2, we get&lt;/p>
&lt;ul>
&lt;li>$FX+YF+ \longrightarrow F[X]+[Y]F+$&lt;/li>
&lt;li>$\phantom{FX+YF+} \longrightarrow F[X+YF+]+[-FX-Y]F+$&lt;/li>
&lt;li>$\phantom{FX+YF+} \longrightarrow FX+YF++-FX-YF+$&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>You can see where this is going.&lt;/p>
&lt;p>The dragon curve takes a number of iterations to get going, but many of the other curves show high levels of self-similarity at a depth of only 1 or 2. It depends on the rewriting rules.&lt;/p>
&lt;h2 id="design-of-the-application">Design of the Application&lt;/h2>
&lt;p>&lt;a href="https://github.com/JoshMermel">Josh Mermelstein&lt;/a> and I wrote a very simple Python script which can parse these sorts of string rules, stored in a dictionary (later, a Javascript object). It then iteratively &lt;em>replaces&lt;/em> the necessary strings, &lt;em>swaps&lt;/em> out those strings for an array of commands, and then &lt;em>evaluates&lt;/em> each of those commands. Using &lt;a href="https://en.wikipedia.org/wiki/Turtle_graphics">Turtle Graphics&lt;/a>, Python then drew that pattern on-screen. The webapp is simply a javascript port of the same code, with the ability to modify the constants and redraw the fractal in real time.&lt;/p>
&lt;h2 id="pseudo-psuedocode">Pseudo-psuedocode&lt;/h2>
&lt;div class="collapsable-code">
&lt;input id="537819642" type="checkbox" />
&lt;label for="537819642">
&lt;span class="collapsable-code__language">python&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-python" >&lt;code>
from turtle import *
# recursively rewrites $str to a given $depth using an $alphabet
def rewrite(str, alphabet, depth):
if depth == 0:
steps = &amp;#39;&amp;#39;
for char in str:
if char in alphabet:
steps &amp;#43;= alphabet[char]
else:
steps &amp;#43;= char
return steps
else:
return rewrite(rewrite(str, alphabet, 0), alphabet, depth-1)
# translates a series of $steps into commands using an $alphabet
def translate(steps, alphabet):
commands = []
for char in steps:
commands.append(alphabet[char])
return commands
if __name__ == &amp;#39;__main__&amp;#39;:
T = Turtle()
T.speed(&amp;#34;fastest&amp;#34;)
segmentLength = 10
depth = 3
# the entire fractal is here
rewritingRules = {
&amp;#39;L&amp;#39; : &amp;#39;&amp;#43;RF-LFL-FR&amp;#43;&amp;#39; ,
&amp;#39;R&amp;#39; : &amp;#39;-LF&amp;#43;RFR&amp;#43;FL-&amp;#39;
}
alphabet = {
&amp;#39;L&amp;#39; : &amp;#39;&amp;#39; ,
&amp;#39;R&amp;#39; : &amp;#39;&amp;#39; ,
&amp;#39;&amp;#43;&amp;#39; : &amp;#39;T.lt(90)&amp;#39; ,
&amp;#39;-&amp;#39; : &amp;#39;T.rt(90)&amp;#39; ,
&amp;#39;F&amp;#39; : &amp;#39;T.fd(segmentLength)&amp;#39;
}
seed = &amp;#39;L&amp;#39;
steps = rewrite(seed, rewritingRules, depth)
instructions = translate(steps, alphabet)
for item in instructions:
if item:
eval(item)
&lt;/code>&lt;/pre>
&lt;/div></content></item><item><title>Building a Zoomable JSON Visualizer</title><link>https://jbuckland.com/blog/graphics-seesaw/</link><pubDate>Wed, 29 Jul 2015 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/graphics-seesaw/</guid><description>App http://jsrmath.github.io/seesaw Github http://github.com/jsrmath/seesaw Seesaw is a web-based tool Julian and I built for inspecting complex JSON structures, without having to click to expand and collapse folders. You can click a box to zoom to it, or use the arrow keys to navigate around the structure.
The central conceit of Seesaw is that everything is either a bottom-level key-value pair, or a box containing either key-value pairs or other boxes.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">App&lt;/th>
&lt;td> &lt;a href="http://jsrmath.github.io/seesaw">http://jsrmath.github.io/seesaw&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;th style="text-align: right;">Github&lt;/th>
&lt;td> &lt;a href="http://github.com/jsrmath/seesaw">http://github.com/jsrmath/seesaw&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;p>&lt;strong>&lt;a href="http://jsrmath.github.io/seesaw/">Seesaw&lt;/a>&lt;/strong> is a web-based tool &lt;a href="http://julianrosenblum.com">Julian&lt;/a> and I built for inspecting complex JSON structures, without having to click to expand and collapse folders. You can click a box to zoom to it, or use the arrow keys to navigate around the structure.&lt;/p>
&lt;p>The central conceit of Seesaw is that everything is either a bottom-level key-value pair, or a box containing either key-value pairs or other boxes. Every JSON element is drawn onscreen on load, and each associated div is printed nested inside its parent. Bottom-level key-value pairs are colored consistently, so that a &lt;code>name&lt;/code> key is, for example, always red. This, coupled with the tendency of arrays to look visually consistent, makes exploring a complex, nested data structure much easier.&lt;/p>
&lt;p>The up and down arrows go up and down a level in the object, and the left and right arrows navigate to siblings.&lt;/p>
&lt;p>Seesaw uses &lt;a href="http://www.simplicitydesign.fi/">Janne Aukia&lt;/a>&amp;rsquo;s library &lt;a href="http://jaukia.github.io/zoomooz/">Zoomooz&lt;/a> for a suspiciously smooth zooming experience across browsers.&lt;/p></content></item><item><title>Cultural and Ecological Analysis of Mining in Norrbotten, Sweden</title><link>https://jbuckland.com/blog/essay-norbotten-sweden/</link><pubDate>Wed, 15 Jul 2015 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/essay-norbotten-sweden/</guid><description>Paper The Norrbotten Technological Megasystem: Impact on Society and Environment This is a report Jessica Malmberg and I compiled during the summer of 2015 under the direction of Dag Avango and Dr. Mark Safstrom, as a part of the course AK1214 Environment and Society in a Changing Arctic, hosted jointly by KTH in Stockholm and the University of Illinois at Urbana-Champaign.
The course involved three weeks of lectures in Stockholm on the topic of environmental and social change in the Arctic region, with an emphasis on Norrbotten, Sweden, as well as two weeks of travel and field research in Norrbotten, including on-site visits to Aitik, Malmberget, and Kiruna, as well as a stay at the Tarfala Research Station in the Tarfala Valley and visits to the Samí Museum in Jokkmokk, Laponia national park, and other cultural heritage sites in Norrbotten.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">Paper&lt;/th>
&lt;td> &lt;a href="norrbotten.pdf">The Norrbotten Technological Megasystem: Impact on Society and Environment&lt;/a> &lt;/td>
&lt;/tr>
&lt;/table>
&lt;p>This is a report Jessica Malmberg and I compiled during the summer of 2015 under the direction of &lt;a href="https://www.kth.se/profile/avango/">Dag Avango&lt;/a> and &lt;a href="http://www.germanic.illinois.edu/people/safstrom">Dr. Mark Safstrom&lt;/a>, as a part of the course &lt;a href="http://www.kth.se/student/kurser/kurs/AK1214?l=en">AK1214 Environment and Society in a Changing Arctic&lt;/a>, hosted jointly by &lt;em>KTH&lt;/em> in Stockholm and the University of Illinois at Urbana-Champaign.&lt;/p>
&lt;p>The course involved three weeks of lectures in Stockholm on the topic of environmental and social change in the Arctic region, with an emphasis on Norrbotten, Sweden, as well as two weeks of travel and field research in Norrbotten, including on-site visits to &lt;a href="http://www.boliden.com/Operations/Mines/Aitik/">Aitik&lt;/a>, &lt;a href="https://www.lkab.com/en/About-us/Overview/Operations-Areas/Malmberget/">Malmberget&lt;/a>, and &lt;a href="https://www.lkab.com/en/About-us/Overview/Operations-Areas/Kiruna/">Kiruna&lt;/a>, as well as a stay at the &lt;a href="http://www.natgeo.su.se/english/tarfala-research-station/tarfala-research-station-1.53731">Tarfala Research Station&lt;/a> in the Tarfala Valley and visits to the &lt;a href="http://www.ajtte.com/english/">Samí Museum&lt;/a> in Jokkmokk, &lt;a href="laponia.nu/">Laponia&lt;/a>
national park, and other cultural heritage sites in Norrbotten.&lt;/p>
&lt;p>The paper incorporates first-hand research into the environmental and social impacts of mining, hydropower, industrialization, and the railway - together known as the Norrbotten Technological Megasystem.&lt;/p></content></item><item><title>Design and Operation of a Crowd-Run Distributed Outdoor Game</title><link>https://jbuckland.com/blog/game-distributed-outdoor/</link><pubDate>Mon, 01 Jun 2015 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/game-distributed-outdoor/</guid><description>App http://hideandseek.ninja/stockholm.html NB: This project is down for maintenance.
The Pre-Game In May 2015, Aaron Luo, Anna Robbins, Claudio Paganini, and I sat down and designed a model for a new type of event: a free, outdoor, massive game of hide-and-seek. We would attract attention through a Facebook group, and have participants read rules and register on a website (http://hideandseek.ninja/) (which I would build and maintain) during the game.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">App&lt;/th>
&lt;td> &lt;a href="http://hideandseek.ninja/stockholm.html">http://hideandseek.ninja/stockholm.html&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;blockquote>
&lt;p>NB: This project is down for maintenance.&lt;/p>
&lt;/blockquote>
&lt;figure class="center" >
&lt;img src="images/map.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;h2 id="the-pre-game">The Pre-Game&lt;/h2>
&lt;p>In May 2015, Aaron Luo, Anna Robbins, Claudio Paganini, and I sat down and designed a model for a new type of event: a free, outdoor, massive game of &lt;strong>hide-and-seek&lt;/strong>. We would attract attention through a &lt;a href="https://www.facebook.com/events/1568336823455138/">Facebook group&lt;/a>, and have participants read rules and register on a website (&lt;a href="http://hideandseek.ninja/">http://hideandseek.ninja/&lt;/a>) (which I would build and maintain) during the game.&lt;/p>
&lt;p>What was the incentive for such an unusual event?&lt;/p>
&lt;h3 id="legal-considerations">Legal Considerations&lt;/h3>
&lt;p>We were inspired by stories of people playing Hide and Seek in an Ikea. This is both a) &lt;em>very&lt;/em> Swedish, and b) &lt;em>very&lt;/em> illegal. Private property and unregistered events don&amp;rsquo;t mix well. So our event had to be outdoors, public, unlisted, and legal.&lt;/p>
&lt;p>We realized that concerns of legality rarely had to do with attendance &amp;ndash; they had more to do with &lt;strong>density&lt;/strong>. Enough people in the same place would clog traffic, call attention, get us shut down, etc. But thousands of people could still play - as long as they were not all in the same place.&lt;/p>
&lt;p>But, most importantly, it had to be &lt;strong>disorganized&lt;/strong>. The idea of standing in a city square with a megaphone trying to shout instructions to hundreds of people was not an appealing one.&lt;/p>
&lt;p>Thus was born the &lt;strong>distributed game&lt;/strong>.&lt;/p>
&lt;p>Imagine twenty or so little games of Hide-and-Seek going on all around the city. There are under 40 people per game, and each game is self-contained, each with its own meeting point and region. Stray too far from one zone, and you begin to encounter players from another. The game could grow organically as more and more people were invited, and it wouldn&amp;rsquo;t affect gameplay at all &amp;ndash; as long as we &lt;em>distributed&lt;/em> the game over a large enough area.&lt;/p>
&lt;p>But how would the game work?&lt;/p>
&lt;h3 id="math-and-scoring">Math and Scoring&lt;/h3>
&lt;p>&lt;em>Hide and Seek in Gamla Stan&lt;/em> operated on a unique scoring system designed by Claudio Paganini.&lt;/p>
&lt;p>Each player starts with &lt;em>100&lt;/em> points. They then &lt;em>share these 100 points&lt;/em> with those seekers find them. Thus, if I am found three times, each seeker recieves 25 points, and I, the hider, keep 25 points of my points. When the roles are reversed, I do my best to find many hiders and recoup my losses.&lt;/p>
&lt;p>(How do you prove you found me? We&amp;rsquo;ll get there in a moment.)&lt;/p>
&lt;p>The benefit of this system is that suddenly the game requires strategy. In a short game period, the seeker can only find so many hiders. The share system means that seekers are rewarded not only for quantity of finds, but quality &amp;ndash; those hiders who are more difficult to find are found less, and each share of their points is worth more. One really good find can be worth as many as &lt;em>50&lt;/em> points. A bad find could easily be worth 5.&lt;/p>
&lt;p>How would you prove you found someone?&lt;/p>
&lt;h3 id="proof-of-find">Proof of Find&lt;/h3>
&lt;p>From the beginning, we wanted people to be able to play without supervision or oversight. Thus, rather than verify finds by hand (impossible) or rely on an honor system, we chose to verify finds mathematically, with a small bit of information.&lt;/p>
&lt;p>Each hider was, at registration, given a &lt;em>secret code&lt;/em> (mine was &lt;strong>B030&lt;/strong>) to share with those seekers who found them. The seekers would then write down the code in a notepad, on their phone, whatever. After the game, they could go to the website, enter the code, and verify their find.&lt;/p>
&lt;p>As the players logged their finds, we began to generate a graph of finds across town.&lt;/p>
&lt;p>How were the codes generated?&lt;/p>
&lt;h3 id="design-of-the-secret-code">Design of the Secret Code&lt;/h3>
&lt;p>Anna Robbins and I struggled with the concept of a &amp;lsquo;secret code&amp;rsquo; for a few days. A short text string was easier than something more verifiable, like a QR code, but language barriers, spelling errors, and handwriting meant that strings, words, phrases, were untenable. We wanted it to be short and sweet, something easy to write and easy to type in afterwards.&lt;/p>
&lt;p>Additionally, the codes had to be &lt;em>salted&lt;/em>: that is, it ought to be difficult to randomly guess a string and have it be an extant secret code.&lt;/p>
&lt;p>$$\underbrace{\texttt{B}}_{\texttt{email_string[0]}} \overbrace{\texttt{030}}^{\texttt{dec2hex}(id)}$$&lt;/p>
&lt;p>The &lt;em>first&lt;/em> character is an alphanumeric character, the first in their email. The next three characters are the truncated hexadecimal representation of their identification number (&lt;code>$id&lt;/code>) in the database. (The &lt;code>$id&lt;/code> represents if they were first, second, etc. to sign up for the game.)&lt;/p>
&lt;p>The first bit was known to the user; the second was not. The second bit was easy to figure out, if you knew hex; the first was not. It doesn&amp;rsquo;t stand up to real cryptographic analysis, of course, but it did the trick &amp;ndash; we didn&amp;rsquo;t detect any cheating.&lt;/p>
&lt;p>An additional failsafe against cheating was that anybody sharing codes with their friends was effectively diminishing the value of the code. A 25pt find, split an additional way, becomes a 20pt find.&lt;/p>
&lt;p>How did we invite people?&lt;/p>
&lt;h3 id="social-engineering">Social Engineering&lt;/h3>
&lt;p>Once the game was designed, the rest was simple. We started a Facebook event and invited every student we knew. I personally asked a few social mavens to invite everyone they knew.&lt;/p>
&lt;p>After the first few days, Facebook&amp;rsquo;s attendance measurement began to lose digits of accuracy. Thus, I only have rate-of-attendance data for the first thousand attendees or so. We eventually reached just over 6k invited / 3.5k attending on Facebook, with just over 1k of those people actually registering to play on &lt;a href="http://hideandseek.ninja">http://hideandseek.ninja&lt;/a>.&lt;/p>
&lt;p>&lt;strong>Those Attending on Facebook, vs&amp;hellip;&lt;/strong>&lt;/p>
&lt;figure class="center" >
&lt;img src="images/attending.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>&lt;strong>&amp;hellip;those Officially Registered on the Website.&lt;/strong>&lt;/p>
&lt;figure class="center" >
&lt;img src="images/registered.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>The registration system collected the player&amp;rsquo;s first and last name, email, and hometown. Upon submission of the registration form, each player recieved a customized email containing more detailed gameplay instructions, their &lt;strong>secret code&lt;/strong>, their team (whether they were to hide first or seek first), and their &lt;strong>Zone&lt;/strong> &amp;ndash; initially, merely a random number between 1 and 24. (24 is a very divisible number.)&lt;/p>
&lt;p>How did we assign zones?&lt;/p>
&lt;h3 id="zones">Zones&lt;/h3>
&lt;p>Once we reached ~800 registered, we began to divide up Gamla Stan and the surrounding neighborhood into 12 zones, and demarcate a region of fair play.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/zones.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>We picked (hopefully) good meeting points (public squares, parks, places far from traffic and palaces) and emailed instructions on a per-zone basis, indicating where to meet, when to begin playing, etc.&lt;/p>
&lt;p>Players needed to bring a pen and pad, to write down the secret codes they found. They also needed a watch or something similar to keep an eye on the time &amp;ndash; each team had 15 minutes to hide, and 30 minutes in which to stay hidden while the other team sought. Then, after another 15 minutes, they would switch. The game wouldn&amp;rsquo;t work if people didn&amp;rsquo;t know where to go when.&lt;/p>
&lt;p>Additionally, the players needed another key ingredient&amp;hellip;&lt;/p>
&lt;h3 id="socks">Socks&lt;/h3>
&lt;p>The game was called, colloquially, &lt;strong>Hide and Sock&lt;/strong>. Players had to know who was playing and who was just a bystander. We instructed players to wear a sock on their hand, so that they could be found. So they had to bring a sock.&lt;/p>
&lt;h2 id="the-game">The Game&lt;/h2>
&lt;p>On the day of the event, I got my sock, pen, paper, watch, secret code, zone, etc &amp;ndash; everything I needed &amp;ndash; and went to my assigned location, where I found &amp;ndash; a dozen or so people hanging around, waiting to meet their zone. It turned out not all of our players were college students &amp;ndash; plenty of families, tourists, elderly, etc. We synced knowledge about gameplay and schedules, discussed how we found out about the event, and then - suddenly - it was time to hide.&lt;/p>
&lt;p>I hid quite badly, because I wanted to see how many people were actually playing. I was found just over 40 times in 30 minutes, which I think is quite impressive - close to 1000 people were actually playing that day, and I personally shook the hand of just over 1/25 of them.&lt;/p>
&lt;p>The game went fast - I regret how tight we made the schedule, and how long we made the hiding period. Too many people were too good at hiding, and were never found.&lt;/p>
&lt;h2 id="the-aftergame">The Aftergame&lt;/h2>
&lt;p>As per instructions, we all met in Kungstradgarden for a group photo, where I met many of the players and got feedback on how the event went. We also used Aaron&amp;rsquo;s selfie stick to take a group photo. (I&amp;rsquo;m in the center, behind the photographer.)&lt;/p>
&lt;figure class="center" >
&lt;img src="images/photo.jpg" style="border-radius: 8px;" />
&lt;/figure>
&lt;h3 id="countries-of-origin">Countries of Origin&lt;/h3>
&lt;p>When a player registered, we collected their first and last name, email, and hometown (just for fun data analysis). We ended up with 1141 registered players, hailing from 127 regions (countries, large cities, neighborhoods). It&amp;rsquo;s difficult to graph, but the top three countries were Sweden, Germany, India, Italy, France, Singapore, China, and then over one hundred more with shares under 1%.&lt;/p>
&lt;figure class="center" >
&lt;img src="images/origins.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;h3 id="finds">Finds&lt;/h3>
&lt;p>We expected players from each group to mostly end up finding players from their own group. Instead, most people in most zones had the bright idea of spreading as far from their home base as they could. As a result, there was far more cross-pollination than expected. With 1105 registered finds from 139 participating players, 1026 of those finds (92.8%) were of players from other zones. Some zones contained no same-zone finds. The groups quickly disseminated and play roamed free across the city &amp;ndash; as intended.&lt;/p>
&lt;p>&lt;strong>Finds between Zones.&lt;/strong> Each find is a very light red line. The more inter-zone finds, the darker the red line becomes. Point location is approximate ;)&lt;/p>
&lt;figure class="center" >
&lt;img src="images/finds.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>Altogether, the game was a rousing success, with &lt;a href="http://hideandseek.ninja/rankings.php">some players&lt;/a> scoring as high as 481 points (found 13 people, was found once).&lt;/p>
&lt;h2 id="what-did-we-learn">What Did We Learn?&lt;/h2>
&lt;h3 id="people-are-reliable">People are Reliable&lt;/h3>
&lt;p>The groups were surprisingly self-organizing, and the players were polite, honest, and fun. I met a ton of people, and I think everyone had a great time. (It helped that the weather was great.)&lt;/p>
&lt;h3 id="people-are-unreliable">People are Unreliable&lt;/h3>
&lt;p>On Facebook, anyway. We had 6k invited, 3.5k attending, 1k registered, and just over 150 estimated participants. Under 100 showed up for the final photo.&lt;/p>
&lt;p>Knowing this now, we could have made the map a &lt;em>lot&lt;/em> denser - I heard reports that some zones were so sparsely populated that finding anybody at all was a challenge.&lt;/p>
&lt;p>&lt;code>¯\_(ツ)_/¯&lt;/code>&lt;/p></content></item><item><title>A Walkthrough of the Advection-Differencing Scheme</title><link>https://jbuckland.com/blog/math-advection-differencing-scheme/</link><pubDate>Thu, 28 May 2015 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/math-advection-differencing-scheme/</guid><description>$$ \def\phi{\varphi} $$
Disclaimer: This is something a little different. I&amp;rsquo;m going to step through solving a practice problem for the MJ2424 Numerical Methods final exam, partially as practice teaching (and understanding) the material, partially as practice writing scientifically, and partially for fun. I&amp;rsquo;ll be working off my own derivation, but checking my answers, so I really hope the material is accurate.
What is Computational Fluid Dynamics? CFD is the process of reducing problems in Fluid Dynamics to stepwise, iterative processes computers can solve.</description><content>&lt;p>$$ \def\phi{\varphi} $$&lt;/p>
&lt;blockquote>
&lt;p>Disclaimer: This is something a little different. I&amp;rsquo;m going to step through solving a practice problem for the MJ2424 Numerical Methods final exam, partially as practice teaching (and understanding) the material, partially as practice writing scientifically, and partially for fun. I&amp;rsquo;ll be working off my own derivation, but checking my answers, so I really hope the material is accurate.&lt;/p>
&lt;/blockquote>
&lt;h2 id="what-is-computational-fluid-dynamics">What is Computational Fluid Dynamics?&lt;/h2>
&lt;p>CFD is the process of reducing problems in Fluid Dynamics to stepwise, iterative processes computers can solve. Specifically, it involves taking a real-life continuum and reducing it to a finite-volume calculation with a set number of cells, across each of which a very simple calculation will be performed. This allows us to turn a complex calculus problem, difficult for computational problem solving, into a linear algebra problem, which is much simpler.&lt;/p>
&lt;h2 id="the-problem">The Problem&lt;/h2>
&lt;p>As seen in Classroom Example 3 of Section 4.7 &lt;strong>Discretising Advection&lt;/strong> of David D Apsley&amp;rsquo;s &lt;a href="http://personalpages.manchester.ac.uk/staff/david.d.apsley/lectures/comphydr/scalar.pdf">CFD Notes&lt;/a>:&lt;/p>
&lt;figure class="center" >
&lt;img src="images/problem.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;blockquote>
&lt;p>A pipe of cross-section $A = 0.01 m^2$ and length $L = 1 m$ carries water (density $\rho = 1000 kg / m^{3}$ at velocity $u = 0.1 m / s$.&lt;/p>
&lt;p>A faulty valve introduces a reactive chemical into the pipe half-way along its length at a rate of $0.01 kg /s$. The diffusivity of the chemical in water is $\Gamma = 0.1 kg / m s$. The chemical is subsequently broken down at a rate proportional to its concentration $\phi$ (mass of chemical per unit mass of water), this rate amounting to $–\gamma\phi$ per metre, where $\gamma = 0.5 kg / m s$.&lt;/p>
&lt;p>Assuming that the downstream boundary condition is $\d{\phi}{x}=0$, set up a finite-volume calculation with 7 cells to estimate the concentration along the pipe using:&lt;/p>
&lt;ul>
&lt;li>(a) central&lt;/li>
&lt;li>(b) upwind&lt;/li>
&lt;/ul>
&lt;p>differencing schemes for advection.&lt;/p>
&lt;/blockquote>
&lt;h3 id="the-method">The Method&lt;/h3>
&lt;p>From the normal steady-state one-dimensional advection diffusion equation&lt;/p>
&lt;p>$$\pd{\rho\phi}{t} + \div(\rho\phi\v u) = div(\Gamma\grad\phi) + S_\phi $$&lt;/p>
&lt;p>We make the equation steady-state and one-dimensional:&lt;/p>
&lt;p>$$\d{}{x}\left(\rho u A \phi - \Gamma A \d{\phi}{x} \right)=S$$&lt;/p>
&lt;p>We must now introduce the east/west convention. The current cell in question is
$P$; its east and west neighbors are $E$ and $W$. Its east and west &lt;em>faces&lt;/em> -
that is, the boundaries it shares with those neighbors - are designated as the
lowercase versions of those cardinal directions, $e$ and $w$. We can then write:&lt;/p>
&lt;p>$$\d{}{x}\left( \rho u \phi \right)^e_w = \d{}{x}\left( \Gamma \d{\phi}{x} \right)^E_W$$&lt;/p>
&lt;p>Integrating the transport equation, we find:&lt;/p>
&lt;p>$$(\rho u A \phi)_e - (\rho u A \phi)_w = (\Gamma A)\left(\d{\phi}{x}\right)_E - (\Gamma A)\left( \d{\phi}{x} \right)$$&lt;/p>
&lt;p>We define the constants: $ F = \rho A u_w^e$ and $D = \left( \frac{\Gamma A}{\Delta x} \right)_w^e$&lt;/p>
&lt;p>Such that the integrated convection-diffusion equation reads:&lt;/p>
&lt;p>$$F_e \phi_e - F_w \phi_w = D_e(\phi_E - \phi_P) - D_w(\phi_P - \phi_W)$$&lt;/p>
&lt;h3 id="the-central-differencing-scheme-cds">The Central Differencing Scheme (CDS)&lt;/h3>
&lt;p>Here we introduce a scheme for finding the values of a given property - in this
problem, a chemical concentration - at a boundary between two cells, given its
value at the centers of those two cells. We introduce:&lt;/p>
&lt;p>$$\phi_e = \frac{\phi_E + \phi_P}{2} \qquad \phi_w = \frac{\phi_P + \phi_W}{2} \qquad \text{(This is the CDS)}$$&lt;/p>
&lt;p>Expanding generally - we will expand a little more specifically in a moment - we
find:&lt;/p>
&lt;p>$$F_e \phi_e - F_w \phi_w = D_e (\phi_E - \phi_P) - D_w (\phi_P - \phi_w)$$&lt;/p>
&lt;p>Expanding out for our CDS:&lt;/p>
&lt;p>$$\frac{F_e}{2}(\phi_P + \phi_E) - \frac{F_w}{2}(\phi_W + \phi_P) = D_e(\phi_E - \phi_P) - D_w (\phi_P - \phi_W)$$&lt;/p>
&lt;p>And, finally, gathering by $\phi$:&lt;/p>
&lt;p>$$\left(\left(D_w + \tfrac{F_w}{2}\right)+\left(D_e - \tfrac{F_e}{2}\right)+\left(F_e - F_w\right)\right)\phi_P = \left(D_w + \tfrac{F_w}{2}\right)\phi_W + \left(D_e - \tfrac{F_e}{2}\right) \phi_E$$&lt;/p>
&lt;p>Essentially, this allows us to define a set of variables, and use them
repeatedly to solve this generalized cell at unique conditions, such as at
boundaries or sources.&lt;/p>
&lt;ul>
&lt;li>$a_W = D_w + \tfrac{F_w}{2}$&lt;/li>
&lt;li>$a_E = D_e - \tfrac{F_e}{2}$&lt;/li>
&lt;li>$a_P = a_w + a_e + F_e - F_w$&lt;/li>
&lt;/ul>
&lt;h3 id="sources">Sources&lt;/h3>
&lt;p>Our problem has an additional complexity - the variable in question, $\phi$, the
concentration of a given chemical, has both an internal (not boundary) source,
and a constant rate of dissipation. Thus we must introduce a source term. Each
cell is assigned a quantity $S_u$ corresponding to the rate of material
addition, and a scaling quantity $S_P$, corresponding to the dissipation of the
material over space.&lt;/p>
&lt;p>$$\Sigma S = S_u + S_p \phi_p$$&lt;/p>
&lt;p>The problem description tells us that material is injected only once, at cell
#4, at a rate of $\dot m = 0.01 kg/s$. Thus $S_u(4) = 0.01$, and $S_u = 0$
elsewhere. Additionally, the chemical dissipates at a rate $\gamma\phi$ per
meter, where $\gamma = 0.5 kg / m s$. Thus, $S_P = \gamma\Delta x$, where
$\Delta x$ is the step distance ($1m / 7$). $S_P$ is applicable on all cells,
not just those with the chemical.&lt;/p>
&lt;p>We introduce these terms on the right-hand side of the quation, as seen in the
normal steady-state one-dimensional diffusion equation at the top of the page.
Thus, the general form:&lt;/p>
&lt;p>$$F_e \phi_e - F_w \phi_w = D_e (\phi_E - \phi_P) - D_w (\phi_P - \phi_w)$$&lt;/p>
&lt;p>becomes:&lt;/p>
&lt;p>$$F_e \phi_e - F_w \phi_w = D_e (\phi_E - \phi_P) - D_w (\phi_P - \phi_w) + S_u + S_P \phi_P$$&lt;/p>
&lt;p>and the $S_P \phi_P$ term is often factored to the left-hand side, as above.&lt;/p>
&lt;h2 id="solving-the-problem">Solving the Problem&lt;/h2>
&lt;p>We solve this problem on four unique cases, where we
simplify $D_e = D_w$, $F_e = F_w$, and $S_p = \gamma\Delta x$ always.&lt;/p>
&lt;h3 id="the-default-cell-cells-2-3-5-6">The Default Cell (Cells 2, 3, 5, 6)&lt;/h3>
&lt;p>If the cell has no sources and no boundaries, the transport equation is
unchanged. We write:&lt;/p>
&lt;p>$$F_e \phi_e - F_w \phi_w = D_e (\phi_E - \phi_P) - D_w (\phi_P - \phi_W) + S_u + S_p\phi_P$$&lt;/p>
&lt;p>where $S_u = 0$. We expand by &lt;strong>CDS&lt;/strong> and find:&lt;/p>
&lt;p>$$(2D - S_P)\phi_P = (D+\tfrac{F}{2})\phi_W + (D-\tfrac{F}{2})\phi_E$$&lt;/p>
&lt;p>And thus, $a_W = D+F/2$, $a_E = D-F/2$, $S_u = 0$, $a_p = a_E + a_W - S_P = 2D - S_P$.&lt;/p>
&lt;h3 id="the-injection-cell-cell-4">The Injection Cell (Cell 4)&lt;/h3>
&lt;p>The Injection Cell behaves in a very similar manner to the Default Cell, except
that there is a source, so $S_u$ is nonzero. Thus: $a_W = D+F/2$, $a_E = D-F/2$,
$S_u = 0.1$, $a_p = a_E + a_W - S_P = 2D - S_P$.&lt;/p>
&lt;h3 id="the-left-boundary-cell-1">The Left Boundary (Cell 1)&lt;/h3>
&lt;p>The boundary conditions are unique &amp;ndash; on the left-hand face, designated $A$, we
know $\phi(A) = 0$. We expand the standard form more slowly:&lt;/p>
&lt;p>$$F_e \phi_e - F_w \phi_w = D_e (\phi_E - \phi_P) - D_w (\phi_P - \phi_W) + S_u + S_P \phi_P$$&lt;/p>
&lt;p>Expanding by CDS:&lt;/p>
&lt;p>$$F_e \tfrac{\phi_E + \phi_P}{2} - F_a \phi_A = D_e (\phi_E - \phi_P) - D_A \phi_A + S_u + S_p \phi_P$$&lt;/p>
&lt;p>We note $\phi_A = 0$. This allows us to regroup as:&lt;/p>
&lt;p>$$(3D + \tfrac{F}{2} - S_p)\phi_P = (0)\phi_W + (D-\tfrac{F}{2})\phi_E + S_u$$&lt;/p>
&lt;p>Thus, $a_W = 0$, $a_E = D-F/2$, $S_u = 0$, $a_p = 3D + F/2 - S_P$.&lt;/p>
&lt;h3 id="the-right-boundary-cell-7">The Right Boundary (Cell 7)&lt;/h3>
&lt;p>The right boundary is the inverse of the left boundary. We know
that $\pd{\phi}{x}(B) = 0$, so the $B$ terms similarly go to zero.
Thus, $a_W = D+F/2$, $a_E = 0$, $S_u = 0$, $a_p = D + F/2 - S_P$&lt;/p>
&lt;h2 id="numerically">Numerically&lt;/h2>
&lt;p>We know $D=\Gamma A / \Delta x = 0.007 kg/s$, $F = \rho A u = 1 kg/s$,
and $S_P = \gamma\Delta x = -0.07 kg/s$. We can thus solve:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:center">n&lt;/th>
&lt;th style="text-align:center">$a_w$&lt;/th>
&lt;th style="text-align:center">$a_e$&lt;/th>
&lt;th style="text-align:center">$a_p$&lt;/th>
&lt;th style="text-align:center">$S_u$&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:center">1&lt;/td>
&lt;td style="text-align:center">$\ 0$&lt;/td>
&lt;td style="text-align:center">$\ -0.493$&lt;/td>
&lt;td style="text-align:center">$\ 0.592$&lt;/td>
&lt;td style="text-align:center">$\ 0$&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">2,3,5,6&lt;/td>
&lt;td style="text-align:center">$\ 0.507$&lt;/td>
&lt;td style="text-align:center">$\ -0.493$&lt;/td>
&lt;td style="text-align:center">$\ 0.085$&lt;/td>
&lt;td style="text-align:center">$\ 0$&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">4&lt;/td>
&lt;td style="text-align:center">$\ 0.507$&lt;/td>
&lt;td style="text-align:center">$\ -0.493$&lt;/td>
&lt;td style="text-align:center">$\ 0.085$&lt;/td>
&lt;td style="text-align:center">$\ 0.01$&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">7&lt;/td>
&lt;td style="text-align:center">$\ 0.507$&lt;/td>
&lt;td style="text-align:center">$\ 0$&lt;/td>
&lt;td style="text-align:center">$\ 0.578$&lt;/td>
&lt;td style="text-align:center">$\ 0$&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>These constants hold for any instance of:&lt;/p>
&lt;p>$$a_P \phi_P = a_W \phi_W + a_E \phi_E + S_u$$&lt;/p>
&lt;p>Using all seven iterations of this equations, we can populate a linear equation
with real numerical values, and solve:&lt;/p>
&lt;p>$$
\begin{pmatrix} 0.592 &amp;amp; 0.493 &amp;amp; 0 &amp;amp; 0 &amp;amp; 0 &amp;amp; 0 &amp;amp; 0\\ -0.507 &amp;amp; 0.085 &amp;amp; 0.493 &amp;amp; 0 &amp;amp; 0 &amp;amp; 0 &amp;amp; 0\\ 0 &amp;amp; -0.507 &amp;amp; 0.085 &amp;amp; 0.493 &amp;amp; 0 &amp;amp; 0 &amp;amp; 0\\ 0 &amp;amp; 0 &amp;amp; 0 &amp;amp; -0.507 &amp;amp; 0.085 &amp;amp; 0.493 &amp;amp; 0\\ 0 &amp;amp; 0 &amp;amp; 0 &amp;amp; 0 &amp;amp; -0.507 &amp;amp; 0.085 &amp;amp; 0.493\\ 0 &amp;amp; 0 &amp;amp; 0 &amp;amp; 0 &amp;amp; 0 &amp;amp; -0.507 &amp;amp; 0.578 \end{pmatrix}
\begin{bmatrix}\phi(1)\\phi(2)\\phi(3)\\phi(4)\\phi(5)\\phi(6)\\phi(7)\end{bmatrix} =
\begin{pmatrix}0\\0\\0\\0.01\\0\\0\\0\end{pmatrix}
$$&lt;/p>
&lt;p>Which we can solve in a computer (or by hand, if you&amp;rsquo;re a sadist) to get real
values for $\phi(n)$ for all values in $n = [1..7]$.&lt;/p></content></item><item><title>Energy Optimization of an HVAC System via Numerical Modeling</title><link>https://jbuckland.com/blog/essay-hvac-numerical-modeling/</link><pubDate>Fri, 15 May 2015 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/essay-hvac-numerical-modeling/</guid><description>Paper Systematic Energy Utilization Optimization in DesignBuilder This is a report Akshaya Kumar and I compiled in May 2015, as part of the MJ2437 Modelling of Energy Systems / Energy Utilization course at KTH, in Stockholm. Under the direction of Dr. Jaime Arias, we created a 3D model of one of the lab buildings on campus, complete with elevation / climate information, construction / insulation methods, and first-hand research on HVAC / lighting / power load details.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">Paper&lt;/th>
&lt;td> &lt;a href="systematic.pdf">Systematic Energy Utilization Optimization in DesignBuilder&lt;/a> &lt;/td>
&lt;/tr>
&lt;/table>
&lt;p>This is a report Akshaya Kumar and I compiled in May 2015, as part of the &lt;em>MJ2437 Modelling of Energy Systems / Energy Utilization&lt;/em> course at &lt;em>KTH&lt;/em>, in Stockholm. Under the direction of &lt;a href="https://www.kth.se/en/itm/inst/energiteknik/forskningsavdelningar/upp-och-vent/personal/jaime-arias-1.73288">Dr. Jaime Arias&lt;/a>, we created a 3D model of one of the lab buildings on campus, complete with elevation / climate information, construction / insulation methods, and first-hand research on HVAC / lighting / power load details. We then populated the model with real information about average activity throughout the year. Tuning this model to match actual power usage data, we were then able to recommend specific HVAC changes, such as the inclusion of a heat recovery system, or the use of a variable air volume HVAC system. Analyzing costs of power vs costs of renovation, we present risk assessments and payback periods for each measure presented.&lt;/p>
&lt;p>The model was constructed in a CAD system and the energy simulated using a copy of &lt;a href="http://www.designbuilder.co.uk/">DesignBuilder&lt;/a> furnished by KTH.&lt;/p></content></item><item><title>Simulating Wind Flow with Star-CCM+ as a Method of Locating High-Velocity Regions</title><link>https://jbuckland.com/blog/essay-simulating-wind-flow/</link><pubDate>Mon, 04 May 2015 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/essay-simulating-wind-flow/</guid><description>Paper Methods of Wind Turbine Placement with Computational Fluid Dynamics This is a report Tamas Pal and I compiled in May 2015, as part of the MJ2424 Numerical Methods course at KTH, in Stockholm. Under the direction of Bahram Saadatfar and Reza Fakhrai, we created a 3D model of the entire KTH campus, complete with boundary conditions. We then populated the model with real building geometry and elevation/climate data. We then tuned the model with reasonable wind speeds data.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">Paper&lt;/th>
&lt;td> &lt;a href="turbine.docx">Methods of Wind Turbine Placement with Computational Fluid Dynamics&lt;/a> &lt;/td>
&lt;/tr>
&lt;/table>
&lt;p>This is a report Tamas Pal and I compiled in May 2015, as part of the &lt;em>MJ2424 Numerical Methods&lt;/em> course at &lt;em>KTH&lt;/em>, in Stockholm. Under the direction of Bahram Saadatfar and &lt;a href="https://www.kth.se/en/itm/inst/energiteknik/forskning/kraft_varme/personal/reza-fakhrai-1.21104">Reza Fakhrai&lt;/a>, we created a 3D model of the entire KTH campus, complete with boundary conditions. We then populated the model with real building geometry and elevation/climate data. We then tuned the model with reasonable wind speeds data. Analyzing streamlines and velocity gradients, we were then able to recommend specific locations on KTH campus for the placement of a wind turbine, for maximum annual average wind velocity and minimal downtime.&lt;/p>
&lt;p>The model was constructed in a CAD system and the fluid dynamics flow simulated using a copy of &lt;a href="http://www.cd-adapco.com/products/star-ccm%C2%AE">Star-CCM+&lt;/a> furnished by KTH.&lt;/p></content></item><item><title>Design and Characteristics of a Solar Field</title><link>https://jbuckland.com/blog/essay-solar-field/</link><pubDate>Tue, 17 Mar 2015 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/essay-solar-field/</guid><description>Paper MJ2412 Renewable Energy Technology - Morocco Proposal This is a report Alexandre Oudet, Jean-Fraņcois Olivier, Heinrich Boeing, Akshaya Kumar, and I compiled in March 2015, as part of the MJ2412 Renewable Energy Technology course at KTH, in Stockholm. We designed and evaluated the feasibility and necessary physical and mechanical constraints on a number of potential technologies involved in the construction and operation of a solar field. Physical models were developed in conjunction with financial data, to estimate the potential supply and forecasted demand in the region.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">Paper&lt;/th>
&lt;td> &lt;a href="Morocco.pdf">MJ2412 Renewable Energy Technology - Morocco Proposal&lt;/a> &lt;/td>
&lt;/tr>
&lt;/table>
&lt;p>This is a report Alexandre Oudet, Jean-Fraņcois Olivier, Heinrich Boeing, Akshaya Kumar, and I compiled in March 2015, as part of the &lt;em>MJ2412 Renewable Energy Technology&lt;/em> course at &lt;em>KTH&lt;/em>, in Stockholm. We designed and evaluated the feasibility and necessary physical and mechanical constraints on a number of potential technologies involved in the construction and operation of a solar field. Physical models were developed in conjunction with financial data, to estimate the potential supply and forecasted demand in the region. An iterative method was used to calculate all relevant power plant variables to some optimal efficiency.&lt;/p>
&lt;h2 id="abstract">Abstract&lt;/h2>
&lt;blockquote>
&lt;p>The Government of the Kingdom of Morocco has recently launched a program to increase the penetration of solar power in the national energy market. Thus, an contract has been promised to the independent energy production corporation able to deliver 100 MWe of base-load capacity from a concentrated solar power (CSP) plant to the grid at the lowest price.&lt;/p>
&lt;p>At SolenKRAFT AB, our group of R&amp;amp;D Engineers has been appointed responsible for providing a suitable initial design to meet the required capacity and operating strategy. This report will present a technically viable solution, and clearly state the limitations and problems with the project. No cost estimation will be performed. Of the different types of solar concentrators (presented in Figure 1) we consider those suitable for industrial use at medium to high power at temperatures of above 250°C.&lt;/p>
&lt;p>These devices use reflective surfaces to mirror sunlight, and are differentiable by their variable geometries. There are three types: a) parabolic trough, b) central tower, and c) parabolic-mirror CSP plants.&lt;/p>
&lt;p>We choose the parabolic trough in order to meet our base-load capacity requirement of 100 MWe. In a parabolic trough, sunlight is concentrated towards a receiver carrying a heat transfer fluid. With water as the transfer fluid, steam is directly generated for use in a power plant. However, the stability of a water-based receiver is uncertain, and steam-based heat storage is complex. We thus propose the use of an oil-based fluid for heat transfer. These oils are single-phase fluids with an efficient heat transfer coefficient, and a temperature range fitting our needs, as well as the possibility for direct storage. Synthetic oils are preferred over mineral oils for their lesser flammability. We will employ this working fluid in a thermodynamic Rankine cycle, detailed in Section 4.&lt;/p>
&lt;/blockquote></content></item><item><title>Partial Mechanical Design of a Pantograph</title><link>https://jbuckland.com/blog/math-pantograph/</link><pubDate>Fri, 14 Mar 2014 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/math-pantograph/</guid><description>App http://jbuckland.com/pantograph Github http://github.com/ambuc/pantograph I was inspired by the above video (depicting a set of record players in control of a pantograph) to derive a closed-form solution for the vector position of the pen in terms of the angles of rotation for each of the arms
Derivations Derivation of nodes $A$, $B$, $P$, $Q$, and $M$ For our pantograph assembly, we define the centers of two circles $A$ and $B$, each with a radius $r_a$ and $r_b$, respectively.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">App&lt;/th>
&lt;td> &lt;a href="http://jbuckland.com/pantograph">http://jbuckland.com/pantograph&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;th style="text-align: right;">Github&lt;/th>
&lt;td> &lt;a href="http://github.com/ambuc/pantograph">http://github.com/ambuc/pantograph&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
&lt;iframe src="https://player.vimeo.com/video/31933085" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="vimeo video" webkitallowfullscreen mozallowfullscreen allowfullscreen>&lt;/iframe>
&lt;/div>
&lt;p>I was inspired by the above video (depicting a set of record players in control
of a &lt;a href="http://en.wikipedia.org/wiki/Pantograph">pantograph&lt;/a>) to derive a
closed-form solution for the vector position of the pen in terms of the angles
of rotation for each of the arms&lt;/p>
&lt;figure class="center" >
&lt;img src="http://33.media.tumblr.com/658c3725652040e76bcb695886fb89f2/tumblr_inline_n2ka0yYvp41qzj7s0.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;h2 id="derivations">Derivations&lt;/h2>
&lt;h3 id="derivation-of-nodes-a-b-p-q-and-m">Derivation of nodes $A$, $B$, $P$, $Q$, and $M$&lt;/h3>
&lt;p>For our pantograph assembly, we define the centers of two circles $A$ and $B$,
each with a radius $r_a$ and $r_b$, respectively. The current positions of nodes
$P$ and $Q$ are determined by angles $\theta_a$ and $\theta_b$.&lt;/p>
&lt;p>Taking the position of $A$ as the origin, we can find&lt;/p>
&lt;p>$$ \vec{A} = \Big(0,\ 0\Big) $$&lt;/p>
&lt;p>$$ \vec{B} = \Big(0,\ (r_a + d + r_b)\Big) $$&lt;/p>
&lt;p>$$ \vec{P} = \vec{A} + \Big( r_a\ \angle\ \theta_a \Big) $$&lt;/p>
&lt;p>$$ \vec{Q} = \vec{B} + \Big( r_b\ \angle\ \theta_b \Big) $$&lt;/p>
&lt;p>$$ \vec{M} = \frac{1}{2}\left( \vec{P} + \vec{Q}\right) = \Big( \tfrac{1}{2}(P_x + Q_x), \tfrac{1}{2}(P_y + Q_y) \Big)$$&lt;/p>
&lt;h3 id="derivation-of-node-r">Derivation of node $R$&lt;/h3>
&lt;p>Attached to the points $P$, $Q$ are two rigid bodies of length $\ell_1$, which
meet at variable point $R$. By finding the midpoint $M$ between $P$ and $Q$, we
can find the right triangle $\triangle PMR$. From the definition of slope, we
find $\overline{PQ}$ to be $$m_{\overline{PQ}} = \dfrac{Q_y - P_y}{Q_x - P_x}$$&lt;/p>
&lt;p>Because $\overline{MR} \perp \overline{PQ}$, we can find the slope of the line
$\overline{MR}$ to be&lt;/p>
&lt;p>$$ m_{\overline{MR}} = \left(m_{\overline{PQ}}\right)^{-1} $$&lt;/p>
&lt;p>In addition, we can construct the right triangle $\triangle PMR$ with known
side lengths $a$ and $g$, where&lt;/p>
&lt;p>$$g = \tfrac{1}{2}|\overline{PQ}| = \tfrac{1}{2}{\sqrt{(Q_x - P_x)^2 + (Q_y - P_y)^2}}$$&lt;/p>
&lt;p>Thus, $h = \sqrt{(\ell_1)^2 - g^2}$. This allows us to find&lt;/p>
&lt;p>$$\vec{R} = \vec{M} + \left( h\ \angle\ \tan^{-1}\left(m_{\overline{MR}}\right) \right)$$&lt;/p>
&lt;p>$$\vec{R} = \vec{M} + \left( \sqrt{a^2 - g^2}\ \angle\ \tan^{-1}\left( \dfrac{-1}{m_{\overline{PQ}}} \right) \right) $$&lt;/p>
&lt;h3 id="derivation-of-nodes-s-t">Derivation of nodes $S$, $T$&lt;/h3>
&lt;p>We can easily find $\varphi_a$, $\varphi_b$ from the slopes of lines
$\overline{PR}$ and $\overline{QR}$, respectively.&lt;/p>
&lt;p>$$ \varphi_a = \tan^{-1}\left( \dfrac{R_y - P_y}{R_x - P_x} \right) $$&lt;/p>
&lt;p>$$ \varphi_b = \tan^{-1}\left( \dfrac{R_y - Q_y}{R_x - Q_x} \right )$$&lt;/p>
&lt;p>We can then write nodes $S$ and $T$ as
$$\vec{S} = \vec{R} + \left( \ell_2\ \angle\ \varphi_b\right) $$ $$
\vec{T} = \vec{R} + \left( \ell_2\ \angle\ \varphi_a\right)$$&lt;/p>
&lt;h3 id="derivation-of-node-u">Derivation of node $U$&lt;/h3>
&lt;p>It is trivial to present a construction of triangle $\triangle TUS$ that
directly mirrors that of triangle $\triangle QRP$. We find&lt;/p>
&lt;p>$$\vec{N} = \frac{1}{2}\left(\vec{S} + \vec{T}\right) $$&lt;/p>
&lt;p>$$ d = \sqrt{(\ell_3)^2 - c^2} $$&lt;/p>
&lt;p>$$ m_{\overline{NU}} = \dfrac{-1}{m_{\overline{ST}}} = -\dfrac{T_x - S_x}{T_y - S_y}$$&lt;/p>
&lt;p>Thus, we can write node $U$ as
$$\vec{U} = \vec{N} + \left(d\ \angle\ \tan^{-1}\left( m_{\overline{NU}} \right)\right)$$&lt;/p>
&lt;p>Distressingly, this appears to be a function that cannot be resolved into a
one-to-one mapping from the vector space $(\phi_a, \phi_b)$ onto the $(x,y)$
coordinate system of the pen by any conventional means: that is, for each
$(x,y)$ on the pad, there are two or three sets of $(\phi_a, \phi_b)$ positions
that could achieve that position.&lt;/p>
&lt;p>At a loss with the tools of continuous mathematics, I turned to discrete
simulations: a set of three processing.js simulations illustrating what turned
out to be a very complex space.&lt;/p>
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/processing.js/1.4.8/processing.min.js">&lt;/script>
&lt;h2 id="demos">Demos&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="http://ambuc.github.io/pantograph/alpha.html">Demo $\alpha$&lt;/a>: Mouse-driven geometry demo of pantograph behavior&lt;/li>
&lt;li>&lt;a href="http://ambuc.github.io/pantograph/beta.html">Demo $\beta$&lt;/a>: Mouse-driven geometry demo of point names&lt;/li>
&lt;li>&lt;a href="http://ambuc.github.io/pantograph/delta.html">Demo $\delta$&lt;/a>: Sample space of points available from an array of rotational values&lt;/li>
&lt;/ul></content></item><item><title>Using d3.js to Model the NYC Subway Map Independent of Physical Geometry</title><link>https://jbuckland.com/blog/graphics-subway/</link><pubDate>Thu, 12 Dec 2013 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/graphics-subway/</guid><description>App http://jbuckland.com/subway Github http://github.com/ambuc/subway Here I attempted to render the NYC Subway system as a force-directed graph in d3.js. The purpose of a force-directed graph is to
position the nodes of a graph in two-dimensional or three-dimensional space so that all the edges are of more or less equal length and there are as few crossing edges as possible, by assigning forces among the set of edges and the set of nodes, based on their relative positions, and then using these forces either to simulate the motion of the edges and nodes or to minimize their energy.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">App&lt;/th>
&lt;td> &lt;a href="http://jbuckland.com/subway">http://jbuckland.com/subway&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;th style="text-align: right;">Github&lt;/th>
&lt;td> &lt;a href="http://github.com/ambuc/subway">http://github.com/ambuc/subway&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;figure class="center" >
&lt;img src="images/thumbnail.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>Here I attempted to render the NYC Subway system as a &lt;a href="http://en.wikipedia.org/wiki/Force-directed_graph_drawing">force-directed graph&lt;/a> in &lt;a href="http://d3js.org/">d3.js&lt;/a>. The purpose of a force-directed graph is to&lt;/p>
&lt;blockquote>
&lt;p>position the nodes of a &lt;a href="http://en.wikipedia.org/wiki/Graph_(mathematics)">graph&lt;/a> in two-dimensional or three-dimensional space so that all the edges are of more or less equal length and there are as few crossing edges as possible, by assigning forces among the set of edges and the set of nodes, based on their relative positions, and then using these forces either to simulate the motion of the edges and nodes or to minimize their energy.&lt;br>
~ &lt;a href="http://en.wikipedia.org/wiki/Force-directed_graph_drawing">Wikipedia&lt;/a>&lt;/p>
&lt;/blockquote>
&lt;p>The subway data was entered manually, (doing the work by hand took less time and energy than its automation would have) and is rendered live in your browser. The hard part is finding a satisfying set of parameters for a clean display. Still, it’s fun to play around with.&lt;/p></content></item><item><title>Dimensional Analysis through Brute Force</title><link>https://jbuckland.com/blog/math-moon/</link><pubDate>Wed, 04 Dec 2013 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/math-moon/</guid><description>App http://jbuckland.com/moon Github http://github.com/ambuc/moon WA dimensional analysis engine based on the SI Base/Derived Units. Uses input on the seven base units (mass, distance, time, electric current, temperature, luminous intensity, and amount of substance) to display many common derived units of measurement. Basically a unit calculator.
Designed and built by Julian and me.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">App&lt;/th>
&lt;td> &lt;a href="http://jbuckland.com/moon">http://jbuckland.com/moon&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;th style="text-align: right;">Github&lt;/th>
&lt;td> &lt;a href="http://github.com/ambuc/moon">http://github.com/ambuc/moon&lt;/a>&lt;/td>
&lt;/tr>
&lt;/table>
&lt;figure class="center" >
&lt;img src="images/thumbnail.png" style="border-radius: 8px;" />
&lt;/figure>
&lt;p>WA dimensional analysis engine based on the &lt;a href="http://en.wikipedia.org/wiki/SI_base_unit">SI Base/Derived Units&lt;/a>. Uses input on the seven base units (mass, distance, time, electric current, temperature, luminous intensity, and amount of substance) to display many common derived units of measurement. Basically a unit calculator.&lt;/p>
&lt;p>Designed and built by &lt;a href="http://julianrosenblum.com/">Julian&lt;/a> and me.&lt;/p></content></item><item><title>Design and Manufacture of an Dodecahedral Planetarium Made of Laser-Cut Acrylic</title><link>https://jbuckland.com/blog/manufacturing-dodecahedral-planetarium/</link><pubDate>Sat, 12 Jan 2013 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/manufacturing-dodecahedral-planetarium/</guid><description>When I first came to college, I had big plans to cover my room in glow-in-the-dark stars. It occurred to me upon arrival that my roommate might not be as crazy about the idea as I was.
During some research into low-cost cardboard construction methods, I stumbled across this picture of a press-fit geodesic dome, which was made at the MIT media lab, laser-cut from a sheet of cardboard. I realized that a smaller, simpler geometric shape could easily approximate the perfect sphere that a real planetarium could require.</description><content>&lt;p>When I first came to college, I had big plans to cover my room in glow-in-the-dark stars. It occurred to me upon arrival that my roommate might not be as crazy about the idea as I was.&lt;/p>
&lt;p>During some research into low-cost cardboard construction methods, I stumbled across &lt;a href="http://fab.cba.mit.edu/classes/MIT/863.08/people/nadya/geoconstruction_b.jpg">this picture&lt;/a> of a press-fit &lt;a href="http://fab.cba.mit.edu/classes/MIT/863.08/people/nadya/week2.html">geodesic dome&lt;/a>, which was made at the MIT media lab, laser-cut from a sheet of cardboard. I realized that a smaller, simpler geometric shape could easily approximate the perfect sphere that a real planetarium could require.&lt;/p>
&lt;p>I began by designing a sky map spread across twelve pentagons, to be folded into a dodecahedron shape. Using some simple geometry and &lt;a href="http://mathworld.wolfram.com/Dodecahedron.html">Wolfram MathWorld&lt;/a>, I found the &lt;a href="http://mathworld.wolfram.com/DihedralAngle.html">dihedral angle&lt;/a> necessary for the joining pieces. I got the star patterns off &lt;a href="http://www.fourmilab.ch/yoursky/">John Walker’s Fourmilab&lt;/a>, and spread them by hand across the pentagons. (This should have been done geometrically via Mathematica, alas.)&lt;/p>
&lt;p>I lasercut the patterns from a large piece of opaque blue acrylic, and cut the joiners from clear acrylic scrap. I discovered too late, as a planetarium, my project failed: I got too large of a lightbulb (I got a little excited at the Home Depot) and the focal length was totally wrong; the light shone out of the holes but would not project outwards onto the walls. With this in mind, I repurposed the planetarium into a hanging lamp.&lt;/p>
&lt;p>
&lt;figure class="center" >
&lt;img src="images/1.jpg" style="border-radius: 8px;" />
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/2.jpg" style="border-radius: 8px;" />
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/3.jpg" style="border-radius: 8px;" />
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/4.jpg" style="border-radius: 8px;" />
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/5.jpg" style="border-radius: 8px;" />
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/6.jpg" style="border-radius: 8px;" />
&lt;/figure>
&lt;/p></content></item><item><title>Tolerancing Laser-cut Acrylic</title><link>https://jbuckland.com/blog/manufacturing-tardis/</link><pubDate>Sat, 12 Jan 2013 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/manufacturing-tardis/</guid><description>While working at the Champaign-Urbana Community Fab Lab on a previous project, I discovered a sheet of opalescent acrylic. At first transparent, it became unpredictably multicolored when a light was shone through it. I decided it would make a brilliant lamp.
I designed the TARDIS in Illustrator, modeling its proportions after whatever photo references I could find. The edges are connected by well-toleranced finger joints, and the entire body, including the base, are constructed without any glue or sealant.</description><content>&lt;p>While working at the Champaign-Urbana Community Fab Lab on a previous project, I
discovered a sheet of opalescent acrylic. At first transparent, it became
unpredictably multicolored when a light was shone through it. I decided it would
make a brilliant lamp.&lt;/p>
&lt;p>I designed the TARDIS in Illustrator, modeling its proportions after whatever
photo references I could find. The edges are connected by well-toleranced finger
joints, and the entire body, including the base, are constructed without any
glue or sealant. The base was built using an LED, some resistors, and a single
D-cell battery.&lt;/p>
&lt;p>
&lt;figure class="center" >
&lt;img src="images/1.jpg" style="border-radius: 8px;" />
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/2.jpg" style="border-radius: 8px;" />
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/3.jpg" style="border-radius: 8px;" />
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/4.jpg" style="border-radius: 8px;" />
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/5.jpg" style="border-radius: 8px;" />
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/6.jpg" style="border-radius: 8px;" />
&lt;/figure>
&lt;/p></content></item><item><title>Design and Manufacture of an Orrery Made of Wood</title><link>https://jbuckland.com/blog/manufacturing-orrery/</link><pubDate>Fri, 11 Jan 2013 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/manufacturing-orrery/</guid><description>While working at the Champaign-Urbana Community Fab Lab, I stumbled upon plans for a clock built entirely from wooden gears; the plans had been made in a previous decade, and were designed to be cut carefully by scroll saw. The possessor of the plans mentioned that the lasercutter was the perfect tool to create precision shapes such as gears.
I realized that a long-time fascination of mine, the orrery, would be the perfect project to attempt with this technology: it’s small but very extensible, requires some clever mathematics for gear ratios, and also involves a level of precision that my previous projects often lacked.</description><content>&lt;p>While working at the &lt;a href="cucfablab.org">Champaign-Urbana Community Fab Lab&lt;/a>, I stumbled upon plans for a clock built entirely from wooden gears; the plans had been made in a previous decade, and were designed to be cut carefully by scroll saw. The possessor of the plans mentioned that the lasercutter was the perfect tool to create precision shapes such as gears.&lt;/p>
&lt;p>I realized that a long-time fascination of mine, the &lt;a href="http://en.wikipedia.org/wiki/Orrery">orrery&lt;/a>, would be the perfect project to attempt with this technology: it’s small but very extensible, requires some clever mathematics for gear ratios, and also involves a level of precision that my previous projects often lacked.&lt;/p>
&lt;p>This project is still a work in progress: the following are pictures of my first draft of the tellurion portion, which only depicts the sun, moon, and earth. The current draft has a few flaws: although the frequencies of orbital ratios are accurate (3:40), the sun rotates (it shouldn’t), and the earth doesn’t (it should).&lt;/p>
&lt;p>The gears were made using an online &lt;a href="http://woodgears.ca/gear_cutting/template.html">gear template generator&lt;/a>, and the calculations were inspired by a 1938 paper by Charles E. Balleisen titled &lt;a href="http://articles.adsabs.harvard.edu/cgi-bin/nph-iarticle_query?1938PA.....46..567B&amp;amp;defaultprint=YES&amp;amp;filetype=.pdf">Revised Orrery Calculations&lt;/a>, and aided by the American Scientist article &lt;a href="http://www.americanscientist.org/issues/pub/on-the-teeth-of-wheels/7">On the Teeth of Wheels&lt;/a> as well as an AMS article on &lt;a href="http://www.ams.org/samplings/feature-column/fcarc-stern-brocot">Stern-Brocot trees&lt;/a>.&lt;/p>
&lt;p>
&lt;figure class="center" >
&lt;img src="images/1.jpg" style="border-radius: 8px;" />
&lt;figcaption class="center" style="color: inherit; background-color: inherit;" >The orrery in profile.&lt;/figcaption>
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/2.jpg" style="border-radius: 8px;" />
&lt;figcaption class="center" style="color: inherit; background-color: inherit;" >Another profile.&lt;/figcaption>
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/3.jpg" style="border-radius: 8px;" />
&lt;figcaption class="center" style="color: inherit; background-color: inherit;" >Top-down view.&lt;/figcaption>
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/4.jpg" style="border-radius: 8px;" />
&lt;figcaption class="center" style="color: inherit; background-color: inherit;" >Partially disassembled.&lt;/figcaption>
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/5.jpg" style="border-radius: 8px;" />
&lt;figcaption class="center" style="color: inherit; background-color: inherit;" >Close-up of Earth-Moon linkage.&lt;/figcaption>
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/6.jpg" style="border-radius: 8px;" />
&lt;figcaption class="center" style="color: inherit; background-color: inherit;" >Teeth failing to mesh.&lt;/figcaption>
&lt;/figure>
&lt;/p></content></item><item><title>Circuit Design and Programming of an Arduino Alarm Clock</title><link>https://jbuckland.com/blog/manufacturing-alarm-clock/</link><pubDate>Sat, 03 Nov 2012 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/manufacturing-alarm-clock/</guid><description>Paper Alarm Clock Automation In my first semester as a Freshman at Illinois, I took IEFX Projects, an ENG 198 course focused on letting students work on their own self-assigned projects to completion.
This fully automated alarm clock, which I built with Chris Marry and Steve Hefferman as a final project for ENG 198: IEFX, can turn the rod on a set of venetian blinds to let in the sunlight in the morning.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">Paper&lt;/th>
&lt;td> &lt;a href="IEFXFinalReport.pdf">Alarm Clock Automation&lt;/a> &lt;/td>
&lt;/tr>
&lt;/table>
&lt;p>In my first semester as a Freshman at Illinois, I took IEFX Projects, an ENG 198 course focused on letting students work on their own self-assigned projects to completion.&lt;/p>
&lt;p>This fully automated alarm clock, which I built with Chris Marry and Steve Hefferman as a final project for ENG 198: &lt;a href="http://iefx.engineering.illinois.edu/">IEFX&lt;/a>, can turn the rod on a set of venetian blinds to let in the sunlight in the morning. My responsibilities were to construct the circuit board and program an Arduino backend to mirror the functions of a real alarm clock.&lt;/p>
&lt;p>
&lt;figure class="center" >
&lt;img src="images/1.jpg" alt="Image 1" style="border-radius: 8px;" />
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/2.jpg" alt="Image 2" style="border-radius: 8px;" />
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/3.jpg" alt="Image 3" style="border-radius: 8px;" />
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/4.jpg" alt="Image 4" style="border-radius: 8px;" />
&lt;/figure>
&lt;figure class="center" >
&lt;img src="images/5.jpg" alt="Image 5" style="border-radius: 8px;" />
&lt;/figure>
&lt;/p></content></item><item><title>Simulating Galactic Chemical Evolution in Python</title><link>https://jbuckland.com/blog/essay-galactic-chemical-evolution/</link><pubDate>Mon, 30 Jul 2012 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/essay-galactic-chemical-evolution/</guid><description>Paper On the Role of Viscous Transport in the Oxygen Abundance Gradient over Extended Galactic Disks This is a report Catherine Hong and I compiled during the 2010-2011 academic year under the direction of Colin McNally, as a part of the NASA Science Research Mentoring Program at the American Museum of Natural History in NYC.
Abstract Inspired by recent observations of radial abundance gradients in the extended HI disks of galaxies, we present a numerical model to address the role of viscous transport with regard to the oxygen distribution in extended galactic disks.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">Paper&lt;/th>
&lt;td> &lt;a href="transport.pdf">On the Role of Viscous Transport in the Oxygen Abundance Gradient over Extended Galactic Disks&lt;/a> &lt;/td>
&lt;/tr>
&lt;/table>
&lt;p>This is a report Catherine Hong and I compiled during the 2010-2011 academic year under the direction of &lt;a href="http://www.colinmcnally.ca/index_astro.html">Colin McNally&lt;/a>, as a part of the &lt;a href="https://informal.jpl.nasa.gov/museum/CP4SMP/nasa-science-research-mentoring-program">NASA Science Research Mentoring Program&lt;/a> at the &lt;a href="http://www.amnh.org/learn-teach/grades-9-12/science-research-mentoring-program">American Museum of Natural History&lt;/a> in NYC.&lt;/p>
&lt;h2 id="abstract">Abstract&lt;/h2>
&lt;blockquote>
&lt;p>Inspired by recent observations of radial abundance gradients in the extended HI disks of galaxies, we present a numerical model to address the role of viscous transport with regard to the oxygen distribution in extended galactic disks. The viscocity in our one-dimensional disk approximation is provided by turbulent motions of the interstellar medium. These motions mix oxygen and other metals throughout the gaseous component of the galaxy. Our model runs for 7-10 Gyr, with the galaxy attaining mass through infalling gas and forming stars based on the Schmidt-Kennicutt law. Through supernovae, these stars enrich their neighborhood of the galaxy with oxygen. Our model shows that viscous transport alone cannot account for the observed abundance of oxygen past the boundary of star formation in sample galaxies. These results argue in favor of the existence of another mechanism for the transport of oxygen.&lt;/p>
&lt;/blockquote></content></item><item><title>Comprehensive Design and Thermodynamic Analysis of a Greenhouse</title><link>https://jbuckland.com/blog/essay-thermodynamic-analysis-greenhouse/</link><pubDate>Thu, 01 Sep 2011 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/essay-thermodynamic-analysis-greenhouse/</guid><description>Paper Comprehensive Design and Thermodynamic Analysis of a Greenhouse Context This is a report Michael Clark, Nick Jones, Chase Kauffman, Thomas Mayo-Smith, Cory Nissly, and I compiled in Summer 2011, as part of the Cornell Summer College program at Cornell Univeristy, in Ithaca, NY.
As part of the report, we performed a thorough economic analysis on potential locations, a small amount of construction rendering and CAD, a thermodynamic analysis of energy balance and thermal conductivity, and a model circuit.</description><content>&lt;table>
&lt;tr>
&lt;th style="text-align: right;">Paper&lt;/th>
&lt;td> &lt;a href="greenhouse.pdf">Comprehensive Design and Thermodynamic Analysis of a Greenhouse&lt;/a> &lt;/td>
&lt;/tr>
&lt;/table>
&lt;h2 id="context">Context&lt;/h2>
&lt;p>This is a report Michael Clark, Nick Jones, Chase Kauffman, Thomas Mayo-Smith, Cory Nissly, and I compiled in Summer 2011, as part of the &lt;em>Cornell Summer College&lt;/em> program at Cornell Univeristy, in Ithaca, NY.&lt;/p>
&lt;p>As part of the report, we performed a thorough economic analysis on potential locations, a small amount of construction rendering and CAD, a thermodynamic analysis of energy balance and thermal conductivity, and a model circuit.&lt;/p>
&lt;h2 id="abstract">Abstract&lt;/h2>
&lt;blockquote>
&lt;p>The recent discoveries involving carbon emissions and global warming have led to worldwide concern for more “green” ways of producing goods and energy; our team has been researching and conceptually designing a “green” greenhouse, to produce crops in an energy efficient and environmentally friendly manner. In a modern world, where capitalist democratic policies reign in most civilized countries, it is important to understand global concerns and to adapt business and production methods to what the world needs, collectively. In this, pursuing this “green” greenhouse was a legitimate business endeavor as well as an attempt to meet the world’s desires.&lt;/p>
&lt;p>Since different geographical locations all have their respective crops, prices, and growing seasons, it was important to understand what the people wanted and where that product could be grown cheaply. Our team researched the prices of crops in different places, the prices of land in different countries, the efficiency of each location’s growing seasons, and many other factors of the business end of things. As a result of the geographical information that we reviewed, we decided that the best location for a greenhouse would be the Dominican Republic for its cheap land and climate stability. Our economic research into the construction and energy costs of a greenhouse yielded a very efficient rectangular model, designed to maximize sun time and climate stability on minimal energy usage and pollution. Our crop research showed that the most economically feasible crops were Garlic Chives, Cilantro, Spinach, Poinsetta, and Orchid. Given all of this information and the calculated results that constituted them, the T14 group has constructed a very comprehensive array of graphs and statistics, a physical scale-model of their future greenhouse, a detailed and very accurate computer aided design of the model to portray it for the public in a reasonable fashion.&lt;/p>
&lt;p>The implications of this very well thought out design are that these ideas are completely feasible. It has overarching energy, economic, and environmental implications for the future that are relatively astounding. With the proper research, motivation, funding and willpower this new greenhouse just may be the future of crops and the water to douse the fire of global energy demands.&lt;/p>
&lt;/blockquote></content></item><item><title>Lockpick</title><link>https://jbuckland.com/blog/puzzle-lockpick/</link><pubDate>Thu, 01 Sep 2011 00:00:00 -0400</pubDate><guid>https://jbuckland.com/blog/puzzle-lockpick/</guid><description>This post, and the programming it contains, were some of the first programming I ever did. As a result, they&amp;rsquo;re really quite bad. I include it for posterity, and because it&amp;rsquo;s funny.
The problem In high school I used to use a Wordlock on my locker. It had four dials of ten letters each, and each ring rotated independently. Predictably, one summer I returned to school to find that I had forgotten my combination - one of a thousand possibilities.</description><content>&lt;blockquote>
&lt;p>&lt;em>This post, and the programming it contains, were some of the first programming I ever did. As a result, they&amp;rsquo;re really quite bad. I include it for posterity, and because it&amp;rsquo;s funny.&lt;/em>&lt;/p>
&lt;/blockquote>
&lt;h2 id="the-problem">The problem&lt;/h2>
&lt;p>In high school I used to use a Wordlock on my locker. It had four dials of ten letters each, and each ring rotated independently. Predictably, one summer I returned to school to find that I had forgotten my combination - one of a thousand possibilities.&lt;/p>
&lt;p>But not probabilities. There were a few caveats. Most of the words that could have been formed (&lt;code>AEJX&lt;/code>, &lt;code>WQRX&lt;/code>) were unlikely. I had the advantage of knowing that I would have chosen a real word (&lt;code>MATH&lt;/code>, &lt;code>OXEN&lt;/code>) rather than a jumble of letters. I figured the 1000 possibilities could be narrowed down to just a few (~100) probabilities, and that a little scripting would do the trick.&lt;/p>
&lt;h2 id="lockpick_combogenpy">&lt;code>lockpick_combogen.py&lt;/code>&lt;/h2>
&lt;p>I had been learning just a little bit of Python over the summer and this challenge seemed like the very definition of a real-world application. It was combinatorics, arrays, recursion, and language analysis. I found the Enchant module for Python (although, in retrospect, just a simple check against wordlist.txt would have done the trick).&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="794386215" type="checkbox" />
&lt;label for="794386215">
&lt;span class="collapsable-code__language">python&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-python" >&lt;code>
#!/usr/bin/env python
import numpy
import enchant
d = enchant.Dict(&amp;#34;en_US&amp;#34;)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>After a standard preamble, I wanted to create a structure to approximate the physical structure of the rings on the real lock. My ring function took the alphabet and turned it from a straight string of characters into an array of string literals.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="231564897" type="checkbox" />
&lt;label for="231564897">
&lt;span class="collapsable-code__language">python&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-python" >&lt;code>
alphabet = &amp;#39;abcdefghijklmnopqrstuvwxyz&amp;#39;
def ring(x):
y = []
for i in range(len(x)):
y.append(x[i])
return y
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>The four rings ran &lt;code>[k,t]&lt;/code>, &lt;code>[a-j]&lt;/code>, &lt;code>[u-z]&lt;/code>, and &lt;code>[k-t]&lt;/code> again, respectively. I assigned four ring arrays using the ring function.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="649578123" type="checkbox" />
&lt;label for="649578123">
&lt;span class="collapsable-code__language">python&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-python" >&lt;code>
r1 = ring(alphabet)[10:20]
r2 = ring(alphabet)[0:10]
r3 = ring(alphabet)[20:30]
r4 = ring(alphabet)[10:20]
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Then came the combinatorics. Using every possible combination of the four rings, I wanted to compile the string literals back into a word, check the word against a dictionary, and, if it was a real word, print it. Despite there being a thousand combinations, there were rarely more than about 50 words.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="581679324" type="checkbox" />
&lt;label for="581679324">
&lt;span class="collapsable-code__language">python&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-python" >&lt;code>
for i in range(len(r1)):
for j in range(len(r2)):
for k in range(len(r3)):
for l in range(len(r4)):
x = (r1[i])&amp;#43;(r2[j])&amp;#43;(r3[k])&amp;#43;(r4[l])
if d.check(x) == True:
print x
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Using this method, I found my combination (&lt;code>NEXT&lt;/code>) almost immediately from the list of twenty or so words, but the thought occurred to me: what if there had been fifty words to sift through? A hundred?&lt;/p>
&lt;h2 id="lockpick_freqgenpy">&lt;code>lockpick_freqgen.py&lt;/code>&lt;/h2>
&lt;p>The next thing I did was to write a frequency generator. I compiled a few large, well-known texts off Project Gutenberg (the Declaration of Independence, Sherlock Holmes, Leaves of Grass, Huckleberry Finn, and Ulysses - that seemed to cover all my bases) and wrote a script to strip the text of punctuation, divide it by spaces into words, and create a table with each word and the number of times it appears in a good sampling of the english language.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="598624173" type="checkbox" />
&lt;label for="598624173">
&lt;span class="collapsable-code__language">python&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-python" >&lt;code>
# !/usr/bin/env python
import re, string, operator, pickle
regex = re.compile(&amp;#39;[&amp;amp;#37;s]&amp;#39; &amp;amp;#37; re.escape(string.punctuation)) #regex rule
a = {} #frequencies dictionary
b = {} #output dictionary
temp = []
def strip(x): #strips a string of punctuation
for i in x:
temp.append(regex.sub(&amp;#39;&amp;#39;,i).lower())
return temp
# stages: plaintext to string to words
f = strip(open(&amp;#39;/home/james/code/declaration.txt&amp;#39;, &amp;#39;r&amp;#39;) .read().split(None))
for i in f:
a[i] = a.get(i,0) &amp;#43; 1
klist = sorted(a, key=a.get)
# write python dict to a file
output = open(&amp;#39;frequencylist.pkl&amp;#39;, &amp;#39;wb&amp;#39;)
pickle.dump(a, output)
output.close()
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>The script took nearly no time at all to run, and it gave me a pickl’d version of the dictionary class I could import later into another program without having to run lockpick_freqgen.py all over again. The next step was to take my list of candidates and reorder them by frequency.&lt;/p>
&lt;h2 id="lockpick_sortpy">&lt;code>lockpick_sort.py&lt;/code>&lt;/h2>
&lt;p>Once the dictionary (not the Enchant english-language dictionary, the python-array-classification dictionary) was imported, every word in the candidates was checked against the array and sorted into a new list by frequency; words like &lt;code>this&lt;/code> went to the top, and words like &lt;code>tank&lt;/code> went to the bottom. I assumed (hopefully with some degree of accuracy) that the words most likely to be chosen for a word lock would fall somewhere near the middle-top; not too likely, but not seriously obscure either.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="597143268" type="checkbox" />
&lt;label for="597143268">
&lt;span class="collapsable-code__language">python&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-python" >&lt;code>
#!/usr/bin/env python
import pickle
pkl_file = open(&amp;#39;frequencylist.pkl&amp;#39;, &amp;#39;rb&amp;#39;)
list = pickle.load(pkl_file)
pkl_file.close()
candidates = {}
rejects = []
words = open(&amp;#39;/home/james/code/words.txt&amp;#39;, &amp;#39;r&amp;#39;).read().split(None)
for word in words:
if list.has_key(word) == True:
candidates[word] = list.get(word,0)
else: rejects.append(word)
candidates = sorted(candidates.items(), key=lambda(k,v):(v,k))
print &amp;#34;candidates:&amp;#34;, candidates
print &amp;#34;rejects:&amp;#34; , rejects
&lt;/code>&lt;/pre>
&lt;/div>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>The general workflow was to take the 1000 possibilities, reduce them down to far, far fewer probabilities, and sort them byfrequency in the hopes of finding the correct combination sooner than later. There were a few implicit assumptions, however:&lt;/p>
&lt;ul>
&lt;li>That the combination would have meaning. (WQXR rather than XRQW)&lt;/li>
&lt;li>That the combination would be a real word. (WOOT rather than WQXR)&lt;/li>
&lt;li>That the word would be in the dictionary. (FORK rather than WOOT)&lt;/li>
&lt;li>And then a final hope that the word would be a likely one.&lt;/li>
&lt;/ul>
&lt;p>As it turned out, the script worked nearly perfectly, running in under five seconds total and figuring out my combination nearly immediately. However, there were a few issues:&lt;/p>
&lt;p>The scripts were written very early on during my Python learning curve, and are thus clunky. They do array manipulations that could have more easily been done with mapping or list comprehensions, they juggle strings and string literals, and they&amp;rsquo;re actually pretty slow. Compared to the far preferable grep-based solution, five seconds is abysmal.&lt;/p>
&lt;ul>
&lt;li>
&lt;p>They make assumptions. In a real cryptographic situation, this would be a last-ditch effort, hoping the password was truly &lt;code>password&lt;/code> rather than &lt;code>p4s5w0r_d&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>The scripts were distributed. They did in three scripts what should have been just one. This is because I wasn’t sure of the exact terms and conditions of the problem I was working on, and I wasn’t confident enough to risk damaging the first step in the process to add the second step in the same script. There’s lots of juggling of text files and pickled dictionaries and the like.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>To my further shame, I learned a few weeks later that regular expressions (of which I heard much but understood little) were designed to handle exactly these sorts of problems. With 20/20 hindsight, opening up a terminal and entering&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="415896273" type="checkbox" />
&lt;label for="415896273">
&lt;span class="collapsable-code__language">shell&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-shell" >&lt;code>
egrep &amp;#34;^[k-t][a-j][u-z][k-t]$&amp;#34; /usr/share/dict/words
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>would have done the trick about a million times faster, but that wasn’t the point, was it?&lt;/p></content></item></channel></rss>