<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Eli Bendersky's website - Programming</title><link href="https://eli.thegreenplace.net/" rel="alternate"></link><link href="https://eli.thegreenplace.net/feeds/programming.atom.xml" rel="self"></link><id>https://eli.thegreenplace.net/</id><updated>2025-09-28T13:13:02-07:00</updated><entry><title>Consistent hashing</title><link href="https://eli.thegreenplace.net/2025/consistent-hashing/" rel="alternate"></link><published>2025-09-27T05:54:00-07:00</published><updated>2025-09-28T13:13:02-07:00</updated><author><name>Eli Bendersky</name></author><id>tag:eli.thegreenplace.net,2025-09-27:/2025/consistent-hashing/</id><summary type="html">&lt;p&gt;This post is an introduction to &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Consistent_hashing"&gt;consistent hashing&lt;/a&gt;,
an algorithm for designing a hash table such that only a small portion of
keys has to be recomputed when the table's size changes.&lt;/p&gt;
&lt;div class="section" id="motivating-use-case"&gt;
&lt;h2&gt;Motivating use case&lt;/h2&gt;
&lt;p&gt;Suppose we're designing a &lt;a class="reference external" href="https://eli.thegreenplace.net/2022/go-and-proxy-servers-part-1-http-proxies/"&gt;caching web proxy&lt;/a&gt;,
but the expected storage demands are higher than …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;This post is an introduction to &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Consistent_hashing"&gt;consistent hashing&lt;/a&gt;,
an algorithm for designing a hash table such that only a small portion of
keys has to be recomputed when the table's size changes.&lt;/p&gt;
&lt;div class="section" id="motivating-use-case"&gt;
&lt;h2&gt;Motivating use case&lt;/h2&gt;
&lt;p&gt;Suppose we're designing a &lt;a class="reference external" href="https://eli.thegreenplace.net/2022/go-and-proxy-servers-part-1-http-proxies/"&gt;caching web proxy&lt;/a&gt;,
but the expected storage demands are higher than what a single machine can
handle. So we distribute the cache across multiple machines. How do we do that?
Given a URL, how do we make sure that we can easily find out which server we
should approach for a potentially cached version &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;An approach that immediately comes to mind is &lt;em&gt;hashing&lt;/em&gt;. Let's calculate a
numeric hash of the URL and distribute it evenly between N nodes (that's what
we'll call the servers in this post):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;hash := calculateHashFunction(url)
nodeId := hash % N
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This process works but turns out to have serious downsides in real-world
applications.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-problem-with-the-naive-hashing-approach"&gt;
&lt;h2&gt;The problem with the naive hashing approach&lt;/h2&gt;
&lt;p&gt;Consider our caching use case again; in a realistic application at &amp;quot;internet
scale&amp;quot;, one of the assumptions we made implicitly doesn't hold - the cache
nodes are not static. New nodes are added to the system if the load is
high (or if new machines come into service); existing nodes can crash or
be taken offline for maintenance. In other words, the number &lt;tt class="docutils literal"&gt;N&lt;/tt&gt; in our
application is not a constant.&lt;/p&gt;
&lt;p&gt;The problem may be apparent now; to demonstrate it directly, let's take an
actual implementation of &lt;tt class="docutils literal"&gt;hashItem&lt;/tt&gt; using Go's &lt;tt class="docutils literal"&gt;md5&lt;/tt&gt; package &lt;a class="footnote-reference" href="#footnote-2" id="footnote-reference-2"&gt;[2]&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;// hashItem computes the slot an item hashes to, given a total number of slots.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hashItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nslots&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;digest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;digestHigh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BigEndian&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;digestLow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BigEndian&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;digestHigh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;^&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;digestLow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nslots&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The terminology is slightly adjusted:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Instead of &lt;tt class="docutils literal"&gt;url&lt;/tt&gt;, we'll just refer to a generic &lt;tt class="docutils literal"&gt;item&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&amp;quot;Slot&amp;quot; is a common concept in hash tables: our &lt;tt class="docutils literal"&gt;hashItem&lt;/tt&gt; computes a slot
number for an item, given the total number of available slots&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let's say we started with 32 slots, and we hashed the strings &lt;tt class="docutils literal"&gt;&amp;quot;hello&amp;quot;&lt;/tt&gt;,
&lt;tt class="docutils literal"&gt;&amp;quot;consistent&amp;quot;&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;&amp;quot;marmot&amp;quot;&lt;/tt&gt;. We get these slots:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;hello       (n=32): 4
consistent  (n=32): 14
marmot      (n=32): 5
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now suppose that another node is added, and the total &lt;tt class="docutils literal"&gt;nslots&lt;/tt&gt; grows to 33.
Hashing our items again:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;hello       (n=33): 23
consistent  (n=33): 18
marmot      (n=33): 31
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;All the slots changed!&lt;/p&gt;
&lt;p&gt;This is a significant problem with the naive hashing approach. Whenever
&lt;tt class="docutils literal"&gt;nslots&lt;/tt&gt; changes, we get completely different slots for pretty much any item.
In a realistic application it means that whenever a new node joins or leaves
our caching cluster, there will be a flood of cache misses on every query until
the new cluster settles. And node changes sometimes occur at the most
incovenient times; imagine that the load is spiking (maybe a site was mentioned
in a high-profile news outlet, or there's a live event streaming) and new
nodes are added to handle it. This isn't a great time to temporarily lose all
caching!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="consistent-hashing-1"&gt;
&lt;h2&gt;Consistent hashing&lt;/h2&gt;
&lt;p&gt;The consistent hashing algorithm solves the problem
in an elegant way. The key idea is to map both nodes and items onto
an interval, and then an item belongs to a node closest to it. Concretely,
we take the unit circle &lt;a class="footnote-reference" href="#footnote-3" id="footnote-reference-3"&gt;[3]&lt;/a&gt;, and map nodes and items to angles on this circle.
Here's an example that explains how this method works in more detail:&lt;/p&gt;
&lt;img alt="Circle showing consistent hashing in action" class="align-center" src="https://eli.thegreenplace.net/images/2025/consistent-hashing-circle.png" /&gt;
&lt;p&gt;This shows five nodes: N1 through N5, and three items: Ix, Iy, Iz.
Initially, we add the nodes: using a hashing operation we map them onto the
circle (details later). Then, as items come in, we determine which node they
belong to, as follows:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Use the same hashing operation to find the item's location on the circle&lt;/li&gt;
&lt;li&gt;The node this item belongs to is the closest one, in the clockwise direction&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In our diagram, Ix is mapped to N1, Iy to N2, and Iz to N3. So far so good, but
the benefit of this approach becomes apparent when the nodes change. In our
diagram, suppose N3 is removed. Then Iz will map to N5.
&lt;strong&gt;The mapping of the other items doesn't change!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Adding nodes has a similar outcome. If a new node N6 is added and it hashes to a
position between Iy and N2 on the circle, from that moment Iy will be mapped to
N6, but the other items keep their mapping.&lt;/p&gt;
&lt;p&gt;Suppose we have a total of &lt;em&gt;M&lt;/em&gt; items that we need to distribute across &lt;em&gt;N&lt;/em&gt;
nodes. Using the naive hashing approach, whenever we add or remove a node, all
&lt;em&gt;M&lt;/em&gt; items change their mapping. On the other hand, with consistent hashing only
about &lt;object class="valign-m6" data="https://eli.thegreenplace.net/images/math/ab937f504862098986b70c147198d7de0636b19d.svg" style="height: 22px;" type="image/svg+xml"&gt;\frac{M}{N}&lt;/object&gt; need to change. This is a huge difference.&lt;/p&gt;
&lt;p&gt;The original consistent hashing paper (see &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-4"&gt;[1]&lt;/a&gt;) calls this the &lt;em&gt;monotonicity
property&lt;/em&gt; of the algorithm:&lt;/p&gt;
&lt;blockquote&gt;
If items are initially assigned to a set of buckets &lt;object class="valign-m3" data="https://eli.thegreenplace.net/images/math/5f90004c9690d47aa17405ea93d48dd64a2584fd.svg" style="height: 15px;" type="image/svg+xml"&gt;V_1&lt;/object&gt;, and then
some new buckets are added to form &lt;object class="valign-m3" data="https://eli.thegreenplace.net/images/math/1de7baa05bc432a1a68efd5d6a7e7606da511451.svg" style="height: 15px;" type="image/svg+xml"&gt;V_2&lt;/object&gt;, then an item may move from
an old bucket to a new bucket, but not from one old bucket to another.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class="section" id="implementing-consistent-hashing"&gt;
&lt;h2&gt;Implementing consistent hashing&lt;/h2&gt;
&lt;p&gt;Implementing the consistent hashing algorithm as described above is fairly easy.
The most critical part of the implementation is finding which node an item maps
to - this involves some kind of search. The original consistent hashing paper
suggests using a balanced binary tree for the search; the implementation I'm
demonstrating here uses a slightly different but equivalent approach:
binary search in a linear array of node positions (slots) &lt;a class="footnote-reference" href="#footnote-4" id="footnote-reference-5"&gt;[4]&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;First, some practical considerations:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Theoretically, the unit circle can be seen as the continuous range
&lt;tt class="docutils literal"&gt;[0, 1)&lt;/tt&gt;. In programming we much prefer the discrete domain, however,
so we're going to &amp;quot;quantize&amp;quot; this range to &lt;tt class="docutils literal"&gt;[0, ringSize)&lt;/tt&gt;, where
&lt;tt class="docutils literal"&gt;ringSize&lt;/tt&gt; is some suitably large number that avoids collisions.&lt;/li&gt;
&lt;li&gt;Looking at the circle diagram above, imagine that 0 degrees is the &amp;quot;north&amp;quot;
direction (12 o'clock), and angles increase clockwise. In our discrete
domain, 12 o'clock is 0, 3 o'clock is &lt;tt class="docutils literal"&gt;ringSize/4&lt;/tt&gt;, and so on.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When a node is added to the consistent hash, its location is found by applying
a hash function like &lt;tt class="docutils literal"&gt;hashItem&lt;/tt&gt; as described above, with
&lt;tt class="docutils literal"&gt;nslots=ringSize&lt;/tt&gt;. The nodes are stored using a pair of data structures,
as follows; this example uses the approximate locations of the nodes N1
through N5 in the circle diagram above (assume &lt;tt class="docutils literal"&gt;ringSize=1024&lt;/tt&gt; here):&lt;/p&gt;
&lt;img alt="Nodes and slots arrays for the has shown above" class="align-center" src="https://eli.thegreenplace.net/images/2025/nodes-slots.png" /&gt;
&lt;p&gt;The positions of the nodes on the circle are stored in &lt;tt class="docutils literal"&gt;slots&lt;/tt&gt;, which is
sorted. &lt;tt class="docutils literal"&gt;nodes&lt;/tt&gt; holds the corresponding node names. For each &lt;tt class="docutils literal"&gt;i&lt;/tt&gt;,
&lt;tt class="docutils literal"&gt;nodes[i]&lt;/tt&gt; is at position &lt;tt class="docutils literal"&gt;slots[i]&lt;/tt&gt; on the circle.&lt;/p&gt;
&lt;p&gt;Here's the &lt;tt class="docutils literal"&gt;ConsistentHasher&lt;/tt&gt; data structure in Go:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ConsistentHasher&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// nodes is a list of nodes in the hash ring; it&amp;#39;s sorted in the same order&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// as slots: for each i, the node at index slots[i] is nodes[i].&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// slots is a sorted slice of node indices.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;slots&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;ringSize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="c1"&gt;// NewConsistentHasher creates a new consistent hasher with a given maximal&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="c1"&gt;// ring size.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;NewConsistentHasher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ringSize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;ConsistentHasher&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;ConsistentHasher&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;ringSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ringSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And this is how finding which node a given item maps to is implemented:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;// FindNodeFor finds the node an item hashes to. It&amp;#39;s an error to call this&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="c1"&gt;// method if the hasher doesn&amp;#39;t have any nodes.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;ConsistentHasher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;FindNodeFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;FindNodeFor called when ConsistentHasher has no nodes&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;ih&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hashItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ringSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// Since ch.slots is a sorted list of all the node indices for our nodes, a&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// binary search is what we need here. ih is mapped to the node that has the&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// same or the next larger node index. slices.BinarySearch does exactly this,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// by returning the index where the value would be inserted.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;slotIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;slices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BinarySearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slots&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ih&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// When the returned index is len(slots), it means the search wrapped&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// around.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;slotIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slots&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;slotIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;slotIndex&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The key here is the binary search invocation. Adding and removing nodes is done
similarly using binary search - see &lt;a class="reference external" href="https://github.com/eliben/code-for-blog/tree/main/2025/consistent-hashing"&gt;the full code&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="better-item-distribution-with-virtual-nodes"&gt;
&lt;h2&gt;Better item distribution with virtual nodes&lt;/h2&gt;
&lt;p&gt;A common issue that comes up in the implementation of consistent hashing is
unbalanced distribution of items across the different nodes. With &lt;object class="valign-0" data="https://eli.thegreenplace.net/images/math/c63ae6dd4fc9f9dda66970e827d13f7c73fe841c.svg" style="height: 12px;" type="image/svg+xml"&gt;M&lt;/object&gt;
items and a total of &lt;object class="valign-0" data="https://eli.thegreenplace.net/images/math/b51a60734da64be0e618bacbea2865a8a7dcd669.svg" style="height: 12px;" type="image/svg+xml"&gt;N&lt;/object&gt; nodes, the &lt;em&gt;average&lt;/em&gt; distribution will be
about &lt;object class="valign-m6" data="https://eli.thegreenplace.net/images/math/ab937f504862098986b70c147198d7de0636b19d.svg" style="height: 22px;" type="image/svg+xml"&gt;\frac{M}{N}&lt;/object&gt; per node, but in practice it won't be very balanced -
some nodes will have many more items assigned to them than others
(see the Appendix for more details).&lt;/p&gt;
&lt;p&gt;In a real application, this may mean that some cache servers will be much busier
than others, which is a bad thing as far as capacity planning and efficient use
of HW. Luckily, there's an elegant tweak to the consistent hashing algorithm
that significantly mitigates the problem: virtual nodes.&lt;/p&gt;
&lt;p&gt;Instead of mapping each node to a single location on the circle, we'll map
it to &lt;em&gt;V&lt;/em&gt; locations instead. There are several ways to do this - the
simplest is just to tweak the node name in some way. For example, when
&lt;tt class="docutils literal"&gt;AddNode&lt;/tt&gt; is called to add &lt;tt class="docutils literal"&gt;node&lt;/tt&gt;, it will run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;V&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;vnodeName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;%v@%v&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// ... now add vnodeName to the nodes/slots slices&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then, when looking up an item we'll run into one of the virtual nodes, decode
the node's name from it (in our example just strip the &lt;tt class="docutils literal"&gt;&amp;#64;&amp;lt;number&amp;gt;&lt;/tt&gt; suffix)
and return that. Implementing node removal is similarly simple.&lt;/p&gt;
&lt;p&gt;The idea is that given a node named &lt;tt class="docutils literal"&gt;foo&lt;/tt&gt;, the virtual node names
&lt;tt class="docutils literal"&gt;foo&amp;#64;0&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;foo&amp;#64;1&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;foo&amp;#64;2&lt;/tt&gt; etc. will be spread all around the circle
and not cluster in a single place. See the Appendix for a calculation of how
this affects the final distribution.&lt;/p&gt;
&lt;p&gt;The source code for this post includes a &lt;tt class="docutils literal"&gt;ConsistentHasherV&lt;/tt&gt; type that
is very similar to &lt;tt class="docutils literal"&gt;ConsistentHasher&lt;/tt&gt;, except that it implements the virtual
node strategy. The user interface remains exactly the same - it's only the
internal implementation that changes slightly.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="code"&gt;
&lt;h2&gt;Code&lt;/h2&gt;
&lt;p&gt;The full source code for this post is &lt;a class="reference external" href="https://github.com/eliben/code-for-blog/tree/main/2025/consistent-hashing"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="appendix"&gt;
&lt;h2&gt;Appendix&lt;/h2&gt;
&lt;p&gt;The quality of the hash function is very important for good shuffling of nodes
on the circle, but even if we take a perfect hash function that produces
uniformly distributed values, the outcome is likely to be sub-optimal for our
needs.&lt;/p&gt;
&lt;p&gt;Let's say we select &lt;object class="valign-0" data="https://eli.thegreenplace.net/images/math/b51a60734da64be0e618bacbea2865a8a7dcd669.svg" style="height: 12px;" type="image/svg+xml"&gt;N&lt;/object&gt; points on the unit circle, uniformly in the range
&lt;tt class="docutils literal"&gt;[0, 1)&lt;/tt&gt;. If we sort the points by angle, the gaps between neighboring angles
are the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Order_statistic"&gt;order statistics&lt;/a&gt;.
These follow the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Beta_distribution"&gt;Beta distribution&lt;/a&gt;
with parameters &lt;tt class="docutils literal"&gt;(1, &lt;span class="pre"&gt;N-1)&lt;/span&gt;&lt;/tt&gt;, which has a mean of &lt;object class="valign-m6" data="https://eli.thegreenplace.net/images/math/9b449288a63a4dfad75b920fdd5eefa2478ada7a.svg" style="height: 22px;" type="image/svg+xml"&gt;\frac{1}{N}&lt;/object&gt; and a
variance of &lt;object class="valign-m11" data="https://eli.thegreenplace.net/images/math/d8e11ee692ae7269b8c666e5dd6597129db2dbe2.svg" style="height: 27px;" type="image/svg+xml"&gt;\frac{N-1}{N^2(N+1)}&lt;/object&gt;.&lt;/p&gt;
&lt;p&gt;This is quite significant. Consider a circle with 20 nodes. The standard
deviation of the distribution is the square root of the variance; substituting
&lt;object class="valign-0" data="https://eli.thegreenplace.net/images/math/5d6b87cee7577e70335f849b561ca9fa2d7c58ed.svg" style="height: 12px;" type="image/svg+xml"&gt;N=20&lt;/object&gt;, we get:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/math/dedd9be6ea6fe84cbd36e553e54f707913caf981.svg" style="height: 43px;" type="image/svg+xml"&gt;\[\sigma=\sqrt{\frac{19}{20^2\cdot 21}}=0.048\]&lt;/object&gt;
&lt;p&gt;With 20 nodes uniformly distributed around a circle, we can expect an average of
18 degrees distance between two nodes. A standard deviation of 0.048 means 17
degrees, which is comparable to the average!&lt;/p&gt;
&lt;p&gt;We can also do a realistic example to demonstrate this. Let's generate 20 random
angles on a circle, and show how the node distribution looks:&lt;/p&gt;
&lt;img alt="Circle with randomly distributed points" class="align-center" src="https://eli.thegreenplace.net/images/2025/circle-random-distribution.png" /&gt;
&lt;p&gt;In this particular sample, the average angle between two adjacent nodes is
18 degrees (as expected). The smallest angle is just 1.04 degrees, while the
largest one is 42 degrees. This means that some nodes will get 40x as many
items assigned to them as others!&lt;/p&gt;
&lt;p&gt;It's easy to see how virtual nodes help; imagine that each server maps to
some number of randomly distributed nodes on the circle; some of these will
be farther than others from their closest neighbor, but the average will have
much less variety. Mathematically, given a set of &lt;em&gt;n&lt;/em&gt; uniformly distributed
random variables with variance &lt;em&gt;v&lt;/em&gt;, the variance of their average is
&lt;object class="valign-m6" data="https://eli.thegreenplace.net/images/math/8bc0f62c31ffd5dcad4e282dea1285d2fcf82262.svg" style="height: 19px;" type="image/svg+xml"&gt;\frac{v}{n}&lt;/object&gt;.&lt;/p&gt;
&lt;p&gt;As a concrete experiment, I ran a similar simulation to the one above, but
with 10 virtual nodes per node. We'll consider the total portion of the circle
mapped to a node when it maps to either of its virtual nodes. While the
average remains 18 degrees, the variance is reduced drastically - the smallest
one is 11 degrees and the largest 26.&lt;/p&gt;
&lt;p&gt;You can find the code for
these experiments in the &lt;tt class="docutils literal"&gt;demo.go&lt;/tt&gt; file of the source code repository.&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-1"&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;p class="first"&gt;This is close to the original motivation for the development of
consistent caching by researchers at MIT. Their work, described in
the paper
&lt;em&gt;&amp;quot;Consistent Hashing and Random Trees:
Distributed Caching Protocols for Relieving Hot Spots on the World
Wide Web&amp;quot;&lt;/em&gt; became the foundation of &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Akamai_Technologies"&gt;Akamai Technologies&lt;/a&gt;.&lt;/p&gt;
&lt;p class="last"&gt;There are other use cases of consistent hashing in distributed systems.
AWS popularized one common application in their paper
&lt;em&gt;&amp;quot;Dynamo: Amazon’s Highly Available Key-value Store&amp;quot;&lt;/em&gt;, where the
algorithm is used to distribute storage keys across servers.&lt;/p&gt;
&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-2" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-2"&gt;[2]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;In a real application, we'd probably want to find a different hash
function. &lt;tt class="docutils literal"&gt;md5&lt;/tt&gt; is relatively slow, and we don't need any of its
cryptographic guarantees in this use case. That said, the chosen hash
function should be suitable and produce a good mixing of bits even for
items that are only slightly different (e.g. &amp;quot;node-1&amp;quot; vs. &amp;quot;node-2&amp;quot;);
I found that Go's built-in &lt;tt class="docutils literal"&gt;hash/fnv&lt;/tt&gt; package isn't great for this
purpose, for example.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-3" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-3"&gt;[3]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;A circle is used instead of a linear range to conveniently handle
boundary conditions. It's similar to using modular arithmetic for the
naive hashing approach.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-4" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-5"&gt;[4]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;I didn't bother to compare performance, but I suspect the array-based
approach will be faster for lookup because of being
relatively cache-friendly. For insertion/removal, the array approach
is &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/ebc75cd71fe8ecc45d16e8fbe4ca608d05d1efe0.svg" style="height: 19px;" type="image/svg+xml"&gt;O(n)&lt;/object&gt; whereas the tree is &lt;object class="valign-m5" data="https://eli.thegreenplace.net/images/math/d7fff6fa55b0a8556bd4b8f0b67820a60ce92451.svg" style="height: 19px;" type="image/svg+xml"&gt;O(\log\ n)&lt;/object&gt;, but it's safe to
assume that lookup performance is significantly more important. Nodes
don't change very often, and lookups happen orders of magnitude more
frequently.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Go"></category><category term="Programming"></category></entry><entry><title>Building abstractions using higher-order functions</title><link href="https://eli.thegreenplace.net/2023/building-abstractions-using-higher-order-functions/" rel="alternate"></link><published>2023-02-04T05:40:00-08:00</published><updated>2023-03-06T13:26:10-08:00</updated><author><name>Eli Bendersky</name></author><id>tag:eli.thegreenplace.net,2023-02-04:/2023/building-abstractions-using-higher-order-functions/</id><summary type="html">&lt;p&gt;A &lt;em&gt;higher-order function&lt;/em&gt; is a function that takes other functions as arguments,
or returns a function as its result. Higher-order functions are an exceptionally
powerful software design tool because they can easily create new abstractions
and are composable. In this post I will present a case study - a set of …&lt;/p&gt;</summary><content type="html">&lt;p&gt;A &lt;em&gt;higher-order function&lt;/em&gt; is a function that takes other functions as arguments,
or returns a function as its result. Higher-order functions are an exceptionally
powerful software design tool because they can easily create new abstractions
and are composable. In this post I will present a case study - a set of
functions that defines an interesting problem domain. By reading and
understanding this code, hopefully anyone can appreciate the power and beauty of
higher-order functions and how they enable constructing powerful abstractions
from basic building blocks.&lt;/p&gt;
&lt;p&gt;One of my &lt;a class="reference external" href="https://eli.thegreenplace.net/2005/06/12/lessons-from-paip"&gt;all-time favorite&lt;/a&gt; programming
books is Peter Norvig's &lt;a class="reference external" href="https://norvig.github.io/paip-lisp/#/"&gt;PAIP&lt;/a&gt; . In
section &lt;em&gt;6.4 - A set of Searching Tools&lt;/em&gt;, it presents some code for defining
different variants of tree searching that I've always found very elegant.&lt;/p&gt;
&lt;p&gt;Here's a quick reimplementation of the main idea in Clojure (see &lt;a class="reference external" href="https://github.com/eliben/paip-in-clojure/tree/master/src/paip/6_tools"&gt;this
repository&lt;/a&gt; for
the full, runnable code); I'm using Clojure since it's a modern Lisp that &lt;a class="reference external" href="https://eli.thegreenplace.net/2017/clojure-the-perfect-language-to-expand-your-brain/"&gt;I
enjoy learning and using&lt;/a&gt;
from time to time.&lt;/p&gt;
&lt;p&gt;First, some prerequisites. As is often the case in dynamically-typed Lisp,
entities can be described in a very abstract way. The code presented here
searches trees, but there is no tree data structure per-se; it's defined using
functions. Specifically, there's a notion of a &amp;quot;state&amp;quot; (tree node) and a way
to get from a given state to its children states (successors); a function maps
between the two.&lt;/p&gt;
&lt;p&gt;In our case let's have integers as states; then, an infinite binary tree can
be defined using the following successor function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn &lt;/span&gt;&lt;span class="nv"&gt;binary-tree&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;A successors function representing a binary tree.&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;* &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;+ &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;* &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Given a state (a number), it returns its children as a list. Simplistically, in
this tree, node N has the children 2N and 2N+1.&lt;/p&gt;
&lt;p&gt;Here are the first few layers of such a tree:&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/2023/binary-tree-graph.svg" type="image/svg+xml"&gt;Binary tree with 15 nodes 1-15&lt;/object&gt;
&lt;p&gt;In one sense, the tree is infinite because &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;binary-tree&lt;/span&gt;&lt;/tt&gt; will happily return
the successors for any node we ask:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;paip.core=&amp;gt; (binary-tree 9999)
(19998 19999)
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But in another sense, &lt;em&gt;there is no tree&lt;/em&gt;. This is a beautiful implication
of using functions instead of concrete data - they easily enable &lt;em&gt;lazy
evaluation&lt;/em&gt;. We cannot materialize an infinite tree inside a
necessarily finite computer, but we can operate on it all the same because of
this abstraction. As far as the search algorithm is concerned, there exists an
abstract &lt;em&gt;state space&lt;/em&gt; and we tell it how to navigate and interpret it.&lt;/p&gt;
&lt;p&gt;Now we're ready to look at the generic search function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn &lt;/span&gt;&lt;span class="nv"&gt;tree-search&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Finds a state that satisfies goal?-fn; Starts with states, and searches&lt;/span&gt;
&lt;span class="s"&gt;  according to successors and combiner. If successful, returns the state;&lt;/span&gt;
&lt;span class="s"&gt;  otherwise returns nil.&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;states&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;goal?-fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;successors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;combiner&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;cond &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;states&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;goal?-fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;first &lt;/span&gt;&lt;span class="nv"&gt;states&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;first &lt;/span&gt;&lt;span class="nv"&gt;states&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="ss"&gt;:else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tree-search&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;combiner&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;successors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;first &lt;/span&gt;&lt;span class="nv"&gt;states&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;                                     &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;rest &lt;/span&gt;&lt;span class="nv"&gt;states&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;                           &lt;/span&gt;&lt;span class="nv"&gt;goal?-fn&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;                           &lt;/span&gt;&lt;span class="nv"&gt;successors&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;                           &lt;/span&gt;&lt;span class="nv"&gt;combiner&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's dig in. The function accepts the following parameters:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;states&lt;/tt&gt;: a list of starting states for the search. When invoked by the
user, this list will typically have a single element; when &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;tree-search&lt;/span&gt;&lt;/tt&gt;
calls itself, this list is the states that it plans to explore next.&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;goal?-fn&lt;/span&gt;&lt;/tt&gt;: a goal detection function. The search doesn't know anything
about states and what the goal of the search is, so this is parameterized
by a function. &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;goal?-fn&lt;/span&gt;&lt;/tt&gt; is expected to return &lt;tt class="docutils literal"&gt;true&lt;/tt&gt; for a goal
state (the state we were searching for) and &lt;tt class="docutils literal"&gt;false&lt;/tt&gt; for all other states.&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;successors&lt;/tt&gt;: the search function also doesn't know anything about what
kind of tree it's searching through; what are the children of a given state?
Is it searching a binary tree? A N-nary tree? Something more exotic? All of
this is parameterized via the &lt;tt class="docutils literal"&gt;successors&lt;/tt&gt; function provided by the user.&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;combiner&lt;/tt&gt;: finally, the search strategy can be parameterized as well.
There are many different kinds of searches possible - BFS, DFS and others.
&lt;tt class="docutils literal"&gt;combiner&lt;/tt&gt; takes a list of successors for the current state the search is
looking at, as well as a list of all the other states the search still plans
to look at. It combines these into a single list &lt;em&gt;somehow&lt;/em&gt;, and thus guides
the order in which the search happens.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Even before we see how this function is used, it's already apparent that this
is quite a powerful abstraction. &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;tree-search&lt;/span&gt;&lt;/tt&gt; defines the essence of what
it means to &amp;quot;search a tree&amp;quot;, while being oblivious to what the tree contains,
how it's structured and even what order it should be searched in; all of this
is supplied by functions passed in as parameters.&lt;/p&gt;
&lt;p&gt;Let's see an example, doing a BFS search on our infinite binary tree. First,
we define a &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;breadth-first-search&lt;/span&gt;&lt;/tt&gt; function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn &lt;/span&gt;&lt;span class="nv"&gt;breadth-first-search&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Search old states first until goal is reached.&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;goal?-fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;successors&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tree-search&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list &lt;/span&gt;&lt;span class="nv"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;goal?-fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;successors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;prepend&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This function takes a start state (a single state, not a list), &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;goal?-fn&lt;/span&gt;&lt;/tt&gt; and
&lt;tt class="docutils literal"&gt;successors&lt;/tt&gt;, but it sets the &lt;tt class="docutils literal"&gt;combiner&lt;/tt&gt; parameter to the &lt;tt class="docutils literal"&gt;prepend&lt;/tt&gt;
function, which is defined as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn &lt;/span&gt;&lt;span class="nv"&gt;prepend&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;concat &lt;/span&gt;&lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It defines the search strategy (BFS = first look at the rest of the states and
only then at successors of the current state), but still leaves the tree
structure and the notion of what a goal is to parameters. Let's see it in
action:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;paip.core=&amp;gt; (breadth-first-search 1 #(= % 9) binary-tree)
9
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here we pass the anonymous function literal &lt;tt class="docutils literal"&gt;#(= % 9)&lt;/tt&gt; as the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;goal?-fn&lt;/span&gt;&lt;/tt&gt;
parameter. This function simply checks whether the state passed to it is the
number 9. We also pass &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;binary-tree&lt;/span&gt;&lt;/tt&gt; as the &lt;tt class="docutils literal"&gt;successors&lt;/tt&gt;, since we're going
to be searching in our infinite binary tree. BFS works layer by layer, so it
has no issue with that and finds the state quickly.&lt;/p&gt;
&lt;p&gt;We can turn on verbosity (refer to &lt;a class="reference external" href="https://github.com/eliben/paip-in-clojure/tree/master/src/paip/6_tools"&gt;the full code&lt;/a&gt; to
see how it works) to see what &lt;tt class="docutils literal"&gt;states&lt;/tt&gt; parameter &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;tree-search&lt;/span&gt;&lt;/tt&gt; gets called
with, observing the progression of the search:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;paip.core=&amp;gt; (with-verbose (breadth-first-search 1 #(= % 9) binary-tree))
;; Search: (1)
;; Search: (2 3)
;; Search: (3 4 5)
;; Search: (4 5 6 7)
;; Search: (5 6 7 8 9)
;; Search: (6 7 8 9 10 11)
;; Search: (7 8 9 10 11 12 13)
;; Search: (8 9 10 11 12 13 14 15)
;; Search: (9 10 11 12 13 14 15 16 17)
9
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is the &lt;tt class="docutils literal"&gt;prepend&lt;/tt&gt; combiner in action; for example, after &lt;tt class="docutils literal"&gt;(3 4 5)&lt;/tt&gt;, the
combiner prepends &lt;tt class="docutils literal"&gt;(4 5)&lt;/tt&gt; to the successors of 3 (the list &lt;tt class="docutils literal"&gt;(6 7)&lt;/tt&gt;), getting
&lt;tt class="docutils literal"&gt;(4 5 6 7)&lt;/tt&gt; as the set of states to search through. Overall, observing the
first element in the &lt;tt class="docutils literal"&gt;states&lt;/tt&gt; list through the printed lines, it's clear this
is classical BFS where the tree is visited in &amp;quot;layers&amp;quot;.&lt;/p&gt;
&lt;p&gt;Implementing DFS using &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;tree-search&lt;/span&gt;&lt;/tt&gt; is similarly easy:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn &lt;/span&gt;&lt;span class="nv"&gt;depth-first-search&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Search new states first until goal is reached.&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;goal?-fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;successors&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tree-search&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list &lt;/span&gt;&lt;span class="nv"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;goal?-fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;successors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The only difference from BFS is the &lt;tt class="docutils literal"&gt;combiner&lt;/tt&gt; parameter - here we use
&lt;tt class="docutils literal"&gt;concat&lt;/tt&gt; since we want to examine the successors of the first state &lt;em&gt;before&lt;/em&gt;
we examine the other states on the list. If we run &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;depth-first-search&lt;/span&gt;&lt;/tt&gt; on our
infinite binary tree we'll get a stack overflow (unless we're looking for a
state that's on the left-most path), so let's create a safer tree first. This
function can serve as a &lt;tt class="docutils literal"&gt;successors&lt;/tt&gt; to define a &amp;quot;finite&amp;quot; binary tree, with
the given maximal state value:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn &lt;/span&gt;&lt;span class="nv"&gt;finite-binary-tree&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Returns a successor function that generates a binary tree with n nodes.&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;filter &lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;&amp;lt;= &lt;/span&gt;&lt;span class="nv"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;binary-tree&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note the clever use of higher-order functions here. &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;finite-binary-tree&lt;/span&gt;&lt;/tt&gt; is
not a &lt;tt class="docutils literal"&gt;successors&lt;/tt&gt; function itself - rather it's a generator of such
functions; given a value, it creates a new function that acts as &lt;tt class="docutils literal"&gt;successors&lt;/tt&gt;
but limits the the states' value to &lt;tt class="docutils literal"&gt;n&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;For example, &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;(finite-binary-tree&lt;/span&gt; 15)&lt;/tt&gt; will create a &lt;tt class="docutils literal"&gt;successors&lt;/tt&gt; function
that represents exactly the binary tree on the diagram above; if we ask it about
successors of states on the fourth layer, it will say there are none:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;paip.core=&amp;gt; (def f15 (finite-binary-tree 15))
#&amp;#39;paip.core/f15
paip.core=&amp;gt; (f15 4)
(8 9)
paip.core=&amp;gt; (f15 8)
()
paip.core=&amp;gt; (f15 7)
(14 15)
paip.core=&amp;gt; (f15 15)
()
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As another test, let's try to look for a state that's not in our finite tree.
Out infinite tree theoretically has &lt;em&gt;all&lt;/em&gt; the states:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;paip.core=&amp;gt; (breadth-first-search 1 #(= % 33) binary-tree)
33
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But not the finite tree:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;paip.core=&amp;gt; (breadth-first-search 1 #(= % 33) (finite-binary-tree 15))
nil
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With our finite tree, we are ready to use &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;depth-first-search&lt;/span&gt;&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;paip.core=&amp;gt; (with-verbose (depth-first-search 1 #(= % 9) (finite-binary-tree 15)))
;; Search: (1)
;; Search: (2 3)
;; Search: (4 5 3)
;; Search: (8 9 5 3)
;; Search: (9 5 3)
9
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note the search order; when &lt;tt class="docutils literal"&gt;(2 3)&lt;/tt&gt; is explored, 2's successors &lt;tt class="docutils literal"&gt;(4 5)&lt;/tt&gt; then
come &lt;em&gt;before&lt;/em&gt; 3 in the next call; this is the definition of DFS.&lt;/p&gt;
&lt;p&gt;We can implement more advanced search strategies using this infrastructure. For
example, suppose we have a heuristic that tells us which states to prioritize in
order to get to the goal faster (akin to A* search on graphs). We can define
a &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;best-first-search&lt;/span&gt;&lt;/tt&gt; that sorts the states according to our heuristic and
tries the most promising states first (&amp;quot;best&amp;quot; as in &amp;quot;best looking among the
current candidates&amp;quot;, not as in &amp;quot;globally best&amp;quot;).&lt;/p&gt;
&lt;p&gt;First, let's define a couple of helper higher-order functions:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn &lt;/span&gt;&lt;span class="nv"&gt;diff&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Given n, returns a function that computes the distance of its argument from n.&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Math/abs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;- &lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;n&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn &lt;/span&gt;&lt;span class="nv"&gt;sorter&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Returns a combiner function that sorts according to cost-fn.&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;cost-fn&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;new &lt;/span&gt;&lt;span class="nv"&gt;old&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sort-by &lt;/span&gt;&lt;span class="nv"&gt;cost-fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;concat &lt;/span&gt;&lt;span class="k"&gt;new &lt;/span&gt;&lt;span class="nv"&gt;old&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;diff&lt;/tt&gt; is a function generator like &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;finite-binary-tree&lt;/span&gt;&lt;/tt&gt;; it takes a target
number &lt;tt class="docutils literal"&gt;n&lt;/tt&gt; and returns a function that computes its parameter &lt;tt class="docutils literal"&gt;x&lt;/tt&gt;'s distance
from &lt;tt class="docutils literal"&gt;n&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;sorter&lt;/tt&gt; returns a function that serves as the &lt;tt class="docutils literal"&gt;combiner&lt;/tt&gt; for our
search, based on a cost function. This is done by concatenating the two lists
(successors of first state and the rest of the states) first, and then sorting
them by the cost function. &lt;tt class="docutils literal"&gt;sorter&lt;/tt&gt; is a powerful example of modeling with
higher-order functions.&lt;/p&gt;
&lt;p&gt;With these building blocks in place, we can define &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;best-first-search&lt;/span&gt;&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn &lt;/span&gt;&lt;span class="nv"&gt;best-first-search&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Search lowest cost states first until goal is reached.&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;goal?-fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;successors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;cost-fn&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tree-search&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list &lt;/span&gt;&lt;span class="nv"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;goal?-fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;successors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sorter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;cost-fn&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Once again, this is just like the earlier BFS and DFS - only the strategy
(&lt;tt class="docutils literal"&gt;combiner&lt;/tt&gt;) changes. Let's use it to find 9 again:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;paip.core=&amp;gt; (with-verbose (best-first-search 1 #(= % 9) (finite-binary-tree 15) (diff 9)))
;; Search: (1)
;; Search: (3 2)
;; Search: (7 6 2)
;; Search: (6 14 15 2)
;; Search: (12 13 14 15 2)
;; Search: (13 14 15 2)
;; Search: (14 15 2)
;; Search: (15 2)
;; Search: (2)
;; Search: (5 4)
;; Search: (10 11 4)
;; Search: (11 4)
;; Search: (4)
;; Search: (9 8)
9
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;While it finds the state eventually, we discover that our heuristic is not a
great match for this problem, as it sends the search astray. The goal of this
post is to demonstrate the power of higher-order functions in building modular
code, not to discover an optimal heuristic for searching in binary trees, though
:-)&lt;/p&gt;
&lt;p&gt;One last search variant before we're ready to wrap up. As we've seen with the
infinite tree, sometimes the search space is too large and we have to compromise
on which states to look at and which to ignore. This technique works
particularly well if the target is not some single value that we must find, but
rather we want to get a &amp;quot;good enough&amp;quot; result in a sea of bad options. We can
use a technique called &lt;em&gt;beam search&lt;/em&gt;; think of a beam of light a flashlight
produces in a very dark room; we can see what the beam points at, but not much
else.&lt;/p&gt;
&lt;p&gt;Beam search is somewhat similar to our &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;best-first-search&lt;/span&gt;&lt;/tt&gt;, but after combining
and sorting the list of states to explore, it only keeps the first N, where
N is given by the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;beam-width&lt;/span&gt;&lt;/tt&gt; parameter:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn &lt;/span&gt;&lt;span class="nv"&gt;beam-search&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Search highest scoring states first until goal is reached, but never consider&lt;/span&gt;
&lt;span class="s"&gt;  more than beam-width states at a time.&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;goal?-fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;successors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;cost-fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;beam-width&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tree-search&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list &lt;/span&gt;&lt;span class="nv"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;goal?-fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;successors&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;old&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;new&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;                 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;sorted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nf"&gt;sorter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;cost-fn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;old&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;                   &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;take &lt;/span&gt;&lt;span class="nv"&gt;beam-width&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;)))))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Once again, higher-order functions at play: as its &lt;tt class="docutils literal"&gt;combiner&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;beam-search&lt;/span&gt;&lt;/tt&gt;
creates an anonymous function that sorts the list based on &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;cost-fn&lt;/span&gt;&lt;/tt&gt;, and then
keeps only the first &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;beam-width&lt;/span&gt;&lt;/tt&gt; states on that list.&lt;/p&gt;
&lt;p&gt;Exercise: Try to run it - what beam width do you need to set in order to
successfully find 9 using our cost heuristic? How can this be improved?&lt;/p&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This post attempts a code-walkthrough approach to demonstrating the power of
higher-order functions. I always found this particular example from PAIP very
elegant; a particularly powerful insight is the distilled difference between
DFS and BFS. While most programmers intuitively understand the difference and
could write down the pseudo-code for both search strategies, modeling the
problem with higher-order functions lets us really get to the essence of the
difference - &lt;tt class="docutils literal"&gt;concat&lt;/tt&gt; vs. &lt;tt class="docutils literal"&gt;prepend&lt;/tt&gt; as the combiner step.&lt;/p&gt;
&lt;p&gt;See also: &lt;a class="reference external" href="https://eli.thegreenplace.net/2023/higher-order-functions-in-go/"&gt;this code sample ported to Go&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Clojure"></category><category term="Lisp"></category><category term="Programming"></category></entry><entry><title>Why coding interviews aren't all that bad</title><link href="https://eli.thegreenplace.net/2022/why-coding-interviews-arent-all-that-bad/" rel="alternate"></link><published>2022-03-19T07:07:00-07:00</published><updated>2022-10-04T14:08:24-07:00</updated><author><name>Eli Bendersky</name></author><id>tag:eli.thegreenplace.net,2022-03-19:/2022/why-coding-interviews-arent-all-that-bad/</id><summary type="html">&lt;p&gt;Coding interviews have never been popular in the programming community; I mean,
they are prevalent, since many companies still use them to filter
candidates, but they are vastly unpopular &lt;em&gt;in the community&lt;/em&gt; because people find
them too hard, too unfair, too unrepresentative of reality and so on. There are
viral …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Coding interviews have never been popular in the programming community; I mean,
they are prevalent, since many companies still use them to filter
candidates, but they are vastly unpopular &lt;em&gt;in the community&lt;/em&gt; because people find
them too hard, too unfair, too unrepresentative of reality and so on. There are
viral stories all around - like when the creator of Homebrew &lt;a class="reference external" href="https://www.quora.com/Whats-the-logic-behind-Google-rejecting-Max-Howell-the-author-of-Homebrew-for-not-being-able-to-invert-a-binary-tree/answer/Max-Howell?share=100e0bb6&amp;amp;srid=unBJ9"&gt;failed Google's
interviews&lt;/a&gt;
&lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In this post I want to make the case that coding interviews aren't all that bad.
They're certainly not a perfect way to filter candidates to SWE positions, but
they are among the best tools we've got. I've been interviewing folks for SWE
positions for almost 20 years now, and I regularly review hiring packets in
which all of the candidate's interviews are laid out with their results and
recommendations.&lt;/p&gt;
&lt;p&gt;First, what do I mean by &amp;quot;coding interviews&amp;quot;? Typically, writing some moderate
amount of code (in the order of 50 lines) throughout the interview, involving
nothing more than simple and fundamental data structures. Linked lists, graphs,
arrays, binary search trees at most. Many interviews will also examine one or
more horizontal aspects like recursion, parsing simple data, and discussing the
runtime and storage complexity and performance of simple algorithms.&lt;/p&gt;
&lt;img alt="Example of binary tree inversion, taken from educative.io" class="align-center" src="https://eli.thegreenplace.net/images/2022/invert-binary-tree.png" /&gt;
&lt;p&gt;To reiterate, the best coding interviews are relatively &lt;em&gt;simple&lt;/em&gt; questions that
don't require deep familiarity with advanced data structres like skip-lists or
quad-trees, but &lt;em&gt;do&lt;/em&gt; require a good understanding of programming fundamentals. I
don't ask candidates to &lt;a class="reference external" href="https://leetcode.com/problems/invert-binary-tree/"&gt;invert binary trees&lt;/a&gt;, but whoever does ask
this question most likely doesn't ask it because it is relevant to their work
projects; rather, they ask it because it probes the candidate's understanding of
recursion, basic data structures, debugging and careful treatment of pointers or
references (depending on the language).&lt;/p&gt;
&lt;div class="section" id="objectivity"&gt;
&lt;h2&gt;Objectivity&lt;/h2&gt;
&lt;p&gt;The reason I prefer asking coding questions is because I firmly believe they
are the most &lt;strong&gt;objective&lt;/strong&gt; way to evaluate candidates. Solving a coding question
removes &lt;em&gt;so many&lt;/em&gt; subjective factors, especially related to the candidate's
demographics and background, and the majority of the common
&lt;a class="reference external" href="https://www.groupmgmt.com/blog/post/seven-common-interview-biases/"&gt;interview biases&lt;/a&gt;.&lt;/p&gt;
&lt;img alt="Different interview biases" class="align-center" src="https://eli.thegreenplace.net/images/2022/interview-biases.png" /&gt;
&lt;p&gt;A good way to think about this is to consider one of the alternatives commonly
proposed to coding interviews: just &lt;em&gt;talk&lt;/em&gt; to the candidate, ask about their
background, their past job, the problems they were solving, etc. I actually have
an educational story related to this, from my own experience.&lt;/p&gt;
&lt;p&gt;Many years ago in a company far away, I was just starting to interview full-time
candidates (before that I only had experience interviewing interns). In that
company, we would do 90-minute interviews in pairs, and I was
paired with an experienced engineer. The candidate was &lt;em&gt;amazing&lt;/em&gt; at talking
about themselves and after 20 minutes I was convinced they should be accepted;
my experienced partner, however, calmly proceeded to asking a relatively simple
technical question (this was a HW design interview and the question had to do
with designing a basic low-pass filter). The candidate was immediately lost
and fumbled for an hour. I remember feeling shocked; how is it possible that
someone &lt;em&gt;obviously so good&lt;/em&gt; can't do something so basic?&lt;/p&gt;
&lt;p&gt;I keep encountering a variation of this scenario all the time, to this day, but
I'm rarely surprised any more. I learned that just talking to candidates is
a sure way to get an extremely biased view of their skills. I do think that in
a slate of 4-5 interviews it's important for one of them to be more personal
where the interviewer gets to know the candidate and tries to assess if they
are pleasant to work with. But the majority of the interviews have to objective
and technical, IMO.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="take-home-projects"&gt;
&lt;h2&gt;Take-home projects&lt;/h2&gt;
&lt;p&gt;Another commonly mentioned alternative to coding interviews is &lt;em&gt;take-home
projects&lt;/em&gt;, where the candidates get a sizable assignment to complete at home;
this assignment can take on the order of 2-20 hours and is meant to evaluate
the candidate on a much more realistic project than a 45-min coding interview.&lt;/p&gt;
&lt;p&gt;There are at least three major challenges with this approach.&lt;/p&gt;
&lt;p&gt;First of all, candidates don't like these - since they may take a long time to
finish, and this doesn't scale well when you're applying to multiple jobs. Many
strong candidates will be put off by the requirement to spend multiple hours on
a programming assignment; since finding strong candidates is one of the biggest
challenges companies face, this is an important factor.&lt;/p&gt;
&lt;p&gt;Second, these assignments are a burden on the hiring team as well, since they
take a long time to prepare and evaluate. While this may not be a problem for
&amp;quot;one off&amp;quot; hiring quests, anyone who's familiar with the plight of SWEs who
have to spend lots of time on interviews and hiring while also doing their
daily jobs because the company is in a growth spurt, will recognize this issue.&lt;/p&gt;
&lt;p&gt;Third and most important - these assignments are very vulnerable to cheating,
and cheating is &lt;em&gt;absolutely rampant&lt;/em&gt; in the industry. These days it's easy to
encounter cheating even on mainstream, open platforms &lt;a class="reference external" href="https://web.archive.org/web/20220219014945/https://www.reddit.com/r/golang/comments/svyem0/go_app_in_gcp_cloud_function_using_terraform/"&gt;like Reddit&lt;/a&gt;,
not to mention dedicated services like LeetCode or &amp;quot;code for hire&amp;quot; services
where candidates can buy solutions for money. This isn't a new problem, either.
When I was &lt;a class="reference external" href="https://eli.thegreenplace.net/2007/07/14/more-about-my-rentacoder-experiment"&gt;toying with RentACoder 15 years ago&lt;/a&gt;
the majority of projects I ended up doing were either homework or &amp;quot;do my work
for me&amp;quot; assignments.&lt;/p&gt;
&lt;p&gt;Now, coding questions are vulnerable to a kind of &amp;quot;cheating&amp;quot; as well - for
example, you can probably find 95% of the questions Google asks on LeetCode
these days, but this is very different IMHO. Sure, you can engorge yourself by
reading hundreds of questions &amp;amp; answers on LeetCode, and come to the interview
ready. But in my experience, when you're faced with an interview you're still on
your own and the interviewer can typically ask tangential questions that will
expose someone who just memorized the answers. Enforcement is much harder
with take-home exercises.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="diversity"&gt;
&lt;h2&gt;Diversity&lt;/h2&gt;
&lt;p&gt;Lately I've seen a lot of discussion about how the way interviews are currently
done is bad for diversity. The issue of diversity is very important for the
industry, for sure. I agree that companies should be seeking ways to diversify
their work force - and how to do this properly is a genuinely hard question that
is an active area of academic research.&lt;/p&gt;
&lt;p&gt;That said, IMHO coding interviews are a significantly &lt;em&gt;more&lt;/em&gt;
objective way to evaluate candidates from the diversity standpoint.
The &amp;quot;tell me about yourself&amp;quot; style of interviews is extremely subjective and
open to bias. What better way to activate the automatic rapport people feel
for someone with background similar to theirs, and the ingrained lizard-brain
antagonism to anyone different?&lt;/p&gt;
&lt;p&gt;&amp;quot;Take-home projects&amp;quot; are much more subjective too, because guess what - the
cheating is much more available for someone with the money already. As &lt;a class="reference external" href="https://stanforddaily.com/2021/03/18/former-sailing-coach-implicates-athletics-director-in-college-admissions-scandal/"&gt;recent
exposés&lt;/a&gt;
demonstrate, when important life-long goals are in play, people will do whatever
in their power to get ahead. Folks from a poor background looking for their
first high-paying job will not be in a position to pop a $1,000 for some
off-shore programmer to solve their take-home question, but someone else would.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Hopefully this post clarifies why I think that coding interviews, while not
perfect, are an important objective evaluation technique for hiring SWEs. I'm
not saying that coding interviews should be the only criterion, only that they
are an important signal that should not be dropped from any hiring process.&lt;/p&gt;
&lt;p&gt;To paraphrase a well-worn &lt;a class="reference external" href="https://winstonchurchill.org/resources/quotes/the-worst-form-of-government/"&gt;Churchill quote&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
Indeed it has been said that a coding interview is the worst form of
interview except for all those other forms that have been tried from
time to time&lt;/blockquote&gt;
&lt;p&gt;All of this is IMHO, of course, and I only speak for myself here.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update (2022-03-29)&lt;/strong&gt;: shortly after this post went out, MIT published an
&lt;a class="reference external" href="https://mitadmissions.org/blogs/entry/we-are-reinstating-our-sat-act-requirement-for-future-admissions-cycles/"&gt;article on reinstating standardized scores&lt;/a&gt;
(SAT/ACT) as &lt;em&gt;one of criteria&lt;/em&gt; for admission. I think it's a very well-written,
evidence-driven and thoughtful article. They explicitly state that such tests,
while not perfect, provide a more objective criteria for under-represented
groups' admissions than other criteria in use. They also provide evidence on how
these tests are predictive of students' success in MIT. I found it to be a very
nice parallel for this post.&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-1"&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Clearly, this could be a problem in the process
but (1) we don't know the full details of the case here and (2) this is a
&lt;em&gt;very rare&lt;/em&gt; occurrence compared to the tens of thousands of SWE
interviews that are conducted every day. The odds of
J-random-SWE-candidate being the primary author of well-known SW are
extremely low, and the vast majority of these candidates will easily
solve typical coding questions. The remaining probability of &lt;tt class="docutils literal"&gt;P(fail |
eminent programmer)&lt;/tt&gt; is negligible.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Programming"></category></entry><entry><title>Building binary trees from inorder-depth lists</title><link href="https://eli.thegreenplace.net/2022/building-binary-trees-from-inorder-depth-lists/" rel="alternate"></link><published>2022-02-05T06:33:00-08:00</published><updated>2024-05-04T19:46:23-07:00</updated><author><name>Eli Bendersky</name></author><id>tag:eli.thegreenplace.net,2022-02-05:/2022/building-binary-trees-from-inorder-depth-lists/</id><summary type="html">&lt;p&gt;I ran into an interesting algorithm while hacking on &lt;a class="reference external" href="https://adventofcode.com/"&gt;Advent of Code&lt;/a&gt; a while ago. This post is a summary of what I've
learned.&lt;/p&gt;
&lt;p&gt;Consider a binary tree that represents nested pairs, where each pair consists
of two kinds of elements: a number, or another pair. For example, the nested …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I ran into an interesting algorithm while hacking on &lt;a class="reference external" href="https://adventofcode.com/"&gt;Advent of Code&lt;/a&gt; a while ago. This post is a summary of what I've
learned.&lt;/p&gt;
&lt;p&gt;Consider a binary tree that represents nested pairs, where each pair consists
of two kinds of elements: a number, or another pair. For example, the nested
pair &lt;tt class="docutils literal"&gt;((6 9) ((3 4) 2))&lt;/tt&gt; is represented with this tree &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt;:&lt;/p&gt;
&lt;img alt="Binary tree with depth marks" class="align-center" src="https://eli.thegreenplace.net/images/2022/depthtree-plain.png" /&gt;
&lt;p&gt;&lt;em&gt;(ignore the numbered lines on the right for now, we'll get to them shortly)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Trees representing such pairs have the following characteristics:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Leaves hold numbers, while internal nodes don't hold numbers, but only
pointers to child nodes.&lt;/li&gt;
&lt;li&gt;Each node in the tree has either 0 or 2 children.&lt;/li&gt;
&lt;li&gt;A non-empty tree has at least one internal node.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While looking for alternative solutions to a
problem, I ran across &lt;a class="reference external" href="https://github.com/timvisee/"&gt;Tim Visée&lt;/a&gt;'s Rust
solution which uses an interesting representation of this tree. It's represented
by an in-order traversal of the tree, with a list of &lt;tt class="docutils literal"&gt;(value depth)&lt;/tt&gt; pairs
where &lt;tt class="docutils literal"&gt;value&lt;/tt&gt; is a leaf value and &lt;tt class="docutils literal"&gt;depth&lt;/tt&gt; is its depth in the tree. The
depth starts from 0 at the root - this is what the numbered lines in the diagram
above represent.&lt;/p&gt;
&lt;p&gt;For our sample tree, the inorder-depth representation is as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(6 2) (9 2) (3 3) (4 3) (2 2)
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The surprising realization (at least for me) is that the original tree can
be reconstructed from this representation! Note that it's just a list of leaf
values - the internal nodes are not specified. It's well known that we can't
reconstruct a tree just from its in-order traversal, but a combination of the
added depth markers and the restrictions on the tree make it possible.&lt;/p&gt;
&lt;p&gt;I'll present a recursive algorithm to reconstruct the tree (based on Tim Visée's
code, which does not explicitly rebuild the tree but computes something on it);
this algorithm is very clever and isn't easy to grok. Then, I'll present an
iterative algorithm which IMHO is easier to understand and explain.&lt;/p&gt;
&lt;p&gt;But first, let's start with the data structures. The full (Go) code is available
&lt;a class="reference external" href="https://github.com/eliben/code-for-blog/tree/main/2022/depthtree"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;Value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;Depth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DList&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;DItem&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is our representation of the inorder-depth list - a slice of &lt;tt class="docutils literal"&gt;DItem&lt;/tt&gt;
values, each of which has a numeric value and depth.&lt;/p&gt;
&lt;p&gt;The tree itself is just what you'd expect in Go:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;Value&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;Left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="recursive-algorithm"&gt;
&lt;h2&gt;Recursive algorithm&lt;/h2&gt;
&lt;p&gt;Here is the recursive version of the tree reconstruction algorithm:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DList&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BuildTreeRec&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;builder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;builder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dl&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;Depth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dl&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dl&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;Depth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dl&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I find this algorithm fairly tricky to understand; the combination of double
recursion with mutable state is powerful. Some tips:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;cursor&lt;/tt&gt; represents the next item in the inorder-depth list; it may
help thinking of it as a queue; taking &lt;tt class="docutils literal"&gt;dl[cursor]&lt;/tt&gt; and advancing &lt;tt class="docutils literal"&gt;cursor&lt;/tt&gt;
is akin to popping from the head of the queue.&lt;/li&gt;
&lt;li&gt;The &lt;tt class="docutils literal"&gt;depth&lt;/tt&gt; parameter represents the depth in the tree the builder is
currently on. If the next item in the queue has a matching depth, we construct
a leaf from it. Otherwise, we recurse with higher depth to construct an
internal node starting from it.&lt;/li&gt;
&lt;li&gt;The basic recursive invariant for &lt;tt class="docutils literal"&gt;builder&lt;/tt&gt; is: the remaining items in
&lt;tt class="docutils literal"&gt;dl&lt;/tt&gt; represent a pair: build its left side, then build its right side.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If it's still not 100% clear, that's OK. In what follows, I'll describe an
alternative formulation of this algorithm - without recursion. IMHO this version
is easier to follow, and once one gets it - it's also easier to understand the
recursive approach.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="iterative-algorithm"&gt;
&lt;h2&gt;Iterative algorithm&lt;/h2&gt;
&lt;p&gt;To get some intuition for how the algorithm works, let's first work through the
example we've using throughout this post. We'll take the inorder-depth
representation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(6 2) (9 2) (3 3) (4 3) (2 2)
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And will see how to construct a tree from it, step by step. In what follows, the
numbered list walks through inserting the first 6 child nodes into the tree,
and the steps correspond one-to-one to the diagrams below the list. Each step
of the algorithm inserts one node into the tree (either an internal node or
a leaf node with the value). The red &amp;quot;pointer&amp;quot; in the diagrams corresponds to
the node inserted by each step.&lt;/p&gt;
&lt;p&gt;Let's assume we begin with the root node already created.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;To insert &lt;tt class="docutils literal"&gt;(6 2)&lt;/tt&gt; we have to get to depth 2. The children of the root node
would be at depth 1, so we have to create a new internal node first. Since
the list is in-order, we create the left child first and move our pointer to
it.&lt;/li&gt;
&lt;li&gt;Now our current node's children are depth 2, so we can insert &lt;tt class="docutils literal"&gt;(6 2)&lt;/tt&gt;.
Since the current node has no left child, we insert 6 as its left child.&lt;/li&gt;
&lt;li&gt;The next node to insert is &lt;tt class="docutils literal"&gt;(9 2)&lt;/tt&gt;. The node we've just inserted is a leaf,
so we backtrack to its parent. Its children are depth two, and it has no
right child, so we insert 9 as its right child.&lt;/li&gt;
&lt;li&gt;The next node to insert is &lt;tt class="docutils literal"&gt;(3 3)&lt;/tt&gt;. The current node is a leaf so it can't
have children; we climb up to the parent, which already has both its children
links created. So we climb up again, to the root. The root has a left child,
but no right child. We create the right child.&lt;/li&gt;
&lt;li&gt;Since the current node's children are depth 2, we can't insert &lt;tt class="docutils literal"&gt;(3 3)&lt;/tt&gt; yet.
The current node has no left child, so we create it and move into it.&lt;/li&gt;
&lt;li&gt;The current node's children are depth 3, so we can insert 3 as its left
child.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And so on, until we proceed to insert all the values.&lt;/p&gt;
&lt;img alt="Six steps constructing a binary tree" class="align-center" src="https://eli.thegreenplace.net/images/2022/depthtree-steps.png" /&gt;
&lt;p&gt;The main thing to notice here is that the insertion follows a strict in-order.
We go left as far as possible, then backtrack through the parent and turn right.
How much is &amp;quot;possible&amp;quot; is determined by the depth markers in the representation,
so there's actually no ambiguity &lt;a class="footnote-reference" href="#footnote-2" id="footnote-reference-2"&gt;[2]&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Before we move on to the code, one important point about reaching a parent
from a given node. There are at least two common ways to do this: one is keeping
parent links in the nodes, and another is using a stack of parents while
constructing the tree. In the code shown below, I opt for the second option -
an explicit stack of parent nodes. This code can be easily rewritten with parent
links instead (try it as an exercise!)&lt;/p&gt;
&lt;p&gt;With all that in place, the code shouldn't be hard to understand; here it is,
with copious comments:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;// BuildTree builds a Tree from a DList using an iterative algorithm.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DList&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BuildTree&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// result is the tree this function is building. The result pointer always&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// points at the root, so we can return it to the caller. t points to the&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// current node being constructed throughout the algorithm.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// depth is the current depth of t&amp;#39;s children.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// stack of parent nodes to implement backtracking up the tree once we&amp;#39;re done&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// with a subtree.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// The outer loop iterates over all the items in a DList, inserting each one&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// into the tree. Loop invariant: all items preceding this item in dl have&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// already been inserted into the tree, and t points to the node where the&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// last insertion was made.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="nx"&gt;nextItem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// The inner loop find the right place for item in the tree and performs&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// insertion.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Loop invariant: t points at the node where we&amp;#39;re trying to insert, depth&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// is the depth of its children and stack holds a stack of t&amp;#39;s parents.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// Check if item can be inserted as a child of t; this can be done only if&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// our depth matches the item&amp;#39;s and t doesn&amp;#39;t have both its children yet.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// Otherwise, t is not the right place and we have to keep looking.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Depth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nextItem&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Depth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nextItem&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// We can&amp;#39;t insert at t.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// * If t does not have a left child yet, create it and repeat loop with&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;//   this left child as t.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// * If t does not have a right child yet, create it and repeat loop with&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;//   this right child as t.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// * If t has both children, we have to backtrack up the tree to t&amp;#39;s&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;//   parent.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Left&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Right&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Pop from the stack to make t point to its parent&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="final-words"&gt;
&lt;h2&gt;Final words&lt;/h2&gt;
&lt;p&gt;If you take some time to convince yourself that the iterative algorithm works,
it becomes easier to understand the recursive one... because it's doing the
exact same thing! The loops are replaced by recursion; the explicit parent stack
is replaced by an implicit call stack of the recursive function, but otherwise -
it's the same algorithm &lt;a class="footnote-reference" href="#footnote-3" id="footnote-reference-3"&gt;[3]&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Finally, some credits are due. Thanks to my wife for helping me come up with the
iterative formulation of the algorithm. Thanks to Tim Visée for the inspiration
for this post.&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-1"&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Note that this is not a binary &lt;em&gt;search&lt;/em&gt; tree; the order of values in the
leaves is entirely arbitrary.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-2" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-2"&gt;[2]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;One way the algorithm avoids ambiguity is by requiring that nodes in the
tree have either no children or two children. Nodes with one child would
confuse the algorithm; can you see why?&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-3" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-3"&gt;[3]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Here is an exercise: the code of the iterative algorithm is currently
structured to ease understanding, but what happens if we merge the
conditions of &lt;tt class="docutils literal"&gt;t.Left == nil&lt;/tt&gt;, checking it in just one place and
then either inserting (if the depth matches) or keep looking; and the
same for &lt;tt class="docutils literal"&gt;t.Right&lt;/tt&gt;. If you make these changes the algorithm will still
work (feel free to use the tests in the accompanying code), and it starts
resembling the recursive version even more.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Programming"></category><category term="Go"></category></entry><entry><title>Asimov, programming and the meta ladder</title><link href="https://eli.thegreenplace.net/2022/asimov-programming-and-the-meta-ladder/" rel="alternate"></link><published>2022-01-22T17:02:00-08:00</published><updated>2022-12-15T13:27:28-08:00</updated><author><name>Eli Bendersky</name></author><id>tag:eli.thegreenplace.net,2022-01-22:/2022/asimov-programming-and-the-meta-ladder/</id><summary type="html">&lt;p&gt;One of my favorite stories by Isaac Asimov is &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Profession_(novella)"&gt;Profession&lt;/a&gt;. The following is a
spoiler, so please read the story before proceeding if you don't like spoilers.&lt;/p&gt;
&lt;p&gt;In the futuristic society of year 6000-something, people no longer need to learn
their profession from books, lectures or hands-on experience. Each person …&lt;/p&gt;</summary><content type="html">&lt;p&gt;One of my favorite stories by Isaac Asimov is &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Profession_(novella)"&gt;Profession&lt;/a&gt;. The following is a
spoiler, so please read the story before proceeding if you don't like spoilers.&lt;/p&gt;
&lt;p&gt;In the futuristic society of year 6000-something, people no longer need to learn
their profession from books, lectures or hands-on experience. Each person has
their brain analyzed at a certain age and then the know-how for the occupation
that's best suited for them is simply uploaded into the brain using special
cassettes (hey, this story is 60 years old) and electrodes. The folks who end up
the best at their craft (determined via competitions) end up with high-demand
assignments on &amp;quot;Class A&amp;quot; outer worlds.&lt;/p&gt;
&lt;p&gt;The protagonist, George Platen, has a dream of getting &amp;quot;educated&amp;quot; in a certain
profession and reaching a desirable assignment. But he runs into trouble when
his brain assessment determines that no profession is a good fit for him, and
he's placed in a special &amp;quot;house for the feeble-minded&amp;quot; to spend his time however
he wants, even reading books.&lt;/p&gt;
&lt;p&gt;Long story short, after some adventures George discovers the truth on his own;
someone has to create these training cassettes, advance human technology and
update training materials to account for these advances. There's a
&amp;quot;meta-profession&amp;quot;, something akin to scientist, and George was selected for this
meta-profession.&lt;/p&gt;
&lt;p&gt;I always loved this story for the meta aspect; many occupations are prone to
automation, and this has become much more true since Asimov first put the plot
to paper. But some human professions are necessarily &amp;quot;meta&amp;quot;; you can automate
them, but this just generates new professions that have to develop said
automation. Ad infinitum, or at least until &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Technological_singularity"&gt;Singularity&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the course of my career, I've heard the promises of &amp;quot;no code&amp;quot; programming
many times. These tools didn't cause the demand for programmers to plummet, but
to simply shift in other directions. More recently, I treat the hype about AI
coding assistants like &lt;a class="reference external" href="https://en.wikipedia.org/wiki/GitHub_Copilot"&gt;GitHub Copilot&lt;/a&gt; with similar calm. These are
great tools that are going to make some programmers' lives easier, but replace
programmers? Nope; only move programmers another notch up the meta ladder.&lt;/p&gt;
&lt;p&gt;By the way, do you know what profession George Platen was aiming at before he
knew the truth? Computer programmer. A far-sighted move by Asimov, given that
the story was written in 1957!&lt;/p&gt;
</content><category term="misc"></category><category term="Programming"></category><category term="Philosophical"></category></entry></feed>