<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Alan Grow&#39;s Blog</title>
  <link href="https://alangrow.com/blog"/>
  <link type="application/atom+xml" rel="self" href="https://alangrow.com/blog/atom.xml"/>
  <id>https://alangrow.com/blog/</id>
  <author>
    <name>Alan</name>
    <email>alangrow+blog@gmail.com</email>
  </author>

  
    
  <entry>
    <id>https://alangrow.com/blog/rose-tinted-classes</id>
    <link type="text/html" rel="alternate" href="https://alangrow.com/blog/rose-tinted-classes?from=atom"/>
    <title>Rose Tinted Classes: An SVG Filter Function CSS Trick</title>
    <updated>2025-08-12T00:00:00</updated>
    <author>
      <name>Alan</name>
      <email>alangrow+blog@gmail.com</email>
    </author>
    <content type="html">&lt;p&gt;In this post, we&#39;ll use CSS and SVG filter functions to tint an element&#39;s content Rose Quartz, &lt;a href=&#34;https://www.pantone.com/articles/color-of-the-year/color-of-the-year-2016&#34;&gt;Pantone&#39;s 2016 Color of the Year&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The exact hex triplet for Rose Quartz is probably a trade secret, so I&#39;ll be using &lt;span class=&#34;swatch&#34; style=&#34;--color: #F7CAC9&#34; data-color=&#34;#F7CAC9&#34;&gt;&lt;/span&gt; and praying I don&#39;t get sued by Big Color.&lt;/p&gt;
&lt;p&gt;Here&#39;s the content we&#39;re trying to rose tint:&lt;/p&gt;
&lt;div class=&#34;illustration&#34;&gt;
&lt;figure&gt;
  &lt;h3&gt;Homesteading in Pie Town&lt;/h3&gt;
  &lt;img src=&#34;../images/blog/caudill-homesteaders.jpg&#34; /&gt;
  &lt;figcaption&gt;&lt;i&gt;Faro and Doris Caudill, homesteaders, Pie Town, New Mexico.&lt;/i&gt; Faro and Doris got divorced a couple years after this picture was taken; she ended up homesteading in Alaska. Kodachrome by Russell Lee, 1940.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;

&lt;h3 id=&#34;the-old-solution&#34;&gt;&lt;a class=&#34;toclink&#34; href=&#34;#the-old-solution&#34;&gt;The Old Solution&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Alas, there&#39;s no CSS filter function that colorizes content. For many years, the &lt;a href=&#34;https://stackoverflow.com/a/29958459&#34;&gt;recommended hack&lt;/a&gt; was to apply the &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/CSS/filter-function/sepia&#34;&gt;&lt;code&gt;sepia()&lt;/code&gt;&lt;/a&gt; filter, then &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/CSS/filter-function/hue-rotate&#34;&gt;&lt;code&gt;hue-rotate()&lt;/code&gt;&lt;/a&gt; your way to the target tint color. The &lt;code&gt;sepia()&lt;/code&gt; filter did double duty in this approach. It desaturated the content to grayscale, then applied a common tint color to everything.&lt;/p&gt;
&lt;p&gt;If traveling back to the 1800s to introduce color seems bonkers, you&#39;re not alone. That part was strange, but the hard part was figuring out how to rotate sepia to your target hue, then adjusting brightness and saturation to match the target. People even wrote &lt;a href=&#34;https://codepen.io/sosuke/pen/Pjoqqp&#34;&gt;calculators for this&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Unfortunately, the whole sepia rotation approach is doomed. You can&#39;t precisely reach all target colors this way. For some target colors, things will always look a little bit off.&lt;/p&gt;
&lt;p&gt;There must be a better way!&lt;/p&gt;
&lt;h3 id=&#34;the-new-solution&#34;&gt;&lt;a class=&#34;toclink&#34; href=&#34;#the-new-solution&#34;&gt;The New Solution&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Enter &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/feColorMatrix&#34;&gt;&lt;code&gt;&amp;lt;feColorMatrix&amp;gt;&lt;/code&gt;&lt;/a&gt;. This SVG filter element lets you define an &lt;code&gt;(R,G,B,A)&lt;/code&gt; &lt;code&gt;-&amp;gt;&lt;/code&gt; &lt;code&gt;(R&#39;,G&#39;,B&#39;,A&#39;)&lt;/code&gt; color transform in matrix multiplication terms. Armed with &lt;a href=&#34;https://stackoverflow.com/a/46536304&#34;&gt;this&lt;/a&gt;, you can finally do precise colorization. After desaturating the content with a &lt;code&gt;grayscale()&lt;/code&gt; filter, the new solution uses &lt;code&gt;&amp;lt;feColorMatrix&amp;gt;&lt;/code&gt; to tint it with the target color:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;&amp;lt;svg&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;id=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;quot;tint&amp;quot;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;xmlns=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;quot;http://www.w3.org/2000/svg&amp;quot;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&#34;nt&#34;&gt;&amp;lt;defs&amp;gt;&lt;/span&gt;
    &lt;span class=&#34;nt&#34;&gt;&amp;lt;filter&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;id=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;quot;colorize&amp;quot;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;color-interpolation-filters=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;quot;sRGB&amp;quot;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&#34;nt&#34;&gt;&amp;lt;feColorMatrix&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;type=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;quot;matrix&amp;quot;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;values=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;quot;0.969 0 0 0 0&lt;/span&gt;
&lt;span class=&#34;s&#34;&gt;                                           0 0.792 0 0 0&lt;/span&gt;
&lt;span class=&#34;s&#34;&gt;                                           0 0 0.788 0 0&lt;/span&gt;
&lt;span class=&#34;s&#34;&gt;                                           0 0 0 1 0&amp;quot;&lt;/span&gt; &lt;span class=&#34;nt&#34;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&#34;nt&#34;&gt;&amp;lt;/filter&amp;gt;&lt;/span&gt;
  &lt;span class=&#34;nt&#34;&gt;&amp;lt;/defs&amp;gt;&lt;/span&gt;
&lt;span class=&#34;nt&#34;&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note how our Rose Quartz tint color – &lt;code&gt;#F7CAC9&lt;/code&gt; &lt;code&gt;=&lt;/code&gt; &lt;code&gt;(0.969,0.792,0.788)&lt;/code&gt; – appears along the diagonal. Change that to whatever tint color you&#39;re targeting.&lt;/p&gt;
&lt;p&gt;Using these SVG filters from CSS is easy. Just chain them:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nc&#34;&gt;rose&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
  &lt;span class=&#34;k&#34;&gt;filter&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;grayscale&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;url&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sx&#34;&gt;#colorize&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;span class=&#34;nt&#34;&gt;svg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;#&lt;/span&gt;&lt;span class=&#34;nn&#34;&gt;tint&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
  &lt;span class=&#34;k&#34;&gt;display&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;none&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And here&#39;s the result:&lt;/p&gt;
&lt;div class=&#34;illustration&#34;&gt;
&lt;figure class=&#34;rose&#34;&gt;
  &lt;h3&gt;Homesteading in Pie Town&lt;/h3&gt;
  &lt;img src=&#34;../images/blog/caudill-homesteaders.jpg&#34; /&gt;
  &lt;figcaption&gt;&lt;i&gt;Faro and Doris Caudill, homesteaders, Pie Town, New Mexico.&lt;/i&gt; Faro and Doris got divorced a couple years after this picture was taken; she ended up homesteading in Alaska. Kodachrome by Russell Lee, 1940.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;Notice how only the content is tinted, and the background behind the photograph is unchanged. This approach will work even for image content with transparent and translucent pixels. The final row of the &lt;code&gt;feColorMatrix&lt;/code&gt; passes the alpha channel through unchanged.&lt;/p&gt;
&lt;p&gt;What doesn&#39;t quite work: the resulting brightness falls off more steeply than it should. This is because we&#39;re effectively squaring a &lt;code&gt;[0,1]&lt;/code&gt; brightness value when grayscale pixels are multiplied by the original color. Ideally, like translucency, luminance would also be conserved. If you have a suggestion here, please drop me a line!&lt;/p&gt;
&lt;p&gt;&lt;svg id=&#34;tint&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;
  &lt;defs&gt;
    &lt;filter id=&#34;colorize&#34; color-interpolation-filters=&#34;sRGB&#34;&gt;
      &lt;feColorMatrix type=&#34;matrix&#34; values=&#34;0.969 0 0 0 0
                                           0 0.792 0 0 0
                                           0 0 0.788 0 0
                                           0 0 0 1 0&#34; /&gt;
    &lt;/filter&gt;
  &lt;/defs&gt;
&lt;/svg&gt;&lt;/p&gt;
&lt;style&gt;
.swatch {
 --color: attr(data-color);
  white-space: nowrap;
}
.swatch::before {
  content: &#39;■&#39;;
  color: var(--color);
}
.swatch::after {
  content: attr(data-color);
}
.illustration {
  background-color: rgb(32, 32, 32);
  padding: 0.5em 0;
  margin: 1.5em 0;
}
.illustration h3 {
  margin: 0.5em 0;
}
.illustration img {
  width: 100%;
}
.illustration figcaption {
  color: white;
  margin: 1em 0;
}
.rose {
  filter: grayscale(1) url(#colorize);
}
svg#tint {
  width: 0;
  height: 0;
}
&lt;/style&gt;</content>
  </entry>
    
  
    
  <entry>
    <id>https://alangrow.com/blog/mental-models-and-potemkin-understanding-in-llms</id>
    <link type="text/html" rel="alternate" href="https://alangrow.com/blog/mental-models-and-potemkin-understanding-in-llms?from=atom"/>
    <title>Mental Models and Potemkin Understanding in LLMs</title>
    <updated>2025-06-28T00:00:00</updated>
    <author>
      <name>Alan</name>
      <email>alangrow+blog@gmail.com</email>
    </author>
    <content type="html">&lt;p&gt;When you count &#34;one, two, three...&#34; what&#39;s actually happening in your head? Maybe you see or hear something. Does your best friend use that same mental model? Now what about an LLM?&lt;/p&gt;
&lt;p&gt;(What&#39;s that you say, your best friend is an LLM? Pardon me for assuming!)&lt;/p&gt;
&lt;h3 id=&#34;let-me-count-the-ways-to-count&#34;&gt;Let Me Count the Ways to Count&lt;/h3&gt;
&lt;p&gt;During grad school Feynman went through an &lt;a href=&#34;https://calteches.library.caltech.edu/607/2/Feynman.pdf&#34;&gt;obsessive counting phase&lt;/a&gt;. At first, he was curious whether he could count in his head at a steady rate, or if not, what variables might affect the rate. Disproving a crackpot psych paper was at least part of the motivation here.&lt;/p&gt;
&lt;p&gt;Unfortunately his head counting rate was steady, and Feynman got bored. But his counting obsession lingered. So he moved on to experiments with head counting and multitasking. Could he fold laundry and count? Could he count in his head while also counting out his socks? What about reading and writing, could they be combined with head counting?&lt;/p&gt;
&lt;p&gt;Feynman discovered he could count &amp;amp; read at the same time, but he couldn&#39;t count &amp;amp; talk at the same time. His fellow grad student &lt;a href=&#34;https://en.wikipedia.org/wiki/John_Tukey&#34;&gt;Tukey&lt;/a&gt; was skeptical because for him, it was the opposite. Tukey could count &amp;amp; talk, but couldn&#39;t count &amp;amp; read.&lt;/p&gt;
&lt;p&gt;When they compared notes, it turned out Feynman counted in his head by hearing a voice say the numbers. So the voice interfered with Feynman talking. Tukey, on the other hand, counted in his head by watching a ticker tape of numbers go past. (Boy this seems useful for inventing the &lt;a href=&#34;https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm&#34;&gt;FFT&lt;/a&gt;!) But Tukey&#39;s visualization interfered with his reading.&lt;/p&gt;
&lt;div class=&#34;image&#34;&gt;
&lt;img src=&#34;../images/blog/feynman-tukey-counting-mental-models.jpg&#34; width=&#34;100%&#34;/&gt;
&lt;/div&gt;

&lt;p&gt;Even for a simple thing like counting, these two humans had developed very different mental models. If you surveyed all humans, I&#39;d expect to find a huge variety of mental models in the mix. But they all generate the same output in the end (&#34;one, two, three...&#34;).&lt;/p&gt;
&lt;p&gt;This got me wondering. Do LLMs have a mental model for counting? Does it resemble Feynman&#39;s or Tukey&#39;s, or is it some totally alien third thing?&lt;/p&gt;
&lt;p&gt;If an LLM has a non-alien mental model of counting, is it acquired by training on stories like this one, where Feynman makes his mental model for counting explicit? Or is it extrapolated from all the &#34;one, two, three...&#34; examples we&#39;ve generated in the training data, and winds up as some kind of messy, non-mechanistically-interpretable NN machinery (&#34;alien&#34;)?&lt;/p&gt;
&lt;h3 id=&#34;potemkin-understanding-in-llms&#34;&gt;Potemkin Understanding in LLMs&lt;/h3&gt;
&lt;p&gt;I&#39;m not convinced present-day LLMs even have a &#34;mental model.&#34; But let&#39;s look at a new preprint with something to say on the matter, &lt;a href=&#34;https://arxiv.org/abs/2506.21521&#34;&gt;Potemkin Understanding in LLMs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In this paper, the authors ask an LLM a high-level conceptual question like &#34;define a haiku.&#34; The LLM coughs up the correct 5-7-5 syllables answer. They then ask it some follow-up questions to test its understanding. These follow-up questions deal with concrete examples and fall into three categories:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Classify&lt;/strong&gt;: &#34;Is the following a haiku?&#34;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Generate&lt;/strong&gt;: &#34;Provide an example of a haiku about friendship that uses the word “shield”.&#34;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Edit&lt;/strong&gt;: &#34;What could replace the blank in the following poem to make it a haiku?&#34;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The LLM fails these follow-up questions 40% - 80% of the time. These &lt;a href=&#34;https://en.wikipedia.org/wiki/Potemkin_village&#34;&gt;Potemkin&lt;/a&gt; rates are surprisingly high. They suggest the LLM only appeared to understand the concept of a haiku. The paper calls this phenomenon Potemkin Understanding.&lt;/p&gt;
&lt;div class=&#34;image&#34;&gt;
&lt;img src=&#34;../images/blog/llm-potemkins.png&#34; width=&#34;100%&#34;/&gt;
&lt;/div&gt;

&lt;p&gt;Now, when you ask a human to define a haiku, and they cough up the correct 5-7-5 answer, it&#39;s very likely they&#39;ll get the concrete follow-up questions right. So you can probably skip them. Standardized tests exploit this fact and, for brevity, will ask a single question that can only be correctly answered by a human who fully understands the concept.&lt;/p&gt;
&lt;p&gt;The authors call this a Keystone Question. Their insight is that a question that&#39;s a keystone for humans might not be a keystone for LLMs. LLMs can correctly answer the conceptual question, but fail to apply the concept, showing they never fully understood it in the first place.&lt;/p&gt;
&lt;p&gt;Apparently LLMs are wired very differently from us. So differently that we should probably stop publishing misleading LLM benchmarks on tests full of Human Keystone Questions (&#34;OMG ChatGPT aced the ACT / LSAT / MCAT!&#34;), and starting coming up with LLM Keystone Questions. Or, maybe we should discard this keystone question approach entirely, and instead benchmark on huge synthetic datasets of concrete examples that do, by sheer number of examples worked, demonstrate understanding.&lt;/p&gt;
&lt;p&gt;I like this paper because it bodychecks the AI hype, but still leaves many doors open. Maybe we could lower the Potemkin rate during training and force these unruly pupils of ours to finally understand the concepts, instead of cramming for the test. And if we managed that, maybe we&#39;d get brand new mental models to marvel at. Some might even be worth borrowing for our own thinking.&lt;/p&gt;</content>
  </entry>
    
  
    
  <entry>
    <id>https://alangrow.com/blog/git-filewise-blame</id>
    <link type="text/html" rel="alternate" href="https://alangrow.com/blog/git-filewise-blame?from=atom"/>
    <title>Fast Filewise Git Blame</title>
    <updated>2024-05-18T00:00:00</updated>
    <author>
      <name>Alan</name>
      <email>alangrow+blog@gmail.com</email>
    </author>
    <content type="html">&lt;p&gt;When was each file in a git repository last changed, and who changed it? &lt;a href=&#34;https://gist.github.com/acg/41e147c15e36c6db87db5bd286c03ba3&#34;&gt;Here&#39;s a one liner shell script&lt;/a&gt; that produces a fast filewise git blame report:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&#34;ch&#34;&gt;#!/bin/sh&lt;/span&gt;
&lt;span class=&#34;nv&#34;&gt;TZ&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;UTC git log --name-status --date&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;iso-strict-local --pretty&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;quot;%ad%x09%ae&amp;quot;&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$@&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;quot;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;
perl -F&lt;span class=&#34;s1&#34;&gt;&amp;#39;/\t/&amp;#39;&lt;/span&gt; -lane &lt;span class=&#34;s1&#34;&gt;&amp;#39;&lt;/span&gt;
&lt;span class=&#34;s1&#34;&gt;  if (/^[ACDMRTUXB]/) {&lt;/span&gt;
&lt;span class=&#34;s1&#34;&gt;    $path = @F&amp;gt;2 ? $F[2] : $F[1];&lt;/span&gt;
&lt;span class=&#34;s1&#34;&gt;    print &amp;quot;$date\t$email\t$path&amp;quot; if -e &amp;quot;$path&amp;quot;;&lt;/span&gt;
&lt;span class=&#34;s1&#34;&gt;  } elsif (@F) {&lt;/span&gt;
&lt;span class=&#34;s1&#34;&gt;    ($date, $email) = @F;&lt;/span&gt;
&lt;span class=&#34;s1&#34;&gt;  }&lt;/span&gt;
&lt;span class=&#34;s1&#34;&gt;&amp;#39;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;
sort -k3,3 -k1,1r &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;
uniq -f2
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The report looks roughly like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;~/src/coreutils $ git-filewise-blame src | head
2024-01-01T13:22:42    a@b.com    basename.c
2024-03-19T15:55:18    a@b.com    basenc.c
2023-10-27T15:56:39    x@y.com    blake2/b2sum.c
2021-11-01T05:30:38    x@y.com    blake2/b2sum.h
2021-12-18T17:34:31    x@y.com    blake2/blake2b-ref.c
2021-12-18T17:34:31    x@y.com    blake2/blake2.h
2022-09-15T05:30:31    x@y.com    blake2/blake2-impl.h
2016-10-31T13:29:34    a@b.com    blake2/.gitignore
2024-04-06T22:13:23    x@y.com    cat.c
2024-01-01T13:22:42    a@b.com    chcon.c
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The keyword here is &lt;strong&gt;fast&lt;/strong&gt;. All other approaches I&#39;ve found execute &lt;code&gt;git log&lt;/code&gt; for every file in your checkout. Here&#39;s &lt;a href=&#34;https://stackoverflow.com/a/5183273&#34;&gt;one example&lt;/a&gt; of the slow approach:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;git ls-files &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;read&lt;/span&gt; file&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
  git log -n &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; --pretty&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;quot;Filename: &lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$file&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;, commit: %h, date: %ad&amp;quot;&lt;/span&gt; -- &lt;span class=&#34;s2&#34;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$file&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;quot;&lt;/span&gt;
&lt;span class=&#34;k&#34;&gt;done&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If your repository has many files and deep history, the standard exec-for-every-file approach will be horrifically slow – on the order of minutes or even hours.&lt;/p&gt;
&lt;p&gt;By contrast, the &lt;a href=&#34;https://gist.github.com/acg/41e147c15e36c6db87db5bd286c03ba3&#34;&gt;&lt;code&gt;git-filewise-blame&lt;/code&gt;&lt;/a&gt; approach consumes the output of a single &lt;code&gt;git log&lt;/code&gt; command. On my laptop, it takes 1 minute to filewise blame the entire &lt;code&gt;webkit&lt;/code&gt; git repository, which has 405k files and 275k commits (!).&lt;/p&gt;</content>
  </entry>
    
  
    
  <entry>
    <id>https://alangrow.com/blog/qmail-with-letsencrypt-ssl-cert</id>
    <link type="text/html" rel="alternate" href="https://alangrow.com/blog/qmail-with-letsencrypt-ssl-cert?from=atom"/>
    <title>Qmail with a Let&#39;s Encrypt SSL Cert</title>
    <updated>2024-05-04T00:00:00</updated>
    <author>
      <name>Alan</name>
      <email>alangrow+blog@gmail.com</email>
    </author>
    <content type="html">&lt;p&gt;After a long hiatus, I am once again running a mailserver.&lt;/p&gt;
&lt;h3 id=&#34;are-you-crazy-its-2024&#34;&gt;&lt;a class=&#34;toclink&#34; href=&#34;#are-you-crazy-its-2024&#34;&gt;Are You Crazy? It&#39;s 2024&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A lot of us nerds got lazy and switched to hosted gmail about a decade ago. This consolidation has gradually corrupted some of our email ecosystem norms, and I think it&#39;s important to push back on that.&lt;/p&gt;
&lt;p&gt;But it&#39;s also important to have an email that&#39;s fully under your control, even if you only use it as the recovery email for your primary gmail account. Watching &lt;a href=&#34;https://www.jacobcammack.com/&#34;&gt;a friend&lt;/a&gt; lose access to his primary gmail recently – with no working recovery email, and no recourse – scared me into action.&lt;/p&gt;
&lt;p&gt;Finally, I also wanted to generate a &lt;a href=&#34;/alangrow.asc&#34;&gt;new gpg key&lt;/a&gt; now that &lt;a href=&#34;https://musigma.blog/2021/05/09/gpg-ssh-ed25519.html&#34;&gt;ed25519&lt;/a&gt; is widely supported. A gpg key with a gmail identity feels silly and unserious. My new key&#39;s identity is &lt;code&gt;i@&lt;/code&gt; this domain. That&#39;s cool factor! In the apocalypse, you can send me email at this new address, and sleep well knowing that it&#39;s both encrypted-in-transit and encrypted-at-rest.&lt;/p&gt;
&lt;p&gt;That&#39;s three reasons why. Onward with the writeup.&lt;/p&gt;
&lt;h3 id=&#34;lets-encrypt&#34;&gt;&lt;a class=&#34;toclink&#34; href=&#34;#lets-encrypt&#34;&gt;Let&#39;s Encrypt&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The world of mail evolved over the past decade, and TLS is now a must. Luckily, the world also evolved a free and easy way to get an SSL cert: &lt;a href=&#34;https://letsencrypt.org/&#34;&gt;Let&#39;s Encrypt&lt;/a&gt;. It&#39;s great. This site uses it.&lt;/p&gt;
&lt;p&gt;But that&#39;s https. We&#39;d like to reuse the Let&#39;s Encrypt cert for smtp.&lt;/p&gt;
&lt;h3 id=&#34;patching-qmail&#34;&gt;&lt;a class=&#34;toclink&#34; href=&#34;#patching-qmail&#34;&gt;Patching Qmail&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Out of the box, &lt;code&gt;qmail&lt;/code&gt; doesn&#39;t support SSL for inbound or outbound mail. To get both, you can apply the &lt;a href=&#34;https://inoa.net/qmail-tls/&#34;&gt;&lt;code&gt;qmail-tls&lt;/code&gt; patch&lt;/a&gt; from &lt;a href=&#34;https://notes.sagredo.eu/en/qmail-notes-185/patching-qmail-82.html&#34;&gt;this &lt;code&gt;qmail&lt;/code&gt; patch directory&lt;/a&gt;. You may also want the &lt;code&gt;force-tls&lt;/code&gt; patch.&lt;/p&gt;
&lt;p&gt;I&#39;ve also found the &lt;code&gt;any-to-cname&lt;/code&gt; and &lt;code&gt;remove-cname-check&lt;/code&gt; patches necessary to avoid DNSSEC-related nonsense and deferred / delayed emails. Like I said, things have evolved, and not always in a good way.&lt;/p&gt;
&lt;h3 id=&#34;networking&#34;&gt;&lt;a class=&#34;toclink&#34; href=&#34;#networking&#34;&gt;Networking&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Here&#39;s another revolting development: gmail will not attempt mail delivery to ports 465 of 587. It sure would be nice if it did, because then we could use always-on SSL. That would let us run stock &lt;code&gt;qmail-smtpd&lt;/code&gt; under &lt;a href=&#34;https://www.fehcom.de/ipnet/ucspi-ssl.html&#34;&gt;&lt;code&gt;sslserver&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Alas, you need to run an smtp server with STARTTLS support on port 25. So make sure that most-intimidating-of-ports is open. The silver lining is that you&#39;ll be able to test your setup with &lt;code&gt;telnet&lt;/code&gt;. So will the spammers, but SSL doesn&#39;t seem to slow them down these days anyway.&lt;/p&gt;
&lt;h3 id=&#34;configuring-qmail-for-ssl&#34;&gt;&lt;a class=&#34;toclink&#34; href=&#34;#configuring-qmail-for-ssl&#34;&gt;Configuring Qmail for SSL&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To do one-time generation of DH conversation keys:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;/var/lib/qmail/bin/update_tmprsadh
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Our patched &lt;code&gt;qmail&lt;/code&gt; expects a combined public cert + chain + private key at:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/var/lib/qmail/control/servercert.pem
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since Let&#39;s Encrypt rotates your cert every 60-90 days, we can&#39;t just cat some files into place and forget about it. We need to regenerate &lt;code&gt;qmail-smtpd&lt;/code&gt;&#39;s cert and bounce the service whenever rotation happens.&lt;/p&gt;
&lt;p&gt;Fortunately Let&#39;s Encrypt has renewal hooks. Put the following in a &lt;code&gt;+x&lt;/code&gt; file at the location on line 2:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&#34;ch&#34;&gt;#!/bin/sh&lt;/span&gt;
&lt;span class=&#34;c1&#34;&gt;# /etc/letsencrypt/renewal-hooks/deploy/deploy-qmail-servercert &lt;/span&gt;
&lt;span class=&#34;nb&#34;&gt;set&lt;/span&gt; -e
&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; /etc/qmail
touch servercert.pem.tmp
chmod go-rwx servercert.pem.tmp
chown qmaild servercert.pem.tmp
cat &lt;span class=&#34;s2&#34;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$RENEWED_LINEAGE&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;quot;&lt;/span&gt;/fullchain.pem &lt;span class=&#34;s2&#34;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$RENEWED_LINEAGE&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;quot;&lt;/span&gt;/privkey.pem &amp;gt; servercert.pem.tmp
mv servercert.pem.tmp servercert.pem
&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; qmail-smtpd
svc -h .
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note the careful permissioning of &lt;code&gt;servercert.pem&lt;/code&gt; – it contains private key material.&lt;/p&gt;
&lt;p&gt;To generate the cert the first time, and test that the hook will work for future cert rotations, force a renewal as root:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;certbot renew --force-renewal
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Be careful during testing that you don&#39;t force too many renewals. Let&#39;s Encrypt will throttle you after a few.&lt;/p&gt;
&lt;p&gt;If you have multiple domains under Let&#39;s Encrypt, the hook will run multiple times. I&#39;m not sure how to avoid that. Maybe it could skip out unless the primary domain is renewing.&lt;/p&gt;
&lt;h3 id=&#34;testing-it&#34;&gt;&lt;a class=&#34;toclink&#34; href=&#34;#testing-it&#34;&gt;Testing It&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;First try &lt;code&gt;telnet example.com 25&lt;/code&gt;. After the smtp server&#39;s &lt;code&gt;220&lt;/code&gt;, respond with &lt;code&gt;EHLO&lt;/code&gt;. If TLS is available you should see &lt;code&gt;250-STARTTLS&lt;/code&gt;. Example test session:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ telnet example.com 25
Trying 1.2.3.4...
Connected to example.com.
Escape character is &#39;^]&#39;.
220 example.com ESMTP
EHLO
250-example.com
250-STARTTLS
250-PIPELINING
250 8BITMIME
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you don&#39;t see &lt;code&gt;250-STARTTLS&lt;/code&gt;, try these &lt;a href=&#34;https://inoa.net/qmail-tls/debug.html&#34;&gt;debug tips&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To test that the server&#39;s cert chain is trusted and you can send mail over SSL, use this:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;openssl s_client -starttls smtp -ign_eof -crlf -connect example.com:25
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As an smtp protocol refresher, you can type in mail to deliver by hand, like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MAIL FROM:&amp;lt;you@home.example.com&amp;gt;
250 ok
RCPT TO:&amp;lt;you@example.com&amp;gt;
250 ok
DATA
354 go ahead
Subject: test subject

test body
.
250 ok 1714841964 qp 843479
QUIT
221 example.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once this works, you can try sending mails to your server from gmail. If you&#39;re a gsuite admin, their &lt;a href=&#34;https://admin.google.com/ac/emaillogsearch&#34;&gt;Email Log Search&lt;/a&gt; will tell you about delivery errors, and whether successful deliveries were actually encrypted in transit.&lt;/p&gt;
&lt;p&gt;Outbound mail from your server should also be encrypted thanks to the &lt;code&gt;qmail-tls&lt;/code&gt; patch. You&#39;ll see a big fat warning in gmail if not. For better deliverability, you&#39;ll also want to configure SPF, DKIM, and DMARC, which is way beyond the scope of this humble post.&lt;/p&gt;</content>
  </entry>
    
  
    
  <entry>
    <id>https://alangrow.com/blog/maximally-generative-letter-tiles</id>
    <link type="text/html" rel="alternate" href="https://alangrow.com/blog/maximally-generative-letter-tiles?from=atom"/>
    <title>What Letter-Pair Tileset Forms the Most Words?</title>
    <updated>2024-03-23T00:00:00</updated>
    <author>
      <name>Alan</name>
      <email>alangrow+blog@gmail.com</email>
    </author>
    <content type="html">&lt;style type=&#34;text/css&#34;&gt;
table.tiles {
  margin: 2em 0 1em 1em;
  font-family: monospace;
  font-size: 80%;
}
table.tiles td {
  width: 2em;
  height: 2em;
  text-align: center;
  vertical-align: middle;
}
:root {
  --chosen-bg: 0, 0, 255;
  --chosen-bg-alpha: 0.1;
}
article p code.chosen {
  background-color: rgba(var(--chosen-bg), calc(var(--chosen-bg-alpha) * 0.75));
}
table.tiles td.chosen {
  background-color: rgba(var(--chosen-bg), var(--chosen-bg-alpha));
}
@media (prefers-color-scheme:dark) {
  :root {
    --chosen-bg: 144, 144, 255;
    --chosen-bg-alpha: 0.35;
  }
}
@media screen and (max-width: 899px) {
  table.tiles {
    margin-left: 0;
    font-size: 45%;
  }
}
&lt;/style&gt;

&lt;p&gt;While building &lt;a href=&#34;https://dfeldman.github.io/ambigame/game.html&#34;&gt;a word game&lt;/a&gt;, Daniel Feldman ran into a problem that nerdsniped me instantly: what choice of twenty letter-pair tiles generates the most words?&lt;/p&gt;
&lt;p&gt;A number of approaches were proposed in &lt;a href=&#34;https://twitter.com/d_feldman/status/1761611250776117504&#34;&gt;the ensuing thread&lt;/a&gt;, with some folks even wondering if the problem might be NP-complete. In this post I&#39;ll present a &lt;a href=&#34;https://en.wikipedia.org/wiki/Greedy_algorithm&#34;&gt;greedy algorithm&lt;/a&gt; that&#39;s linear in the dictionary size and quadratic in the squared alphabet size. I believe this finds an optimal solution, but haven&#39;t proven that formally.&lt;/p&gt;
&lt;p&gt;Let&#39;s first define the problem.&lt;/p&gt;
&lt;h3 id=&#34;the-problem-definition&#34;&gt;&lt;a class=&#34;toclink&#34; href=&#34;#the-problem-definition&#34;&gt;The Problem Definition&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There are 26 alphabet letters, and each tile has two letters on it, so that works out to a total of 26 * 26 = 676 possible tiles. We only get to choose a meager 20 of these 676 to form our tileset. Like in Scrabble, you can then rearrange subsets of the tileset to form dictionary words. The problem: find the tileset of size 20 that lets you form the most dictionary words.&lt;/p&gt;
&lt;h3 id=&#34;a-much-smaller-example-tileset&#34;&gt;&lt;a class=&#34;toclink&#34; href=&#34;#a-much-smaller-example-tileset&#34;&gt;A Much Smaller Example Tileset&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Here&#39;s all possible tiles, with a specific size-3 tileset &lt;code class=&#34;chosen&#34;&gt;ed,pi,ti&lt;/code&gt; highlighted:&lt;/p&gt;
&lt;table class=&#34;tiles&#34;&gt;
&lt;tr&gt; &lt;td&gt;aa&lt;/td&gt; &lt;td&gt;ab&lt;/td&gt; &lt;td&gt;ac&lt;/td&gt; &lt;td&gt;ad&lt;/td&gt; &lt;td&gt;ae&lt;/td&gt; &lt;td&gt;af&lt;/td&gt; &lt;td&gt;ag&lt;/td&gt; &lt;td&gt;ah&lt;/td&gt; &lt;td&gt;ai&lt;/td&gt; &lt;td&gt;aj&lt;/td&gt; &lt;td&gt;ak&lt;/td&gt; &lt;td&gt;al&lt;/td&gt; &lt;td&gt;am&lt;/td&gt; &lt;td&gt;an&lt;/td&gt; &lt;td&gt;ao&lt;/td&gt; &lt;td&gt;ap&lt;/td&gt; &lt;td&gt;aq&lt;/td&gt; &lt;td&gt;ar&lt;/td&gt; &lt;td&gt;as&lt;/td&gt; &lt;td&gt;at&lt;/td&gt; &lt;td&gt;au&lt;/td&gt; &lt;td&gt;av&lt;/td&gt; &lt;td&gt;aw&lt;/td&gt; &lt;td&gt;ax&lt;/td&gt; &lt;td&gt;ay&lt;/td&gt; &lt;td&gt;az&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;ba&lt;/td&gt; &lt;td&gt;bb&lt;/td&gt; &lt;td&gt;bc&lt;/td&gt; &lt;td&gt;bd&lt;/td&gt; &lt;td&gt;be&lt;/td&gt; &lt;td&gt;bf&lt;/td&gt; &lt;td&gt;bg&lt;/td&gt; &lt;td&gt;bh&lt;/td&gt; &lt;td&gt;bi&lt;/td&gt; &lt;td&gt;bj&lt;/td&gt; &lt;td&gt;bk&lt;/td&gt; &lt;td&gt;bl&lt;/td&gt; &lt;td&gt;bm&lt;/td&gt; &lt;td&gt;bn&lt;/td&gt; &lt;td&gt;bo&lt;/td&gt; &lt;td&gt;bp&lt;/td&gt; &lt;td&gt;bq&lt;/td&gt; &lt;td&gt;br&lt;/td&gt; &lt;td&gt;bs&lt;/td&gt; &lt;td&gt;bt&lt;/td&gt; &lt;td&gt;bu&lt;/td&gt; &lt;td&gt;bv&lt;/td&gt; &lt;td&gt;bw&lt;/td&gt; &lt;td&gt;bx&lt;/td&gt; &lt;td&gt;by&lt;/td&gt; &lt;td&gt;bz&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;ca&lt;/td&gt; &lt;td&gt;cb&lt;/td&gt; &lt;td&gt;cc&lt;/td&gt; &lt;td&gt;cd&lt;/td&gt; &lt;td&gt;ce&lt;/td&gt; &lt;td&gt;cf&lt;/td&gt; &lt;td&gt;cg&lt;/td&gt; &lt;td&gt;ch&lt;/td&gt; &lt;td&gt;ci&lt;/td&gt; &lt;td&gt;cj&lt;/td&gt; &lt;td&gt;ck&lt;/td&gt; &lt;td&gt;cl&lt;/td&gt; &lt;td&gt;cm&lt;/td&gt; &lt;td&gt;cn&lt;/td&gt; &lt;td&gt;co&lt;/td&gt; &lt;td&gt;cp&lt;/td&gt; &lt;td&gt;cq&lt;/td&gt; &lt;td&gt;cr&lt;/td&gt; &lt;td&gt;cs&lt;/td&gt; &lt;td&gt;ct&lt;/td&gt; &lt;td&gt;cu&lt;/td&gt; &lt;td&gt;cv&lt;/td&gt; &lt;td&gt;cw&lt;/td&gt; &lt;td&gt;cx&lt;/td&gt; &lt;td&gt;cy&lt;/td&gt; &lt;td&gt;cz&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;da&lt;/td&gt; &lt;td&gt;db&lt;/td&gt; &lt;td&gt;dc&lt;/td&gt; &lt;td&gt;dd&lt;/td&gt; &lt;td&gt;de&lt;/td&gt; &lt;td&gt;df&lt;/td&gt; &lt;td&gt;dg&lt;/td&gt; &lt;td&gt;dh&lt;/td&gt; &lt;td&gt;di&lt;/td&gt; &lt;td&gt;dj&lt;/td&gt; &lt;td&gt;dk&lt;/td&gt; &lt;td&gt;dl&lt;/td&gt; &lt;td&gt;dm&lt;/td&gt; &lt;td&gt;dn&lt;/td&gt; &lt;td&gt;do&lt;/td&gt; &lt;td&gt;dp&lt;/td&gt; &lt;td&gt;dq&lt;/td&gt; &lt;td&gt;dr&lt;/td&gt; &lt;td&gt;ds&lt;/td&gt; &lt;td&gt;dt&lt;/td&gt; &lt;td&gt;du&lt;/td&gt; &lt;td&gt;dv&lt;/td&gt; &lt;td&gt;dw&lt;/td&gt; &lt;td&gt;dx&lt;/td&gt; &lt;td&gt;dy&lt;/td&gt; &lt;td&gt;dz&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;ea&lt;/td&gt; &lt;td&gt;eb&lt;/td&gt; &lt;td&gt;ec&lt;/td&gt; &lt;td class=&#34;chosen&#34;&gt;ed&lt;/td&gt; &lt;td&gt;ee&lt;/td&gt; &lt;td&gt;ef&lt;/td&gt; &lt;td&gt;eg&lt;/td&gt; &lt;td&gt;eh&lt;/td&gt; &lt;td&gt;ei&lt;/td&gt; &lt;td&gt;ej&lt;/td&gt; &lt;td&gt;ek&lt;/td&gt; &lt;td&gt;el&lt;/td&gt; &lt;td&gt;em&lt;/td&gt; &lt;td&gt;en&lt;/td&gt; &lt;td&gt;eo&lt;/td&gt; &lt;td&gt;ep&lt;/td&gt; &lt;td&gt;eq&lt;/td&gt; &lt;td&gt;er&lt;/td&gt; &lt;td&gt;es&lt;/td&gt; &lt;td&gt;et&lt;/td&gt; &lt;td&gt;eu&lt;/td&gt; &lt;td&gt;ev&lt;/td&gt; &lt;td&gt;ew&lt;/td&gt; &lt;td&gt;ex&lt;/td&gt; &lt;td&gt;ey&lt;/td&gt; &lt;td&gt;ez&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;fa&lt;/td&gt; &lt;td&gt;fb&lt;/td&gt; &lt;td&gt;fc&lt;/td&gt; &lt;td&gt;fd&lt;/td&gt; &lt;td&gt;fe&lt;/td&gt; &lt;td&gt;ff&lt;/td&gt; &lt;td&gt;fg&lt;/td&gt; &lt;td&gt;fh&lt;/td&gt; &lt;td&gt;fi&lt;/td&gt; &lt;td&gt;fj&lt;/td&gt; &lt;td&gt;fk&lt;/td&gt; &lt;td&gt;fl&lt;/td&gt; &lt;td&gt;fm&lt;/td&gt; &lt;td&gt;fn&lt;/td&gt; &lt;td&gt;fo&lt;/td&gt; &lt;td&gt;fp&lt;/td&gt; &lt;td&gt;fq&lt;/td&gt; &lt;td&gt;fr&lt;/td&gt; &lt;td&gt;fs&lt;/td&gt; &lt;td&gt;ft&lt;/td&gt; &lt;td&gt;fu&lt;/td&gt; &lt;td&gt;fv&lt;/td&gt; &lt;td&gt;fw&lt;/td&gt; &lt;td&gt;fx&lt;/td&gt; &lt;td&gt;fy&lt;/td&gt; &lt;td&gt;fz&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;ga&lt;/td&gt; &lt;td&gt;gb&lt;/td&gt; &lt;td&gt;gc&lt;/td&gt; &lt;td&gt;gd&lt;/td&gt; &lt;td&gt;ge&lt;/td&gt; &lt;td&gt;gf&lt;/td&gt; &lt;td&gt;gg&lt;/td&gt; &lt;td&gt;gh&lt;/td&gt; &lt;td&gt;gi&lt;/td&gt; &lt;td&gt;gj&lt;/td&gt; &lt;td&gt;gk&lt;/td&gt; &lt;td&gt;gl&lt;/td&gt; &lt;td&gt;gm&lt;/td&gt; &lt;td&gt;gn&lt;/td&gt; &lt;td&gt;go&lt;/td&gt; &lt;td&gt;gp&lt;/td&gt; &lt;td&gt;gq&lt;/td&gt; &lt;td&gt;gr&lt;/td&gt; &lt;td&gt;gs&lt;/td&gt; &lt;td&gt;gt&lt;/td&gt; &lt;td&gt;gu&lt;/td&gt; &lt;td&gt;gv&lt;/td&gt; &lt;td&gt;gw&lt;/td&gt; &lt;td&gt;gx&lt;/td&gt; &lt;td&gt;gy&lt;/td&gt; &lt;td&gt;gz&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;ha&lt;/td&gt; &lt;td&gt;hb&lt;/td&gt; &lt;td&gt;hc&lt;/td&gt; &lt;td&gt;hd&lt;/td&gt; &lt;td&gt;he&lt;/td&gt; &lt;td&gt;hf&lt;/td&gt; &lt;td&gt;hg&lt;/td&gt; &lt;td&gt;hh&lt;/td&gt; &lt;td&gt;hi&lt;/td&gt; &lt;td&gt;hj&lt;/td&gt; &lt;td&gt;hk&lt;/td&gt; &lt;td&gt;hl&lt;/td&gt; &lt;td&gt;hm&lt;/td&gt; &lt;td&gt;hn&lt;/td&gt; &lt;td&gt;ho&lt;/td&gt; &lt;td&gt;hp&lt;/td&gt; &lt;td&gt;hq&lt;/td&gt; &lt;td&gt;hr&lt;/td&gt; &lt;td&gt;hs&lt;/td&gt; &lt;td&gt;ht&lt;/td&gt; &lt;td&gt;hu&lt;/td&gt; &lt;td&gt;hv&lt;/td&gt; &lt;td&gt;hw&lt;/td&gt; &lt;td&gt;hx&lt;/td&gt; &lt;td&gt;hy&lt;/td&gt; &lt;td&gt;hz&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;ia&lt;/td&gt; &lt;td&gt;ib&lt;/td&gt; &lt;td&gt;ic&lt;/td&gt; &lt;td&gt;id&lt;/td&gt; &lt;td&gt;ie&lt;/td&gt; &lt;td&gt;if&lt;/td&gt; &lt;td&gt;ig&lt;/td&gt; &lt;td&gt;ih&lt;/td&gt; &lt;td&gt;ii&lt;/td&gt; &lt;td&gt;ij&lt;/td&gt; &lt;td&gt;ik&lt;/td&gt; &lt;td&gt;il&lt;/td&gt; &lt;td&gt;im&lt;/td&gt; &lt;td&gt;in&lt;/td&gt; &lt;td&gt;io&lt;/td&gt; &lt;td&gt;ip&lt;/td&gt; &lt;td&gt;iq&lt;/td&gt; &lt;td&gt;ir&lt;/td&gt; &lt;td&gt;is&lt;/td&gt; &lt;td&gt;it&lt;/td&gt; &lt;td&gt;iu&lt;/td&gt; &lt;td&gt;iv&lt;/td&gt; &lt;td&gt;iw&lt;/td&gt; &lt;td&gt;ix&lt;/td&gt; &lt;td&gt;iy&lt;/td&gt; &lt;td&gt;iz&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;ja&lt;/td&gt; &lt;td&gt;jb&lt;/td&gt; &lt;td&gt;jc&lt;/td&gt; &lt;td&gt;jd&lt;/td&gt; &lt;td&gt;je&lt;/td&gt; &lt;td&gt;jf&lt;/td&gt; &lt;td&gt;jg&lt;/td&gt; &lt;td&gt;jh&lt;/td&gt; &lt;td&gt;ji&lt;/td&gt; &lt;td&gt;jj&lt;/td&gt; &lt;td&gt;jk&lt;/td&gt; &lt;td&gt;jl&lt;/td&gt; &lt;td&gt;jm&lt;/td&gt; &lt;td&gt;jn&lt;/td&gt; &lt;td&gt;jo&lt;/td&gt; &lt;td&gt;jp&lt;/td&gt; &lt;td&gt;jq&lt;/td&gt; &lt;td&gt;jr&lt;/td&gt; &lt;td&gt;js&lt;/td&gt; &lt;td&gt;jt&lt;/td&gt; &lt;td&gt;ju&lt;/td&gt; &lt;td&gt;jv&lt;/td&gt; &lt;td&gt;jw&lt;/td&gt; &lt;td&gt;jx&lt;/td&gt; &lt;td&gt;jy&lt;/td&gt; &lt;td&gt;jz&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;ka&lt;/td&gt; &lt;td&gt;kb&lt;/td&gt; &lt;td&gt;kc&lt;/td&gt; &lt;td&gt;kd&lt;/td&gt; &lt;td&gt;ke&lt;/td&gt; &lt;td&gt;kf&lt;/td&gt; &lt;td&gt;kg&lt;/td&gt; &lt;td&gt;kh&lt;/td&gt; &lt;td&gt;ki&lt;/td&gt; &lt;td&gt;kj&lt;/td&gt; &lt;td&gt;kk&lt;/td&gt; &lt;td&gt;kl&lt;/td&gt; &lt;td&gt;km&lt;/td&gt; &lt;td&gt;kn&lt;/td&gt; &lt;td&gt;ko&lt;/td&gt; &lt;td&gt;kp&lt;/td&gt; &lt;td&gt;kq&lt;/td&gt; &lt;td&gt;kr&lt;/td&gt; &lt;td&gt;ks&lt;/td&gt; &lt;td&gt;kt&lt;/td&gt; &lt;td&gt;ku&lt;/td&gt; &lt;td&gt;kv&lt;/td&gt; &lt;td&gt;kw&lt;/td&gt; &lt;td&gt;kx&lt;/td&gt; &lt;td&gt;ky&lt;/td&gt; &lt;td&gt;kz&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;la&lt;/td&gt; &lt;td&gt;lb&lt;/td&gt; &lt;td&gt;lc&lt;/td&gt; &lt;td&gt;ld&lt;/td&gt; &lt;td&gt;le&lt;/td&gt; &lt;td&gt;lf&lt;/td&gt; &lt;td&gt;lg&lt;/td&gt; &lt;td&gt;lh&lt;/td&gt; &lt;td&gt;li&lt;/td&gt; &lt;td&gt;lj&lt;/td&gt; &lt;td&gt;lk&lt;/td&gt; &lt;td&gt;ll&lt;/td&gt; &lt;td&gt;lm&lt;/td&gt; &lt;td&gt;ln&lt;/td&gt; &lt;td&gt;lo&lt;/td&gt; &lt;td&gt;lp&lt;/td&gt; &lt;td&gt;lq&lt;/td&gt; &lt;td&gt;lr&lt;/td&gt; &lt;td&gt;ls&lt;/td&gt; &lt;td&gt;lt&lt;/td&gt; &lt;td&gt;lu&lt;/td&gt; &lt;td&gt;lv&lt;/td&gt; &lt;td&gt;lw&lt;/td&gt; &lt;td&gt;lx&lt;/td&gt; &lt;td&gt;ly&lt;/td&gt; &lt;td&gt;lz&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;ma&lt;/td&gt; &lt;td&gt;mb&lt;/td&gt; &lt;td&gt;mc&lt;/td&gt; &lt;td&gt;md&lt;/td&gt; &lt;td&gt;me&lt;/td&gt; &lt;td&gt;mf&lt;/td&gt; &lt;td&gt;mg&lt;/td&gt; &lt;td&gt;mh&lt;/td&gt; &lt;td&gt;mi&lt;/td&gt; &lt;td&gt;mj&lt;/td&gt; &lt;td&gt;mk&lt;/td&gt; &lt;td&gt;ml&lt;/td&gt; &lt;td&gt;mm&lt;/td&gt; &lt;td&gt;mn&lt;/td&gt; &lt;td&gt;mo&lt;/td&gt; &lt;td&gt;mp&lt;/td&gt; &lt;td&gt;mq&lt;/td&gt; &lt;td&gt;mr&lt;/td&gt; &lt;td&gt;ms&lt;/td&gt; &lt;td&gt;mt&lt;/td&gt; &lt;td&gt;mu&lt;/td&gt; &lt;td&gt;mv&lt;/td&gt; &lt;td&gt;mw&lt;/td&gt; &lt;td&gt;mx&lt;/td&gt; &lt;td&gt;my&lt;/td&gt; &lt;td&gt;mz&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;na&lt;/td&gt; &lt;td&gt;nb&lt;/td&gt; &lt;td&gt;nc&lt;/td&gt; &lt;td&gt;nd&lt;/td&gt; &lt;td&gt;ne&lt;/td&gt; &lt;td&gt;nf&lt;/td&gt; &lt;td&gt;ng&lt;/td&gt; &lt;td&gt;nh&lt;/td&gt; &lt;td&gt;ni&lt;/td&gt; &lt;td&gt;nj&lt;/td&gt; &lt;td&gt;nk&lt;/td&gt; &lt;td&gt;nl&lt;/td&gt; &lt;td&gt;nm&lt;/td&gt; &lt;td&gt;nn&lt;/td&gt; &lt;td&gt;no&lt;/td&gt; &lt;td&gt;np&lt;/td&gt; &lt;td&gt;nq&lt;/td&gt; &lt;td&gt;nr&lt;/td&gt; &lt;td&gt;ns&lt;/td&gt; &lt;td&gt;nt&lt;/td&gt; &lt;td&gt;nu&lt;/td&gt; &lt;td&gt;nv&lt;/td&gt; &lt;td&gt;nw&lt;/td&gt; &lt;td&gt;nx&lt;/td&gt; &lt;td&gt;ny&lt;/td&gt; &lt;td&gt;nz&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;oa&lt;/td&gt; &lt;td&gt;ob&lt;/td&gt; &lt;td&gt;oc&lt;/td&gt; &lt;td&gt;od&lt;/td&gt; &lt;td&gt;oe&lt;/td&gt; &lt;td&gt;of&lt;/td&gt; &lt;td&gt;og&lt;/td&gt; &lt;td&gt;oh&lt;/td&gt; &lt;td&gt;oi&lt;/td&gt; &lt;td&gt;oj&lt;/td&gt; &lt;td&gt;ok&lt;/td&gt; &lt;td&gt;ol&lt;/td&gt; &lt;td&gt;om&lt;/td&gt; &lt;td&gt;on&lt;/td&gt; &lt;td&gt;oo&lt;/td&gt; &lt;td&gt;op&lt;/td&gt; &lt;td&gt;oq&lt;/td&gt; &lt;td&gt;or&lt;/td&gt; &lt;td&gt;os&lt;/td&gt; &lt;td&gt;ot&lt;/td&gt; &lt;td&gt;ou&lt;/td&gt; &lt;td&gt;ov&lt;/td&gt; &lt;td&gt;ow&lt;/td&gt; &lt;td&gt;ox&lt;/td&gt; &lt;td&gt;oy&lt;/td&gt; &lt;td&gt;oz&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;pa&lt;/td&gt; &lt;td&gt;pb&lt;/td&gt; &lt;td&gt;pc&lt;/td&gt; &lt;td&gt;pd&lt;/td&gt; &lt;td&gt;pe&lt;/td&gt; &lt;td&gt;pf&lt;/td&gt; &lt;td&gt;pg&lt;/td&gt; &lt;td&gt;ph&lt;/td&gt; &lt;td class=&#34;chosen&#34;&gt;pi&lt;/td&gt; &lt;td&gt;pj&lt;/td&gt; &lt;td&gt;pk&lt;/td&gt; &lt;td&gt;pl&lt;/td&gt; &lt;td&gt;pm&lt;/td&gt; &lt;td&gt;pn&lt;/td&gt; &lt;td&gt;po&lt;/td&gt; &lt;td&gt;pp&lt;/td&gt; &lt;td&gt;pq&lt;/td&gt; &lt;td&gt;pr&lt;/td&gt; &lt;td&gt;ps&lt;/td&gt; &lt;td&gt;pt&lt;/td&gt; &lt;td&gt;pu&lt;/td&gt; &lt;td&gt;pv&lt;/td&gt; &lt;td&gt;pw&lt;/td&gt; &lt;td&gt;px&lt;/td&gt; &lt;td&gt;py&lt;/td&gt; &lt;td&gt;pz&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;qa&lt;/td&gt; &lt;td&gt;qb&lt;/td&gt; &lt;td&gt;qc&lt;/td&gt; &lt;td&gt;qd&lt;/td&gt; &lt;td&gt;qe&lt;/td&gt; &lt;td&gt;qf&lt;/td&gt; &lt;td&gt;qg&lt;/td&gt; &lt;td&gt;qh&lt;/td&gt; &lt;td&gt;qi&lt;/td&gt; &lt;td&gt;qj&lt;/td&gt; &lt;td&gt;qk&lt;/td&gt; &lt;td&gt;ql&lt;/td&gt; &lt;td&gt;qm&lt;/td&gt; &lt;td&gt;qn&lt;/td&gt; &lt;td&gt;qo&lt;/td&gt; &lt;td&gt;qp&lt;/td&gt; &lt;td&gt;qq&lt;/td&gt; &lt;td&gt;qr&lt;/td&gt; &lt;td&gt;qs&lt;/td&gt; &lt;td&gt;qt&lt;/td&gt; &lt;td&gt;qu&lt;/td&gt; &lt;td&gt;qv&lt;/td&gt; &lt;td&gt;qw&lt;/td&gt; &lt;td&gt;qx&lt;/td&gt; &lt;td&gt;qy&lt;/td&gt; &lt;td&gt;qz&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;ra&lt;/td&gt; &lt;td&gt;rb&lt;/td&gt; &lt;td&gt;rc&lt;/td&gt; &lt;td&gt;rd&lt;/td&gt; &lt;td&gt;re&lt;/td&gt; &lt;td&gt;rf&lt;/td&gt; &lt;td&gt;rg&lt;/td&gt; &lt;td&gt;rh&lt;/td&gt; &lt;td&gt;ri&lt;/td&gt; &lt;td&gt;rj&lt;/td&gt; &lt;td&gt;rk&lt;/td&gt; &lt;td&gt;rl&lt;/td&gt; &lt;td&gt;rm&lt;/td&gt; &lt;td&gt;rn&lt;/td&gt; &lt;td&gt;ro&lt;/td&gt; &lt;td&gt;rp&lt;/td&gt; &lt;td&gt;rq&lt;/td&gt; &lt;td&gt;rr&lt;/td&gt; &lt;td&gt;rs&lt;/td&gt; &lt;td&gt;rt&lt;/td&gt; &lt;td&gt;ru&lt;/td&gt; &lt;td&gt;rv&lt;/td&gt; &lt;td&gt;rw&lt;/td&gt; &lt;td&gt;rx&lt;/td&gt; &lt;td&gt;ry&lt;/td&gt; &lt;td&gt;rz&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;sa&lt;/td&gt; &lt;td&gt;sb&lt;/td&gt; &lt;td&gt;sc&lt;/td&gt; &lt;td&gt;sd&lt;/td&gt; &lt;td&gt;se&lt;/td&gt; &lt;td&gt;sf&lt;/td&gt; &lt;td&gt;sg&lt;/td&gt; &lt;td&gt;sh&lt;/td&gt; &lt;td&gt;si&lt;/td&gt; &lt;td&gt;sj&lt;/td&gt; &lt;td&gt;sk&lt;/td&gt; &lt;td&gt;sl&lt;/td&gt; &lt;td&gt;sm&lt;/td&gt; &lt;td&gt;sn&lt;/td&gt; &lt;td&gt;so&lt;/td&gt; &lt;td&gt;sp&lt;/td&gt; &lt;td&gt;sq&lt;/td&gt; &lt;td&gt;sr&lt;/td&gt; &lt;td&gt;ss&lt;/td&gt; &lt;td&gt;st&lt;/td&gt; &lt;td&gt;su&lt;/td&gt; &lt;td&gt;sv&lt;/td&gt; &lt;td&gt;sw&lt;/td&gt; &lt;td&gt;sx&lt;/td&gt; &lt;td&gt;sy&lt;/td&gt; &lt;td&gt;sz&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;ta&lt;/td&gt; &lt;td&gt;tb&lt;/td&gt; &lt;td&gt;tc&lt;/td&gt; &lt;td&gt;td&lt;/td&gt; &lt;td&gt;te&lt;/td&gt; &lt;td&gt;tf&lt;/td&gt; &lt;td&gt;tg&lt;/td&gt; &lt;td&gt;th&lt;/td&gt; &lt;td class=&#34;chosen&#34;&gt;ti&lt;/td&gt; &lt;td&gt;tj&lt;/td&gt; &lt;td&gt;tk&lt;/td&gt; &lt;td&gt;tl&lt;/td&gt; &lt;td&gt;tm&lt;/td&gt; &lt;td&gt;tn&lt;/td&gt; &lt;td&gt;to&lt;/td&gt; &lt;td&gt;tp&lt;/td&gt; &lt;td&gt;tq&lt;/td&gt; &lt;td&gt;tr&lt;/td&gt; &lt;td&gt;ts&lt;/td&gt; &lt;td&gt;tt&lt;/td&gt; &lt;td&gt;tu&lt;/td&gt; &lt;td&gt;tv&lt;/td&gt; &lt;td&gt;tw&lt;/td&gt; &lt;td&gt;tx&lt;/td&gt; &lt;td&gt;ty&lt;/td&gt; &lt;td&gt;tz&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;ua&lt;/td&gt; &lt;td&gt;ub&lt;/td&gt; &lt;td&gt;uc&lt;/td&gt; &lt;td&gt;ud&lt;/td&gt; &lt;td&gt;ue&lt;/td&gt; &lt;td&gt;uf&lt;/td&gt; &lt;td&gt;ug&lt;/td&gt; &lt;td&gt;uh&lt;/td&gt; &lt;td&gt;ui&lt;/td&gt; &lt;td&gt;uj&lt;/td&gt; &lt;td&gt;uk&lt;/td&gt; &lt;td&gt;ul&lt;/td&gt; &lt;td&gt;um&lt;/td&gt; &lt;td&gt;un&lt;/td&gt; &lt;td&gt;uo&lt;/td&gt; &lt;td&gt;up&lt;/td&gt; &lt;td&gt;uq&lt;/td&gt; &lt;td&gt;ur&lt;/td&gt; &lt;td&gt;us&lt;/td&gt; &lt;td&gt;ut&lt;/td&gt; &lt;td&gt;uu&lt;/td&gt; &lt;td&gt;uv&lt;/td&gt; &lt;td&gt;uw&lt;/td&gt; &lt;td&gt;ux&lt;/td&gt; &lt;td&gt;uy&lt;/td&gt; &lt;td&gt;uz&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;va&lt;/td&gt; &lt;td&gt;vb&lt;/td&gt; &lt;td&gt;vc&lt;/td&gt; &lt;td&gt;vd&lt;/td&gt; &lt;td&gt;ve&lt;/td&gt; &lt;td&gt;vf&lt;/td&gt; &lt;td&gt;vg&lt;/td&gt; &lt;td&gt;vh&lt;/td&gt; &lt;td&gt;vi&lt;/td&gt; &lt;td&gt;vj&lt;/td&gt; &lt;td&gt;vk&lt;/td&gt; &lt;td&gt;vl&lt;/td&gt; &lt;td&gt;vm&lt;/td&gt; &lt;td&gt;vn&lt;/td&gt; &lt;td&gt;vo&lt;/td&gt; &lt;td&gt;vp&lt;/td&gt; &lt;td&gt;vq&lt;/td&gt; &lt;td&gt;vr&lt;/td&gt; &lt;td&gt;vs&lt;/td&gt; &lt;td&gt;vt&lt;/td&gt; &lt;td&gt;vu&lt;/td&gt; &lt;td&gt;vv&lt;/td&gt; &lt;td&gt;vw&lt;/td&gt; &lt;td&gt;vx&lt;/td&gt; &lt;td&gt;vy&lt;/td&gt; &lt;td&gt;vz&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;wa&lt;/td&gt; &lt;td&gt;wb&lt;/td&gt; &lt;td&gt;wc&lt;/td&gt; &lt;td&gt;wd&lt;/td&gt; &lt;td&gt;we&lt;/td&gt; &lt;td&gt;wf&lt;/td&gt; &lt;td&gt;wg&lt;/td&gt; &lt;td&gt;wh&lt;/td&gt; &lt;td&gt;wi&lt;/td&gt; &lt;td&gt;wj&lt;/td&gt; &lt;td&gt;wk&lt;/td&gt; &lt;td&gt;wl&lt;/td&gt; &lt;td&gt;wm&lt;/td&gt; &lt;td&gt;wn&lt;/td&gt; &lt;td&gt;wo&lt;/td&gt; &lt;td&gt;wp&lt;/td&gt; &lt;td&gt;wq&lt;/td&gt; &lt;td&gt;wr&lt;/td&gt; &lt;td&gt;ws&lt;/td&gt; &lt;td&gt;wt&lt;/td&gt; &lt;td&gt;wu&lt;/td&gt; &lt;td&gt;wv&lt;/td&gt; &lt;td&gt;ww&lt;/td&gt; &lt;td&gt;wx&lt;/td&gt; &lt;td&gt;wy&lt;/td&gt; &lt;td&gt;wz&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;xa&lt;/td&gt; &lt;td&gt;xb&lt;/td&gt; &lt;td&gt;xc&lt;/td&gt; &lt;td&gt;xd&lt;/td&gt; &lt;td&gt;xe&lt;/td&gt; &lt;td&gt;xf&lt;/td&gt; &lt;td&gt;xg&lt;/td&gt; &lt;td&gt;xh&lt;/td&gt; &lt;td&gt;xi&lt;/td&gt; &lt;td&gt;xj&lt;/td&gt; &lt;td&gt;xk&lt;/td&gt; &lt;td&gt;xl&lt;/td&gt; &lt;td&gt;xm&lt;/td&gt; &lt;td&gt;xn&lt;/td&gt; &lt;td&gt;xo&lt;/td&gt; &lt;td&gt;xp&lt;/td&gt; &lt;td&gt;xq&lt;/td&gt; &lt;td&gt;xr&lt;/td&gt; &lt;td&gt;xs&lt;/td&gt; &lt;td&gt;xt&lt;/td&gt; &lt;td&gt;xu&lt;/td&gt; &lt;td&gt;xv&lt;/td&gt; &lt;td&gt;xw&lt;/td&gt; &lt;td&gt;xx&lt;/td&gt; &lt;td&gt;xy&lt;/td&gt; &lt;td&gt;xz&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;ya&lt;/td&gt; &lt;td&gt;yb&lt;/td&gt; &lt;td&gt;yc&lt;/td&gt; &lt;td&gt;yd&lt;/td&gt; &lt;td&gt;ye&lt;/td&gt; &lt;td&gt;yf&lt;/td&gt; &lt;td&gt;yg&lt;/td&gt; &lt;td&gt;yh&lt;/td&gt; &lt;td&gt;yi&lt;/td&gt; &lt;td&gt;yj&lt;/td&gt; &lt;td&gt;yk&lt;/td&gt; &lt;td&gt;yl&lt;/td&gt; &lt;td&gt;ym&lt;/td&gt; &lt;td&gt;yn&lt;/td&gt; &lt;td&gt;yo&lt;/td&gt; &lt;td&gt;yp&lt;/td&gt; &lt;td&gt;yq&lt;/td&gt; &lt;td&gt;yr&lt;/td&gt; &lt;td&gt;ys&lt;/td&gt; &lt;td&gt;yt&lt;/td&gt; &lt;td&gt;yu&lt;/td&gt; &lt;td&gt;yv&lt;/td&gt; &lt;td&gt;yw&lt;/td&gt; &lt;td&gt;yx&lt;/td&gt; &lt;td&gt;yy&lt;/td&gt; &lt;td&gt;yz&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;za&lt;/td&gt; &lt;td&gt;zb&lt;/td&gt; &lt;td&gt;zc&lt;/td&gt; &lt;td&gt;zd&lt;/td&gt; &lt;td&gt;ze&lt;/td&gt; &lt;td&gt;zf&lt;/td&gt; &lt;td&gt;zg&lt;/td&gt; &lt;td&gt;zh&lt;/td&gt; &lt;td&gt;zi&lt;/td&gt; &lt;td&gt;zj&lt;/td&gt; &lt;td&gt;zk&lt;/td&gt; &lt;td&gt;zl&lt;/td&gt; &lt;td&gt;zm&lt;/td&gt; &lt;td&gt;zn&lt;/td&gt; &lt;td&gt;zo&lt;/td&gt; &lt;td&gt;zp&lt;/td&gt; &lt;td&gt;zq&lt;/td&gt; &lt;td&gt;zr&lt;/td&gt; &lt;td&gt;zs&lt;/td&gt; &lt;td&gt;zt&lt;/td&gt; &lt;td&gt;zu&lt;/td&gt; &lt;td&gt;zv&lt;/td&gt; &lt;td&gt;zw&lt;/td&gt; &lt;td&gt;zx&lt;/td&gt; &lt;td&gt;zy&lt;/td&gt; &lt;td&gt;zz&lt;/td&gt; &lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;The &lt;code class=&#34;chosen&#34;&gt;ed,pi,ti&lt;/code&gt; tileset generates these 6 words:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&#34;chosen&#34;&gt;pi&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&#34;chosen&#34;&gt;pied&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&#34;chosen&#34;&gt;pitied&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&#34;chosen&#34;&gt;ti&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&#34;chosen&#34;&gt;tied&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&#34;chosen&#34;&gt;tipi&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Note that throughout this post I&#39;ll be using &lt;code&gt;american-english&lt;/code&gt; under &lt;code&gt;/usr/share/dict/&lt;/code&gt; as the dictionary.&lt;/p&gt;
&lt;h3 id=&#34;initial-approach-maximum-letter-pair-frequency&#34;&gt;&lt;a class=&#34;toclink&#34; href=&#34;#initial-approach-maximum-letter-pair-frequency&#34;&gt;Initial Approach: Maximum Letter Pair Frequency&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Before we get into the final greedy approach, let&#39;s try something more straightforward.&lt;/p&gt;
&lt;p&gt;When Alfred Butts designed the Scrabble tileset, he looked at the front page of the New York Times and &lt;a href=&#34;https://en.wikipedia.org/wiki/Alfred_Mosher_Butts#Scrabble&#34;&gt;hand-tabulated letter frequencies&lt;/a&gt;. He then added more copies of frequently occurring letters.&lt;/p&gt;
&lt;p&gt;While our problem is different in a couple ways – unlike Scrabble, duplicate tiles aren&#39;t allowed, and also unlike Scrabble, we can only hope to generate a small fraction of all dictionary words — this approach feels intuitively promising.&lt;/p&gt;
&lt;p&gt;We&#39;ll iterate through the dictionary, split each word into letter-pairs, and count pair occurrences. But note:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Words of odd length get tossed, because they can&#39;t be formed by a sequence of pairs.&lt;/li&gt;
&lt;li&gt;Words that use the same letter pair twice also get tossed, since, per the problem definition, our tileset doesn&#39;t contain repeats.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Among the words that remain, we&#39;ll pick the most frequently occurring pairs as our tileset.&lt;/p&gt;
&lt;p&gt;Here&#39;s the Python &lt;a href=&#34;https://github.com/acg/lettergen/blob/master/maxfreq/lettergen2&#34;&gt;code&lt;/a&gt;:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&#34;kn&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;collections&lt;/span&gt; &lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;defaultdict&lt;/span&gt;
&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;sys&lt;/span&gt;
&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;re&lt;/span&gt;

&lt;span class=&#34;n&#34;&gt;args&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;sys&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;argv&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:]&lt;/span&gt;
&lt;span class=&#34;n&#34;&gt;NTILES&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pop&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;
&lt;span class=&#34;n&#34;&gt;RE_VALID_WORD&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;re&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;compile&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sa&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;^([a-z][a-z]){1,&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;%s&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;}$&amp;#39;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;%&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;NTILES&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;span class=&#34;n&#34;&gt;RE_REPEATED_PAIR&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;re&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;compile&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sa&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class=&#34;s1&#34;&gt;  ^(..)*&lt;/span&gt;
&lt;span class=&#34;s1&#34;&gt;  (?P&amp;lt;letter1&amp;gt;.)(?P&amp;lt;letter2&amp;gt;.)&lt;/span&gt;
&lt;span class=&#34;s1&#34;&gt;  (?P=letter1)(?P=letter2)&lt;/span&gt;
&lt;span class=&#34;s1&#34;&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;re&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;X&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;

&lt;span class=&#34;n&#34;&gt;freqs&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;defaultdict&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;lambda&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;span class=&#34;n&#34;&gt;words&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[]&lt;/span&gt;

&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;word&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;open&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pop&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;args&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;sys&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;stdin&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
  &lt;span class=&#34;n&#34;&gt;word&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;word&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rstrip&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\n&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;

  &lt;span class=&#34;c1&#34;&gt;# discard words with odd length, capitals, apostrophes.&lt;/span&gt;
  &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;not&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;RE_VALID_WORD&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;match&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;word&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;
    &lt;span class=&#34;k&#34;&gt;continue&lt;/span&gt;

  &lt;span class=&#34;c1&#34;&gt;# split the word into letter pairs.&lt;/span&gt;
  &lt;span class=&#34;n&#34;&gt;pairs&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;re&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;findall&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sa&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;.{2}&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;word&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;

  &lt;span class=&#34;c1&#34;&gt;# discard words with repeated pairs.&lt;/span&gt;
  &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;RE_REPEATED_PAIR&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;match&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;join&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;sorted&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pairs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))):&lt;/span&gt;
    &lt;span class=&#34;k&#34;&gt;continue&lt;/span&gt;

  &lt;span class=&#34;c1&#34;&gt;# add to valid word list. update letter pair statistics.&lt;/span&gt;
  &lt;span class=&#34;n&#34;&gt;words&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;append&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;word&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
  &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;pair&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;pairs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
    &lt;span class=&#34;n&#34;&gt;freqs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pair&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;

&lt;span class=&#34;c1&#34;&gt;# our tileset is the top N most frequently occurring pairs.&lt;/span&gt;
&lt;span class=&#34;n&#34;&gt;metric&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;lambda&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;pair&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;freqs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pair&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;span class=&#34;n&#34;&gt;tileset&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;set&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;sorted&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;freqs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;keys&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(),&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;metric&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)[&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;NTILES&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:])&lt;/span&gt;

&lt;span class=&#34;c1&#34;&gt;# report the tileset.&lt;/span&gt;
&lt;span class=&#34;k&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;,&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;join&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;sorted&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;tileset&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)))&lt;/span&gt;

&lt;span class=&#34;c1&#34;&gt;# report all formable dictionary words.&lt;/span&gt;
&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;word&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;words&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
  &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;not&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;set&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;re&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;findall&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sa&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;.{2}&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;word&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;difference&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;tileset&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;
    &lt;span class=&#34;k&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;word&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You&#39;ll notice the program takes the tileset size, &lt;code&gt;NTILES&lt;/code&gt;, as a command line argument. We can use this to run a sanity check on much smaller tilesets. When we do, we immediately see a problem:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;./lettergen2 &lt;span class=&#34;m&#34;&gt;3&lt;/span&gt; /usr/share/dict/american-english
ed,er,es
es
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It&#39;s no surprise that &lt;code class=&#34;chosen&#34;&gt;ed,er,es&lt;/code&gt; occur most frequently, since they&#39;re common English word endings. However, word endings by themselves don&#39;t play nice together. They rely on word beginnings and middles to form complete words. And we already know from the small example above that &lt;code class=&#34;chosen&#34;&gt;ed,pi,ti&lt;/code&gt; generates 6 words. Generating 1 word, &lt;code class=&#34;chosen&#34;&gt;es&lt;/code&gt;, is suboptimal.&lt;/p&gt;
&lt;p&gt;This maximum letter pair frequency approach was a greedy algorithm, and its failure makes one despair of finding any optimal greedy (read: simple) algorithm. To construct the optimal tileset from scratch, perhaps you need to perform search on a graph of successively longer words, so you&#39;re passing from word beginnings to word middles to word endings? &lt;a href=&#34;https://twitter.com/alangrow/status/1761638946939994133&#34;&gt;I pursued this approach myself&lt;/a&gt; before giving up. Ruminate long enough, and your thoughts may even turn to dark subjects like the &lt;a href=&#34;https://en.wikipedia.org/wiki/Set_cover_problem&#34;&gt;set cover problem&lt;/a&gt;, which is NP-Complete.&lt;/p&gt;
&lt;p&gt;That level of despair didn&#39;t sit right with me though. Our problem isn&#39;t the same as the set cover problem, which seeks a set-of-sets that union together to form a bigger set. Let&#39;s accept a set-theoretic framing for a moment to see why.&lt;/p&gt;
&lt;h3 id=&#34;thinking-in-terms-of-sets&#34;&gt;&lt;a class=&#34;toclink&#34; href=&#34;#thinking-in-terms-of-sets&#34;&gt;Thinking in Terms of Sets&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Suppose we&#39;re working with 676 sets, one for each letter pair. Each set contains all the words a particular letter pair occurs in. I&#39;ll denote these word sets W&lt;sub&gt;aa&lt;/sub&gt;, W&lt;sub&gt;ab&lt;/sub&gt;, … W&lt;sub&gt;zz&lt;/sub&gt;.&lt;/p&gt;
&lt;p&gt;The tileset we seek is a subset of these 676 word sets. But it isn&#39;t a set-of-sets we get to union together like in the set cover problem. Consider: just because our tileset includes W&lt;sub&gt;ed&lt;/sub&gt; doesn&#39;t mean we can form the word &lt;code&gt;need&lt;/code&gt; – our tileset must also contain W&lt;sub&gt;ne&lt;/sub&gt; for that. The &#34;and&#34; logic here feels more like set intersection – W&lt;sub&gt;ne&lt;/sub&gt;&amp;nbsp;∩&amp;nbsp;W&lt;sub&gt;ed&lt;/sub&gt; – than set union.&lt;/p&gt;
&lt;p&gt;But set intersection isn&#39;t the right operation either. For example, just because our tileset contains W&lt;sub&gt;ne&lt;/sub&gt; and W&lt;sub&gt;ed&lt;/sub&gt;, and both contain the word &lt;code&gt;needle&lt;/code&gt;, doesn&#39;t mean we can actually form the word &lt;code&gt;needle&lt;/code&gt;. We&#39;d also need W&lt;sub&gt;le&lt;/sub&gt; for that.&lt;/p&gt;
&lt;p&gt;So it looks like constructing the list of formable words isn&#39;t a basic set operation over the tileset.&lt;/p&gt;
&lt;p&gt;However, hidden in the negative space here, there is a basic set operation at work. We&#39;ve seen that if our tileset doesn&#39;t include W&lt;sub&gt;ne&lt;/sub&gt; and doesn&#39;t contain W&lt;sub&gt;ed&lt;/sub&gt;, we have no hope of forming &lt;code&gt;need&lt;/code&gt;, &lt;code&gt;needle&lt;/code&gt;, &lt;code&gt;nerd&lt;/code&gt;, &lt;code&gt;edit&lt;/code&gt; or any of the words in either word set. When we omit W&lt;sub&gt;ne&lt;/sub&gt; and W&lt;sub&gt;ed&lt;/sub&gt; from the tileset, we lose all the words in the union W&lt;sub&gt;ne&lt;/sub&gt;&amp;nbsp;∪&amp;nbsp;W&lt;sub&gt;ed&lt;/sub&gt;.&lt;/p&gt;
&lt;p&gt;Another name for &#34;the set of all word sets omitted from our tileset&#34; is the tileset&#39;s &lt;a href=&#34;https://en.wikipedia.org/wiki/Complement_(set_theory)&#34;&gt;complement&lt;/a&gt;. The unformable words are the union of those omitted word sets. So what we&#39;re really seeking is &lt;em&gt;a tileset whose complement has minimal union size&lt;/em&gt;. Now we&#39;re dealing with basic set operations!&lt;/p&gt;
&lt;p&gt;At this point it occurred to me to try a new greedy approach, but working backwards this time. Instead of constructing the tileset from scratch by greedily picking the next best tile to add, what if we started with the full size 676 tileset, and greedily picked the least-bad tile to remove, until only 20 tiles remained?&lt;/p&gt;
&lt;h3 id=&#34;final-approach-subtractive-minimum-damage&#34;&gt;&lt;a class=&#34;toclink&#34; href=&#34;#final-approach-subtractive-minimum-damage&#34;&gt;Final Approach: Subtractive Minimum Damage&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Without further ado, here&#39;s Python &lt;a href=&#34;https://github.com/acg/lettergen/blob/master/subtractive/lettergen2&#34;&gt;code&lt;/a&gt; for this new approach:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&#34;kn&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;collections&lt;/span&gt; &lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;defaultdict&lt;/span&gt;
&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;sys&lt;/span&gt;
&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;re&lt;/span&gt;

&lt;span class=&#34;n&#34;&gt;args&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;sys&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;argv&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:]&lt;/span&gt;
&lt;span class=&#34;n&#34;&gt;NTILES&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pop&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;
&lt;span class=&#34;n&#34;&gt;RE_VALID_WORD&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;re&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;compile&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sa&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;^([a-z][a-z]){1,&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;%s&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;}$&amp;#39;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;%&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;NTILES&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;span class=&#34;n&#34;&gt;RE_REPEATED_PAIR&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;re&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;compile&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sa&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class=&#34;s1&#34;&gt;  ^(..)*&lt;/span&gt;
&lt;span class=&#34;s1&#34;&gt;  (?P&amp;lt;letter1&amp;gt;.)(?P&amp;lt;letter2&amp;gt;.)&lt;/span&gt;
&lt;span class=&#34;s1&#34;&gt;  (?P=letter1)(?P=letter2)&lt;/span&gt;
&lt;span class=&#34;s1&#34;&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;re&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;X&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;

&lt;span class=&#34;n&#34;&gt;words&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;defaultdict&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;set&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;

&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;word&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;open&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pop&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;args&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;sys&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;stdin&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
  &lt;span class=&#34;n&#34;&gt;word&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;word&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rstrip&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\n&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;

  &lt;span class=&#34;c1&#34;&gt;# discard words with odd length, capitals, apostrophes.&lt;/span&gt;
  &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;not&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;RE_VALID_WORD&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;match&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;word&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;
    &lt;span class=&#34;k&#34;&gt;continue&lt;/span&gt;

  &lt;span class=&#34;c1&#34;&gt;# split the word into letter pairs.&lt;/span&gt;
  &lt;span class=&#34;n&#34;&gt;pairs&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;re&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;findall&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sa&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;.{2}&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;word&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;

  &lt;span class=&#34;c1&#34;&gt;# discard words with repeated pairs.&lt;/span&gt;
  &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;RE_REPEATED_PAIR&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;match&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;join&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;sorted&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pairs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))):&lt;/span&gt;
    &lt;span class=&#34;k&#34;&gt;continue&lt;/span&gt;

  &lt;span class=&#34;c1&#34;&gt;# add the word to the wordsets for its letter pairs.&lt;/span&gt;
  &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;pair&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;pairs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
    &lt;span class=&#34;n&#34;&gt;words&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pair&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;add&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;word&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;

&lt;span class=&#34;c1&#34;&gt;# work backwards from 676 to NTILES.&lt;/span&gt;
&lt;span class=&#34;c1&#34;&gt;# remove the least-damaging letter pair at each step.&lt;/span&gt;
&lt;span class=&#34;n&#34;&gt;damage&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;lambda&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;pair&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;words&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pair&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;])&lt;/span&gt;
&lt;span class=&#34;k&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;words&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;NTILES&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
  &lt;span class=&#34;n&#34;&gt;least_damaging_pair&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;min&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;words&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;keys&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(),&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;damage&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
  &lt;span class=&#34;n&#34;&gt;lost_words&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;words&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pop&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;least_damaging_pair&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
  &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;wordset&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;words&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;values&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;():&lt;/span&gt;
    &lt;span class=&#34;n&#34;&gt;wordset&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;difference_update&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;lost_words&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;

&lt;span class=&#34;c1&#34;&gt;# report the tileset.&lt;/span&gt;
&lt;span class=&#34;k&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;,&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;join&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;sorted&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;words&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;keys&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())))&lt;/span&gt;

&lt;span class=&#34;c1&#34;&gt;# report all formable dictionary words.&lt;/span&gt;
&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;word&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;sorted&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;set&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;union&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;words&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;values&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())):&lt;/span&gt;
  &lt;span class=&#34;k&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;word&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As you can see, the setup is substantially the same. This time, instead of counting appearances of each letter pair, we&#39;re populating the W&lt;sub&gt;aa&lt;/sub&gt; … W&lt;sub&gt;zz&lt;/sub&gt; word sets for the 676 letter pairs. If a word uses a letter pair, it appears in that pair&#39;s word set.&lt;/p&gt;
&lt;p&gt;This gives us a direct and &lt;code&gt;O(1)&lt;/code&gt; way of measuring the damage incurred by removing a letter pair: it is simply the size of its word set.&lt;/p&gt;
&lt;p&gt;After the dictionary has been scanned, all letter pairs are in play, and we can start greedy removal. At each step we pick the letter that inflicts the least amount of damage in terms of formable words. Note that any word longer than 2 letters will appear in multiple word sets, and we have to remember to remove these extra copies of every &#34;lost&#34; word. Python&#39;s &lt;a href=&#34;https://docs.python.org/3.8/library/stdtypes.html#set&#34;&gt;&lt;code&gt;set&lt;/code&gt; data type&lt;/a&gt; is doing a fair bit of work here.&lt;/p&gt;
&lt;h3 id=&#34;results&#34;&gt;&lt;a class=&#34;toclink&#34; href=&#34;#results&#34;&gt;Results&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You can use &lt;a href=&#34;https://github.com/acg/lettergen&#34;&gt;acg/lettergen&lt;/a&gt; to compare the results of the two approaches, which I&#39;m calling &lt;code&gt;maxfreq&lt;/code&gt; and &lt;code&gt;subtractive&lt;/code&gt;. Simply type &lt;code&gt;make&lt;/code&gt; and wait a few seconds:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;make
running maxfreq/results1 ...
running maxfreq/results2 ...
running subtractive/results1 ...
running subtractive/results2 ...

maxfreq/results1: &lt;span class=&#34;m&#34;&gt;12392&lt;/span&gt; words
a,b,c,d,e,f,g,h,i,k,l,m,n,o,p,r,s,t,u,y

maxfreq/results2: &lt;span class=&#34;m&#34;&gt;172&lt;/span&gt; words
al,at,co,de,ed,en,er,es,in,le,​li,ly,ng,on,re,ri,rs,st,te,ti

subtractive/results1: &lt;span class=&#34;m&#34;&gt;12392&lt;/span&gt; words
a,b,c,d,e,f,g,h,i,k,l,m,n,o,p,r,s,t,u,y

subtractive/results2: &lt;span class=&#34;m&#34;&gt;292&lt;/span&gt; words
ar,ca,co,de,di,ed,er,es,in,li,​ng,nt,ra,re,ri,si,st,te,ti,ve
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For completeness, I wrote Python scripts that handle the 1-letter tile case (&lt;code&gt;lettergen1&lt;/code&gt;). There are only 26 tiles to pick the 20 from, and you can see that both approaches arrive at the same result of 12,392 formable words.&lt;/p&gt;
&lt;p&gt;The 2-letter tile case (&lt;code&gt;lettergen2&lt;/code&gt;) is another story. Maximum Letter Pair Frequency comes up with a tileset that generates 172 words, but Subtractive Minimum Damage does substantially better by finding a 292-word-generating tileset – a 1.7x improvement. You&#39;ll find the full lists of formable words at &lt;code&gt;*/results2&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So according to Subtractive Minimum Damage, the optimal tileset is the following:&lt;/p&gt;
&lt;p&gt;&lt;code class=&#34;chosen&#34; style=&#34;white-space: normal; overflow-wrap: break-word; padding: 0&#34;&gt;ar,ca,co,de,di,ed,er,es,in,li,​ng,nt,ra,re,ri,si,st,te,ti,ve&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;It&#39;s also interesting to experiment with different tileset sizes. For instance, try &lt;code&gt;make -B NTILES=100&lt;/code&gt;. You&#39;ll notice as &lt;code&gt;NTILES&lt;/code&gt; gets larger, the &lt;code&gt;maxfreq&lt;/code&gt; approach converges on the &lt;code&gt;subtractive&lt;/code&gt; approach. This makes sense: they should agree for &lt;code&gt;NTILES=676&lt;/code&gt; because there are no letter pair decisions to make. And in fact they should agree even earlier than that, since English &lt;a href=&#34;./how-many-consonant-pairs&#34;&gt;doesn&#39;t use all possible letter pairs&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;open-questions-further-research&#34;&gt;&lt;a class=&#34;toclink&#34; href=&#34;#open-questions-further-research&#34;&gt;Open Questions &amp;amp; Further Research&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Yes, but is Subtractive Minimum Damage optimal?&lt;/strong&gt; The answer is I don&#39;t know! I vaguely remember proving greedy optimality once in undergrad computer science, but that was two decades ago. Pointers welcome.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What if there&#39;s a tie for least damaging letter pair?&lt;/strong&gt; If there&#39;s no path dependence here, you should be able to pick either one at random, and the greedy subtractive approach should still arrive at an optimal solution. To explore this idea, I decided to pick from the top 2 least damaging tiles at random. Then I ran the script thousands of times. To my surprise, it did find a couple tilesets that produced 293 and 294 words – slightly better than the thought-to-be-optimal tileset! A revolting development. But this gap (1-2 tiles) is suspiciously small, and I&#39;m just gonna go to press with what I&#39;ve got.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What happens if the tileset can have repeats?&lt;/strong&gt; I haven&#39;t thought about this too deeply, but it seems like it would spell trouble for a greedy approach, which can no longer make stepwise progress towards &lt;a href=&#34;https://en.wikipedia.org/wiki/Greedy_algorithm#Specifics&#34;&gt;optimal subproblems&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What about words of odd length?&lt;/strong&gt; Yeah it&#39;s awkward we have to exclude those, and it makes even less sense when you look at what motivated this problem (&lt;a href=&#34;https://dfeldman.github.io/ambigame/game.html&#34;&gt;Daniel&#39;s Ambigame&lt;/a&gt;). One approach would be to pad all odd-lettered dictionary words with a trailing period, and then add &lt;code&gt;a.&lt;/code&gt;, &lt;code&gt;b.&lt;/code&gt;, &lt;code&gt;c.&lt;/code&gt;, and so on to the possible letter tiles. A similar trick with leading padding might let you split words after the 1st, 3rd, 5th etc character instead of always splitting at even indexes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Is this a known problem?&lt;/strong&gt; I mean surely Knuth solved this like 50 years ago? I found many related problems, but not this specific one. I worry this means it&#39;s considered too easy / too obvious, and I should feel embarrassed for writing a whole blog post about it. Anyway, please reach out if you know.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Is Scrabble&#39;s tileset optimal?&lt;/strong&gt; I dabble in Scrabble myself, and I&#39;d always heard it wasn&#39;t. In researching this, I learned that Peter Norvig has calculated &lt;a href=&#34;https://norvig.com/scrabble-letter-scores.html&#34;&gt;more accurate English letter frequencies&lt;/a&gt; than Alfred Butt&#39;s. Norvig has a couple proposals for a better Scrabble tileset at the link. TL;DR no.&lt;/p&gt;
&lt;h3 id=&#34;the-formable-word-list&#34;&gt;&lt;a class=&#34;toclink&#34; href=&#34;#the-formable-word-list&#34;&gt;The Formable Word List&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I buried the lede to avoid a wall of text. Here&#39;s the complete list of 292 formable words found by Subtractive Minimum Damage:&lt;/p&gt;
&lt;p&gt;&lt;code class=&#34;chosen&#34;&gt;arcade&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;ardent&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;ares&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;arranged&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;arranger&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;arranges&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;arrant&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;arrest&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;arrested&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;arrive&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;arteries&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;artier&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;artist&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;artistes&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;calico&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;calicoes&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;cant&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;canted&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;canter&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;cantered&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;care&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;career&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;careered&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;caries&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;caring&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;casing&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;cast&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;caster&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;castes&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;castling&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;castrate&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;castrating&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;catering&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;cave&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;code&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;coding&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;coed&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;coin&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;coined&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;congestive&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;contesting&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;contraries&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;contrast&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;contrasted&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;contrite&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;contrive&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;core&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;coring&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;cosier&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;cosies&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;cost&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;costar&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;costarring&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;costed&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;costlier&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;cote&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;coteries&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;cove&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;covering&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;coveting&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;dear&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;dearer&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;decant&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;decanted&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;decanter&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;decoding&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;decorate&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;decorating&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;decorative&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;dedicate&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;dedicating&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;deed&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;deer&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;deli&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;delicate&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;deliveries&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;delivering&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;dent&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;dented&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;dentin&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;deranged&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;deranges&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;deriding&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;derisive&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;derive&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;desire&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;desiring&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;desist&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;desisted&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;destined&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;destines&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;detentes&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;detest&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;detested&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;died&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;dies&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;ding&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;dinged&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;dint&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;dire&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;direst&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;disinter&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;disinterring&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;dive&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;divest&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;divested&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;eddies&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;errant&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;erring&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;es&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;in&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;indeed&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;indelicate&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;indent&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;indented&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;indicate&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;indicating&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;indicative&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;inside&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;insist&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;insisted&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;intent&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;interest&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;interested&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;invent&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;invented&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;invest&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;invested&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;liar&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;lied&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;lies&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;linger&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;lingered&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;lint&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;lira&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;lire&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;list&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;listed&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;lite&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;literati&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;live&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;liveried&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;liveries&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;livest&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;rain&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;rained&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;rang&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;ranged&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;ranger&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;ranges&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;rant&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;ranted&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;ranter&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;rare&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;rarest&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;raring&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;rarities&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;raster&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;rate&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;rating&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;rave&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;raveling&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;re&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;rear&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;reared&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;rearranged&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;rearranges&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;recant&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;recanted&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;recast&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;recoveries&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;recovering&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;redecorate&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;redecorating&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;rededicate&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;rededicating&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;reed&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;rein&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;reindeer&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;reined&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;reinvent&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;reinvented&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;reinvest&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;reinvested&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;relied&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;relies&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;relive&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;rent&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;rented&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;renter&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;reside&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;resident&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;residing&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;resist&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;resisted&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;resister&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;rest&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;restarting&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;rested&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;restrain&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;restrained&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;retiring&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;reveling&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;revenged&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;revenges&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;reveries&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;revering&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;ride&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;riding&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;riling&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;ring&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;ringed&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;ringer&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;ringside&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;rising&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;rite&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;riveting&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;side&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;siding&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;sierra&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;silica&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;silicate&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;sing&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;singed&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;singer&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;singes&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;sire&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;siring&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;sister&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;site&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;siting&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;star&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;stared&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;stares&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;starling&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;starrier&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;starring&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;starting&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;starve&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;sterling&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;stinting&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;strain&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;strained&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;strainer&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;stranger&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;stride&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;strident&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;striding&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;string&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;stringed&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;stringer&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;strive&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;tear&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;teared&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;teed&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;tees&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;tent&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;tented&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;test&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;tested&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;tester&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;testes&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;ti&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;tide&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;tidied&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;tidier&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;tidies&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;tiding&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;tied&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;tier&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;ties&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;tiling&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;ting&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;tinged&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;tinges&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;tint&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;tinted&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;tirade&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;tire&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;tiredest&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;tiring&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;veer&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;veered&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;vein&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;veined&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;vent&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;vented&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;verier&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;verities&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;vest&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;vested&lt;/code&gt;, &lt;code class=&#34;chosen&#34;&gt;vestries&lt;/code&gt;&lt;/p&gt;</content>
  </entry>
    
  
    
  <entry>
    <id>https://alangrow.com/blog/music-to-program-to</id>
    <link type="text/html" rel="alternate" href="https://alangrow.com/blog/music-to-program-to?from=atom"/>
    <title>Music To Program To</title>
    <updated>2021-02-27T20:36:24</updated>
    <author>
      <name>Alan</name>
      <email>alangrow+blog@gmail.com</email>
    </author>
    <content type="html">&lt;style type=&#34;text/css&#34;&gt;
@media only screen and (max-width: 899px) {
  body {
    width: 95%;
  }
}
.post .content h2,
.post .content h3 {
  margin-top: 0;
  margin-bottom: 0;
}
.post .content h2 {
  line-height: 2.5;
}
.post .content h3 {
  line-height: 1.5;
}
.post .content p {
  padding: 1em 0;
}
.post .content p + p {
  padding-top: 0;
}
.post .content h2 + p {
  padding: 1em 0 2em 0;
}
.post ul {
  margin-top: 1em;
  margin-bottom: 1em;
}
.post .content h3 + p {
  padding: 0;
}
.post .content h3 + p a {
  display: block;
  padding-top: 1em;
  width: 100%;
  height: 1px;
}
.post .content h3 + p a:target {
  margin-top: -5em;
  padding-top: 6em;
}
.spotify {
  width: 100%;
  height: 360px;
  margin-bottom: 4em;
}
.spotify.ep {
  height: 240px;
  margin-bottom: 0;
}
&lt;/style&gt;

&lt;p&gt;Programming is deep work. Tuning out distractions is key, and music is one of the most effective tools at your disposal.&lt;/p&gt;
&lt;p&gt;But not all music helps you program. Music with lyrics can interfere with your ability to read and write code. Music with too many surprises can add rather than remove distraction. After some experimentation, many programmers arrive at the same conclusion: repetitive electronic music helps them program.&lt;/p&gt;
&lt;p&gt;After a couple decades of programming, including a decade of remote work with the talented musician-programmers at &lt;a href=&#34;https://blend.io&#34;&gt;blend.io&lt;/a&gt; and &lt;a href=&#34;https://roli.com&#34;&gt;ROLI&lt;/a&gt;, here&#39;s some of the music I turn to when I need to Get Shit Done.&lt;/p&gt;
&lt;p&gt;All music has a Spotify embed and a quick review. Know the mood you&#39;re after? Start with this index of mental states. YMMV. Enjoy! ✌️&lt;/p&gt;
&lt;h2 id=&#34;by-desired-mental-state&#34;&gt;By Desired Mental State&lt;/h2&gt;
&lt;h3 id=&#34;focus-intensity-urgency&#34;&gt;Focus, Intensity, Urgency 🎯&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#deep-dark-minimal&#34;&gt;Deep Dark Minimal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#basic-channel-sampler&#34;&gt;Basic Channel - Sampler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#maurizio-m-series&#34;&gt;Maurizio: M-Series&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#jonas-kopp-desire-ep&#34;&gt;Jonas Kopp: Desire EP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#etapp-kyle-klockworks-10&#34;&gt;Etapp Kyle: Klockworks 10&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#luke-hess-facette&#34;&gt;Luke Hess: Facette&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#the-field-sound-of-light-nordic-light-hotel&#34;&gt;The Field: Sound of Light - Nordic Hotel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#eod-questionmarks&#34;&gt;EOD: Questionmarks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#trickfinger-sampler&#34;&gt;Trickfinger - Sampler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#tin-man-dripping-acid&#34;&gt;Tin Man: Dripping Acid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#anthony-naples-fog-fm&#34;&gt;Anthony Naples: Fog FM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#john-tejada-parabolas&#34;&gt;John Tejada: Parabolas&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;calmness-contemplation-perfection&#34;&gt;Calmness, Contemplation, Perfection 🧘&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#microlith-dance-with-me&#34;&gt;Microlith: Dance With Me&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#martin-schulte-slow-beauty&#34;&gt;Martin Schulte: Slow Beauty&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#steve-reich-music-for-18-musicians&#34;&gt;Steve Reich: Music for 18 Musicians&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#terry-riley-in-c&#34;&gt;Terry Riley: In C&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#substance-session-elements&#34;&gt;Substance: Session Elements&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#eod-utrecht&#34;&gt;EOD: Utrecht&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#automatic-tasty-fieldworks-ep&#34;&gt;Automatic Tasty: Fieldwork EP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#khotin-baikal-acid&#34;&gt;Khotin: Baikal Acid&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;creative-energetic-mischevious&#34;&gt;Creative, Energetic, Mischevious 👿&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#modern-acid&#34;&gt;Modern Acid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#beatwife-cornbrail-acid-2&#34;&gt;Beatwife: Cornbrail Acid 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#dmx-krew-broken-sd140-part-ii&#34;&gt;DMX Krew: Broken SD140 Part II&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#ceephax-sampler&#34;&gt;Ceephax - Sampler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#superski-mondo-moderno&#34;&gt;Superski: Mondo Moderno&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;wistful-reflection&#34;&gt;Wistful, Reflection 🍂&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#mikron-severance&#34;&gt;Mikron: Severance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#boards-of-canada-music-has-the-right-to-children&#34;&gt;Boards of Canada: Music Has The Right To Children&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#seb-wildblood-emoticon&#34;&gt;Seb Wildblood: The One with the Emoticon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#rx-101-dopamine&#34;&gt;RX-101: Dopamine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#cn-the-expedition-beyond&#34;&gt;CN: The Expedition Beyond&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#four-tet-new-energy&#34;&gt;Four Tet: New Energy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;playlists&#34;&gt;Playlists&lt;/h2&gt;
&lt;p&gt;If you&#39;re not sure where to start, pick one of these 2+ hour playlists and dig in. These artists have deeper catalogs you can branch out into.&lt;/p&gt;
&lt;h3 id=&#34;deep-dark-minimal&#34;&gt;Deep Dark Minimal&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;deep-dark-minimal&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Repetitive, trance-inducing electronic music for intense focus and deep work. No vocals or lame chord progressions. Mostly German.&lt;/p&gt;
&lt;div class=&#34;spotify&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/playlist/64It3ioYct9CzrPeuIq3xh&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h3 id=&#34;modern-acid&#34;&gt;Modern Acid&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;modern-acid&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The tasty sounds of the &lt;a href=&#34;https://en.wikipedia.org/wiki/Roland_TB-303&#34;&gt;303&lt;/a&gt; / &lt;a href=&#34;https://en.wikipedia.org/wiki/Roland_TR-808&#34;&gt;808&lt;/a&gt; / 909 used in new ways. All tracks post 2000. Higher energy, faster tempos, and busier arrangements. 🧠&lt;/p&gt;
&lt;div class=&#34;spotify&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/playlist/42Opdl26Go26eWo7oiEhmK&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h2 id=&#34;albums&#34;&gt;Albums&lt;/h2&gt;
&lt;p&gt;Some full-length albums that won&#39;t disappoint. Each is good for about an hour of listening.&lt;/p&gt;
&lt;h3 id=&#34;microlith-dance-with-me-2016&#34;&gt;Microlith: Dance With Me (2016)&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;microlith-dance-with-me&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;An album of sublime electro from Maltese producer Rhys Celeste. Everything Rhys made until his &lt;a href=&#34;https://ra.co/news/38277&#34;&gt;tragic death at age 24&lt;/a&gt; is worth a listen. See also the &lt;a href=&#34;https://open.spotify.com/playlist/4CvNSwKn7f9ngVLlP2Jr0O&#34;&gt;Float House microgenre&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;spotify&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/album/6yzYSkVba6an7sxMfWcFOg&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h3 id=&#34;beatwife-cornbrail-acid-2-2014&#34;&gt;Beatwife: Cornbrail Acid 2 (2014)&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;beatwife-cornbrail-acid-2&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A Scottish acid madman with an artist name you can&#39;t mention in polite company. Fast, frenetic music with a quirky sense of humor. See also the &lt;a href=&#34;https://daily.bandcamp.com/lists/braindance-feature&#34;&gt;Braindance microgenre&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;spotify&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/album/3qku0YYzoZPtU7G5zpY9lL&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h3 id=&#34;tin-man-dripping-acid-2017&#34;&gt;Tin Man: Dripping Acid (2017)&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;tin-man-dripping-acid&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;How much acid is too much acid? This monster neo acid album may provide the answer. Haunting, hypnotic tunes with slow builds.&lt;/p&gt;
&lt;div class=&#34;spotify&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/album/1rVsIxC6nSp6I3VC0QQVyy&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h3 id=&#34;mikron-severance-2019&#34;&gt;Mikron: Severance (2019)&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;mikron-severance&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Peaceful, aquatic, ambient techno landscapes from an Irish duo. Track 4, &#34;Ghost Node&#34;, highsteps out of a thick fog.&lt;/p&gt;
&lt;div class=&#34;spotify&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/album/7d33r7YgvCP120orCDLVJT&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h3 id=&#34;anthony-naples-fog-fm-2019&#34;&gt;Anthony Naples: Fog FM (2019)&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;anthony-naples-fog-fm&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Another case of driving beats shrouded in fog, this time from an NYC-based producer.&lt;/p&gt;
&lt;div class=&#34;spotify&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/album/0XBtXweoCbA6MkNZ5F2NUW&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h3 id=&#34;boards-of-canada-music-has-the-right-to-children-1998&#34;&gt;Boards of Canada: Music Has The Right To Children (1998)&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;boards-of-canada-music-has-the-right-to-children&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;By law I am required to include this album, and I&#39;m happy to comply. A landmark in electronic music from the Scottish duo. This album hasn&#39;t aged a day.&lt;/p&gt;
&lt;div class=&#34;spotify&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/album/1vWnB0hYmluskQuzxwo25a&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h3 id=&#34;maurizio-m-series-1997&#34;&gt;Maurizio: M-Series (1997)&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;maurizio-m-series&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Minimal dub techno from the master of the genre, &lt;a href=&#34;#basic-channel-sampler&#34;&gt;Basic Channel&lt;/a&gt; co-founder Moritz von Oswald. If you&#39;re new to dub techno you may be forgiven for thinking &#34;nothing ever happens.&#34; That&#39;s kind of the point, but it&#39;s also not &lt;em&gt;quite&lt;/em&gt; true: there&#39;s lots of subtle variation if you start looking for it, yet never enough to distract if you aren&#39;t.&lt;/p&gt;
&lt;div class=&#34;spotify&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/album/3dQcknWb1G439u0whEtJCQ&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h3 id=&#34;substance-session-elements-1998&#34;&gt;Substance: Session Elements (1998)&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;substance-session-elements&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Lush but restrained minimal German techno variations.&lt;/p&gt;
&lt;div class=&#34;spotify&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/album/0gZqMjLTDZrSAGIfsDrvvF&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h3 id=&#34;rx-101-dopamine-2019&#34;&gt;RX-101: Dopamine (2019)&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;rx-101-dopamine&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Bask in the warm analog glow cast by these 13 tracks from Dutch producer Erik Jong.&lt;/p&gt;
&lt;div class=&#34;spotify&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/album/32dttKpIJvL2ndTAUSyQui&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h3 id=&#34;john-tejada-parabolas-2011&#34;&gt;John Tejada: Parabolas (2011)&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;john-tejada-parabolas&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Dark, intelligent tech house from an Austrian-Californian producer. Tejada teaches at CalArts and consistently puts out great tech house albums, among them &lt;a href=&#34;https://open.spotify.com/album/5Rr2NMSWTD6F7rsKAbhTXb?si=OeaOL5eaTmqrwshsFqgMjQ&#34;&gt;Signs Under Test (2015)&lt;/a&gt;, &lt;a href=&#34;https://open.spotify.com/album/4tuihdFNCR2nz6WeIiQsLC?si=fZNo8_yER0aGd6MpT87LQg&#34;&gt;Live Rytm Trax (2018)&lt;/a&gt;, and &lt;a href=&#34;https://open.spotify.com/album/0jwjHNhMdj3V6fOvx36ozD?si=IT0PBz_EQQOBoSzNsWHs8A&#34;&gt;Year Of The Living Dead (2021)&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;spotify&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/album/1ysY4ZKWker8yinW7hg5Jx&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h3 id=&#34;superski-mondo-moderno-2023&#34;&gt;Superski: Mondo Moderno (2023)&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;superski-mondo-moderno&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I&#39;ll be honest: trance is not my jam. Yet somehow these trancey, cinematic, Italo-disco-influenced techno tracks from Litrowski &amp;amp; Voiski won me over. Look, you can wear your sunglasses at night. They can be fine Italian sunglasses. You can even be the protagonist in a Fellini film. But if you raise them and wink like Ferris Bueller, we&#39;re not going to take you entirely seriously -- and I think that&#39;s the idea.&lt;/p&gt;
&lt;div class=&#34;spotify&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/album/5NCPvM8NsBT12SwVXmUWdB&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h3 id=&#34;cn-the-expedition-beyond-2011&#34;&gt;CN: The Expedition Beyond (2011)&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;cn-the-expedition-beyond&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The year is 3984, and this is the soundtrack to our mysterious space explorations. CN is one of several projects from the outrageously talented and prolific Norwegian producer &lt;a href=&#34;https://eodtracks.bandcamp.com/&#34;&gt;Stian Gjevik&lt;/a&gt;. There&#39;s &lt;a href=&#34;https://open.spotify.com/album/0YPxzyy8doEk5wh5XT0AkW?si=gUmFatscRnKPufh5M1SfpA&#34;&gt;a second album&lt;/a&gt; that picks up where this one left off.&lt;/p&gt;
&lt;div class=&#34;spotify&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/album/65zlbg1882ABbikYSMkmZ9&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h3 id=&#34;martin-schulte-slow-beauty-2012&#34;&gt;Martin Schulte: Slow Beauty (2012)&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;martin-schulte-slow-beauty&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Ambient that gets its inspiration from nature. While most music is busy painting portraits, these tracks are content to paint landscapes. If you like this stuff, Schulte has a whole series of albums exploring different seasons and places.&lt;/p&gt;
&lt;div class=&#34;spotify&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/album/42jWm6Kiir3tsV5EFGe3M2&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h3 id=&#34;four-tet-new-energy-2017&#34;&gt;Four Tet: New Energy (2017)&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;four-tet-new-energy&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Natural inspiration in this one too, which comes to you from a cabin in upstate New York.&lt;/p&gt;
&lt;div class=&#34;spotify&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/album/74r6JJ97ipO0CREXP9PMqZ&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h3 id=&#34;steve-reich-music-for-18-musicians-1976&#34;&gt;Steve Reich: Music for 18 Musicians (1976)&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;steve-reich-music-for-18-musicians&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A minimalist classical masterpiece from 1976 that anticipated electronic music as we know it: layering, envelopes, precise rhythms, repetitiveness, gradual rather than sudden harmonic changes...it&#39;s all in there. I find it incredible that 18 skilled humans can approximate dense electronic music like this. &#34;18 Musicians&#34; is structurally interesting too, as the interior sections are organized around a cycle of eleven chords articulated in the opening and closing &#34;Pulses&#34; movements.&lt;/p&gt;
&lt;div class=&#34;spotify&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/album/1w9O7mS9WEp5xlZUpYbDt9&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h3 id=&#34;terry-riley-in-c-1964&#34;&gt;Terry Riley: In C (1964)&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;terry-riley-in-c&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The granddaddy of all minimalist classical masterpieces. For about an hour we never leave the key of C. Unlike Spinal Tap, Riley pulls it off. A fascinating and elevating listen.&lt;/p&gt;
&lt;div class=&#34;spotify&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/album/3dz5dt5vdJEKhTvTI0ZR9J&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h2 id=&#34;eps&#34;&gt;EPs&lt;/h2&gt;
&lt;p&gt;These are half-albums that nonetheless stand out as excellent music to work to. They vary in length, but are ½ an hour on average.&lt;/p&gt;
&lt;h3 id=&#34;eod-utrecht-2010&#34;&gt;EOD: Utrecht (2010)&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;eod-utrecht&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Lush synth landscapes collide with hard-edge acid techno, leaving you stranded in the best of both worlds. &lt;a href=&#34;https://eodtracks.bandcamp.com/&#34;&gt;EOD&lt;/a&gt; is Norwegian producer Stian Gjevik&#39;s main shingle. His melodic gift and arranging skills are on full display here.&lt;/p&gt;
&lt;div class=&#34;spotify ep&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/album/7IPZCf0sK3jXZoXSRoKIeJ&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h3 id=&#34;eod-questionmarks-2012&#34;&gt;EOD: Questionmarks (2012)&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;eod-questionmarks&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;On this EP Gjevik strips away the lushness and lets the hard-edge techno rip. Sweet, intricately arranged melodies take a back seat to an urgency and raw speed that&#39;s borderline frightening. Fear not: Gjevik is a professional driver on a closed course.&lt;/p&gt;
&lt;div class=&#34;spotify ep&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/album/0uVF1xoHTzpsWeGIq00ILF&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h3 id=&#34;automatic-tasty-fieldwork-ep-2012&#34;&gt;Automatic Tasty: Fieldwork EP (2012)&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;automatic-tasty-fieldworks-ep&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Morning, afternoon, evening, night: you must admit this is a nice four-part cyclic structure for an EP. Although the instrumentation uses the innocent but dated sounds of early techno, Dillon also weaves in real field recordings from different times of day. The result is charming and feel-good.&lt;/p&gt;
&lt;div class=&#34;spotify ep&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/album/0zgcTeyhSIBGCiJI17mEgp&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h3 id=&#34;the-field-sound-of-light-nordic-light-hotel-2007&#34;&gt;The Field: Sound of Light - Nordic Light Hotel (2007)&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;the-field-sound-of-light-nordic-light-hotel&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Another four-part day cycle EP from Sweden. True to form, these tracks are driving, repetitive, and awash in sound -- the kind of thing that makes you hitch up the sled dogs and log a couple hundred miles of frozen tundra.&lt;/p&gt;
&lt;div class=&#34;spotify ep&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/album/2asOJ9R5vdPsqKX3hvNCEE&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h3 id=&#34;seb-wildblood-the-one-with-the-emoticon-2017&#34;&gt;Seb Wildblood: The One with the Emoticon (2017)&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;seb-wildblood-emoticon&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Before emoji conquered the world, we typed things like &lt;code&gt;:~^&lt;/code&gt;, which is the actual name of this album, and possibly a self-portrait? Lush, organic deep house from the UK.&lt;/p&gt;
&lt;div class=&#34;spotify ep&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/album/0xNHXAqiVHPSSRO9ZxiewT&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h3 id=&#34;dmx-krew-broken-sd140-part-ii-2013&#34;&gt;DMX Krew: Broken SD140 Part II (2013)&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;dmx-krew-broken-sd140-part-ii&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;What is an SD140, and are we sure it&#39;s safe to use a broken one? Harsh electro rhythm sounds topped with sweet melodies. &#34;Apple Grid&#34; is a standout track.&lt;/p&gt;
&lt;div class=&#34;spotify ep&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/album/1eDpqYQe0AgJKLEVd0TsMz&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h3 id=&#34;khotin-baikal-acid-2016&#34;&gt;Khotin: Baikal Acid (2016)&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;khotin-baikal-acid&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Dancy, imaginative acid house from up north. Khotin saves the best for last: side B has not one, but two lovely, warm tunes.&lt;/p&gt;
&lt;div class=&#34;spotify ep&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/album/6c9E0fOhQBFImF2tqS0Ocl&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h3 id=&#34;jonas-kopp-desire-ep-2013&#34;&gt;Jonas Kopp: Desire EP (2013)&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;jonas-kopp-desire-ep&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;German minimal techno by way of Argentina. It&#39;s dark, but the opener is funkier than your typical Tresor track, and the closer feels like some kind of ceremonial ascension.&lt;/p&gt;
&lt;div class=&#34;spotify ep&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/album/4MS9uxTHx0zLQbgdiGABFc&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h3 id=&#34;etapp-kyle-klockworks-10-2015&#34;&gt;Etapp Kyle: Klockworks 10 (2015)&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;etapp-kyle-klockworks-10&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Dark, driving, haunted minimal techno of the German variety. All of the albums on &lt;a href=&#34;https://ra.co/dj/benklock/biography&#34;&gt;Ben Klok&#39;s&lt;/a&gt; Klockworks series are worth a listen, but Klockworks 10 and 16 from this Ukranian producer are standouts.&lt;/p&gt;
&lt;div class=&#34;spotify ep&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/album/0KpFLppuoYEtWSsmLq42lz&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h3 id=&#34;luke-hess-facette-2017&#34;&gt;Luke Hess: Facette (2017)&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;luke-hess-facette&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Modern Detroit minimal techno. A propulsion system made from deep, dark textures and thumping beats.&lt;/p&gt;
&lt;div class=&#34;spotify ep&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/album/6U1eW8QyHMjxnAZpzPUy90&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h2 id=&#34;artist-samplers&#34;&gt;Artist Samplers&lt;/h2&gt;
&lt;p&gt;Some artists don&#39;t fit well into the album box. And some artists make albums of such breadth that they no longer fit into the &#34;music to work to&#34; box. Here&#39;s a few sampler playlists from artists not featured above, but no less deserving.&lt;/p&gt;
&lt;h3 id=&#34;basic-channel-sampler&#34;&gt;Basic Channel - Sampler&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;basic-channel-sampler&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As &lt;a href=&#34;https://en.wikipedia.org/wiki/Basic_Channel&#34;&gt;Basic Channel&lt;/a&gt;, the duo of Moritz von Oswald and Mark Ernestus pioneered minimal dub techno in the early 90s. Except for BCD and BCD-2, their output consists of a series of cryptically labeled singles. Here&#39;s a curated selection.&lt;/p&gt;
&lt;div class=&#34;spotify&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/playlist/6jLp6IXv49MaE4MYHn077N&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h3 id=&#34;trickfinger-sampler&#34;&gt;Trickfinger - Sampler&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;trickfinger-sampler&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Did you know John Frusciante -- yes, &lt;a href=&#34;https://en.wikipedia.org/wiki/John_Frusciante&#34;&gt;that John Frusciante&lt;/a&gt; -- has a side gig making acid techno? Insane. There are a couple tracks here where it&#39;s hard to believe he didn&#39;t pick the melody out first on a guitar.&lt;/p&gt;
&lt;div class=&#34;spotify&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/playlist/4wApGodUxCIQ0a5My3wsuM&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h3 id=&#34;ceephax-sampler&#34;&gt;Ceephax - Sampler&lt;/h3&gt;
&lt;p&gt;&lt;a name=&#34;ceephax-sampler&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;No list would be complete without Andy Jenkinson, AKA Ceephax. Personally I like his stuff more than &lt;a href=&#34;https://en.wikipedia.org/wiki/Squarepusher&#34;&gt;his brother&#39;s&lt;/a&gt;. It&#39;s funny, nostalgic, slightly unhinged, and brimming with bonafide musical genius.&lt;/p&gt;
&lt;div class=&#34;spotify&#34;&gt;
  &lt;iframe src=&#34;https://open.spotify.com/embed/playlist/4spSHMH3oRUB9bYvFio93p&#34; width=&#34;100%&#34; height=&#34;100%&#34; frameborder=&#34;0&#34; allowtransparency=&#34;true&#34; allow=&#34;encrypted-media&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;</content>
  </entry>
    
  
    
  <entry>
    <id>https://alangrow.com/blog/slippery-device-names</id>
    <link type="text/html" rel="alternate" href="https://alangrow.com/blog/slippery-device-names?from=atom"/>
    <title>Slippery Device Names and Portable AMIs</title>
    <updated>2020-12-10T21:12:32</updated>
    <author>
      <name>Alan</name>
      <email>alangrow+blog@gmail.com</email>
    </author>
    <content type="html">&lt;p&gt;Pain, thy name is hotpluggable device name assignment.&lt;/p&gt;
&lt;p&gt;In the course of migrating some EC2 servers from C3 to C5, I learned why this feature in newer linux kernels is controversial.&lt;/p&gt;
&lt;p&gt;To be clear, most people couldn&#39;t care less whether their primary network interface is called &lt;code&gt;eth0&lt;/code&gt; or &lt;code&gt;enx0150b6e42dfe&lt;/code&gt;, or whether a drive appears as &lt;code&gt;/dev/xvda&lt;/code&gt; or &lt;code&gt;/dev/nvme9n5&lt;/code&gt;, as long as they can continue to do their Computer Stuff. For ops folks trying to make a portable system image, though, this can be a real problem.&lt;/p&gt;
&lt;p&gt;My goal was to create an AMI that can be booted on a variety of EC2 instance types. Here&#39;s how I got there.&lt;/p&gt;
&lt;h3 id=&#34;network-interfaces&#34;&gt;Network Interfaces&lt;/h3&gt;
&lt;p&gt;Hotpluggable network interface names make sense for multi-homed systems, and systems that might change network configuration later.&lt;/p&gt;
&lt;p&gt;They also make sense for consumer devices that don&#39;t need to be portably imaged. When was the last time you pulled the hard drive out of your laptop, put it in a different brand of laptop, and had everything just work? Would you even expect this to work? No, this is crazy talk.&lt;/p&gt;
&lt;p&gt;However, those of us who operate and upgrade servers have different (higher?) expectations.&lt;/p&gt;
&lt;p&gt;The essential problem is described in gory detail on debian&#39;s &lt;a href=&#34;https://wiki.debian.org/NetworkInterfaceNames&#34;&gt;NetworkInterfaceNames wiki page&lt;/a&gt;. &#34;We&#39;re not in the 90s anymore, network devices come and go, deal with it. That said, here are a dozen different ways to avoid this new nonsense...&#34;&lt;/p&gt;
&lt;p&gt;Adding &lt;code&gt;net.ifnames=0&lt;/code&gt; boot parameters to &lt;code&gt;/etc/default/grub&lt;/code&gt; worked for me:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;GRUB_CMDLINE_LINUX_DEFAULT&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;quot;... net.ifnames=0&amp;quot;&lt;/span&gt;
&lt;span class=&#34;nv&#34;&gt;GRUB_CMDLINE_LINUX&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;quot;net.ifnames=0&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Follow this up with a perfunctory &lt;code&gt;update-grub&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Importantly for portability, this means any config files under &lt;code&gt;/etc/&lt;/code&gt; can refer to &lt;code&gt;eth0&lt;/code&gt; directly, and that will continue to work even if you make an AMI and boot it on another instance type — so long as it has just one network interface.&lt;/p&gt;
&lt;h3 id=&#34;nvme-disks&#34;&gt;NVMe Disks&lt;/h3&gt;
&lt;p&gt;Next we have the disk problem. C5 instances use NVMe throughout, even for EBS storage. &lt;a href=&#34;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/nvme-ebs-volumes.html&#34;&gt;The AWS docs warn us&lt;/a&gt; that:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The block device driver can assign NVMe device names in a different order than you specified for the volumes in the block device mapping.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And oh boy, do they ever like to assign in different order.&lt;/p&gt;
&lt;p&gt;If you only have one disk, you might never have this problem. I use multiple disks because:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Different subsystems have different access patterns and performance requirements. Using separate disks for, say, &lt;code&gt;/var/lib/postgresql/&lt;/code&gt; and &lt;code&gt;/var/log/&lt;/code&gt; lets you provision and tune them separately.&lt;/li&gt;
&lt;li&gt;The disk boundary is a convenient blast radius. Have you ever had a server fill up with log files and grind to a halt? A separate &lt;code&gt;/var/log/&lt;/code&gt; disk will contain the problem and let other subsystems continue to run normally.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So I&#39;ve got 5 different NVMe disks attaching to an EC2 instance in random order. Once in a while it works, but usually home directories have become the database, logfiles are now volatile runtime state, and so on. A real Mister Potato Head mess.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://russell.ballestrini.net/aws-nvme-to-block-mapping/&#34;&gt;Russell Ballestrini ran into this same issue&lt;/a&gt; and found a script, &lt;a href=&#34;https://russell.ballestrini.net/uploads/2019/ebsnvme-id&#34;&gt;&lt;code&gt;ebsnvme-id&lt;/code&gt;&lt;/a&gt;, that ships with Amazon Linux. This script interrogates an EBS NVMe device (eg &lt;code&gt;/dev/nvme1n1&lt;/code&gt;) and outputs the original name specified in the block mapping (eg &lt;code&gt;/dev/xvdb&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;But we&#39;re not quite there yet. Armed with &lt;code&gt;ebsnvme-id&lt;/code&gt;, you can create symlinks like &lt;code&gt;/dev/nvme1n1 -&amp;gt; /dev/xvdb&lt;/code&gt;, but how and when you should you do this?&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;/dev&lt;/code&gt; directory gets populated anew via &lt;code&gt;udev&lt;/code&gt; during boot. So there&#39;s a right time to do this, and there are many wrong times to do this. My first attempt via &lt;code&gt;/etc/rc.local&lt;/code&gt; failed horribly — it ran too late.&lt;/p&gt;
&lt;p&gt;Eventually I came around to the idea of using &lt;code&gt;udev&lt;/code&gt;, and I learned from this &lt;a href=&#34;http://www.reactivated.net/writing_udev_rules.html&#34;&gt;nice udev primer&lt;/a&gt; that udev rules can be flexible in the extreme. You can pattern match on device names. You can also run an external program that figures out how to rename a device. This culminated in the following magical one liner:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;KERNEL&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;quot;nvme[0-9]*n1&amp;quot;&lt;/span&gt;, &lt;span class=&#34;nv&#34;&gt;PROGRAM&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;quot;ebsnvme-namer %k&amp;quot;&lt;/span&gt;, &lt;span class=&#34;nv&#34;&gt;SYMLINK&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;quot;%c&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Save this to &lt;code&gt;70-persistent-ebsnvme.rules&lt;/code&gt; under &lt;code&gt;/etc/udev/rules.d/&lt;/code&gt;. You&#39;ll notice it doesn&#39;t hardcode any device names, so it&#39;s safe to include in a portable machine image. It creates &lt;code&gt;/dev&lt;/code&gt; symlinks that look like:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;lrwxrwxrwx 1 root root 7 Dec  9 14:34 xvda1 -&amp;gt; nvme0n1
lrwxrwxrwx 1 root root 7 Dec  9 14:34 xvdd -&amp;gt; nvme1n1
lrwxrwxrwx 1 root root 7 Dec  9 14:34 xvde -&amp;gt; nvme3n1
lrwxrwxrwx 1 root root 7 Dec  9 14:34 xvdf -&amp;gt; nvme4n1
lrwxrwxrwx 1 root root 7 Dec  9 14:34 xvdg -&amp;gt; nvme2n1
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;These match the old disk device names on my C3 instances. All my scripts and config files that reference specific &lt;code&gt;xvd*&lt;/code&gt; names? In the end, not a single one needed changing for the C3 -&amp;gt; C5 upgrade!&lt;/p&gt;
&lt;p&gt;Finally, here&#39;s the &lt;code&gt;ebsnvme-namer&lt;/code&gt; script:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&#34;ch&#34;&gt;#!/bin/sh -e&lt;/span&gt;
&lt;span class=&#34;nv&#34;&gt;ebsdev&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;ebsnvme-id --block-dev /dev/&lt;span class=&#34;s2&#34;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$1&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;
&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; xvd&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;ebsdev&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;##sd&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
</content>
  </entry>
    
  
    
  
    
  <entry>
    <id>https://alangrow.com/blog/weechat-matrix</id>
    <link type="text/html" rel="alternate" href="https://alangrow.com/blog/weechat-matrix?from=atom"/>
    <title>Matrix Chat in the Terminal with weechat-matrix</title>
    <updated>2020-08-30T19:27:00</updated>
    <author>
      <name>Alan</name>
      <email>alangrow+blog@gmail.com</email>
    </author>
    <content type="html">&lt;div class=&#34;image&#34;&gt;
&lt;img src=&#34;../images/blog/weechat-matrix.png&#34; width=&#34;100%&#34;/&gt;
&lt;/div&gt;

&lt;p&gt;I&#39;ve been using the &lt;a href=&#34;https://apps.apple.com/app/vector/id1083446067&#34;&gt;Element iOS App&lt;/a&gt; to chat with a few security-conscious friends. It works fine, but at some point you outgrow chatting with your thumbs, and long for the full ten-fingered chat experience. (A 5x improvement!!)&lt;/p&gt;
&lt;p&gt;Fortunately the &lt;a href=&#34;https://en.wikipedia.org/wiki/Matrix_(protocol)&#34;&gt;Matrix protocol&lt;/a&gt; is an open standard with &lt;a href=&#34;https://matrix.org/clients/&#34;&gt;plenty of clients&lt;/a&gt;. I like terminal programs, and &lt;a href=&#34;https://github.com/wee-slack/wee-slack&#34;&gt;this Slack plugin for weechat&lt;/a&gt; has been a pleasant surprise of late. It turns out there&#39;s a &lt;a href=&#34;https://github.com/poljar/weechat-matrix&#34;&gt;Matrix plugin for weechat&lt;/a&gt; too. A few months back I tried and failed to set up &lt;code&gt;weechat-matrix&lt;/code&gt;, but today things went a little better.&lt;/p&gt;
&lt;p&gt;So here&#39;s what worked for me. But first...&lt;/p&gt;
&lt;h3 id=&#34;are-you-sure-you-want-to-do-this&#34;&gt;Are You Sure You Want to Do This&lt;/h3&gt;
&lt;p&gt;The Matrix ecosystem is new, peopled with technical users, and hardcore about security. This doesn&#39;t make for a pleasant experience for most human beings. If you don&#39;t like messing around with tech for its own sake, and you just want 1:1 secure chats, might I recommend &lt;a href=&#34;https://signal.org/&#34;&gt;Signal&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;Still here? Onward...&lt;/p&gt;
&lt;h3 id=&#34;forget-python2&#34;&gt;Forget Python2&lt;/h3&gt;
&lt;p&gt;Although weechat still supports Python2, and &lt;code&gt;weechat-matrix&lt;/code&gt; claims to support it, its &lt;a href=&#34;https://pypi.org/project/matrix-nio/&#34;&gt;&lt;code&gt;matrix-nio&lt;/code&gt; dependency doesn&#39;t&lt;/a&gt;. Don&#39;t waste your time. Start with Python3.&lt;/p&gt;
&lt;h3 id=&#34;isolate-the-install&#34;&gt;Isolate the Install&lt;/h3&gt;
&lt;p&gt;To avoid hosing my existing weechat setup or my base system, I started from a clean &lt;a href=&#34;https://wiki.debian.org/Debootstrap&#34;&gt;Debian 10 &lt;code&gt;debootstrap&lt;/code&gt;&lt;/a&gt;. If you&#39;re hipper than me maybe you prefer docker. Either way, it pays to isolate!&lt;/p&gt;
&lt;h3 id=&#34;use-the-weechat-development-packages&#34;&gt;Use the Weechat Development Packages&lt;/h3&gt;
&lt;p&gt;Once your environment is up and running, you&#39;ll want to grab the &lt;a href=&#34;https://weechat.org/download/debian/active/files/&#34;&gt;&lt;code&gt;weechat-devel&lt;/code&gt;&lt;/a&gt; packages for maximum Python3 compat.&lt;/p&gt;
&lt;h3 id=&#34;follow-the-readme&#34;&gt;Follow the README&lt;/h3&gt;
&lt;p&gt;Follow the &lt;a href=&#34;https://github.com/poljar/weechat-matrix#installation&#34;&gt;&lt;code&gt;weechat-matrix&lt;/code&gt; README&lt;/a&gt;. If all goes well you&#39;ll be connected, logged in, and automatically joined to your channels. But there&#39;s a problem: your new &#34;device&#34; isn&#39;t verified.&lt;/p&gt;
&lt;h3 id=&#34;verify-devices&#34;&gt;Verify Devices&lt;/h3&gt;
&lt;p&gt;Immediately after &lt;code&gt;weechat-matrix&lt;/code&gt; successfully connected, Element popped up a modal that it was verifying the new device. This was the interactive verification flow, and it didn&#39;t work for me. Close out of it.&lt;/p&gt;
&lt;p&gt;What worked instead was going to Element -&amp;gt; Settings -&amp;gt; Security, finding my new &#34;Weechat Matrix&#34; session, and manually verifying it. To make sure things match on the other end, switch to any Matrix channel in weechat, type &lt;code&gt;/olm info&lt;/code&gt;, then switch back to the first weechat buffer. You should see your new weechat device&#39;s identity keys. If they match, you can verify them in Element.&lt;/p&gt;
&lt;p&gt;You can also verify devices from weechat&#39;s perspective via &lt;code&gt;/olm verify &amp;lt;username&amp;gt;&lt;/code&gt;. There&#39;s even some pattern syntax that lets you verify multiple devices at once — but be careful with this.&lt;/p&gt;
&lt;p&gt;At this point, you should be able to chat safely with other devices you&#39;ve done the verification dance with, but there&#39;s still a problem: you can&#39;t read channel history. You&#39;ll see usernames and timestamps in weechat, but each message will start with &lt;code&gt;&amp;lt;Unable to decrypt&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;decrypt-old-messages&#34;&gt;Decrypt Old Messages&lt;/h3&gt;
&lt;p&gt;To decrypt channel history, you&#39;ll need to export keys from Element and import them into &lt;code&gt;weechat-matrix&lt;/code&gt;. Element makes this pretty easy: go to Settings -&amp;gt; Security -&amp;gt; &#34;Export keys manually.&#34; Create a passphrase for the key file, and email it to yourself.&lt;/p&gt;
&lt;p&gt;Back in weechat, use &lt;code&gt;/olm import ~/path/to/riot-keys.txt &amp;lt;passphrase&amp;gt;&lt;/code&gt;. This may take a bit, and &lt;code&gt;weechat&lt;/code&gt; will likely hit 100% cpu during the process.&lt;/p&gt;
&lt;p&gt;On success, you still can&#39;t read channel history...that is, until you restart weechat!&lt;/p&gt;
&lt;h3 id=&#34;feedback&#34;&gt;Feedback&lt;/h3&gt;
&lt;p&gt;Did it work? Did I miss something? &lt;a href=&#34;https://twitter.com/alangrow&#34;&gt;Let me know&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;more-info&#34;&gt;More Info&lt;/h3&gt;
&lt;p&gt;Here&#39;s &lt;a href=&#34;https://hispagatos.org/post/weechat-matrix/&#34;&gt;another useful guide to weechat + matrix&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&#39;d be remiss if I didn&#39;t mention that &lt;code&gt;weechat-matrix&lt;/code&gt; is in maintenance mode, and a &lt;a href=&#34;https://github.com/poljar/weechat-matrix-rs&#34;&gt;new Rust port is underway&lt;/a&gt;. At the time of this writing it isn&#39;t very far along.&lt;/p&gt;</content>
  </entry>
    
  
    
  <entry>
    <id>https://alangrow.com/blog/on-remote-work</id>
    <link type="text/html" rel="alternate" href="https://alangrow.com/blog/on-remote-work?from=atom"/>
    <title>On Remote Work: An Interview</title>
    <updated>2020-03-26T00:00:00</updated>
    <author>
      <name>Alan</name>
      <email>alangrow+blog@gmail.com</email>
    </author>
    <content type="html">&lt;p&gt;This interview originally appeared on &lt;a href=&#34;https://remoteworkers.community/interviews/3-alan-grow&#34;&gt;remote.community in March 2020&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;hello-who-are-you-and-where-do-you-work&#34;&gt;&lt;a class=&#34;toclink&#34; href=&#34;#hello-who-are-you-and-where-do-you-work&#34;&gt;Hello! who are you and where do you work?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Hello! I&#39;m Alan Grow, co-founder at &lt;a href=&#34;https://endcrawl.com&#34;&gt;Endcrawl&lt;/a&gt;. We&#39;re a SaaS that makes credits for film &amp;amp; TV. We&#39;ve been used on thousands of productions including Oscar Winners &#34;Moonlight&#34; and &#34;Nomadland.&#34;&lt;/p&gt;
&lt;h3 id=&#34;how-did-you-get-started-working-remotely&#34;&gt;&lt;a class=&#34;toclink&#34; href=&#34;#how-did-you-get-started-working-remotely&#34;&gt;How did you get started working remotely?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I first tried remote work back in 2006. It was a failure. Part of that failure was lack of consulting experience, but part of it was also my lack of experience with remote work. After two decades of classrooms and offices, I didn&#39;t know how to create productive environments and habits for myself.&lt;/p&gt;
&lt;p&gt;The next time I tried full-time remote work was in 2014. I interviewed over Skype and was hired the next day, by someone I&#39;d never met in real life. Six months would pass before we finally met face to face. But that was fine, because this time, things were actually working great! I&#39;d finally figured out what keeps me focused and motivated within a remote team.&lt;/p&gt;
&lt;p&gt;What I realize now is that remote work starts and ends with a team of one: yourself. If you can’t keep yourself focused and motivated, someone will have to do that for you remotely, and that’s a tall order. You have to figure those out for yourself the hard way. But the good news is, once you have them, they’re a superpower.&lt;/p&gt;
&lt;h3 id=&#34;describe-your-typical-work-day-or-week&#34;&gt;&lt;a class=&#34;toclink&#34; href=&#34;#describe-your-typical-work-day-or-week&#34;&gt;Describe your typical work day or week.&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I begin and end my work days at my home office, but in the middle I almost always leave the house to work from a coffee shop. This is a crucial thing that I learned the hard way: even as my home office improves, I still need to get out of the house to clear my head and avoid cabin fever.&lt;/p&gt;
&lt;p&gt;Coffee shops and coworking spaces are less predictable in a couple ways that affect work. Sometimes there are network issues, and sometimes there&#39;s &lt;strike&gt;an annoying conversation&lt;/strike&gt; noise. Both make it harder to have meetings. So I adapt by taking meetings at home and using these spaces instead for deep work.&lt;/p&gt;
&lt;p&gt;That brings some challenges of its own. How do you focus deeply in a noisy public space, where people you know or strangers may interrupt you? For me: &lt;a href=&#34;./music-to-program-to&#34;&gt;repetitive electronic music&lt;/a&gt;! A decent pair of headphones with focus-inducing music works wonders, both for your own mental state, and to signal to others that you&#39;re uninterruptible.&lt;/p&gt;
&lt;p&gt;Just like a change of venue in the afternoon seems to keep me focused throughout the day, changing activities during nights and weekends helps me reset and avoid burnout. Whether it&#39;s playing music, lifting weights, or exploring the wilderness here in Utah, getting into &lt;a href=&#34;https://media3.giphy.com/media/oWWfwpLj5l0XK/source.gif&#34;&gt;a very different mental state&lt;/a&gt; keeps my &#34;work mind&#34; fresh.&lt;/p&gt;
&lt;p&gt;When I was younger I wanted to program 24/7, and every other activity seemed like a waste of time. Now I know that the opposite is true: the right mix of non-programming activities makes me a much more productive programmer.&lt;/p&gt;
&lt;h3 id=&#34;what-tools-do-you-use-when-working-remotely&#34;&gt;&lt;a class=&#34;toclink&#34; href=&#34;#what-tools-do-you-use-when-working-remotely&#34;&gt;What tools do you use when working remotely?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I spend a fair bit of time in Github and Slack. Video conferencing is usually via Google Meet. Beyond that, I try to stay minimal and inhabit a simpler world of text.&lt;/p&gt;
&lt;p&gt;I keep long-running tmux sessions both locally and on remote machines. For email I use mutt + offlineimap, and try to only poll for new emails at the beginning and end of the day.&lt;/p&gt;
&lt;p&gt;I like spending time in my editor (vim) and at a shell prompt. Learning unix and the Unix toolset has been a 1000x investment over the course of my career.&lt;/p&gt;
&lt;h3 id=&#34;describe-how-working-remotely-has-affected-your-life&#34;&gt;&lt;a class=&#34;toclink&#34; href=&#34;#describe-how-working-remotely-has-affected-your-life&#34;&gt;Describe how working remotely has affected your life.&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Not gonna lie — remote work was a difficult adjustment at first. I&#39;m a social person, and most of my socialization when I lived in NYC in the 2000s was (surprise) via the workplace. I didn&#39;t know any different.&lt;/p&gt;
&lt;p&gt;But learning how to make and maintain friendships turned out to be an orthogonal concern. It doesn&#39;t &lt;em&gt;have&lt;/em&gt; to happen through the workplace. That sort of bundling is just an easy default. Friendship can be &lt;a href=&#34;https://hbr.org/2014/06/how-to-succeed-in-business-by-bundling-and-unbundling&#34;&gt;unbundled&lt;/a&gt; from work.&lt;/p&gt;
&lt;p&gt;When I left NYC, I had to make friends all over again, as well as maintain more remote friendships. Both of those turned out to be good exercises.&lt;/p&gt;
&lt;p&gt;Remote work has let me live away from a major city, but still work with people in major cities on exciting things. I don&#39;t have to deal with the stress of traffic, rush hour, crowds, or the concrete jungle. In 15 minutes I can be enjoying a quiet hike in the wilderness. It&#39;s the best of both worlds.&lt;/p&gt;
&lt;p&gt;Remote work has also let me work with people around the world. I was fortunate to work for London-based music hardware &amp;amp; software maker &lt;a href=&#34;https://roli.com/&#34;&gt;ROLI&lt;/a&gt;, with teams from all over the US and Europe. That kind of geographic diversity comes with all other wonderful kinds of diversity, and it&#39;s a difficult thing to replicate in non-remote companies.&lt;/p&gt;
&lt;p&gt;Finally, remote work has made me much more results-oriented. Physical offices inevitably become a stage for productivity theater. Try as they might, people are pulled towards the things that look busy, and away from the things that actually produce value. Remote work keeps you honest – you&#39;re constantly proving yourself, and you&#39;re measured by your output. Those are good things!&lt;/p&gt;
&lt;h3 id=&#34;what-advice-would-you-give-to-people-working-remotely&#34;&gt;&lt;a class=&#34;toclink&#34; href=&#34;#what-advice-would-you-give-to-people-working-remotely&#34;&gt;What advice would you give to people working remotely?&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Adopt a schedule and commit to it.&lt;/strong&gt; Once it’s “burned in” you can start to experiment, but try to build rhythm first.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Measure your daily output.&lt;/strong&gt; This could be git commits, tasks checked off, new revenue booked, etc. It doesn’t have to be how others measure you. But it should loosely correlate with that, and it should be something trivially easy for you to measure.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Course correct often.&lt;/strong&gt; When something impacts your daily output, act on it. Does your output drop noticeably on less than 7 hours of sleep? Get more sleep!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Focus is sacred.&lt;/strong&gt; Your focus is your temple — don&#39;t let anything or anyone defile it. Especially yourself. Find ways to subdue the part of your brain that craves distraction.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Embrace the written word.&lt;/strong&gt; Always be reading, always be writing, and always be improving your reading and writing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Review, and seek review.&lt;/strong&gt; Critical — but constructive — written dialog with your co-workers will level you both up.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;would-you-like-to-add-anything-else&#34;&gt;&lt;a class=&#34;toclink&#34; href=&#34;#would-you-like-to-add-anything-else&#34;&gt;Would you like to add anything else?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you&#39;re bootstrapping a remote-first company, consider applying to &lt;a href=&#34;https://calmfund.com/&#34;&gt;Calm Fund&lt;/a&gt;. They&#39;re an incredible resource to companies like ours, and their community of founders and mentors is very much aligned with the future of work — which is remote!&lt;/p&gt;</content>
  </entry>
    
  
    
  <entry>
    <id>https://alangrow.com/blog/shell-quirk-assign-from-heredoc</id>
    <link type="text/html" rel="alternate" href="https://alangrow.com/blog/shell-quirk-assign-from-heredoc?from=atom"/>
    <title>Shell Quirk: Assignment From a Heredoc</title>
    <updated>2017-06-10T20:30:00</updated>
    <author>
      <name>Alan</name>
      <email>alangrow+blog@gmail.com</email>
    </author>
    <content type="html">&lt;p&gt;I have a &lt;strike&gt;fetish for&lt;/strike&gt; fascination with POSIX shell corner cases. It all started a decade ago with a segfault: a certain &lt;code&gt;while read&lt;/code&gt; loop ran fine on every Unix except AIX. We were stumped, and I was hooked.&lt;/p&gt;
&lt;p&gt;Here&#39;s a new find. What will the following POSIX shell program print?&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&#34;ch&#34;&gt;#!/bin/sh&lt;/span&gt;
&lt;span class=&#34;nv&#34;&gt;paths&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;tr &lt;span class=&#34;s1&#34;&gt;&amp;#39;\n&amp;#39;&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;:&amp;#39;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; sed -e &lt;span class=&#34;s1&#34;&gt;&amp;#39;s/:$//&amp;#39;&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;lt;&amp;lt;EOPATHS&lt;/span&gt;
&lt;span class=&#34;s&#34;&gt;/foo&lt;/span&gt;
&lt;span class=&#34;s&#34;&gt;/bar&lt;/span&gt;
&lt;span class=&#34;s&#34;&gt;/baz&lt;/span&gt;
&lt;span class=&#34;s&#34;&gt;EOPATHS&lt;/span&gt;
&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$paths&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If you said &lt;code&gt;/foo:/bar:/baz&lt;/code&gt;, you&#39;re right...that is, if you&#39;re on Linux and &lt;code&gt;/bin/sh&lt;/code&gt; is provided by &lt;a href=&#34;https://en.wikipedia.org/wiki/Almquist_shell#dash:_Ubuntu.2C_Debian_and_POSIX_compliance_of_Linux_distributions&#34;&gt;dash&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you&#39;re on MacOS &lt;a href=&#34;#1&#34;&gt;[1]&lt;/a&gt; or FreeBSD instead, this same script will wait for input and print nothing. This is probably the behavior on all BSD derivatives, and it&#39;s likely the correct behavior too, since the BSDs are usually right about these things.&lt;/p&gt;
&lt;p&gt;Correct or not, the &lt;code&gt;dash&lt;/code&gt; behavior is a bit more useful. It also points to a fundamental difference in the way &lt;a href=&#34;http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_07_04&#34;&gt;here-documents&lt;/a&gt; work: &lt;code&gt;dash&lt;/code&gt; interprets the heredoc &lt;em&gt;before&lt;/em&gt; anything else on the line. When the assignment is interpreted next, stdin already has the contents of the heredoc. I&#39;m not even sure what the other POSIX shells do. Is the heredoc interpreted after the assignment? Where does it even go?&lt;/p&gt;
&lt;p&gt;Fortunately there&#39;s an easy portable alternative: wrap the whole thing in backquotes.&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&#34;ch&#34;&gt;#!/bin/sh&lt;/span&gt;
&lt;span class=&#34;nv&#34;&gt;paths&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;tr &lt;span class=&#34;s1&#34;&gt;&amp;#39;\n&amp;#39;&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;:&amp;#39;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; sed -e &lt;span class=&#34;s1&#34;&gt;&amp;#39;s/:$//&amp;#39;&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;lt;&amp;lt;EOPATHS&lt;/span&gt;
&lt;span class=&#34;s&#34;&gt;/foo&lt;/span&gt;
&lt;span class=&#34;s&#34;&gt;/bar&lt;/span&gt;
&lt;span class=&#34;s&#34;&gt;/baz&lt;/span&gt;
&lt;span class=&#34;s&#34;&gt;EOPATHS&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;
&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$paths&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;a name=&#34;1&#34;&gt;[1]&lt;/a&gt; Note that on recent MacOS versions, &lt;code&gt;/bin/sh&lt;/code&gt; is actually &lt;code&gt;bash&lt;/code&gt; in POSIX mode. Don&#39;t believe me? Run &lt;code&gt;/bin/sh --help&lt;/code&gt; and &lt;code&gt;/bin/sh -c &#39;echo $POSIXLY_CORRECT&#39;&lt;/code&gt;.&lt;/p&gt;
&lt;style&gt;
.highlight .s { color: #dd7700; }
&lt;/style&gt;</content>
  </entry>
    
  
    
  <entry>
    <id>https://alangrow.com/blog/blog-refresh-now-with-less</id>
    <link type="text/html" rel="alternate" href="https://alangrow.com/blog/blog-refresh-now-with-less?from=atom"/>
    <title>Blog Refresh: Now With Less</title>
    <updated>2017-05-01T02:13:42</updated>
    <author>
      <name>Alan</name>
      <email>alangrow+blog@gmail.com</email>
    </author>
    <content type="html">&lt;p&gt;To readers who enjoyed the 3-column layout, the Edgar Allen Poe quote, and the engraving of the fragile rowboat disappearing into the mighty maelstrom: I&#39;m sorry. It&#39;s all gone. To me, minimalism is less an aesthetic than it is the search for time invariants, and well...here we are some years later.&lt;/p&gt;
&lt;p&gt;It&#39;s actually a bit more practical than all that. After porting this blog from &lt;a href=&#34;https://jekyllrb.com/&#34;&gt;jekyll&lt;/a&gt; to &lt;a href=&#34;https://github.com/acg/tinysite&#34;&gt;tinysite&lt;/a&gt;, I discovered that the very problem I set out to solve -- fast incremental site rebuilds -- was still a problem. No comment on why this seems to be a common failure mode for shiny two-point-oh-y things.&lt;/p&gt;
&lt;p&gt;The culprit? That index of posts in the right column. The simple act of fixing a typo on a single page would cause &lt;code&gt;posts.json&lt;/code&gt; to rebuild, and then every post would be rebuilt in a cascade, since the right column of every post depended on &lt;code&gt;posts.json&lt;/code&gt;. Other static site generators probably learned to avoid this years ago. I finally came around to it this weekend.&lt;/p&gt;
&lt;p&gt;In the interim, editing posts has been pretty unpleasant. Doubly so because I had no one to blame but myself. Now incremental site rebuilds are quick and can be accelerated with &lt;code&gt;make -j&lt;/code&gt; as before.&lt;/p&gt;
&lt;p&gt;With that out of the way, I decided to take advantage of the &#34;let&#39;s optimize the shit out of everything&#34; mental state I was in and see what could be done to speed up the publishing side of things. I really like the Heroku / Github Pages approach of &#34;just git push and we&#39;ll do the rest,&#34; and have spent the last few years building systems to make everything at &lt;a href=&#34;https://endcrawl.com&#34;&gt;Endcrawl&lt;/a&gt; work like that. Maybe those years would have been better spent learning docker or kube. Maybe the people who regard &lt;a href=&#34;https://news.ycombinator.com/item?id=5927843&#34;&gt;deploying-via-git as an antipattern&lt;/a&gt; are right. But I can&#39;t shake the idea that we&#39;re overengineering the hell out of this problem right now. As &lt;a href=&#34;https://news.ycombinator.com/item?id=14216655&#34;&gt;one HN commenter&lt;/a&gt; put it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In the long term I predict that base OS everywhere will improve support for deployment, workload scheduling, resource allocation, endpoint discovery, and dependency management. These will match and eventually surpass the additional capabilities that containers offer, and &lt;strong&gt;then we can all go back to putting files on a server and restarting a process&lt;/strong&gt;, which is all that 99% of us actually need.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There&#39;s a bit more to the story than the part I emphasized, but that&#39;s one for another day. Suffice to say there&#39;s tooling now that fully realizes the &lt;a href=&#34;./dream-deploys-atomic-zero-downtime-deployments/&#34;&gt;&#34;dream deploys&#34;&lt;/a&gt; idea, this site uses it, and who knows, maybe it&#39;ll get opensourced one day.&lt;/p&gt;
&lt;p&gt;I also took a stab at the horribly clunky &lt;code&gt;{% highlight lang %}&lt;/code&gt; template syntax this blog used for code highlighting. When I started there was no good standard for this kind of thing, but now it seems &lt;a href=&#34;https://help.github.com/articles/creating-and-highlighting-code-blocks/&#34;&gt;fenced code blocks&lt;/a&gt; have won. Good for them, they&#39;re awesome. Switching &lt;code&gt;tinysite&lt;/code&gt; to fenced code turned out to be trivial &lt;a href=&#34;https://github.com/acg/tinysite/commit/d6ea6fe0bf58ef6a28776a7f4f0b622f8c47c747&#34;&gt;(diff)&lt;/a&gt;, mainly because the original approach was a small regex hack rather than a more evolved approach. That Yagni guy they&#39;re always invoking knows what&#39;s up!&lt;/p&gt;
&lt;p&gt;Oh yeah. The Disqus comments section is gone. &lt;a href=&#34;http://donw.io/post/github-comments/&#34;&gt;Good riddance&lt;/a&gt;. It&#39;s been broken for years, ever since I migrated from &lt;code&gt;acg.github.io&lt;/code&gt; to this domain. I probably made a mistake somewhere in the Disqus migration tool but never could figure it out. If you feel a burning desire to rebutt or high-five something, &lt;a href=&#34;https://twitter.com/alangrow&#34;&gt;hit me up on twitter&lt;/a&gt; and I may link to it. Better yet, &lt;a href=&#34;https://github.com/acg/alangrow.com/issues/new&#34;&gt;open a github issue against this blog&lt;/a&gt;.&lt;/p&gt;</content>
  </entry>
    
  
    
  <entry>
    <id>https://alangrow.com/blog/dream-deploys-atomic-zero-downtime-deployments</id>
    <link type="text/html" rel="alternate" href="https://alangrow.com/blog/dream-deploys-atomic-zero-downtime-deployments?from=atom"/>
    <title>Dream Deploys: Atomic, Zero-Downtime Deployments</title>
    <updated>2015-06-05T21:11:00</updated>
    <author>
      <name>Alan</name>
      <email>alangrow+blog@gmail.com</email>
    </author>
    <content type="html">&lt;p&gt;Are you afraid to deploy? Do deployments always mean either downtime, leaving your site in an inconsistent state for a while, or both? It doesn&#39;t have to be this way!&lt;/p&gt;
&lt;p&gt;Let&#39;s conquer our fear. Let&#39;s deploy whenever we damn well feel like it.&lt;/p&gt;
&lt;div class=&#34;image&#34;&gt;
&lt;img src=&#34;../images/blog/donnie-darko-not-afraid-anymore.jpg&#34; width=&#34;100%&#34;/&gt;
&lt;/div&gt;

&lt;p&gt;This is a tiny demo to convince you that Dream Deploys are not only possible, they&#39;re easy. To live the dream, you don&#39;t need much:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You don&#39;t need a fancy load balancer.&lt;/li&gt;
&lt;li&gt;You don&#39;t need magic &#34;clustering&#34; infrastructure.&lt;/li&gt;
&lt;li&gt;You don&#39;t need a specific language or framework.&lt;/li&gt;
&lt;li&gt;You don&#39;t need a queueing system.&lt;/li&gt;
&lt;li&gt;You don&#39;t need a message bus or fancy IPC.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;You don&#39;t even need multiple instances of your server running.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All you need is a couple old-school Unix tricks.&lt;/p&gt;
&lt;h2 id=&#34;a-quick-demo&#34;&gt;A Quick Demo&lt;/h2&gt;
&lt;p&gt;Don&#39;t take my word for it. Grab the toy code &lt;a href=&#34;https://github.com/acg/dream-deploys&#34;&gt;here&lt;/a&gt; with:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;git clone git@github.com:acg/dream-deploys.git
&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; dream-deploys
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In a terminal, run this and visit the link:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;./serve
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In a second terminal, deploy whenever you want:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;./deploy
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Refresh the page to see it change.&lt;/p&gt;
&lt;p&gt;Edit code, static files, or both under &lt;code&gt;./root.unused&lt;/code&gt;. Then leave &lt;code&gt;./root.unused&lt;/code&gt; and run &lt;code&gt;./deploy&lt;/code&gt; to see your changes appear atomically and with zero downtime.&lt;/p&gt;
&lt;h2 id=&#34;questions-answers&#34;&gt;Questions &amp;amp; Answers&lt;/h2&gt;
&lt;h3 id=&#34;what-do-you-mean-by-a-zero-downtime-deployment&#34;&gt;What do you mean by a &#34;zero downtime&#34; deployment?&lt;/h3&gt;
&lt;p&gt;At no point is the site unavailable. Requests will continue to be served before, during, and after the deployment. In other words, this is about &lt;strong&gt;availability&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&#34;what-do-you-mean-by-an-atomic-deployment&#34;&gt;What do you mean by an &#34;atomic&#34; deployment?&lt;/h3&gt;
&lt;p&gt;For a given connection, either you will talk to the new code working against the new files, or you will talk to the old code working against the old files. You will never see a mix of old and new. In other words, this is about &lt;strong&gt;consistency&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&#34;how-does-the-zero-downtime-part-work&#34;&gt;How does the zero downtime part work?&lt;/h3&gt;
&lt;p&gt;This brings us to Unix trick #1. If you keep the same listen socket open throughout the deployment, clients won&#39;t get &lt;code&gt;ECONNREFUSED&lt;/code&gt; under normal circumstances. The kernel places them in a listen backlog until our server gets around to calling &lt;code&gt;accept(2)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This means, however, that our server process can&#39;t be the thing to call &lt;code&gt;listen(2)&lt;/code&gt; if we want to stop and start it, or we&#39;ll incur visible downtime. Something else – some long running process – must call &lt;code&gt;listen(2)&lt;/code&gt; and keep the listen socket open across deployments.&lt;/p&gt;
&lt;p&gt;The trick in a nutshell, then, is this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A &lt;a href=&#34;https://github.com/acg/dream-deploys/blob/master/tcplisten&#34;&gt;tiny, dedicated program&lt;/a&gt; calls &lt;code&gt;listen(2)&lt;/code&gt; and then passes the listen socket to child processes as descriptor 0 (stdin). This process replaces itself by executing a subordinate program.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The subordinate program is &lt;a href=&#34;https://github.com/acg/dream-deploys/blob/master/loop-forever&#34;&gt;just a loop&lt;/a&gt; that repeatedly executes our server program. Because this loop program never exits, the listen socket on descriptor 0 stays open.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Our server program, instead of calling &lt;code&gt;bind(2)&lt;/code&gt; and &lt;code&gt;listen(2)&lt;/code&gt; like everyone &lt;em&gt;loves to do&lt;/em&gt;, humbly calls &lt;code&gt;accept(2)&lt;/code&gt; on stdin in a loop and handles one client connection at a time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When it&#39;s time to restart the server process, we tell the server to exit after handling the current connection, if any. That way deployment doesn&#39;t disrupt any pending requests. We tell the server process to gracefully exit by sending it a &lt;code&gt;SIGHUP&lt;/code&gt; signal.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: a shocking, saddening number of web frameworks force you to call &lt;code&gt;listen(2)&lt;/code&gt; in your Big Ball Of App Code That Needs To Be Restarted. The &lt;a href=&#34;https://github.com/strongloop-forks/connect/blob/7edb875a9f305e38f4d960fa46ac674038241892/lib/proto.js#L231&#34;&gt;connect&lt;/a&gt; HTTP server framework used by &lt;a href=&#34;https://github.com/strongloop/express&#34;&gt;express&lt;/a&gt;, the most popular web app framework for &lt;a href=&#34;https://nodejs.org/&#34;&gt;Node.js&lt;/a&gt;, is one of them.&lt;/p&gt;
&lt;p&gt;&#34;I&#39;ll just use the new &lt;a href=&#34;https://lwn.net/Articles/542629/&#34;&gt;&lt;code&gt;SO_REUSEPORT&lt;/code&gt; socket option in Linux&lt;/a&gt;!&#34; you say.&lt;/p&gt;
&lt;p&gt;Fine, but take care that at least one server process is always running at any given time. This means some handoff coordination between the old and new server processes. Alternately, you could run an unrelated process on the port that just listens.&lt;/p&gt;
&lt;p&gt;An &lt;code&gt;accept(2)&lt;/code&gt;-based server is less fraught than the &lt;code&gt;SO_REUSEPORT&lt;/code&gt; approach. It also has some other nice benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;An &lt;code&gt;accept(2)&lt;/code&gt;-based server is network-agnostic. For instance, you can run it behind a Unix domain socket without modifying a single line of code.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;An &lt;code&gt;accept(2)&lt;/code&gt;-based server is a more secure factoring of concerns. If your server listens directly on a privileged port (80 or 443), you&#39;ll need root privileges or a fancy capabilities setup. After binding, a listen server should also drop root privileges (horrifyingly, some don&#39;t). The &lt;code&gt;accept(2)&lt;/code&gt; factoring means a tiny, well-audited program can bind to the privileged port, drop privileges to a minimally empowered user account, and run a known program. This is a huge security win.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;how-does-the-atomic-part-work&#34;&gt;How does the atomic part work?&lt;/h3&gt;
&lt;p&gt;A connection will either be served by the old server process or the new server process. The question is whether the old process might possibly see new files, or the new process might see old files. If we update files in-place then one of these inconsistencies can happen. This forces us to keep two complete copies of the files, an old copy and a new copy.&lt;/p&gt;
&lt;p&gt;While we&#39;re updating the new files, no server process should use them. If the old server process is restarted during this phase, intentionally or accidentally, it should continue to work off the old files. When the new copy is finally ready, we want to &#34;throw the switch&#34;: deactivate the old files and simultaneously activate the new files for future server processes. The trick is to make throwing the switch an atomic operation.&lt;/p&gt;
&lt;div class=&#34;image&#34;&gt;
&lt;img src=&#34;../images/blog/mad-scientist-with-switch.jpg&#34; width=&#34;100%&#34;/&gt;
&lt;/div&gt;

&lt;p&gt;There are a number of &lt;a href=&#34;http://rcrowley.org/2010/01/06/things-unix-can-do-atomically.html&#34;&gt;things Unix can do atomically&lt;/a&gt;. Among them: use &lt;code&gt;rename(2)&lt;/code&gt; to replace a symlink with another symlink. If the &#34;switch&#34; is a simply a symlink pointing at one directory or the other, then deployments are atomic. This is Unix trick #2.&lt;/p&gt;
&lt;h3 id=&#34;what-about-serving-inconsistent-assets-browsers-open-multiple-connections&#34;&gt;What about serving inconsistent assets? Browsers open multiple connections.&lt;/h3&gt;
&lt;p&gt;This is a problem, but there&#39;s also a straightforward solution.&lt;/p&gt;
&lt;p&gt;To clarify, during a deployment, a client may request a page from the old server, then open more connections that request assets from the new server. (Remember, consistency is only guaranteed within the same connection.) So you can get old page content mixed with new css, js, images, etc.&lt;/p&gt;
&lt;p&gt;The best practice here is to build a new tagged set of static assets for every deployment, then have the page refer to all assets via this tag. You can do this by modifying the &lt;a href=&#34;https://github.com/acg/dream-deploys/blob/master/deploy&#34;&gt;&lt;code&gt;./deploy&lt;/code&gt; script&lt;/a&gt; to do this, like so:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Update the new files.&lt;/li&gt;
&lt;li&gt;Generate a unique tag &lt;code&gt;$TAG&lt;/code&gt;. This could be an epoch timestamp or a git commit hash.&lt;/li&gt;
&lt;li&gt;Record &lt;code&gt;$TAG&lt;/code&gt; in a file inside the new file directory.&lt;/li&gt;
&lt;li&gt;Copy all the static assets into a new directory &lt;code&gt;assets.$TAG&lt;/code&gt; outside of both file copies.&lt;/li&gt;
&lt;li&gt;Continue with the deployment.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When the server starts up, it should read &lt;code&gt;$TAG&lt;/code&gt; from the file, and make sure all asset URLs it generates contain &lt;code&gt;$TAG&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That&#39;s pretty much it. Eventually you&#39;ll want to delete them, but if you keep the old &lt;code&gt;assets.$TAG&lt;/code&gt; directories around for a while, even sessions that haven&#39;t reloaded the page will continue to get consistent results across deployments.&lt;/p&gt;
&lt;p&gt;The long term solution to this problem might be &lt;a href=&#34;https://http2.github.io/faq/#why-is-http2-multiplexed&#34;&gt;HTTP/2 multiplexing&lt;/a&gt;, which makes multiple browser connections unnecessary.&lt;/p&gt;
&lt;h3 id=&#34;what-about-serving-inconsistent-ajax-requests&#34;&gt;What about serving inconsistent ajax requests?&lt;/h3&gt;
&lt;p&gt;To clarify, during a deployment, a client may request a page from the old server, then open more connections that make ajax requests of the new server using old client code.&lt;/p&gt;
&lt;p&gt;There&#39;s a best practice solution to this one as well: make your API endpoints backwards compatible. A good idea in general!&lt;/p&gt;
&lt;h3 id=&#34;what-about-concurrency-your-example-only-serves-one-connection-at-a-time&#34;&gt;What about concurrency? Your example only serves one connection at a time.&lt;/h3&gt;
&lt;p&gt;You can run as many &lt;code&gt;accept(2)&lt;/code&gt;-calling server processes as you want on the same listen socket. The kernel will efficiently multiplex connections to them.&lt;/p&gt;
&lt;p&gt;In production, I use a small program I wrote called &lt;a href=&#34;https://github.com/endcrawl/daemontools-extra/blob/master/bin/forkpool&#34;&gt;&lt;code&gt;forkpool&lt;/code&gt;&lt;/a&gt; that keeps N concurrent child processes running. It only has this one job, so it hasn&#39;t really needed bugfixes or enhancements over the years. This is important because restarting &lt;code&gt;forkpool&lt;/code&gt; itself might interrupt in-flight requests.&lt;/p&gt;
&lt;p&gt;In the unlikely event that &lt;code&gt;forkpool&lt;/code&gt; needs a restart, there is however a graceful way to do this. Sending it &lt;code&gt;SIGQUIT + SIGHUP&lt;/code&gt; will tell it to drain the child process pool from N down to 0, then tell all the children to exit after finishing their current request. When the last child exits, &lt;code&gt;forkpool&lt;/code&gt; itself will exit. If the parent process of &lt;code&gt;forkpool&lt;/code&gt; keeps the listen socket on stdin open while respawning a new &lt;code&gt;forkpool&lt;/code&gt;, clients should be none the wiser.&lt;/p&gt;
&lt;h3 id=&#34;what-about-high-rps-applications&#34;&gt;What about high rps applications?&lt;/h3&gt;
&lt;p&gt;Outside of deployments, the &lt;code&gt;listen(2) + accept(2)&lt;/code&gt; factoring described here doesn&#39;t impose any new rps (requests/sec) or cps (connections/sec) limits.&lt;/p&gt;
&lt;p&gt;During a deployment, as your new server processes are starting up, the kernel will queue new unaccepted connections until the listen backlog is reached, and shed new connections beyond that. So if &lt;code&gt;cps &amp;gt; backlog / startup&lt;/code&gt;, some clients may get an &lt;code&gt;ECONNREFUSED&lt;/code&gt; during a deployment. That is not the dream! To avoid this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Decide what peak &lt;code&gt;cps&lt;/code&gt; you want to support.&lt;/li&gt;
&lt;li&gt;Increase the &lt;a href=&#34;https://man7.org/linux/man-pages/man2/listen.2.html#:~:text=If%20the%20backlog%20argument%20is,SOMAXCONN%2C%20with%20the%20value%20128.&#34;&gt;listen backlog&lt;/a&gt;. Pass a higher number as the second argument to &lt;code&gt;listen(2)&lt;/code&gt;, and make sure the system limit isn&#39;t lower than this value. On Linux the system limit can be read &amp;amp; written at &lt;code&gt;/proc/sys/net/core/somaxconn&lt;/code&gt;. It&#39;s 4096 by default.&lt;/li&gt;
&lt;li&gt;Decrease server startup times. Even with a high backlog, a client calling &lt;code&gt;connect(2)&lt;/code&gt; may time out if the server takes too long to &lt;code&gt;accept(2)&lt;/code&gt;. It&#39;s not a bad idea to monitor server startup times in production and alert above some threshold, as otherwise this metric tends to creep up slowly &amp;amp; invisibly as an app grows.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;what-about-long-lived-requests&#34;&gt;What about long-lived requests?&lt;/h3&gt;
&lt;p&gt;If it takes a long time to respond to a request, that poses a problem for more than just deployments. Long-lived requests tie up resources that could serve other clients, risk blocking on long-held locks, risk hitting timeouts, and generally make changes to the system harder to reason about. Avoid long-lived requests if you can.&lt;/p&gt;
&lt;h3 id=&#34;what-about-deployment-collisions&#34;&gt;What about deployment collisions?&lt;/h3&gt;
&lt;p&gt;Yes, you really should prevent concurrent deployments via a lock. That&#39;s not demonstrated here, but &lt;a href=&#34;http://cr.yp.to/daemontools/setlock.html&#34;&gt;the setlock(8) program from daemontools&lt;/a&gt; makes this easy &amp;amp; reliable.&lt;/p&gt;
&lt;h3 id=&#34;what-about-deploying-database-schema-changes&#34;&gt;What about deploying database schema changes?&lt;/h3&gt;
&lt;p&gt;This topic has been covered &lt;a href=&#34;https://blog.rainforestqa.com/2014-06-27-zero-downtime-database-migrations/&#34;&gt;well elsewhere&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;does-anyone-do-this-in-production-or-is-this-all-hypothetical&#34;&gt;Does anyone do this in production, or is this all hypothetical?&lt;/h3&gt;
&lt;p&gt;We&#39;ve used this deployment approach at &lt;a href=&#34;https://endcrawl.com/&#34;&gt;Endcrawl&lt;/a&gt; for more than a decade, and we&#39;ve published &lt;a href=&#34;https://github.com/endcrawl/deployer&#34;&gt;our implementation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Our deployments take about 10 seconds on average and we&#39;re not afraid to deploy on Fridays. Or any other day of the week, for that matter.&lt;/p&gt;</content>
  </entry>
    
  
    
  <entry>
    <id>https://alangrow.com/blog/turn-vim-into-excel-tips-for-tabular-data-editing</id>
    <link type="text/html" rel="alternate" href="https://alangrow.com/blog/turn-vim-into-excel-tips-for-tabular-data-editing?from=atom"/>
    <title>Turn Vim Into Excel: Tips for Editing Tabular Data</title>
    <updated>2013-03-29T00:00:00</updated>
    <author>
      <name>Alan</name>
      <email>alangrow+blog@gmail.com</email>
    </author>
    <content type="html">&lt;div class=&#34;center image&#34;&gt;
  &lt;a href=&#34;../images/blog/vim-as-spreadsheet.png&#34;&gt;&lt;img src=&#34;../images/blog/vim-as-spreadsheet-thumbnail.png&#34; /&gt;&lt;/a&gt;&lt;br/&gt;
  &lt;small&gt;Vim editing &lt;a href=&#34;http://www.census.gov/econ/cbp/download/&#34;&gt;some 2010 US census data&lt;/a&gt;&lt;/small&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href=&#34;https://www.vim.org/&#34;&gt;Vim&lt;/a&gt; can edit just about anything, including tabular data. This post has a few tips for making stock Vim more spreadsheet-like.&lt;/p&gt;
&lt;p&gt;We&#39;ll assume you&#39;re editing files in tab-separated value format (TSV). CSV is a &lt;a href=&#34;http://en.wikipedia.org/wiki/Comma-separated_values#Lack_of_a_standard&#34;&gt;notoriously thorny&lt;/a&gt; file format with plenty of edge cases and surprises, so if you have CSV files, it&#39;s simpler to sidestep all that and roundtrip CSV to TSV for editing.&lt;/p&gt;
&lt;h3 id=&#34;a-note-on-the-tsv-format&#34;&gt;A Note on the TSV Format&lt;/h3&gt;
&lt;p&gt;To do TSV right, you should escape newline and tab characters in data. Here are two scripts, &lt;a href=&#34;https://gist.github.com/acg/5312217&#34;&gt;csv2tsv&lt;/a&gt; and &lt;a href=&#34;https://gist.github.com/acg/5312238&#34;&gt;tsv2csv&lt;/a&gt;, that will handle escaping during CSV &amp;lt;-&amp;gt; TSV conversions.&lt;/p&gt;
&lt;p&gt;Converting CSV to TSV, with C-style escaping:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;csv2tsv -e &amp;lt; file.csv &amp;gt; file.tsv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Converting TSV back to CSV, with C-style un-escaping:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tsv2csv -e &amp;lt; file.tsv &amp;gt; file.csv
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&#34;setting-up-tabular-editing-in-vim&#34;&gt;Setting up Tabular Editing in Vim&lt;/h3&gt;
&lt;p&gt;Open the file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;:e file.tsv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Excel numbers the rows, why can&#39;t we?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;:set number
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adjust your tab settings so you&#39;re editing with hard tabs:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;:setlocal noexpandtab
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, widen the columns enough so they&#39;re aligned:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;:setlocal shiftwidth=20
:setlocal softtabstop=20
:setlocal tabstop=20
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fiddle with that number 20 as needed. As far as I can tell, Vim doesn&#39;t support variable tab stops. It would be real nifty if I was wrong about this. It would be even niftier if column width detection / tabstop setting could be automated.&lt;/p&gt;
&lt;h3 id=&#34;tall-spreadsheets-always-visible-column-names-above&#34;&gt;Tall Spreadsheets: Always-Visible Column Names Above&lt;/h3&gt;
&lt;p&gt;Typically, the first line of the tsv file is a header containing the column names. We want those column names to always be visible, no matter how far down in the file we scroll. The way we&#39;ll do this is by splitting the current window in two. The top window will only be 1 line high and will show the headers. The bottom window will be for data editing.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;:sp
:0
1 CTRL-W _
CTRL-W j
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point you should have two windows, one above the other showing the first row of column headers. If you don&#39;t have very many columns, then you&#39;re done.&lt;/p&gt;
&lt;h3 id=&#34;wide-spreadsheets-horizontal-scrolling&#34;&gt;Wide Spreadsheets: Horizontal Scrolling&lt;/h3&gt;
&lt;p&gt;If you do have lots of columns, or very wide columns, you&#39;re probably noticing how confusing it looks when lines wrap. Your columns don&#39;t line up so well anymore. So turn off wrapping for both windows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;:set nowrap
CTRL-W k
:set nowrap
CTRL-W j
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One problem remains: when you scroll right to edit columns in the data pane, the header pane doesn&#39;t scroll to the right with it. Once again, your columns aren&#39;t aligned.&lt;/p&gt;
&lt;p&gt;Fortunately Vim has a solution: you can &#34;bind&#34; horizontal scrolling of the two windows. This forces them to scroll left and right in tandem.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;:set scrollopt=hor
:set scrollbind
CTRL-W k
:set scrollbind
CTRL-W j
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Wide spreadsheets also make it harder to eyeball other cells in the current row. You can enable a row highlight with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;:set cursorline
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&#34;but-what-about-formulas-and-calculations&#34;&gt;But What About Formulas and Calculations?!&lt;/h3&gt;
&lt;p&gt;It&#39;s true, Excel does way more than just edit tabular data. Vim is &#34;just&#34; an editor.&lt;/p&gt;
&lt;p&gt;If you&#39;re up for some programming, this approach might work for you:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start with your data tsv.&lt;/li&gt;
&lt;li&gt;Mirror it with a second &#34;formula tsv&#34; that contains interpreted cells.&lt;/li&gt;
&lt;li&gt;Write a program that will apply (2) to (1), &#34;rendering&#34; a tsv with calculated data.&lt;/li&gt;
&lt;li&gt;View (3) in a read-only buffer. Separately edit the data and formula tsvs.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you&#39;re not up for that, I &lt;a href=&#34;https://twitter.com/hillelogram/status/1455949281165250561&#34;&gt;hear good things&lt;/a&gt; about &lt;a href=&#34;https://www.visidata.org&#34;&gt;VisiData&lt;/a&gt;.&lt;/p&gt;</content>
  </entry>
    
  
    
  <entry>
    <id>https://alangrow.com/blog/printf-length-delimited-string</id>
    <link type="text/html" rel="alternate" href="https://alangrow.com/blog/printf-length-delimited-string?from=atom"/>
    <title>How to printf a length-delimited string</title>
    <updated>2012-11-15T00:00:00</updated>
    <author>
      <name>Alan</name>
      <email>alangrow+blog@gmail.com</email>
    </author>
    <content type="html">&lt;p&gt;Sometimes you&#39;re dealing with a string that isn&#39;t null-delimited but rather length-delimited, and you wind up doing somersaults just to print it out:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;logit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;char&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;size_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;length&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
  &lt;span class=&#34;kt&#34;&gt;char&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;buf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;255&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;];&lt;/span&gt;
  &lt;span class=&#34;n&#34;&gt;strncpy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;buf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;sizeof&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;buf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;));&lt;/span&gt;
  &lt;span class=&#34;n&#34;&gt;buf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;sizeof&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;buf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;sc&#34;&gt;&amp;#39;\0&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
  &lt;span class=&#34;n&#34;&gt;fprintf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;stderr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;quot;debug: %s&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\n&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;buf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The extra copying isn&#39;t necessary, and you don&#39;t have to live with the potential length-truncation either. Did you know &lt;code&gt;printf(3)&lt;/code&gt; can format length-delimited strings directly? Buried in the man page is this little gem:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;The precision

An optional precision, in the form of a period (&#39;.&#39;) followed by an optional decimal digit string. Instead of a decimal digit string one may write &#34;*&#34; or &#34;*m$&#34; (for some decimal integer m) to specify that the precision is given in the next argument, or in the m-th argument, respectively, which must be of type int. This gives ... the maximum number of characters to be printed from a string for s and S conversions.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that in mind, we can just write:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;logit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;char&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;size_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;length&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
  &lt;span class=&#34;n&#34;&gt;fprintf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;stderr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;quot;debug: %.*s&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\n&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;length&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
</content>
  </entry>
    
  
    
  
    
  <entry>
    <id>https://alangrow.com/blog/recovering-a-dying-ipod-disk</id>
    <link type="text/html" rel="alternate" href="https://alangrow.com/blog/recovering-a-dying-ipod-disk?from=atom"/>
    <title>Recovering a Dying iPod Disk</title>
    <updated>2012-04-03T00:00:00</updated>
    <author>
      <name>Alan</name>
      <email>alangrow+blog@gmail.com</email>
    </author>
    <content type="html">&lt;p&gt;An &lt;a href=&#34;http://en.wikipedia.org/wiki/IPod_Classic#Sixth_generation&#34;&gt;80GB iPod Classic&lt;/a&gt; filled with 4 years of music started to die on us. The symptom: the menu screen suddenly showed &#34;No Music,&#34; but disk usage was still nearly 100%. I figured this meant the internal 1.8&#34; hard disk had started to go south and had taken some critical sectors with it.&lt;/p&gt;
&lt;p&gt;That turned out to be the case. But here&#39;s how we recovered nearly 10,000 files from the iPod anyway...&lt;/p&gt;
&lt;h3 id=&#34;the-winning-ticket&#34;&gt;The Winning Ticket&lt;/h3&gt;
&lt;p&gt;Before things got any worse, I decided to grab an image of the entire disk:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo dd &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;/dev/sdc &lt;span class=&#34;nv&#34;&gt;bs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;1M &lt;span class=&#34;nv&#34;&gt;conv&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;noerror,sync &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; pv &amp;gt; ipod.img
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &#34;conv=noerror&#34; directive tells dd to keep on going if there are disk read errors instead of erroring out. (There were about a dozen. Sectors had probably been going bad for some time, and finally a critical one bit the dust.)&lt;/p&gt;
&lt;p&gt;The &#34;conv=sync&#34; directive tells dd to write out an appropriately sized block of zeroes whenever there&#39;s an error reading a block. This is necessary, or file offsets will be wrong from the point of the error onward.&lt;/p&gt;
&lt;p&gt;The pv command just shows some nice info about how much data is flowing through and how long it&#39;s taken. It&#39;s not essential here.&lt;/p&gt;
&lt;p&gt;As described &lt;a href=&#34;#deadends_and_other_things_we_tried&#34;&gt;below&lt;/a&gt;, I tried to fsck.vfat the first partition of the disk image, but this reported that an unusually high number of free cluster chains would be reclaimed. This indicated that FAT32 metadata had been damaged and that walking the complete filesystem directory structure wouldn&#39;t be possible anymore.&lt;/p&gt;
&lt;p&gt;The new approach was to say, to hell with directory structure, let&#39;s just linearly scan the disk image for files and extract them. This needles-in-the-haystack approach isn&#39;t for everybody: you will lose filenames, permissions, directory locality etc. But most mp3s have self-identifying id3 tag metadata so we didn&#39;t care too much.&lt;/p&gt;
&lt;p&gt;There are a couple programs that can find file needles in a disk image haystack. The one that worked was &lt;a href=&#34;http://www.cgsecurity.org/wiki/PhotoRec&#34;&gt;PhotoRec&lt;/a&gt;, which can actually find much more than just photo files. For an opensource unix program it has a rather strange set of options and user interface. Anyway, I ran it with:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;photorec /log /debug /d rescue ipod.img
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;All in all photorec recovered over 8,000 mp3s and some other files to boot.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Pass 1 - Reading sector  135045680/155907592, 9944 files found
Elapsed time 1h14m22s - Estimated time for achievement 0h11m29
mp3: 8339 recovered
mov: 1264 recovered
txt: 129 recovered
apple: 96 recovered
tx?: 63 recovered
jpg: 21 recovered
aif: 13 recovered
riff: 12 recovered
mpg: 3 recovered
gpg: 1 recovered
others: 3 recovered
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Afterwards, the files were scattered randomly in flat directories named rescue.1, rescue.2, rescue.3 etc:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;ls rescue.1 &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; grep mp3 &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; head
&lt;/pre&gt;&lt;/div&gt;
&lt;pre&gt;&lt;code&gt;f0234384.mp3
f0241008.mp3
f0247536.mp3
f0254352.mp3
f0257680.mp3
f0263664.mp3
f0271120.mp3
f0277872.mp3
f0284784.mp3
f0292176.mp3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If desired, they can be renamed into Artist + Album + Track + Title directories via a program like &lt;a href=&#34;http://search.cpan.org/~acg/supertag-0.2.1/supertag&#34;&gt;supertag&lt;/a&gt; (disclaimer: I&#39;m the author). But I&#39;m not sure iTunes even cares about filenames.&lt;/p&gt;
&lt;p&gt;Addendum: as time has gone on, we&#39;ve noticed that a fair percentage of the songs were truncated by photorec, something like 1 in 5. One of these rainy weekends I&#39;m going to see if I can patch photorec&#39;s mp3 recognition.&lt;/p&gt;
&lt;h3 id=&#34;dead-ends-and-other-things-we-tried&#34;&gt;Dead-Ends and Other Things We Tried&lt;/h3&gt;
&lt;p&gt;The filesystem was W95 FAT32 but couldn&#39;t be mounted due to the bad sectors. Doing an fsck on the block device was also not possible because of read errors. The errors manifested themselves like this in dmesg:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[64658.941382] sd 6:0:0:0: [sdc] Unhandled sense code
[64658.941395] sd 6:0:0:0: [sdc] Result: hostbyte=DID_OK driverbyte=DRIVER_SENSE
[64658.941407] sd 6:0:0:0: [sdc] Sense Key : Medium Error [current]
[64658.941422] Info fld=0x0
[64658.941428] sd 6:0:0:0: [sdc] Add. Sense: Unrecovered read error
[64658.941442] sd 6:0:0:0: [sdc] CDB: Read(10): 28 00 00 00 00 40 00 00 01 00
[64658.941470] end_request: I/O error, dev sdc, sector 512
[64658.941484] Buffer I/O error on device sdc, logical block 64
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After capturing the disk image, it was possible to run fsck.vfat directly on the partition file; it doesn&#39;t actually require a block device, which is cool.&lt;/p&gt;
&lt;p&gt;To run fsck on the disk image file, we needed to extract the lone FAT32 partition into a file by itself. The trick here was figuring out where the partition started. Doing an fdisk on the actual block device for the iPod (/dev/sdc) to figure out the disk geometry helped. Using that geometry, this command let us figure out the sector offset of the first partition:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;fdisk -u -C &lt;span class=&#34;m&#34;&gt;14991&lt;/span&gt; -b &lt;span class=&#34;m&#34;&gt;4096&lt;/span&gt; -l ipod.img
&lt;/pre&gt;&lt;/div&gt;
&lt;pre&gt;&lt;code&gt;Device Boot         Start         End      Blocks   Id  System
ipod.img1              63    19488469    77953628    b  W95 FAT32
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A trick to extract the partition image:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;{&lt;/span&gt; dd &lt;span class=&#34;nv&#34;&gt;bs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;4096&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;skip&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;63&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;count&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; pv &lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;}&lt;/span&gt; &amp;lt; ipod.img &amp;gt; ipod.img.part1
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This took a while. Disks are slow.&lt;/p&gt;
&lt;p&gt;Then I ran fsck.vfat on the partition image:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;fsck.vfat -v -n ipod.img.part1
&lt;/pre&gt;&lt;/div&gt;
&lt;pre&gt;&lt;code&gt;...
Checking for unused clusters.
Reclaimed 3561014 unused clusters (58343653376 bytes).
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, it thought most of the disk consisted of free clusters -- this is bad. If I had tried to repair the disk via fsck, only a small fraction of the files would have been recovered.&lt;/p&gt;
&lt;p&gt;You can see which file paths were traversed with the -l switch:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;fsck.vfat -v -n -l ipod.img.part1
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In our case this helped me verify that only a small number of files were actually going to be recovered by the fsck.&lt;/p&gt;
&lt;p&gt;Once I gave up on fsck and embarked on needle-in-haystack file extraction, I tried &lt;a href=&#34;http://www.itu.dk/~jobr/magicrescue/&#34;&gt;magicrescue&lt;/a&gt;. It found mp3s but kept saying &#34;invalid mp3 file&#34; and extracted almost none of them. It was also really slow -- it shells out to perl scripts and mpg123 to test mp3 validity. Yuck.&lt;/p&gt;</content>
  </entry>
    
  
    
  <entry>
    <id>https://alangrow.com/blog/how-many-consonant-pairs</id>
    <link type="text/html" rel="alternate" href="https://alangrow.com/blog/how-many-consonant-pairs?from=atom"/>
    <title>How Many Consonant Pairs Do We Actually Use?</title>
    <updated>2012-02-26T00:00:00</updated>
    <author>
      <name>Alan</name>
      <email>alangrow+blog@gmail.com</email>
    </author>
    <content type="html">&lt;p&gt;Of all possible consonant pairs, how many are actually used in the English language?&lt;/p&gt;
&lt;p&gt;The question came up at a party during a disappointing Ouija board session where the spirits conjured gibberish like &#34;QHPEV.&#34; Someone wondered aloud how difficult it was to pick a valid pair of consonants at random. We suspected most were invalid.&lt;/p&gt;
&lt;p&gt;This is a nice little problem for the unix text processing toolset. I used the &lt;a href=&#34;https://norvig.com/ngrams/TWL06.txt&#34;&gt;2006 Scrabble Tournament Word List&lt;/a&gt; because &lt;code&gt;/usr/share/dict/words&lt;/code&gt; contains many proper names and non-words. To get the count:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;curl https://norvig.com/ngrams/TWL06.txt &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;
sed -nEe &lt;span class=&#34;s1&#34;&gt;&amp;#39;s/(..)/\1\n/gp; s/\n//g; s/^.//; s/(..)/\1\n/gp;&amp;#39;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;
grep &lt;span class=&#34;s1&#34;&gt;&amp;#39;[^AEIOUY][^AEIOUY]&amp;#39;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;
sort -u &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;
wc -l
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A quick rundown of the shell pipeline:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;s/(..)/\1\n/gp&lt;/code&gt; splits &amp;amp; print pairs at even boundaries.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;s/\n//gp&lt;/code&gt; undoes the splits.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;s/^.//&lt;/code&gt; shifts even pair boundaries to odd ones.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;s/(..)/\1\n/gp&lt;/code&gt; splits &amp;amp; print pairs at odd boundaries.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grep &#39;[^AEIOUY][^AEIOUY]&#39;&lt;/code&gt; filters out pairs with vowels.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sort -u | wc -l&lt;/code&gt; counts unique consonant pairs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are 20 consonants in the language after removing AEIOUY, so that makes 400 possible pairs of consonants. Surprisingly, the count comes to 320, so 80% of all consonant pairs are in use!&lt;/p&gt;
&lt;h3 id=&#34;finding-example-words&#34;&gt;&lt;a class=&#34;toclink&#34; href=&#34;#finding-example-words&#34;&gt;Finding Example Words&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If, like me, you&#39;re incredulous about this 80% number, then the next thing to do is report an example word for each consonant pair. How can GJ and ZD appear in real words?&lt;/p&gt;
&lt;p&gt;We can modify the pipeline like so:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&amp;lt; TWL06.txt &lt;span class=&#34;se&#34;&gt;\&lt;/span&gt;
sed -nEe &lt;span class=&#34;s1&#34;&gt;&amp;#39;s/(..)/\1\n/gp; s/\n//g; s/^.//; s/(..)/\1\n/gp;&amp;#39;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;
grep &lt;span class=&#34;s1&#34;&gt;&amp;#39;[^AEIOUY][^AEIOUY]&amp;#39;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;
sort -u &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;
xargs -n1 sh -c &lt;span class=&#34;se&#34;&gt;\&lt;/span&gt;
  &lt;span class=&#34;s1&#34;&gt;&amp;#39;printf &amp;quot;%s &amp;quot; &amp;quot;$0&amp;quot;; exec grep --color -m1 &amp;quot;$0&amp;quot; TWL06.txt&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is inefficient. It re-greps the wordlist file 320 times. But computers are fast, and doing the stupid simple thing is also a core tenet of the unix philosophy (&lt;a href=&#34;http://www.catb.org/jargon/html/B/brute-force.html&#34;&gt;&#34;when in doubt, use brute force&#34;&lt;/a&gt;). After a moment you&#39;ll see results:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;BB ABBA
BC ABCOULOMB
BD ABDICABLE
BF ABFARAD
BG CRABGRASS
BH ABHENRIES
BJ ABJECT
BK BABKA
BL ABATABLE
BM ABMHO
BN ABNEGATE
BP BOMBPROOF
BR ABBREVIATE
BS ABCOULOMBS
BT BOBTAIL
BV ABVOLT
BW ABWATT
BZ SUBZERO
CB ECBOLIC
CC ACCEDE
CD ANECDOTA
CH ABRACHIA
CK ABACK
CL ACCLAIM
CM ACMATIC
CN ACNE
CP SECPAR
CQ ACQUAINT
CR ACCREDIT
CS ACADEMICS
CT ABACTERIAL
CW COLICWEED
CZ CZAR
DB BANDBOX
DC BEDCHAIR
DD ADD DF AIDFUL
DG ABRIDGE
DH ACIDHEAD
DJ ADJACENCE
DK BODKIN
DL ABASEDLY
DM ADMAN
DN ABSTRACTEDNESS
DP BALDPATE
DQ BEDQUILT
DR ACHLORHYDRIA
DS ABFARADS
DT BANDWIDTH
DV AARDVARK
DW AARDWOLF
DZ ADZ
FB GOOFBALL
FC BEEFCAKE
FD CHEFDOM
FF AFF
FG AFGHAN
FH HALFHEARTED
FJ FJELD
FK OFFKEY
FL ACRIFLAVINE
FM ENFEOFFMENT
FN ALOOFNESS
FP HALFPENCE
FR AFFRAY
FS AIRPROOFS
FT ABAFT
FW BEEFWOOD
GB BOGBEAN
GC DOGCART
GD AMYGDALA
GF BAGFUL
GG ABEGGING
GH AARGH
GJ GJETOST
GK BANGKOK
GL ABIDINGLY
GM ABRIDGMENT
GN ACCEPTINGNESS
GP BAGPIPE
GR ABOVEGROUND
GS ABLINGS
GT BANGTAIL
GV DOGVANE
GW BAGWIG
GZ ZIGZAG
HB AITCHBONE
HC AHCHOO
HD ARCHDEACON
HF ARCHFIEND
HG BEACHGOER
HH AARRGHH
HJ HIGHJACK
HK BABUSHKA
HL ACHLORHYDRIA
HM ABASHMENT
HN AMATEURISHNESS
HP ARCHPRIEST
HQ EARTHQUAKE
HR ACHROMAT
HS AAHS
HT ABOUGHT
HV BOSCHVARK
HW ARCHWAY
HZ MACHZOR
JD SLOJD
JJ HAJJ
JK PIROJKI
JN JNANA
JR HIJRA
JS RIJSTAFEL
KB ANTIKICKBACK
KC BACKCAST
KD BACKDATE
KF BACKFIELD
KG BACKGAMMON
KH ANKH
KJ BLACKJACK
KK BOOKKEEPER
KL ANKLE
KM ATTACKMAN
KN ACKNOWLEDGE
KP BACKPACK
KR BACKREST
KS AARDVARKS
KT BACKTRACK
KV AKVAVIT
KW ANTICLOCKWISE
LB ALB
LC ACETYLCHOLINE
LD ABUILDING
LF AARDWOLF
LG ALGA
LH ALLHEAL
LJ KABELJOU
LK ALKAHEST
LL ABDOMINALLY
LM ABELMOSK
LN ACCIDENTALNESS
LP ALP
LQ CALQUE
LR ALREADY
LS AALS
LT ABVOLT
LV AARDWOLVES
LW AGALWOOD
LX CALX
LZ BRULZIE
MB ABCOULOMB
MC ARMCHAIR
MD DUMDUM
MF AIMFUL
MG FILMGOER
MH ABMHO
MJ CIRCUMJACENT
MK BOOMKIN
ML AIMLESS
MM ACCOMMODATE
MN ALMNER
MP ABAMP
MQ CUMQUAT
MR ALUMROOT
MS ABLEISMS
MT AMTRAC
MV CIRCUMVALLATE
MW BANTAMWEIGHT
MZ HAMZA
NB BEANBAG
NC ABERRANCE
ND ABANDON
NF ANFRACTUOSITIES
NG AAHING
NH ALPENHORN
NJ BANJAX
NK ANANKE
NL ACTIONLESS
NM ABANDONMENT
NN AGINNER
NP AFFENPINSCHER
NQ BANQUET
NR ABHENRIES
NS ABANDONS
NT ABANDONMENT
NV ANTICONVULSANT
NW BETWEENWHILES
NX ANTIANXIETY
NZ APOENZYME
PB CHAPBOOK
PC CAMPCRAFT
PD CLAMPDOWN
PF CAMPFIRE
PG CAMPGROUND
PH ACALEPH
PJ CHEAPJACK
PK BUMPKIN
PL ACCOMPLICE
PM ANTIDEVELOPMENT
PN ACAPNIA
PP AIRDROPPED
PR ACUPRESSURE
PS ABAMPS
PT ABRUPT
PW DEEPWATER
QS BUQSHA
QW QWERTY
RB ABSORB
RC ACRITARCH
RD AARDVARK
RF AIRFARE
RG AARGH
RH ACHLORHYDRIA
RJ ALFORJA
RK AARDVARK
RL ACTORLY
RM ABNORMAL
RN ABORNING
RP ABSORPTANCE
RQ ARQUEBUS
RR AARRGH
RS ABANDONERS
RT ABORT
RV ACERVATE
RW AFTERWARD
RZ BILHARZIA
SB AMPHISBAENA
SC ABSCESS
SD ASDIC
SF ARMSFUL
SG ALMSGIVER
SH ABASH
SJ CROSSJACK
SK ABELMOSK
SL ABSTEMIOUSLY
SM ABLEISM
SN ABSTEMIOUSNESS
SP ACROSPIRE
SQ ANTIMOSQUITO
SR ASRAMA
SS ABBESS
ST ABIOGENIST
SV AASVOGEL
SW ANSWER
SZ GROSZ
TB ANTBEAR
TC ABBOTCIES
TD CANTDOG
TF ARTFUL
TG BATGIRL
TH ABSINTH
TJ BOOTJACK
TK CATKIN
TL ABERRANTLY
TM ABETMENT
TN ABJECTNESS
TP AGITPROP
TQ COTQUEAN
TR ABSTRACT
TS ABANDONMENTS
TT ABATTIS
TV BODDHISATTVA
TW ARTWORK
TZ BLINTZ
VD HAVDALAH
VG AVGAS
VK SOVKHOZ
VL GRAVLAKS
VN CZAREVNA
VR BAHUVRIHI
VS DEVS
VV CHIVVIED
VZ EVZONE
WB BAWBEE
WC BAWCOCK
WD BAWD
WF AWFUL
WG BELOWGROUND
WH ANTIWHALING
WJ BLOWJOB
WK AWKWARD
WL ACKNOWLEDGE
WM AWMOUS
WN ADOWN
WP BLOWPIPE
WR ANTIWRINKLE
WS ADVOWSON
WT ANTIGROWTH
WW ARROWWOOD
WZ BLOWZED
XB BOXBALL
XC BOXCAR
XD SEXDECILLION
XF BOXFISH
XG FLUXGATE
XH BOXHAUL
XL AXLE
XM AFFIXMENT
XN COMPLEXNESS
XP BLAXPLOITATION
XQ EXQUISITE
XS AXSEED
XT ADMIXT
XV POXVIRUS
XW BOXWOOD
ZB JAZZBO
ZC BUZZCUT
ZD SAMIZDAT
ZG FIZGIG
ZH MUZHIK
ZJ MUZJIK
ZK BLITZKRIEG
ZL AZLON
ZM GIZMO
ZN BIZNAGA
ZP CHUTZPA
ZQ MEZQUIT
ZS BRITZSKA
ZT FUZZTONE
ZV MITZVAH
ZW BUZZWIG
ZZ ABUZZ
&lt;/code&gt;&lt;/pre&gt;</content>
  </entry>
    
  
    
  <entry>
    <id>https://alangrow.com/blog/mutt-tip-attach-multiple-files</id>
    <link type="text/html" rel="alternate" href="https://alangrow.com/blog/mutt-tip-attach-multiple-files?from=atom"/>
    <title>Mutt Tip: Attach Multiple Files</title>
    <updated>2011-11-25T00:00:00</updated>
    <author>
      <name>Alan</name>
      <email>alangrow+blog@gmail.com</email>
    </author>
    <content type="html">&lt;p&gt;You can attach multiple files in &lt;a href=&#34;http://www.mutt.org/&#34;&gt;mutt&lt;/a&gt;&#39;s file browser, if they&#39;re in the same directory: just use &#39;t&#39; to tag them, then &#39;;&#39;-Enter. (Quickly, one after the other.) You can also view files from the file browser before attaching them, just hit Space. Ten years of mutt and I&#39;m still discovering this stuff...&lt;/p&gt;</content>
  </entry>
    
  
    
  
    
  
    
  <entry>
    <id>https://alangrow.com/blog/postgresql-tip-bulk-copying-data-between-tables</id>
    <link type="text/html" rel="alternate" href="https://alangrow.com/blog/postgresql-tip-bulk-copying-data-between-tables?from=atom"/>
    <title>PostgreSQL Tip: Bulk Copying Data Between Tables</title>
    <updated>2011-06-17T00:00:00</updated>
    <author>
      <name>Alan</name>
      <email>alangrow+blog@gmail.com</email>
    </author>
    <content type="html">&lt;p&gt;Suppose you have two different PostgreSQL databases, db1 and db2. You want to populate db2.table2 with data from db1.table1. How?&lt;/p&gt;
&lt;p&gt;Try this:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;psql -c &lt;span class=&#34;s1&#34;&gt;&amp;#39;COPY table1 TO STDOUT&amp;#39;&lt;/span&gt; db1 &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\&lt;/span&gt;
psql -c &lt;span class=&#34;s1&#34;&gt;&amp;#39;COPY table2 FROM STDIN&amp;#39;&lt;/span&gt; db2
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Is there a more efficient way to do this if the two databases are hosted by the same server instance? Probably.&lt;/p&gt;
&lt;p&gt;Then again, if the databases are on different servers, this works:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;psql -c &lt;span class=&#34;s1&#34;&gt;&amp;#39;COPY table1 TO STDOUT&amp;#39;&lt;/span&gt; db1 &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; gzip -c &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\&lt;/span&gt;
ssh host2 &lt;span class=&#34;s2&#34;&gt;&amp;quot;gunzip -c | psql -c &amp;#39;COPY table2 FROM STDIN&amp;#39; db2&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Bonus: with &lt;a href=&#34;http://www.ivarch.com/programs/pv.shtml&#34;&gt;pv(1)&lt;/a&gt;, you can see how quickly the data is flowing:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;psql -c &lt;span class=&#34;s1&#34;&gt;&amp;#39;COPY table1 TO STDOUT&amp;#39;&lt;/span&gt; db1 &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; pv &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\&lt;/span&gt;
psql -c &lt;span class=&#34;s1&#34;&gt;&amp;#39;COPY table2 FROM STDIN&amp;#39;&lt;/span&gt; db2
&lt;/pre&gt;&lt;/div&gt;
</content>
  </entry>
    
  
    
  <entry>
    <id>https://alangrow.com/blog/measuring-the-measurers</id>
    <link type="text/html" rel="alternate" href="https://alangrow.com/blog/measuring-the-measurers?from=atom"/>
    <title>Measuring the Measurers</title>
    <updated>2011-06-10T00:00:00</updated>
    <author>
      <name>Alan</name>
      <email>alangrow+blog@gmail.com</email>
    </author>
    <content type="html">&lt;p&gt;&#34;Projects A and B are your top priority now. Oh, and Project C can&#39;t be impacted.&#34;&lt;/p&gt;
&lt;p&gt;Sound familiar?&lt;/p&gt;
&lt;p&gt;It&#39;s a common complaint of the project-managed: everything can&#39;t be top priority. Something has to give. Resources allocated to Project A must be deallocated from elsewhere, either Project C, or some other project. Declaring everything &#34;top priority&#34; is not helpful.&lt;/p&gt;
&lt;p&gt;If project management accomplishes one thing, it should help each of us answer the question, &#34;What should I work on next?&#34;&lt;/p&gt;
&lt;p&gt;A friend of mine relates a story about a meeting between tech and client services. The tech team came prepared with a list of development tasks in loose priority order. As the meeting progressed, the client services team found more and more reasons to disagree with the priorities.&lt;/p&gt;
&lt;p&gt;Eventually, in frustration, the tech lead said, &#34;Here&#39;s the list. You order it.&#34;&lt;/p&gt;
&lt;p&gt;The client services lead was taken aback and refused: &#34;It all has to be done. As soon as possible.&#34;&lt;/p&gt;
&lt;p&gt;This is not helpful.&lt;/p&gt;
&lt;p&gt;While I do think there are better ways of scheduling work than imposing a single ordering -- which breaks down when multiple workers are able to proceed in parallel -- I also think the ability to see and maintain consistent priorities is an important thing to look for in a project manager. Or any manager, really.&lt;/p&gt;
&lt;p&gt;Which is why I propose the following fun experiment. Present a manager with two randomly sampled work items from their team, side by side, and ask which is higher priority. Repeat until you&#39;ve got a decent number of comparisons. Remember xkcd&#39;s &lt;a href=&#34;http://thefunniest.info/&#34;&gt;project to find the funniest image in the world&lt;/a&gt;? Yeah. It&#39;s kinda like that.&lt;/p&gt;
&lt;p&gt;Now that we&#39;ve turned a human being into a comparison operator ;) we can ask how good that operator is. Does it define an ordering? For any reasonable sample size, probably not.&lt;/p&gt;
&lt;p&gt;Forget about &lt;a href=&#34;http://en.wikipedia.org/wiki/Sorting_algorithm#Stability&#34;&gt;stable sort&lt;/a&gt;. Viewed as a &lt;a href=&#34;http://en.wikipedia.org/wiki/Directed_graph&#34;&gt;directed graph&lt;/a&gt;, there will probably be cycles, like A &amp;gt; B &amp;gt; C &amp;gt; A. In general, you can induce an acyclic digraph from a cyclic digraph by identifying the &lt;a href=&#34;http://en.wikipedia.org/wiki/Strongly_connected_component&#34;&gt;strongly connected components&lt;/a&gt;. So one metric would be to compare the size of the induced acyclic graph to the original graph (&lt;code&gt;1/∥𝐕∥&lt;/code&gt; is the worst, &lt;code&gt;∥𝐕∥/∥𝐕∥=1&lt;/code&gt; is the best). Another metric would be the height of the induced acyclic graph over the number of nodes (work items). A perfect comparison operator would produce a line of nodes in a well-defined order, and would score 1.0.&lt;/p&gt;
&lt;p&gt;Another thing to measure would be the consistency of the ordering over time. Yes, priorities change, but resource re-allocation also has a cost.&lt;/p&gt;
&lt;p&gt;Measuring the measurers seems like a good thing for a number of reasons. Among them, that it exposes the often subtle problems of &lt;em&gt;conflicting directives&lt;/em&gt; and the even subtler problems of &lt;em&gt;competing directives&lt;/em&gt;. Too often, only the people carrying out the directives are aware of them.&lt;/p&gt;</content>
  </entry>
    
  
    
  <entry>
    <id>https://alangrow.com/blog/put-everything-in-vi-mode</id>
    <link type="text/html" rel="alternate" href="https://alangrow.com/blog/put-everything-in-vi-mode?from=atom"/>
    <title>Put *Everything* in vi Mode</title>
    <updated>2011-05-17T00:00:00</updated>
    <author>
      <name>Alan</name>
      <email>alangrow+blog@gmail.com</email>
    </author>
    <content type="html">&lt;p&gt;If you&#39;re a vi user like me, try adding these two lines to your &lt;code&gt;~/.inputrc&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;set keymap vi
set editing-mode vi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, every program that uses the readline library for tty input (&lt;code&gt;perl -d&lt;/code&gt;, the &lt;code&gt;python&lt;/code&gt; REPL, &lt;code&gt;psql&lt;/code&gt;, &lt;code&gt;gdb&lt;/code&gt;, anything you run under &lt;code&gt;rlwrap&lt;/code&gt;, etc.) has vi key bindings instead of the default emacs bindings.&lt;/p&gt;
&lt;p&gt;In short, this means things like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;0&lt;/code&gt; and &lt;code&gt;$&lt;/code&gt; for beginning and end of line&lt;/li&gt;
&lt;li&gt;&lt;code&gt;k&lt;/code&gt; and &lt;code&gt;j&lt;/code&gt; for navigating history forwards and backwards&lt;/li&gt;
&lt;li&gt;&lt;code&gt;b&lt;/code&gt; and &lt;code&gt;e&lt;/code&gt; for skipping words&lt;/li&gt;
&lt;li&gt;&lt;code&gt;u&lt;/code&gt; for undo&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See this &lt;a href=&#34;https://readline.kablamo.org/vi.html&#34;&gt;readline vi mode cheatsheet&lt;/a&gt; for a longer list.&lt;/p&gt;
&lt;p&gt;I&#39;ve been using this for years with bash, where one can do &lt;code&gt;set -o vi&lt;/code&gt;. Apparently vi mode has been present since GNU readline 2.0, released in 1994, so I really have no excuse for this one!&lt;/p&gt;</content>
  </entry>
    
  
    
  <entry>
    <id>https://alangrow.com/blog/how-i-lost-100-and-blamed-cal</id>
    <link type="text/html" rel="alternate" href="https://alangrow.com/blog/how-i-lost-100-and-blamed-cal?from=atom"/>
    <title>How I Lost $100 and Blamed It On cal(1)</title>
    <updated>2011-03-22T00:00:00</updated>
    <author>
      <name>Alan</name>
      <email>alangrow+blog@gmail.com</email>
    </author>
    <content type="html">&lt;style type=&#34;text/css&#34;&gt;
@media screen and (max-width: 899px) {
  pre {
    font-size: 2vw;
    white-space: nowrap;
  }
}
&lt;/style&gt;

&lt;p&gt;True story. Back in September 2008, I decided that this year, I would &lt;strong&gt;not&lt;/strong&gt; wait until the last minute to book my Thanksgiving flight home.&lt;/p&gt;
&lt;p&gt;What&#39;s the rule for Thanksgiving again? Oh right, fourth Thursday in November. So I busted out &lt;a href=&#34;http://www.freebsd.org/cgi/man.cgi?query=cal&amp;amp;apropos=0&amp;amp;sektion=0&amp;amp;manpath=FreeBSD+8.2-RELEASE&amp;amp;format=html&#34;&gt;cal(1)&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cal
   September 2008
Su Mo Tu We Th Fr Sa
    1  2  3  4  5  6
 7  8  9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whoops, it only shows the current month. So I passed it the year:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cal 08
                               8

      January               February               March
Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa
 1  2  3  4  5  6  7            1  2  3  4               1  2  3
 8  9 10 11 12 13 14   5  6  7  8  9 10 11   4  5  6  7  8  9 10
15 16 17 18 19 20 21  12 13 14 15 16 17 18  11 12 13 14 15 16 17
22 23 24 25 26 27 28  19 20 21 22 23 24 25  18 19 20 21 22 23 24
29 30 31              26 27 28 29           25 26 27 28 29 30 31

       April                  May                   June
Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa
 1  2  3  4  5  6  7         1  2  3  4  5                  1  2
 8  9 10 11 12 13 14   6  7  8  9 10 11 12   3  4  5  6  7  8  9
15 16 17 18 19 20 21  13 14 15 16 17 18 19  10 11 12 13 14 15 16
22 23 24 25 26 27 28  20 21 22 23 24 25 26  17 18 19 20 21 22 23
29 30                 27 28 29 30 31        24 25 26 27 28 29 30

        July                 August              September
Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa
 1  2  3  4  5  6  7            1  2  3  4                     1
 8  9 10 11 12 13 14   5  6  7  8  9 10 11   2  3  4  5  6  7  8
15 16 17 18 19 20 21  12 13 14 15 16 17 18   9 10 11 12 13 14 15
22 23 24 25 26 27 28  19 20 21 22 23 24 25  16 17 18 19 20 21 22
29 30 31              26 27 28 29 30 31     23 24 25 26 27 28 29
                                            30
      October               November              December
Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa
    1  2  3  4  5  6               1  2  3                     1
 7  8  9 10 11 12 13   4  5  6  7  8  9 10   2  3  4  5  6  7  8
14 15 16 17 18 19 20  11 12 13 14 15 16 17   9 10 11 12 13 14 15
21 22 23 24 25 26 27  18 19 20 21 22 23 24  16 17 18 19 20 21 22
28 29 30 31           25 26 27 28 29 30     23 24 25 26 27 28 29
                                            30 31
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I booked my flight for Tuesday, November 20th, and forgot about it.&lt;/p&gt;
&lt;p&gt;The day approached. I called home just to make sure someone could pick me up from the airport. That&#39;s when I discovered that Thanksgiving was actually the following week. &lt;strong&gt;I had booked my flight based on the calendar for the year 8 A.D.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What I should have done was this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cal 2008
                             2008

      January               February               March
Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa
       1  2  3  4  5                  1  2                     1
 6  7  8  9 10 11 12   3  4  5  6  7  8  9   2  3  4  5  6  7  8
13 14 15 16 17 18 19  10 11 12 13 14 15 16   9 10 11 12 13 14 15
20 21 22 23 24 25 26  17 18 19 20 21 22 23  16 17 18 19 20 21 22
27 28 29 30 31        24 25 26 27 28 29     23 24 25 26 27 28 29
                                            30 31
       April                  May                   June
Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa
       1  2  3  4  5               1  2  3   1  2  3  4  5  6  7
 6  7  8  9 10 11 12   4  5  6  7  8  9 10   8  9 10 11 12 13 14
13 14 15 16 17 18 19  11 12 13 14 15 16 17  15 16 17 18 19 20 21
20 21 22 23 24 25 26  18 19 20 21 22 23 24  22 23 24 25 26 27 28
27 28 29 30           25 26 27 28 29 30 31  29 30

        July                 August              September
Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa
       1  2  3  4  5                  1  2      1  2  3  4  5  6
 6  7  8  9 10 11 12   3  4  5  6  7  8  9   7  8  9 10 11 12 13
13 14 15 16 17 18 19  10 11 12 13 14 15 16  14 15 16 17 18 19 20
20 21 22 23 24 25 26  17 18 19 20 21 22 23  21 22 23 24 25 26 27
27 28 29 30 31        24 25 26 27 28 29 30  28 29 30
                      31
      October               November              December
Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa
          1  2  3  4                     1      1  2  3  4  5  6
 5  6  7  8  9 10 11   2  3  4  5  6  7  8   7  8  9 10 11 12 13
12 13 14 15 16 17 18   9 10 11 12 13 14 15  14 15 16 17 18 19 20
19 20 21 22 23 24 25  16 17 18 19 20 21 22  21 22 23 24 25 26 27
26 27 28 29 30 31     23 24 25 26 27 28 29  28 29 30 31
                      30
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When all was said and done -- with the change fee and the fare difference -- the mistake cost me $100. But it &#34;inspired&#34; me to actually learn a thing or two about cal(1).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: RTFM, or you will pay.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CAL(1)
...
A single parameter specifies the year (1 - 5875706) to be displayed; note the year must be fully specified: “cal 89” will not display a calendar
for 1989.
&lt;/code&gt;&lt;/pre&gt;</content>
  </entry>
    
  
    
  
    
  
    
  <entry>
    <id>https://alangrow.com/blog/teasing-out-a-new-repository</id>
    <link type="text/html" rel="alternate" href="https://alangrow.com/blog/teasing-out-a-new-repository?from=atom"/>
    <title>Teasing Out a New Git Repository</title>
    <updated>2011-03-02T00:00:00</updated>
    <author>
      <name>Alan</name>
      <email>alangrow+blog@gmail.com</email>
    </author>
    <content type="html">&lt;p&gt;&lt;em&gt;The Ideal Git Law states that the documentation surrounding git(1) will expand to fill all available volume.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I&#39;m building a suite of record processing tools. Up to now, the development has taken place inside the &lt;a href=&#34;https://github.com/acg/lwpb&#34;&gt;lwpb&lt;/a&gt; git repository. But it doesn&#39;t really belong there, since other record formats besides protobuf are supported: the classic unix tab-separated text format, and soon json.&lt;/p&gt;
&lt;p&gt;So how does one extract &lt;em&gt;part&lt;/em&gt; of a git repository into a new repository, preserving history where possible?&lt;/p&gt;
&lt;p&gt;All of the files I want to extract from the main repository live under the same subdirectory, which should become the top-level directory of the new repository. So a good place to start is this &lt;a href=&#34;http://stackoverflow.com/questions/359424/detach-subdirectory-into-separate-git-repository&#34;&gt;stack overflow thread&lt;/a&gt; which explains &lt;code&gt;git filter-branch --subdirectory-filter subdir&lt;/code&gt;. It goes something like this:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;mkdir newrepo
&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; newrepo
git clone --no-hardlinks /oldrepo ./
git filter-branch --subdirectory-filter subdir HEAD
git reset --hard
git gc --aggressive
git prune
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As a comment on the stackoverflow thread mentions, it&#39;s also a good idea to remove the old repo as a remote of the new repo, so you don&#39;t accidentally push changes back to it:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;git remote rm origin
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So far so good. But I only want &lt;em&gt;some&lt;/em&gt; of the files under this subdirectory in the new repo. The rest shouldn&#39;t be there. Can I rewrite the commit history again, this time file-wise?&lt;/p&gt;
&lt;p&gt;Yes. For this I used &lt;code&gt;git filter-branch --tree-filter command&lt;/code&gt;. This works by checking out each commit, running &lt;code&gt;$SHELL -c &#34;$command&#34;&lt;/code&gt;, looking at what changes were made to the checkout, and then formulating a new commit. If the command removes a file in the checkout, it will be removed from the commit. If a command creates a file, it will be added to the commit.&lt;/p&gt;
&lt;p&gt;In my case, I only want to remove certain files, so the filter command is a shell script that looks like this:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&#34;ch&#34;&gt;#!/bin/sh&lt;/span&gt;
find . -type f -not -path &lt;span class=&#34;s2&#34;&gt;&amp;quot;*/.git/*&amp;quot;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;
sed -e &lt;span class=&#34;s1&#34;&gt;&amp;#39;s#^./##&amp;#39;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;
grep -v -E &lt;span class=&#34;s1&#34;&gt;&amp;#39;^(pb.*\.py|flat\.py|percent.*)$&amp;#39;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;
tr &lt;span class=&#34;s1&#34;&gt;&amp;#39;\n&amp;#39;&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;\0&amp;#39;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;
xargs -0 rm -v
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;rm -v&lt;/code&gt; lets me see all the deletions this script makes for each commit. I saved this as my-git-filter and ran&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;git filter-branch -f --prune-empty --tree-filter my-git-filter HEAD
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;-f&lt;/code&gt; option forces the operation even if there&#39;s already a backup of the original repo from a previous &lt;code&gt;git filter-branch&lt;/code&gt; run.&lt;/p&gt;
&lt;p&gt;Follow this up with the same cleanup procedure from the &lt;code&gt;--subdirectory-filter&lt;/code&gt; example:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;git reset --hard
git gc --aggressive
git prune
&lt;/pre&gt;&lt;/div&gt;
</content>
  </entry>
    
  
    
  
    
  <entry>
    <id>https://alangrow.com/blog/profiling-every-command-in-a-makefile</id>
    <link type="text/html" rel="alternate" href="https://alangrow.com/blog/profiling-every-command-in-a-makefile?from=atom"/>
    <title>Profiling every command in a Makefile</title>
    <updated>2011-02-25T00:00:00</updated>
    <author>
      <name>Alan</name>
      <email>alangrow+blog@gmail.com</email>
    </author>
    <content type="html">&lt;p&gt;Here&#39;s the scenario. I&#39;ve got a batch data processing pipeline implemented as a Makefile. (Hey! It&#39;s only a prototype! Trust me, I&#39;m a make hater just like you!) There&#39;s already a lot of data, so an end-to-end full run can take about a day, with some of the individual stages taking hours.&lt;/p&gt;
&lt;p&gt;Now I&#39;m thinking, wouldn&#39;t it be nice to know how long each rule took? Even better, wouldn&#39;t it be nice to get a report of how much cpu it consumed, how much memory it used, how much I/O it performed, etc.? Armed with this information, I could start optimizing poorly performing stages.&lt;/p&gt;
&lt;p&gt;So, let&#39;s suppose we cook up some wrapper program that runs a subordinate program, collects &lt;a href=&#34;http://www.freebsd.org/cgi/man.cgi?query=getrusage&amp;amp;apropos=0&amp;amp;sektion=2&amp;amp;format=html&#34;&gt;rusage&lt;/a&gt; when it exits, and prints out the interesting info. Fortunately, such a wrapper program basically already exists.&lt;/p&gt;
&lt;p&gt;I&#39;d rather not go rewrite every rule in the Makefile, prefixing it with this wrapper program. That wouldn&#39;t even work if the rule was a pipeline: since &lt;code&gt;make(1)&lt;/code&gt; executes rules by wrapping them with &lt;code&gt;$(SHELL) -c&lt;/code&gt;, only the first command in the pipeline would actually run under the wrapper.&lt;/p&gt;
&lt;p&gt;The solution is to &lt;a href=&#34;http://www.gnu.org/software/make/manual/make.html#Choosing-the-Shell&#34;&gt;set the shell&lt;/a&gt; in your Makefile to:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;SHELL&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; rusage sh
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Where &lt;code&gt;rusage&lt;/code&gt; is a wrapper shell script that looks like this:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&#34;ch&#34;&gt;#!/bin/sh&lt;/span&gt;
&lt;span class=&#34;nb&#34;&gt;exec&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;time&lt;/span&gt; -f &lt;span class=&#34;s1&#34;&gt;&amp;#39;rc=%x elapsed=%e user=%U system=%S maxrss=%M avgrss=%t ins=%I outs=%O minflt=%R majflt=%F swaps=%W avgmem=%K avgdata=%D argv=&amp;quot;%C&amp;quot;&amp;#39;&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$@&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note that this uses &lt;code&gt;/usr/bin/time&lt;/code&gt;, &lt;strong&gt;not to be confused&lt;/strong&gt; with the bash builtin &lt;code&gt;time&lt;/code&gt;, which is what you&#39;re using probably 90% of the time at the command line.&lt;/p&gt;
&lt;p&gt;Note also, this unfortunately only works with GNU &lt;code&gt;time(1)&lt;/code&gt;. The BSD (and probably Darwin, haven&#39;t actually checked) versions of &lt;code&gt;time(1)&lt;/code&gt; don&#39;t support the &lt;code&gt;-f&lt;/code&gt; argument to specify a format string. But on BSD derivatives, you should be able to at least get a human readable dump of the rusage structure by using &lt;code&gt;/usr/bin/time -l&lt;/code&gt;. Which looks equivalent to the &lt;code&gt;/usr/bin/time -v&lt;/code&gt; output from GNU time. (It&#39;s just not as convenient if you plan to analyze the logs later.)&lt;/p&gt;</content>
  </entry>
    
  
    
  
    
  <entry>
    <id>https://alangrow.com/blog/bouncing-hopping-tunneling-with-tcpforward</id>
    <link type="text/html" rel="alternate" href="https://alangrow.com/blog/bouncing-hopping-tunneling-with-tcpforward?from=atom"/>
    <title>Bouncing, Hopping and Tunneling with tcpforward</title>
    <updated>2011-02-07T00:00:00</updated>
    <author>
      <name>Alan</name>
      <email>alangrow+blog@gmail.com</email>
    </author>
    <content type="html">&lt;p&gt;This weekend I dusted off a little network utility of mine called &lt;a href=&#34;https://github.com/acg/tcpforward&#34;&gt;tcpforward&lt;/a&gt;. It proved its worth once again, so instead of throwing it back into the rusty toolbox like I always do, here&#39;s why you might want to throw it into your very own rusty toolbox. ;)&lt;/p&gt;
&lt;ul class=&#34;toc&#34;&gt;
  &lt;li&gt;&lt;a href=&#34;#bouncing&#34;&gt;Scenario: Remote Assistance, AKA &#34;Bouncing Your Signal Off The Moon&#34;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&#34;#hopping&#34;&gt;Scenario: Hopping Over the Middleman&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&#34;#tunneling&#34;&gt;Scenario: Tunneling Through Corporate Firewalls &lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&#34;#how-it-works&#34;&gt;How it Works&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;span id=&#34;bouncing&#34;&gt;&lt;/span&gt; &lt;/p&gt;
&lt;h3 id=&#34;scenario-remote-assistance-aka-bouncing-your-signal-off-the-moon&#34;&gt;Scenario: Remote Assistance, AKA &#34;Bouncing Your Signal Off The Moon&#34;&lt;/h3&gt;
&lt;p&gt;Suppose you need to SSH to a friend&#39;s machine, but you&#39;re both behind NATs.&lt;/p&gt;
&lt;p&gt;If your friend is savvy enough to compile it, and you&#39;ve got time for that, you could use &lt;a href=&#34;http://samy.pl/pwnat/&#34;&gt;pwnat&lt;/a&gt;. You could also have your friend configure port forwarding on his router -- again, only if your friend is savvy enough, and doesn&#39;t mind punching a hole in his firewall. Yet another option: give your friend an SSH account on a public machine, and go look up the SSH arguments for reverse port forwarding for the bazillionth time.&lt;/p&gt;
&lt;p&gt;The lowest-hassle option I can think of is to use tcpforward. Suppose you and your friend can both reach a 3rd machine, a public server you own called &lt;em&gt;moon&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Run the following on &lt;em&gt;moon&lt;/em&gt;:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;tcpforward -v -N &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; -l moon:9922 -l moon:9921
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Arrange for your friend to run the following on his local machine:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;./tcpforward -v -N &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; -c moon:9922 -c localhost:22
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now, on your machine, run:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;ssh -p &lt;span class=&#34;m&#34;&gt;9921&lt;/span&gt; moon
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And voila, your SSH connection is forwarded past your friend&#39;s NAT, to his machine. The &lt;code&gt;-N 1&lt;/code&gt; option makes this a one-shot connection. The &lt;code&gt;-v&lt;/code&gt; option gives him something to watch while you go to work -- some realtime transfer statistics.&lt;/p&gt;
&lt;p&gt;(This example assumes port 9921 and 9922 are open on &lt;em&gt;moon&lt;/em&gt;, and that your friend is running sshd).&lt;/p&gt;
&lt;p&gt;&lt;span id=&#34;hopping&#34;&gt;&lt;/span&gt; &lt;/p&gt;
&lt;h3 id=&#34;scenario-hopping-over-the-middleman&#34;&gt;Scenario: Hopping Over the Middleman&lt;/h3&gt;
&lt;p&gt;Ever wanted to copy files to a machine you could only reach from an intermediate machine? For no particular reason, let&#39;s call these machines &lt;em&gt;production&lt;/em&gt; and &lt;em&gt;gateway&lt;/em&gt;. I bet you usually end up scp&#39;ing or rsync&#39;ing files to &lt;em&gt;gateway&lt;/em&gt;, ssh&#39;ing to &lt;em&gt;gateway&lt;/em&gt;, then running scp or rsync again, then cleaning up the files, etc.&lt;/p&gt;
&lt;p&gt;&#34;There must be a better way!&#34; I hear you scream.&lt;/p&gt;
&lt;p&gt;Yes. First, ssh to &lt;em&gt;gateway&lt;/em&gt; and run:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;tcpforward -v -k -l &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;.0.0.0:9922 -c production:22
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In another tty on your local machine, you can now run:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;scp -o &lt;span class=&#34;nv&#34;&gt;Port&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;9922&lt;/span&gt; somefile gateway:somefile
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Or, rsync:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;rsync -e &lt;span class=&#34;s2&#34;&gt;&amp;quot;ssh -p 9922&amp;quot;&lt;/span&gt; -avzp somedir/ gateway:somedir/
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Remember to kill the tcpforward session on &lt;em&gt;gateway&lt;/em&gt;, or your sysadmin may get angry, annoyed, frightened, or all of the above.&lt;/p&gt;
&lt;p&gt;(Once again, assumes port 9922 is open on &lt;em&gt;gateway&lt;/em&gt;.)&lt;/p&gt;
&lt;p&gt;&lt;span id=&#34;tunneling&#34;&gt;&lt;/span&gt; &lt;/p&gt;
&lt;h3 id=&#34;scenario-tunneling-through-corporate-firewalls&#34;&gt;Scenario: Tunneling Through Corporate Firewalls&lt;/h3&gt;
&lt;p&gt;Let&#39;s continue with the slightly subversive examples. Suppose you&#39;re behind a corporate firewall that doesn&#39;t allow SSH connections out, only web traffic. You&#39;ve got a public server out there called &lt;em&gt;freedom&lt;/em&gt;, and you want to log in once in a while.&lt;/p&gt;
&lt;p&gt;You could run &lt;code&gt;hts&lt;/code&gt; from &lt;a href=&#34;http://www.nocrew.org/software/httptunnel.html&#34;&gt;httptunnel&lt;/a&gt; on &lt;em&gt;freedom&lt;/em&gt;. That&#39;s a fair bit of C code to expose to the world though. ;)&lt;/p&gt;
&lt;p&gt;Alternately, let&#39;s say you&#39;re not running anything on &lt;em&gt;freedom:443&lt;/em&gt;. Most corporate firewalls will allow https out, and most of them don&#39;t do deep packet inspection to verify that the initial handshake actually conforms to the TLS protocol.&lt;/p&gt;
&lt;p&gt;Before going off to work, run the following on &lt;em&gt;freedom&lt;/em&gt;:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;tcpforward -v -k -l &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;.0.0.0:443 -c localhost:22
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;From work:&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;ssh -p &lt;span class=&#34;m&#34;&gt;443&lt;/span&gt; freedom  &lt;span class=&#34;c1&#34;&gt;# scream FREEEEEEDOOOOMMM!!! as you&amp;#39;re doing this&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span id=&#34;how-it-works&#34;&gt;&lt;/span&gt; &lt;/p&gt;
&lt;h3 id=&#34;how-it-works&#34;&gt;How it Works&lt;/h3&gt;
&lt;p&gt;The time has come to pull back the curtain, revealing the wizened figure of a &lt;a href=&#34;https://github.com/acg/tcpforward/blob/master/tcpforward&#34;&gt;160 line Perl script&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;How does it work?&lt;/p&gt;
&lt;p&gt;Well, you always run &lt;code&gt;tcpforward&lt;/code&gt; with two arguments that specify a pair of TCP sockets to set up, then copy bytes between. Each socket argument is either a listen / accept socket -- if you specify the &lt;code&gt;-l&lt;/code&gt; flag -- or a connect socket, if you specify the &lt;code&gt;-c&lt;/code&gt; flag. Once both sockets of a pair are accepted or connected, a little async I/O copy loop runs until both sockets close for reading. If you pass the &lt;code&gt;-k&lt;/code&gt; flag, the I/O copy loop runs in a forked process and another socket pair is immediately ready for setup.&lt;/p&gt;
&lt;p&gt;There&#39;s more documentation in the &lt;a href=&#34;https://github.com/acg/tcpforward/blob/master/README.md&#34;&gt;POD&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Happy connection hacking!&lt;/p&gt;</content>
  </entry>
    
  
    
  
    
  <entry>
    <id>https://alangrow.com/blog/thinkpad-key-replacement</id>
    <link type="text/html" rel="alternate" href="https://alangrow.com/blog/thinkpad-key-replacement?from=atom"/>
    <title>Thinkpad T43 Key Removal, Assembly</title>
    <updated>2007-02-18T00:00:00</updated>
    <author>
      <name>Alan</name>
      <email>alangrow+blog@gmail.com</email>
    </author>
    <content type="html">&lt;p&gt;Within a few days of the &lt;a href=&#34;../05/lcd-smashed-so-ratpoison.html&#34;&gt;destruction of my T40&lt;/a&gt;, I got a T43 from a guy on craigslist. The left control key promptly broke so I swapped it for the right one. There&#39;s relatively little info out there about how to assemble and disassemble keys, so here&#39;s some info on the process. Before we begin, get out your jeweler&#39;s eyepiece...&lt;/p&gt;
&lt;p&gt;You can pry off the key face gently as described &lt;a href=&#34;http://dqd.com/~mayoff/notes/thinkpad/key/&#34;&gt;here&lt;/a&gt;, just push away from you and up with a flat object. The face snaps into a cage mechanism consisting of three parts: a top plate and two wickets which anchor it to from the north and south respectively. Each wicket has a bar that wraps over the top plate, and two legs with pegs that secure it to the keyboard bevel. Viewed from the east or west sides, the wickets cross over each other, making an X. There is enough play in the cage&#39;s anchoring that you can squish the whole thing down flat. The only thing that impedes you is a little rubber spring glued to the keyboard bevel. This spring is primarily responsible for that distinctive Thinkpad key feel.&lt;/p&gt;
&lt;p&gt;By squishing the cage flat, you can hook or unhook the wickets. To reassemble and replace a key, I found it easiest to build the cage first. Start by crossing the wickets--they are fitted to each other. While pressing the X sides of the cage in, you can slip in the face plate. Don&#39;t put on the key face yet. Attach the cage to the keyboard bevel by putting it in place and hooking in the south wicket&#39;s legs first. Getting the north wicket in is a bit of a stretch. Flatten the cage by pressing down on it until the north legs slip in. Now you can attach the key face by setting it on top of the cage and applying gentle downward force. You should hear it snap.&lt;/p&gt;</content>
  </entry>
    
  
    
  
    
  <entry>
    <id>https://alangrow.com/blog/tai64-for-all-time</id>
    <link type="text/html" rel="alternate" href="https://alangrow.com/blog/tai64-for-all-time?from=atom"/>
    <title>TAI64 For All Time</title>
    <updated>2006-09-14T00:00:00</updated>
    <author>
      <name>Alan</name>
      <email>alangrow+blog@gmail.com</email>
    </author>
    <content type="html">&lt;p&gt;From Bernstein&#39;s &lt;a href=&#34;http://cr.yp.to/libtai/tai64.html#tai64&#34;&gt;tai64 page&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Integers 2^63 and larger are reserved for future extensions. Under many cosmological theories, the integers under 2^63 are adequate to cover the entire expected lifetime of the universe; in this case no extensions will be necessary.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Phew!&lt;/p&gt;
&lt;p&gt;Dealing with &lt;a href=&#34;http://cr.yp.to/daemontools/multilog.html&#34;&gt;multilog&lt;/a&gt;&#39;s TAI64 timestamps is always a bit annoying, but I suppose old djb may very well be laughing his head off in &lt;a href=&#34;http://www.unixtimestamp.com/index.php&#34;&gt;2038&lt;/a&gt;. Still, the idea of writing software &#34;for all time&#34; has enough allure to the developer mind that it feels like a trap.&lt;/p&gt;</content>
  </entry>
    
  
    
  
    
  <entry>
    <id>https://alangrow.com/blog/colorful-prompt-generator</id>
    <link type="text/html" rel="alternate" href="https://alangrow.com/blog/colorful-prompt-generator?from=atom"/>
    <title>Colorful Bash Prompt Generator</title>
    <updated>2004-12-30T00:00:00</updated>
    <author>
      <name>Alan</name>
      <email>alangrow+blog@gmail.com</email>
    </author>
    <content type="html">&lt;p&gt;(A very old post, but I&#39;ve used this prompt ever since.)&lt;/p&gt;
&lt;p&gt;Customizing a shell prompt often culminates in an impressive plumage display like&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;PS1&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;\[\e]0;\w\a\]\n\[\e[32m\]\u@\h \[\e[33m\]\w\n\[\e[0m\]$ &amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;the idea being that lots of escape sequences = eliteness. Though, I&#39;d guess most people just copy someone else&#39;s bash prompt and foist it off as their own, rather than learn ansi / xterm / bash escape sequences. Like me initially. :)&lt;/p&gt;
&lt;p&gt;However, you can easily make your prompt setup readable by breaking it down.&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;# ansi color escape sequences&lt;/span&gt;
&lt;span class=&#34;nv&#34;&gt;prompt_black&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;\[\e[30m\]&amp;#39;&lt;/span&gt;
&lt;span class=&#34;nv&#34;&gt;prompt_red&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;\[\e[31m\]&amp;#39;&lt;/span&gt;
&lt;span class=&#34;nv&#34;&gt;prompt_green&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;\[\e[32m\]&amp;#39;&lt;/span&gt;
&lt;span class=&#34;nv&#34;&gt;prompt_yellow&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;\[\e[33m\]&amp;#39;&lt;/span&gt;
&lt;span class=&#34;nv&#34;&gt;prompt_blue&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;\[\e[34m\]&amp;#39;&lt;/span&gt;
&lt;span class=&#34;nv&#34;&gt;prompt_magenta&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;\[\e[35m\]&amp;#39;&lt;/span&gt;
&lt;span class=&#34;nv&#34;&gt;prompt_cyan&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;\[\e[36m\]&amp;#39;&lt;/span&gt;
&lt;span class=&#34;nv&#34;&gt;prompt_white&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;\[\e[37m\]&amp;#39;&lt;/span&gt;
&lt;span class=&#34;nv&#34;&gt;prompt_default_color&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;\[\e[0m\]&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;My motivation initially was to avoid beeping console prompts. The xterm escape sequence to set the window title contains a bell character, which was of course interpreted by xterm and friends, but not when I&#39;d sit down at system consoles (where usually &lt;code&gt;TERM=cons25&lt;/code&gt;). I needed to set &lt;code&gt;$PS1&lt;/code&gt; according to &lt;code&gt;$TERM&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In the course of things, I discovered the &lt;code&gt;\t&lt;/code&gt; bash escape sequence, which gives you the current time in &lt;code&gt;hh:mm:ss&lt;/code&gt; form. Nice. By incorporating this into the prompt you can now tell by inspection how long you&#39;ve been sitting with your jaw open trying to remember what you were about to do. Or, how severe one&#39;s random spastic &lt;code&gt;ls&lt;/code&gt;-ing has gotten.&lt;/p&gt;
&lt;div class=&#34;image&#34;&gt;
&lt;a href=&#34;../images/blog/bash-prompt-with-time.png&#34;&gt;
&lt;img src=&#34;../images/blog/bash-prompt-with-time-small.png&#34; /&gt;
&lt;/a&gt;
&lt;/div&gt;

&lt;p&gt;For emergencies, there&#39;s also the no-color prompt.&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;prompt_nocolor&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;\n\u@\h \w\n$ &amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For nostalgia (or out of masochism) there&#39;s the old dos prompt.&lt;/p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;prompt_dos&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;\n\w&amp;gt;&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
</content>
  </entry>
    
  
 
</feed>
