<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://aarondilley.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://aarondilley.com/" rel="alternate" type="text/html" /><updated>2026-04-07T11:08:19+00:00</updated><id>https://aarondilley.com/feed.xml</id><title type="html">Aaron Dilley</title><subtitle>Coding and math musings</subtitle><entry><title type="html">Regular Expressions</title><link href="https://aarondilley.com/regex/2025/06/18/regular-expressions.html" rel="alternate" type="text/html" title="Regular Expressions" /><published>2025-06-18T13:00:00+00:00</published><updated>2025-06-18T13:00:00+00:00</updated><id>https://aarondilley.com/regex/2025/06/18/regular-expressions</id><content type="html" xml:base="https://aarondilley.com/regex/2025/06/18/regular-expressions.html"><![CDATA[<h1 id="regular-expressions">Regular Expressions</h1>

<h2 id="what-regex-is-good-for">What Regex Is Good For</h2>

<p>Regex is a way of identifying a class of strings according to a template. There are many times more than a strict equality check is needed. Regex can help. For instance, if we wanted to check to see if a string might be a phone number we could do:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// is string in 555-555-5555 format</span>
<span class="kd">const</span> <span class="nx">isPhone</span> <span class="o">=</span> <span class="p">(</span><span class="nx">str</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="sr">/^</span><span class="se">\d{3}</span><span class="sr">-</span><span class="se">\d{3}</span><span class="sr">-</span><span class="se">\d{4}</span><span class="sr">$/</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">str</span><span class="p">);</span>
</code></pre></div></div>

<p>Trying to achieve the above without regex is needlessly messy. Regex also makes modifying the template easier. Say we wanted our <code class="language-plaintext highlighter-rouge">isPhone</code> function to also recognize strings without the dashes:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// is string in 555-555-5555/5555555555 format</span>
<span class="kd">const</span> <span class="nx">isPhone</span> <span class="o">=</span> <span class="p">(</span><span class="nx">str</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="sr">/^</span><span class="se">\d{3}(</span><span class="sr">-</span><span class="se">?)\d{3}\1\d{4}</span><span class="sr">$/</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">str</span><span class="p">);</span>
</code></pre></div></div>

<p>Say we wanted to relax the delimiter to be a period or a space. No problem:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// is string in</span>
<span class="c1">// 555-555-5555/555.555.5555/555 555 5555/5555555555 format</span>
<span class="kd">const</span> <span class="nx">isPhone</span> <span class="o">=</span> <span class="p">(</span><span class="nx">str</span><span class="p">)</span> <span class="o">=&gt;</span>
  <span class="sr">/^</span><span class="se">\d{3}([</span><span class="sr">-.</span><span class="se">\s]?)\d{3}\1\d{4}</span><span class="sr">$/</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">str</span><span class="p">);</span>
</code></pre></div></div>

<p>Regex can also consolidate the number of passes over a given string. Here’s one example of a function that aims to extract the attribute name from a data attribute CSS pseudo-selector. The regex-free version:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// [data-test_attribute] -&gt; test_attribute</span>
<span class="kd">const</span> <span class="nx">getDataAttributeKey</span> <span class="o">=</span> <span class="p">(</span><span class="nx">selector</span><span class="p">)</span> <span class="o">=&gt;</span>
  <span class="nx">selector</span>
    <span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="dl">"</span><span class="s2">[data-</span><span class="dl">"</span><span class="p">,</span> <span class="dl">""</span><span class="p">)</span>
    <span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="dl">"</span><span class="s2">]</span><span class="dl">"</span><span class="p">,</span> <span class="dl">""</span><span class="p">);</span>
</code></pre></div></div>

<p>This works, but involves three passes over the input string. Now with regex:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// [data-test_attribute] -&gt; test_attribute</span>
<span class="kd">const</span> <span class="nx">getDataAttributeKey</span> <span class="o">=</span> <span class="p">(</span><span class="nx">selector</span><span class="p">)</span> <span class="o">=&gt;</span>
  <span class="nx">selector</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/</span><span class="se">\[</span><span class="sr">data-</span><span class="se">([^\]]</span><span class="sr">+</span><span class="se">)\]</span><span class="sr">/</span><span class="p">,</span> <span class="dl">"</span><span class="s2">$1</span><span class="dl">"</span><span class="p">);</span>
</code></pre></div></div>

<p>The regex isn’t easier to read, but we’ve cut down the number of passes from 2 to 1.</p>

<p>In general, the clearer the template the easier it is to write the regex. The following are good use cases for regex:</p>
<ul>
  <li>substring existence check (no more <code class="language-plaintext highlighter-rouge">indexOf(str) !== -1</code>)</li>
  <li>short string pattern matching</li>
  <li>enum pattern matching (<code class="language-plaintext highlighter-rouge">/(red|blue|yellow)/</code>)</li>
  <li>multiple match parsing</li>
</ul>

<h2 id="what-regex-is-not-good-for">What Regex Is Not Good For</h2>

<p>With great power… etc, etc, etc.</p>

<p>At first people tend to avoid regex because the syntax is difficult to read or write. However, the bigger problems often arise from those (like myself) who try to solve every problem with regex. And it’s easy to understand why that’s so tempting. Consider:</p>

<p><strong>Prime string length</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">^(?!(..+)\1+$)</code></li>
  <li>Matches: x, xx, xxx, xxxxx, xxxxxxx, …</li>
  <li>Doesn’t match: xxxx, xxxxxx, xxxxxxxx, …</li>
</ul>

<p><strong>Powers of 2</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">^(?!((..)+.)\1*$)</code></li>
  <li>Matches: x, xx, xxxx, xxxxxxxx, …</li>
  <li>Doesn’t match: xxx, xxxxx, xxxxxx, …</li>
</ul>

<p><strong>Binary Divisibility by 3</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">^(0|1(01*0)*1)*$</code></li>
  <li>Matches: 0, 11, 110, 1001, …</li>
  <li>Doesn’t match: 1, 10, 100, 101, 111, 1000, …</li>
</ul>

<p>But there are many instances where regex quickly falls apart. One of the more common cases is using regex to validate or match HTML. Under ideal circumstances this might not be so bad, but HTML is almost always malformed. The browser does a lot of heavy lifting to fill in the gaps when certain tags that should be closed aren’t. Or when tags contain certain unescaped characters.</p>

<p>Regex also struggles with larger texts spanning multiple lines. Consider the <a href="https://vimeo.com/112065252">catastrophic backtracking</a> risk. Innocent assumptions can bring a regex engine to a crawl.</p>

<h2 id="regex-readability-and-testing">Regex Readability and Testing</h2>

<p>A common complaint levied against regex is how unreadable it is. There is no real counterargument. Regex can be parsed by those familiar with it, but there’s no guarantee you’ll be able to catch errors in the regex just by looking at it. For example, consider the RFC Standard regex for email validation:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">EMAIL_REGEX</span> <span class="o">=</span> <span class="sr">/</span><span class="se">(?:[</span><span class="sr">a-z0-9!#$%&amp;'*+</span><span class="se">/</span><span class="sr">=?^_`{|}~-</span><span class="se">]</span><span class="sr">+</span><span class="se">(?:\.[</span><span class="sr">a-z0-9!#$%&amp;'*+</span><span class="se">/</span><span class="sr">=?^_`{|}~-</span><span class="se">]</span><span class="sr">+</span><span class="se">)</span><span class="sr">*|"</span><span class="se">(?:[\x</span><span class="sr">01-</span><span class="se">\x</span><span class="sr">08</span><span class="se">\x</span><span class="sr">0b</span><span class="se">\x</span><span class="sr">0c</span><span class="se">\x</span><span class="sr">0e-</span><span class="se">\x</span><span class="sr">1f</span><span class="se">\x</span><span class="sr">21</span><span class="se">\x</span><span class="sr">23-</span><span class="se">\x</span><span class="sr">5b</span><span class="se">\x</span><span class="sr">5d-</span><span class="se">\x</span><span class="sr">7f</span><span class="se">]</span><span class="sr">|</span><span class="se">\\[\x</span><span class="sr">01-</span><span class="se">\x</span><span class="sr">09</span><span class="se">\x</span><span class="sr">0b</span><span class="se">\x</span><span class="sr">0c</span><span class="se">\x</span><span class="sr">0e-</span><span class="se">\x</span><span class="sr">7f</span><span class="se">])</span><span class="sr">*"</span><span class="se">)</span><span class="sr">@</span><span class="se">(?:(?:[</span><span class="sr">a-z0-9</span><span class="se">](?:[</span><span class="sr">a-z0-9-</span><span class="se">]</span><span class="sr">*</span><span class="se">[</span><span class="sr">a-z0-9</span><span class="se">])?\.)</span><span class="sr">+</span><span class="se">[</span><span class="sr">a-z0-9</span><span class="se">](?:[</span><span class="sr">a-z0-9-</span><span class="se">]</span><span class="sr">*</span><span class="se">[</span><span class="sr">a-z0-9</span><span class="se">])?</span><span class="sr">|</span><span class="se">\[(?:(?:</span><span class="sr">25</span><span class="se">[</span><span class="sr">0-5</span><span class="se">]</span><span class="sr">|2</span><span class="se">[</span><span class="sr">0-4</span><span class="se">][</span><span class="sr">0-9</span><span class="se">]</span><span class="sr">|</span><span class="se">[</span><span class="sr">01</span><span class="se">]?[</span><span class="sr">0-9</span><span class="se">][</span><span class="sr">0-9</span><span class="se">]?)\.){3}(?:</span><span class="sr">25</span><span class="se">[</span><span class="sr">0-5</span><span class="se">]</span><span class="sr">|2</span><span class="se">[</span><span class="sr">0-4</span><span class="se">][</span><span class="sr">0-9</span><span class="se">]</span><span class="sr">|</span><span class="se">[</span><span class="sr">01</span><span class="se">]?[</span><span class="sr">0-9</span><span class="se">][</span><span class="sr">0-9</span><span class="se">]?</span><span class="sr">|</span><span class="se">[</span><span class="sr">a-z0-9-</span><span class="se">]</span><span class="sr">*</span><span class="se">[</span><span class="sr">a-z0-9</span><span class="se">]</span><span class="sr">:</span><span class="se">(?:[\x</span><span class="sr">01-</span><span class="se">\x</span><span class="sr">08</span><span class="se">\x</span><span class="sr">0b</span><span class="se">\x</span><span class="sr">0c</span><span class="se">\x</span><span class="sr">0e-</span><span class="se">\x</span><span class="sr">1f</span><span class="se">\x</span><span class="sr">21-</span><span class="se">\x</span><span class="sr">5a</span><span class="se">\x</span><span class="sr">53-</span><span class="se">\x</span><span class="sr">7f</span><span class="se">]</span><span class="sr">|</span><span class="se">\\[\x</span><span class="sr">01-</span><span class="se">\x</span><span class="sr">09</span><span class="se">\x</span><span class="sr">0b</span><span class="se">\x</span><span class="sr">0c</span><span class="se">\x</span><span class="sr">0e-</span><span class="se">\x</span><span class="sr">7f</span><span class="se">])</span><span class="sr">+</span><span class="se">)\])</span><span class="sr">/</span><span class="p">;</span>
</code></pre></div></div>

<p>No one should be expected to validate this on sight. Instead we should test these with known pass/fail cases:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">assert</span><span class="p">(</span>
  <span class="nx">EMAIL_REGEX</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="dl">"</span><span class="s2">aaron@aarondilley.com</span><span class="dl">"</span><span class="p">),</span>
  <span class="kc">true</span><span class="p">,</span>
<span class="p">);</span>

<span class="nx">assert</span><span class="p">(</span>
  <span class="nx">EMAIL_REGEX</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="dl">"</span><span class="s2">aaron@aarondilley@com</span><span class="dl">"</span><span class="p">),</span>
  <span class="kc">false</span><span class="p">,</span>
<span class="p">);</span>
</code></pre></div></div>

<p>We should avoid writing a regex as complex as this from scratch where possible. But regardless of the origin, we should have coverage for all regex authored.</p>

<h2 id="regex-basics">Regex Basics</h2>

<h3 id="character-ranges">Character ranges</h3>

<ul>
  <li><code class="language-plaintext highlighter-rouge">[abc]</code> - matches any of <code class="language-plaintext highlighter-rouge">a</code>, <code class="language-plaintext highlighter-rouge">b</code>, or <code class="language-plaintext highlighter-rouge">c</code></li>
  <li><code class="language-plaintext highlighter-rouge">[^abc]</code> - matches any character not <code class="language-plaintext highlighter-rouge">a</code>, <code class="language-plaintext highlighter-rouge">b</code>, or <code class="language-plaintext highlighter-rouge">c</code></li>
  <li><code class="language-plaintext highlighter-rouge">[a-z]</code> - matches any lowercase character between <code class="language-plaintext highlighter-rouge">a</code> and <code class="language-plaintext highlighter-rouge">z</code></li>
  <li><code class="language-plaintext highlighter-rouge">[^a-z]</code> - matches any character not in the range from  <code class="language-plaintext highlighter-rouge">a</code> to <code class="language-plaintext highlighter-rouge">z</code></li>
  <li><code class="language-plaintext highlighter-rouge">[a-zA-Z]</code> - matchse any character between <code class="language-plaintext highlighter-rouge">a</code> and <code class="language-plaintext highlighter-rouge">z</code> or between <code class="language-plaintext highlighter-rouge">A</code> and <code class="language-plaintext highlighter-rouge">Z</code></li>
</ul>

<h3 id="single-tokens">Single tokens</h3>

<ul>
  <li><code class="language-plaintext highlighter-rouge">.</code> - matches any character</li>
  <li><code class="language-plaintext highlighter-rouge">\s</code> - matches any whitespace character (like a space)</li>
  <li><code class="language-plaintext highlighter-rouge">\S</code> - matches any non-whitespace character</li>
  <li><code class="language-plaintext highlighter-rouge">\d</code> - matches any digit, same as <code class="language-plaintext highlighter-rouge">[0-9]</code></li>
  <li><code class="language-plaintext highlighter-rouge">\D</code> - matches any non-digit, same as <code class="language-plaintext highlighter-rouge">[^0-9]</code></li>
  <li><code class="language-plaintext highlighter-rouge">\w</code> - matches any word character, same as <code class="language-plaintext highlighter-rouge">[a-zA-Z0-9_]</code></li>
  <li><code class="language-plaintext highlighter-rouge">\W</code> - matches any non-word character, same as <code class="language-plaintext highlighter-rouge">[^a-zA-Z0-9_]</code></li>
  <li><code class="language-plaintext highlighter-rouge">\n</code> - matches newline</li>
  <li><code class="language-plaintext highlighter-rouge">\r</code> - matches carriage return</li>
  <li><code class="language-plaintext highlighter-rouge">\t</code> - matches tab</li>
</ul>

<h3 id="group-constructs">Group constructs</h3>

<ul>
  <li><code class="language-plaintext highlighter-rouge">(…)</code> - matches anything in parens, reference-able later</li>
  <li><code class="language-plaintext highlighter-rouge">(foo|bar)</code> - matches <code class="language-plaintext highlighter-rouge">foo</code> or <code class="language-plaintext highlighter-rouge">bar</code></li>
</ul>

<h3 id="non-capturing-group-constructs">Non-capturing group constructs</h3>

<ul>
  <li><code class="language-plaintext highlighter-rouge">(?:...)</code> - matches anything in parens, not reference-able later</li>
  <li><code class="language-plaintext highlighter-rouge">(?=...)</code> - positive lookahead</li>
  <li><code class="language-plaintext highlighter-rouge">(?!...)</code> - negative lookahead</li>
  <li><code class="language-plaintext highlighter-rouge">(?&lt;=...)</code> - positive lookbehind</li>
  <li><code class="language-plaintext highlighter-rouge">(?&lt;!...)</code> - negative lookbehind</li>
</ul>

<h3 id="quantifiers">Quantifiers</h3>

<ul>
  <li><code class="language-plaintext highlighter-rouge">a?</code> - matches 0 or 1 <code class="language-plaintext highlighter-rouge">a</code></li>
  <li><code class="language-plaintext highlighter-rouge">a*</code> - matches 0 or more repeated <code class="language-plaintext highlighter-rouge">a</code>
    <ul>
      <li>greedy match</li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">a*?</code> - matches 0 or more repeated <code class="language-plaintext highlighter-rouge">a</code>
    <ul>
      <li>lazy match</li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">a+</code> - matches 1 or more repeated <code class="language-plaintext highlighter-rouge">a</code></li>
  <li><code class="language-plaintext highlighter-rouge">a{3}</code> - matches <code class="language-plaintext highlighter-rouge">aaa</code></li>
  <li><code class="language-plaintext highlighter-rouge">a{3,}</code> - matches 3 or more repeated <code class="language-plaintext highlighter-rouge">a</code></li>
  <li><code class="language-plaintext highlighter-rouge">a{3,6}</code> - matches 3 to 6 repeated <code class="language-plaintext highlighter-rouge">a</code></li>
</ul>

<h3 id="anchors">Anchors</h3>

<ul>
  <li><code class="language-plaintext highlighter-rouge">^</code> - starts with… (beginning of regex)</li>
  <li><code class="language-plaintext highlighter-rouge">$</code> - ends with… (end of regex)</li>
</ul>

<h3 id="flags">Flags</h3>

<ul>
  <li><code class="language-plaintext highlighter-rouge">/g</code> - global, find all instances</li>
  <li><code class="language-plaintext highlighter-rouge">/i</code> - ignore case, no distinction between <code class="language-plaintext highlighter-rouge">[a-z]</code> and <code class="language-plaintext highlighter-rouge">[A-Z</code> ranges</li>
  <li><code class="language-plaintext highlighter-rouge">/m</code> - multiline match, default is single line</li>
</ul>

<h2 id="regex-building">Regex Building</h2>

<h3 id="start-with-test-cases">Start with test cases</h3>

<p>Always easiest to compile the cases the pattern <em>should</em> match along with the cases the pattern <em>should not</em> match. For example, if we have</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Matches
afoot
catfoot
dogfoot
fanfoot
foody
foolery
foolish
fooster
footage
foothot
footle
footpad
footway
hotfoot
jawfoot
mafoo
nonfood
padfoot
prefool
sfoot
unfool

# Doesn't match
Atlas
Aymoro
Iberic
Mahran
Ormazd
Silipan
altared
chandoo
crenel
crooked
fardo
folksy
forest
hebamic
idgah
manlike
marly
palazzi
sixfold
tarrock
unfold
</code></pre></div></div>

<p>We can probably surmise that the common thread in all the match cases is they have the substring <code class="language-plaintext highlighter-rouge">foo</code>. Therefore my regex is as easy as <code class="language-plaintext highlighter-rouge">/foo/</code>.</p>

<h3 id="exercises">Exercises</h3>

<ul>
  <li>Dates
    <ul>
      <li><code class="language-plaintext highlighter-rouge">04/09/1987</code></li>
      <li><code class="language-plaintext highlighter-rouge">4/9/1987</code></li>
      <li><code class="language-plaintext highlighter-rouge">9 Apr 1987</code></li>
    </ul>
  </li>
  <li>HTML
    <ul>
      <li>span tags
        <ul>
          <li><code class="language-plaintext highlighter-rouge">&lt;span&gt;Hello&lt;/span&gt;</code></li>
          <li><code class="language-plaintext highlighter-rouge">&lt;span class="complex"&gt;Hello &lt;em&gt;there&lt;/em&gt;&lt;/span&gt;</code></li>
        </ul>
      </li>
      <li>anchor tag href
        <ul>
          <li><code class="language-plaintext highlighter-rouge">&lt;a class="plain-text" href="/about" data-event="click"&gt;About&lt;/a&gt;</code></li>
        </ul>
      </li>
      <li>secure anchor tags href
        <ul>
          <li><code class="language-plaintext highlighter-rouge">&lt;a class="plain-text" href="https://example.com" data-event="click"&gt;About&lt;/a&gt;</code></li>
        </ul>
      </li>
    </ul>
  </li>
  <li>Credit cards
    <ul>
      <li>Visa - starts with 4, 16 or 13 digits long</li>
      <li>Mastercard - starts with 51 through 55 or 2221 through 2720, all 16 digits long</li>
      <li>AmEx - starts with 34 or 37, all 15 digits long</li>
      <li>Discover - starts with 6011 or 65, all 16 digits long</li>
    </ul>
  </li>
</ul>

<h2 id="fp-regex">FP Regex</h2>

<p>Consider our earlier example</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// is string in 555-555-5555 format</span>
<span class="kd">const</span> <span class="nx">isPhone</span> <span class="o">=</span> <span class="p">(</span><span class="nx">str</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="sr">/^</span><span class="se">\d{3}</span><span class="sr">-</span><span class="se">\d{3}</span><span class="sr">-</span><span class="se">\d{4}</span><span class="sr">$/</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">str</span><span class="p">);</span>
</code></pre></div></div>

<p>It would probably be pretty handy if we could eliminate the need to specify the <code class="language-plaintext highlighter-rouge">str</code> param. FP to the rescue:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">curry</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">lodash</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">filter</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">lodash/fp</span><span class="dl">"</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">regexTest</span> <span class="o">=</span> <span class="nx">curry</span><span class="p">((</span><span class="nx">regex</span><span class="p">,</span> <span class="nx">str</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">regex</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">str</span><span class="p">));</span>

<span class="c1">// is string in 555-555-5555 format</span>
<span class="kd">const</span> <span class="nx">isPhone</span> <span class="o">=</span> <span class="nx">regexTest</span><span class="p">(</span><span class="sr">/^</span><span class="se">\d{3}</span><span class="sr">-</span><span class="se">\d{3}</span><span class="sr">-</span><span class="se">\d{4}</span><span class="sr">$/</span><span class="p">);</span>

<span class="nx">isPhone</span><span class="p">(</span><span class="dl">"</span><span class="s2">555-555-5555</span><span class="dl">"</span><span class="p">);</span> <span class="c1">// true</span>

<span class="kd">const</span> <span class="nx">onlyVowels</span> <span class="o">=</span> <span class="nx">filter</span><span class="p">(</span><span class="nx">regexTest</span><span class="p">(</span><span class="sr">/^</span><span class="se">[</span><span class="sr">aeiou</span><span class="se">]</span><span class="sr">+$/i</span><span class="p">));</span>

<span class="nx">onlyVowels</span><span class="p">([</span>
  <span class="dl">"</span><span class="s2">Hello</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">EIEIO</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">aaaaaa</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">aaaaah</span><span class="dl">"</span><span class="p">,</span>
<span class="p">]);</span> <span class="c1">// ["EIEIO", "aaaaaa"]</span>
</code></pre></div></div>

<h2 id="resources">Resources</h2>

<ul>
  <li><a href="https://regex101.com/">Regex101</a></li>
  <li><a href="https://javascript.info/regexp-character-sets-and-ranges#example-multi-language-w">Multi-language word matching</a></li>
  <li><a href="https://alf.nu/RegexGolf">Regex Golf</a></li>
  <li><a href="/assets/pdf/2005-03-16.DFA_to_RegEx.pdf">Converting Deterministic Finite Automata to Regular Expressions</a></li>
</ul>]]></content><author><name></name></author><category term="regex" /><summary type="html"><![CDATA[Regular Expressions]]></summary></entry><entry><title type="html">Functional Programming in JavaScript</title><link href="https://aarondilley.com/composition/fp/2025/02/08/functional-programming-in-javascript.html" rel="alternate" type="text/html" title="Functional Programming in JavaScript" /><published>2025-02-08T15:00:00+00:00</published><updated>2025-02-08T15:00:00+00:00</updated><id>https://aarondilley.com/composition/fp/2025/02/08/functional-programming-in-javascript</id><content type="html" xml:base="https://aarondilley.com/composition/fp/2025/02/08/functional-programming-in-javascript.html"><![CDATA[<h1 id="functional-programming-in-javascript">Functional Programming in JavaScript</h1>

<h2 id="what-is-functional-programming">What is Functional Programming</h2>

<p>Generally, Functional Programming (FP) is a coding style that centers 3 main paradigms:</p>

<ul>
  <li>declarative flow</li>
  <li>immutability</li>
  <li>composition</li>
</ul>

<h2 id="declarative-programming">Declarative Programming</h2>

<p>Declarative programming is a paradigm that relies on expressions that tell what is being computed as opposed to imperative programming that uses statements to tell how something needs to be computed with little clarity as to why. Consider the following example:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Imperative flow</span>
<span class="kd">function</span> <span class="nx">doubleNumbers</span><span class="p">(</span><span class="nx">numbers</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">doubled</span> <span class="o">=</span> <span class="p">[];</span>

  <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">n</span> <span class="k">of</span> <span class="nx">numbers</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">doubled</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">n</span> <span class="o">*</span> <span class="mi">2</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="k">return</span> <span class="nx">doubled</span><span class="p">;</span>
<span class="p">};</span>

<span class="c1">// Declarative flow</span>
<span class="kd">const</span> <span class="nx">doubleNumbers</span> <span class="o">=</span> <span class="nx">numbers</span> <span class="o">=&gt;</span> <span class="nx">numbers</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">n</span> <span class="o">=&gt;</span> <span class="nx">n</span> <span class="o">*</span> <span class="mi">2</span><span class="p">);</span>

<span class="c1">// Declarative with ramda</span>
<span class="kd">const</span> <span class="nx">doubleNumbers</span> <span class="o">=</span> <span class="nx">R</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">n</span> <span class="o">=&gt;</span> <span class="nx">n</span> <span class="o">*</span> <span class="mi">2</span><span class="p">);</span>
</code></pre></div></div>

<p>Here the imperative flow relies on temp variables, loops, actions, and a return all while obfuscating how this does what the function name suggests. Contrast with the declarative flows where we know we have an array of numbers and each number is mapped via the very self-explanatory lambda <code class="language-plaintext highlighter-rouge">n =&gt; n * 2</code>. We’ll continue to see declarative voice cuts down on code, makes code more readable, and the intent more easily known.</p>

<h2 id="immutability">Immutability</h2>

<p>Immutability is the property of data structures not being able to be changed, or mutated, by the functions they’re passed to. Immutability ensures functions stay pure without any side effects, i.e. functions always return the same value given the same starting arguments without altering the global state around them. JavaScript does not enforce immutability at the language level, which means any steps should be taken to ensure this happens. Enter functional programming:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// With mutation</span>
<span class="kd">function</span> <span class="nx">withoutKey</span><span class="p">(</span><span class="nx">key</span><span class="p">,</span> <span class="nx">store</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">delete</span> <span class="nx">store</span><span class="p">[</span><span class="nx">key</span><span class="p">];</span>

  <span class="k">return</span> <span class="nx">store</span><span class="p">;</span>
<span class="p">};</span>

<span class="c1">// Immutability preserved</span>
<span class="kd">function</span> <span class="nx">withoutKey</span><span class="p">(</span><span class="nx">key</span><span class="p">,</span> <span class="nx">store</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="p">{</span>
    <span class="p">[</span><span class="nx">key</span><span class="p">]:</span> <span class="nx">deleted</span><span class="p">,</span>
    <span class="p">...</span><span class="nx">rest</span>
  <span class="p">}</span> <span class="o">=</span> <span class="nx">store</span><span class="p">;</span>

  <span class="k">return</span> <span class="nx">rest</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>

<p>The above example is particularly critical if the store in question is the DOM window object.</p>

<h2 id="composition">Composition</h2>

<p>One of the main benefits of JavaScript over other languages is that it features first-class functions. That is, functions can be assigned to variables or passed to other functions as arguments. In short functions act like any other assigned variable. Among other things this allows functions to be wrapped, or composed with one another. Consider the following example:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">addOne</span> <span class="o">=</span> <span class="nx">n</span> <span class="o">=&gt;</span> <span class="nx">n</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">double</span> <span class="o">=</span> <span class="nx">n</span> <span class="o">=&gt;</span> <span class="nx">n</span> <span class="o">*</span> <span class="mi">2</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">square</span> <span class="o">=</span> <span class="nx">n</span> <span class="o">=&gt;</span> <span class="nx">n</span> <span class="o">*</span> <span class="nx">n</span><span class="p">;</span>

<span class="c1">// ((2 * n)^2) + 1 =&gt; 4n^2 + 1</span>
<span class="kd">const</span> <span class="nx">fourNSquaredPlusOne</span> <span class="o">=</span> <span class="nx">n</span> <span class="o">=&gt;</span> <span class="nx">addOne</span><span class="p">(</span><span class="nx">square</span><span class="p">(</span><span class="nx">double</span><span class="p">(</span><span class="nx">n</span><span class="p">)));</span>
</code></pre></div></div>

<p>Inline composition isn’t that readable and certainly becomes harder to parse as more functions are added to the composition. Libraries like <a href="https://ramdajs.com/">ramda</a> address this with a function <a href="https://ramdajs.com/docs/#compose">compose</a> that can be used like this:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">fourNSquaredPlusOne</span> <span class="o">=</span> <span class="nx">R</span><span class="p">.</span><span class="nx">compose</span><span class="p">(</span>
  <span class="nx">addOne</span><span class="p">,</span>
  <span class="nx">square</span><span class="p">,</span>
  <span class="nx">double</span><span class="p">,</span>
<span class="p">);</span>
</code></pre></div></div>

<p>However this can be a bit misleading since the evaluation happens inside out, or bottom up (right-ro-left if inline). The complementary <a href="https://ramdajs.com/docs/#pipe">pipe</a> function achieves the same outcome in a more intuitive order:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">fourNSquaredPlusOne</span> <span class="o">=</span> <span class="nx">R</span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span>
  <span class="nx">double</span><span class="p">,</span>
  <span class="nx">square</span><span class="p">,</span>
  <span class="nx">addOne</span><span class="p">,</span>
<span class="p">);</span>
</code></pre></div></div>

<p>Under the hood pipe looks something like this:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">pipe</span> <span class="o">=</span> <span class="p">(</span><span class="err">…</span><span class="nx">fns</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">x</span> <span class="o">=&gt;</span> <span class="nx">fns</span><span class="p">.</span><span class="nx">reduce</span><span class="p">((</span><span class="nx">y</span><span class="p">,</span> <span class="nx">f</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">f</span><span class="p">(</span><span class="nx">y</span><span class="p">),</span> <span class="nx">x</span><span class="p">);</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">y</code> here is the accumulator value, which is just the composition to this point. <code class="language-plaintext highlighter-rouge">f</code> is the next listed function which is simply applied to the resulting calculation of the accumulator. <code class="language-plaintext highlighter-rouge">x</code> is the starting value and represents the passed argument to the composed functions.</p>

<p>One of the many nice qualities of pipe is that it is self-flattening and self-composable. For example, these two representations are functionally equivalent:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">fourNSquaredPlusOne</span> <span class="o">=</span> <span class="nx">pipe</span><span class="p">(</span>
  <span class="nx">double</span><span class="p">,</span>
  <span class="nx">square</span><span class="p">,</span>
  <span class="nx">addOne</span><span class="p">,</span>
<span class="p">);</span>

<span class="kd">const</span> <span class="nx">fourNSquaredPlusOne</span> <span class="o">=</span> <span class="nx">pipe</span><span class="p">(</span>
  <span class="nx">double</span><span class="p">,</span>
  <span class="nx">pipe</span><span class="p">(</span>
    <span class="nx">square</span><span class="p">,</span>
    <span class="nx">addOne</span><span class="p">,</span>
  <span class="p">),</span>
<span class="p">);</span>
</code></pre></div></div>

<h2 id="common-recipes">Common recipes</h2>

<h3 id="getting">Getting</h3>

<p>Objects in JavaScript are often deeply nested, but functions also can’t rely on nested paths existing. As such it’s very common to see something like this for getting:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">return</span> <span class="nx">foo</span><span class="p">?.</span><span class="nx">bar</span><span class="p">?.</span><span class="nx">biz</span><span class="p">;</span>
</code></pre></div></div>

<p>This expression is actually quite compact compared to the legacy JavaScript equivalent:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">return</span> <span class="nx">foo</span> <span class="o">&amp;&amp;</span> <span class="nx">foo</span><span class="p">.</span><span class="nx">bar</span> <span class="o">&amp;&amp;</span> <span class="nx">foo</span><span class="p">.</span><span class="nx">bar</span><span class="p">.</span><span class="nx">biz</span><span class="p">;</span>
</code></pre></div></div>

<p>However if I want to <em>compose</em> this value with another operation I’d need an intermediate function like this:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">getBiz</span> <span class="o">=</span> <span class="nx">foo</span> <span class="o">=&gt;</span> <span class="nx">foo</span><span class="p">?.</span><span class="nx">bar</span><span class="p">?.</span><span class="nx">biz</span><span class="p">;</span>
</code></pre></div></div>

<p>Ramda simplifies this with its <code class="language-plaintext highlighter-rouge">path</code> function that takes the object and the desired path returning undefined by default if the path doesn’t exist:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">return</span> <span class="nx">R</span><span class="p">.</span><span class="nx">path</span><span class="p">([</span><span class="dl">'</span><span class="s1">bar</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">biz</span><span class="dl">'</span><span class="p">],</span> <span class="nx">foo</span><span class="p">);</span>
</code></pre></div></div>

<p>This presentation is actually more complex than our <code class="language-plaintext highlighter-rouge">getBiz</code> function above. But ramda leverages <em>currying</em> to make <code class="language-plaintext highlighter-rouge">foo</code> an optional final parameter. When the data object is passed as the last argument the result is the value of <code class="language-plaintext highlighter-rouge">foo?.bar?.biz</code>. But without it the result is our <code class="language-plaintext highlighter-rouge">getBiz</code> function, flexible and composable.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">foo</span> <span class="o">=</span> <span class="p">{</span> <span class="na">bar</span><span class="p">:</span> <span class="p">{</span> <span class="na">biz</span><span class="p">:</span> <span class="mi">4</span> <span class="p">}</span> <span class="p">};</span>

<span class="nx">R</span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span>
  <span class="c1">// foo</span>
  <span class="c1">//  ↳ path(['bar', 'biz'], foo)</span>
  <span class="nx">R</span><span class="p">.</span><span class="nx">path</span><span class="p">([</span><span class="dl">'</span><span class="s1">bar</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">biz</span><span class="dl">'</span><span class="p">]),</span>
  <span class="c1">// foo</span>
  <span class="c1">//  ↳ path(['bar', 'biz'], foo)</span>
  <span class="c1">//    ↳ double(path(['bar', 'biz'], foo))</span>
  <span class="nx">double</span><span class="p">,</span>
<span class="p">)(</span><span class="nx">foo</span><span class="p">);</span> <span class="c1">//=&gt; 8</span>
</code></pre></div></div>

<h3 id="setting">Setting</h3>

<p>Updating values at a specified path is even more tedious as not only does that path need to be verified to exist, but for the sake of immutability nested spread operators are often used to create a mutation-free clone:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="nx">foo</span><span class="p">?.</span><span class="nx">bar</span><span class="p">?.</span><span class="nx">biz</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">{</span>
    <span class="p">...</span><span class="nx">foo</span><span class="p">,</span>
    <span class="na">bar</span><span class="p">:</span> <span class="p">{</span>
      <span class="p">...</span><span class="nx">foo</span><span class="p">.</span><span class="nx">bar</span><span class="p">,</span>
      <span class="na">biz</span><span class="p">:</span> <span class="mi">8</span><span class="p">,</span>
    <span class="p">},</span>
  <span class="p">};</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Ramda comes to the rescue again with <code class="language-plaintext highlighter-rouge">set</code> (and <code class="language-plaintext highlighter-rouge">lensPath</code> which is similar to <code class="language-plaintext highlighter-rouge">path</code> but isn’t concerned with <em>getting</em> at that path but rather <em>pointing</em> to that location):</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">return</span> <span class="nx">R</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="nx">R</span><span class="p">.</span><span class="nx">lensPath</span><span class="p">([</span><span class="dl">'</span><span class="s1">bar</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">biz</span><span class="dl">'</span><span class="p">]),</span> <span class="mi">8</span><span class="p">,</span> <span class="nx">foo</span><span class="p">);</span>
</code></pre></div></div>

<p>Like <code class="language-plaintext highlighter-rouge">path</code>, <code class="language-plaintext highlighter-rouge">set</code> is configured for composition with the last argument optional. However the above doesn’t read very well and it’s annoying we need a separate utility function to handle this common operation. Ramda therefore provides us with <code class="language-plaintext highlighter-rouge">assocPath</code> to combine the getting and setting in a more compact function:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">setBizTo8</span> <span class="o">=</span> <span class="nx">R</span><span class="p">.</span><span class="nx">assocPath</span><span class="p">([</span><span class="dl">'</span><span class="s1">bar</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">biz</span><span class="dl">'</span><span class="p">],</span> <span class="mi">8</span><span class="p">);</span>

<span class="nx">setBizTo8</span><span class="p">(</span><span class="nx">foo</span><span class="p">);</span> <span class="c1">//=&gt; { bar: { biz: 8 } }</span>
</code></pre></div></div>

<p>One common shortcoming of <code class="language-plaintext highlighter-rouge">assocPath</code> is that the new set value is independent of the existing value. Ramda provides a method <code class="language-plaintext highlighter-rouge">over</code> that handles just this.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">squareCurrentBizValue</span> <span class="o">=</span> <span class="nx">R</span><span class="p">.</span><span class="nx">over</span><span class="p">(</span><span class="nx">R</span><span class="p">.</span><span class="nx">lensPath</span><span class="p">([</span><span class="dl">'</span><span class="s1">bar</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">biz</span><span class="dl">'</span><span class="p">]),</span> <span class="nx">square</span><span class="p">);</span>

<span class="nx">squareCurrentBizValue</span><span class="p">(</span><span class="nx">foo</span><span class="p">);</span> <span class="c1">//=&gt; { bar: { biz: 16 } }</span>
</code></pre></div></div>

<p>We could simplify this further and create our own utility <code class="language-plaintext highlighter-rouge">overPath</code>:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">overPath</span> <span class="o">=</span> <span class="nx">R</span><span class="p">.</span><span class="nx">curry</span><span class="p">(</span>
  <span class="p">(</span><span class="nx">path</span><span class="p">,</span> <span class="nx">fn</span><span class="p">,</span> <span class="nx">store</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">R</span><span class="p">.</span><span class="nx">over</span><span class="p">(</span><span class="nx">R</span><span class="p">.</span><span class="nx">lensPath</span><span class="p">(</span><span class="nx">path</span><span class="p">),</span> <span class="nx">fn</span><span class="p">,</span> <span class="nx">store</span><span class="p">),</span>
<span class="p">);</span>

<span class="kd">const</span> <span class="nx">squareCurrentBizValue</span> <span class="o">=</span> <span class="nx">overPath</span><span class="p">([</span><span class="dl">'</span><span class="s1">bar</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">biz</span><span class="dl">'</span><span class="p">],</span> <span class="nx">square</span><span class="p">);</span>

<span class="nx">squareCurrentBizValue</span><span class="p">(</span><span class="nx">foo</span><span class="p">);</span> <span class="c1">// { bar: { biz: 16 } }</span>
</code></pre></div></div>

<p>This is the second mention of currying, so we can talk a bit more about what this is now.</p>

<h3 id="currying">Currying</h3>

<p>Reusable, partially applied functions are a cornerstone of FP. Currying is a process that takes a function and allows its variables to be processed all at once or in chunks. Consider the following application:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">sumTriple</span> <span class="o">=</span> <span class="nx">R</span><span class="p">.</span><span class="nx">curry</span><span class="p">(</span>
  <span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">,</span> <span class="nx">c</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">a</span> <span class="o">+</span> <span class="nx">b</span> <span class="o">+</span> <span class="nx">c</span><span class="p">,</span>
<span class="p">);</span>

<span class="c1">// Following are equivalent</span>
<span class="nx">sumTriple</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">);</span> <span class="c1">// 6</span>
<span class="nx">sumTriple</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">)(</span><span class="mi">3</span><span class="p">);</span> <span class="c1">// 6</span>
<span class="nx">sumTriple</span><span class="p">(</span><span class="mi">1</span><span class="p">)(</span><span class="mi">2</span><span class="p">)(</span><span class="mi">3</span><span class="p">);</span> <span class="c1">// 6</span>
</code></pre></div></div>

<p>If we wanted to write these partials manually it would be a bit of a mess:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">applyTwoArgs</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">,</span> <span class="nx">fn</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nx">c</span> <span class="o">=&gt;</span> <span class="nx">fn</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">,</span> <span class="nx">c</span><span class="p">);</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">applyOneArgAtATime</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">fn</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nx">b</span> <span class="o">=&gt;</span> <span class="nx">c</span> <span class="o">=&gt;</span> <span class="nx">fn</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">,</span> <span class="nx">c</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">curry</code> takes a function <code class="language-plaintext highlighter-rouge">fn</code> and calculates <code class="language-plaintext highlighter-rouge">fn.arguments.length</code> to know when to return the calculated value as opposed to another partially applied function.</p>

<p>If creating custom FP functions, it’s best to wrap with <code class="language-plaintext highlighter-rouge">curry</code> for maximum flexibility.</p>

<h3 id="placeholders">Placeholders</h3>

<p>Sometimes when composing functions, the resolved and passed value isn’t the last accepted argument for the next function. Placeholders (denoted by the double underscore <em>__</em> in ramda) allow us to specify where the resolved value should be passed to the following function.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Aaron</span><span class="dl">'</span><span class="p">,</span> <span class="na">role</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Software Engineer</span><span class="dl">'</span> <span class="p">};</span>
<span class="kd">const</span> <span class="nx">greet</span> <span class="o">=</span> <span class="nx">R</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="dl">''</span><span class="p">,</span> <span class="nx">__</span><span class="p">,</span> <span class="dl">'</span><span class="s1">Hello !</span><span class="dl">'</span><span class="p">);</span>

<span class="kd">const</span> <span class="nx">greetUser</span> <span class="o">=</span> <span class="nx">R</span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span>
  <span class="nx">R</span><span class="p">.</span><span class="nx">prop</span><span class="p">(</span><span class="dl">'</span><span class="s1">name</span><span class="dl">'</span><span class="p">),</span> <span class="c1">//=&gt; Aaron</span>
  <span class="nx">R</span><span class="p">.</span><span class="nx">toUpper</span><span class="p">,</span> <span class="c1">//=&gt; AARON</span>
  <span class="nx">greet</span><span class="p">,</span> <span class="c1">//=&gt; Hello AARON! (AARON becomes second param where __ was)</span>
<span class="p">);</span>

<span class="nx">greetUser</span><span class="p">(</span><span class="nx">user</span><span class="p">);</span> <span class="c1">//=&gt; Hello AARON!</span>
</code></pre></div></div>

<h2 id="examples">Examples</h2>

<h3 id="data-adapters">Data adapters</h3>

<p>Consider this adapter</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">adaptCareSymbols</span><span class="p">(</span><span class="nx">careSymbols</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">(</span><span class="nx">careSymbols</span> <span class="o">??</span> <span class="p">[])</span>
    <span class="p">.</span><span class="nx">filter</span><span class="p">(</span>
      <span class="p">({</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">name</span><span class="p">,</span> <span class="nx">image</span> <span class="p">})</span> <span class="o">=&gt;</span>
        <span class="nx">id</span> <span class="o">!==</span> <span class="kc">undefined</span> <span class="o">&amp;&amp;</span> <span class="nx">name</span> <span class="o">!==</span> <span class="kc">undefined</span> <span class="o">&amp;&amp;</span> <span class="nx">image</span> <span class="o">!==</span> <span class="kc">undefined</span>
    <span class="p">)</span>
    <span class="p">.</span><span class="nx">map</span><span class="p">(({</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">name</span><span class="p">,</span> <span class="nx">image</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">({</span>
      <span class="nx">id</span><span class="p">,</span>
      <span class="nx">name</span><span class="p">,</span>
      <span class="nx">image</span><span class="p">,</span>
    <span class="p">}));</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The adapter filters a <code class="language-plaintext highlighter-rouge">careSymbols</code> array to only include items that have defined values for <code class="language-plaintext highlighter-rouge">id</code>, <code class="language-plaintext highlighter-rouge">image</code>, and <code class="language-plaintext highlighter-rouge">name</code> keys. Then only returns objects with those values.</p>

<p>We can rewrite this with ramda. We’re going to try to return as much as we can inside our <code class="language-plaintext highlighter-rouge">pipe</code> function.</p>

<p>First we have</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nx">careSymbols</span> <span class="o">??</span> <span class="p">[])</span>
</code></pre></div></div>

<p>In ramda, we have the function <code class="language-plaintext highlighter-rouge">or</code> which returns the first argument if it’s truthy, otherwise, returns the second argument. So we want to do something like</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">R</span><span class="p">.</span><span class="nx">or</span><span class="p">(</span><span class="nx">careSymbols</span><span class="p">,</span> <span class="p">[]),</span>
</code></pre></div></div>

<p>But in the context of <code class="language-plaintext highlighter-rouge">pipe</code>, <code class="language-plaintext highlighter-rouge">careSymbols</code> would ordinarily be the last argument. We can use the placeholder to pull this into the right spot:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">R</span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span>
  <span class="c1">// R.__ replaced with careSymbols once careSymbols is passed to pipe</span>
  <span class="nx">R</span><span class="p">.</span><span class="nx">or</span><span class="p">(</span><span class="nx">R</span><span class="p">.</span><span class="nx">__</span><span class="p">,</span> <span class="p">[]),</span>
<span class="p">)</span>
</code></pre></div></div>

<p>Next we have</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">.</span><span class="nx">filter</span><span class="p">(</span>
  <span class="p">({</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">name</span><span class="p">,</span> <span class="nx">image</span> <span class="p">})</span> <span class="o">=&gt;</span>
    <span class="nx">id</span> <span class="o">!==</span> <span class="kc">undefined</span> <span class="o">&amp;&amp;</span> <span class="nx">name</span> <span class="o">!==</span> <span class="kc">undefined</span> <span class="o">&amp;&amp;</span> <span class="nx">image</span> <span class="o">!==</span> <span class="kc">undefined</span>
<span class="p">)</span>
</code></pre></div></div>

<p>This is essentially three parts: <code class="language-plaintext highlighter-rouge">filter</code>, destructuring/selecting <code class="language-plaintext highlighter-rouge">id</code>, <code class="language-plaintext highlighter-rouge">image</code>, and <code class="language-plaintext highlighter-rouge">name</code> values, then checking none of these are <code class="language-plaintext highlighter-rouge">undefined</code>. The first part is straightforward. We can use ramda’s <code class="language-plaintext highlighter-rouge">filter</code> function, and because this is functional programming, our filter callback will be wrapped in <code class="language-plaintext highlighter-rouge">pipe</code> (when in doubt, wrap with <code class="language-plaintext highlighter-rouge">pipe</code>). This looks like</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">R</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">R</span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span>
  <span class="p">...</span>
<span class="p">))</span>
</code></pre></div></div>

<p>For destructuring/selecting <code class="language-plaintext highlighter-rouge">id</code>, <code class="language-plaintext highlighter-rouge">image</code>, and <code class="language-plaintext highlighter-rouge">name</code> values, ramda provides <code class="language-plaintext highlighter-rouge">R.props</code> which takes an array of key names and returns their values in an array in the same order. That is</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">R</span><span class="p">.</span><span class="nx">props</span><span class="p">([</span><span class="dl">'</span><span class="s1">id</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">image</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">name</span><span class="dl">'</span><span class="p">],</span> <span class="nx">data</span><span class="p">)</span> <span class="o">===</span> <span class="p">[</span><span class="nx">data</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span> <span class="nx">data</span><span class="p">.</span><span class="nx">image</span><span class="p">,</span> <span class="nx">data</span><span class="p">.</span><span class="nx">name</span><span class="p">]</span>
</code></pre></div></div>

<p>Adding this to the filter callback above (dropping the <code class="language-plaintext highlighter-rouge">data</code> since it’s passed automatically):</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">R</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">R</span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span>
  <span class="nx">R</span><span class="p">.</span><span class="nx">props</span><span class="p">([</span><span class="dl">'</span><span class="s1">id</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">image</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">name</span><span class="dl">'</span><span class="p">]),</span>
  <span class="p">...</span>
<span class="p">))</span>
</code></pre></div></div>

<p>Then we have to verify none of these resulting values are <code class="language-plaintext highlighter-rouge">undefined</code>. Ramda doesn’t have any direct way to do this. If we wanted to check if <em>one</em> value was <code class="language-plaintext highlighter-rouge">undefined</code> we could use <code class="language-plaintext highlighter-rouge">R.equals(undefined)</code>. If we want to logically <em>negate</em> this assertion, that something is <em>not</em> <code class="language-plaintext highlighter-rouge">undefined</code> we can wrap with <code class="language-plaintext highlighter-rouge">R.complement</code>:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">R</span><span class="p">.</span><span class="nx">complement</span><span class="p">(</span><span class="nx">R</span><span class="p">.</span><span class="nx">equals</span><span class="p">(</span><span class="kc">undefined</span><span class="p">))</span>
</code></pre></div></div>

<p>This isn’t super readable inline with other code, so it’s a good idea to set this as a standalone util function:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">isDefined</span> <span class="o">=</span> <span class="nx">R</span><span class="p">.</span><span class="nx">complement</span><span class="p">(</span><span class="nx">R</span><span class="p">.</span><span class="nx">equals</span><span class="p">(</span><span class="kc">undefined</span><span class="p">));</span>
</code></pre></div></div>

<p>Since we need to ensure all the values return by <code class="language-plaintext highlighter-rouge">R.props</code> are defined, we can wrap with <code class="language-plaintext highlighter-rouge">R.all</code>:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">R</span><span class="p">.</span><span class="nx">all</span><span class="p">(</span><span class="nx">isDefined</span><span class="p">)</span>
</code></pre></div></div>

<p>Now our filter component is complete:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">R</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">R</span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span>
  <span class="nx">R</span><span class="p">.</span><span class="nx">props</span><span class="p">([</span><span class="dl">'</span><span class="s1">id</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">image</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">name</span><span class="dl">'</span><span class="p">]),</span>
  <span class="nx">R</span><span class="p">.</span><span class="nx">all</span><span class="p">(</span><span class="nx">isDefined</span><span class="p">),</span>
<span class="p">))</span>
</code></pre></div></div>

<p>Once our data is filtered, we want to map the filtered items to only return values for <code class="language-plaintext highlighter-rouge">id</code>, <code class="language-plaintext highlighter-rouge">image</code>, and <code class="language-plaintext highlighter-rouge">name</code>. We start with <code class="language-plaintext highlighter-rouge">R.map</code>. For the actual mapper, we can just use <code class="language-plaintext highlighter-rouge">R.pickAll(['id', 'image', 'name'])</code>. Together this is</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">R</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">R</span><span class="p">.</span><span class="nx">pickAll</span><span class="p">([</span><span class="dl">'</span><span class="s1">id</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">image</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">name</span><span class="dl">'</span><span class="p">]))</span>
</code></pre></div></div>

<p>Altogether this looks like:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">isDefined</span> <span class="o">=</span> <span class="nx">R</span><span class="p">.</span><span class="nx">complement</span><span class="p">(</span><span class="nx">R</span><span class="p">.</span><span class="nx">equals</span><span class="p">(</span><span class="kc">undefined</span><span class="p">));</span>

<span class="kd">function</span> <span class="nx">adaptCareSymbols</span><span class="p">(</span><span class="nx">careSymbols</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">definedKeys</span> <span class="o">=</span> <span class="p">[</span><span class="dl">'</span><span class="s1">id</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">image</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">name</span><span class="dl">'</span><span class="p">];</span>

  <span class="k">return</span> <span class="nx">R</span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span>
    <span class="c1">// ensure we pass an empty array</span>
    <span class="c1">// if careSymbols is undefined</span>
    <span class="nx">R</span><span class="p">.</span><span class="nx">or</span><span class="p">(</span><span class="nx">R</span><span class="p">.</span><span class="nx">__</span><span class="p">,</span> <span class="p">[]),</span>
    <span class="nx">R</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">R</span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span>
      <span class="c1">// grab values for each key in definedKeys</span>
      <span class="nx">R</span><span class="p">.</span><span class="nx">props</span><span class="p">(</span><span class="nx">definedKeys</span><span class="p">),</span>
      <span class="c1">// ensure all values are defined</span>
      <span class="nx">R</span><span class="p">.</span><span class="nx">all</span><span class="p">(</span><span class="nx">isDefined</span><span class="p">),</span>
    <span class="p">)),</span>
    <span class="c1">// restrict data to only these key-values</span>
    <span class="nx">R</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">R</span><span class="p">.</span><span class="nx">pickAll</span><span class="p">(</span><span class="nx">definedKeys</span><span class="p">)),</span>
  <span class="p">)(</span><span class="nx">careSymbols</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>But then we see we have this other function</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">adaptCareInstructions</span><span class="p">(</span><span class="nx">careInstructions</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">(</span><span class="nx">careInstructions</span> <span class="o">??</span> <span class="p">[])</span>
    <span class="p">.</span><span class="nx">filter</span><span class="p">(({</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">name</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="nx">id</span> <span class="o">!==</span> <span class="kc">undefined</span> <span class="o">&amp;&amp;</span> <span class="nx">name</span> <span class="o">!==</span> <span class="kc">undefined</span><span class="p">)</span>
    <span class="p">.</span><span class="nx">map</span><span class="p">(({</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">name</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">({</span>
      <span class="na">id</span><span class="p">:</span> <span class="nx">id</span><span class="o">!</span><span class="p">,</span>
      <span class="na">name</span><span class="p">:</span> <span class="nx">name</span><span class="o">!</span><span class="p">,</span>
    <span class="p">}));</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Instead of copy/pasting the inside of our modified <code class="language-plaintext highlighter-rouge">adaptCareSymbols</code> we can abstract to a shared factory:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">pickAllDefined</span><span class="p">(</span><span class="nx">definedKeys</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nx">R</span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span>
    <span class="nx">R</span><span class="p">.</span><span class="nx">or</span><span class="p">(</span><span class="nx">R</span><span class="p">.</span><span class="nx">__</span><span class="p">,</span> <span class="p">[]),</span>
    <span class="nx">R</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">R</span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span>
      <span class="nx">R</span><span class="p">.</span><span class="nx">props</span><span class="p">(</span><span class="nx">definedKeys</span><span class="p">),</span>
      <span class="nx">R</span><span class="p">.</span><span class="nx">all</span><span class="p">(</span><span class="nx">isDefined</span><span class="p">),</span>
    <span class="p">)),</span>
    <span class="nx">R</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">R</span><span class="p">.</span><span class="nx">pickAll</span><span class="p">(</span><span class="nx">definedKeys</span><span class="p">)),</span>
  <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Then we can write</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">adaptCareSymbols</span> <span class="o">=</span> <span class="nx">pickAllDefined</span><span class="p">([</span><span class="dl">'</span><span class="s1">id</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">image</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">name</span><span class="dl">'</span><span class="p">]);</span>
<span class="kd">const</span> <span class="nx">adaptCareInstructions</span> <span class="o">=</span> <span class="nx">pickAllDefined</span><span class="p">([</span><span class="dl">'</span><span class="s1">id</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">name</span><span class="dl">'</span><span class="p">]);</span>
</code></pre></div></div>

<p>What if I have this function</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">adaptTechnicalInfos</span><span class="p">(</span><span class="nx">technicalInfos</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">(</span><span class="nx">technicalInfos</span> <span class="o">??</span> <span class="p">[])</span>
    <span class="p">.</span><span class="nx">filter</span><span class="p">(({</span> <span class="nx">description</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="nx">description</span> <span class="o">!==</span> <span class="kc">undefined</span><span class="p">)</span>
    <span class="p">.</span><span class="nx">map</span><span class="p">(({</span> <span class="nx">name</span><span class="p">,</span> <span class="nx">description</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">({</span>
      <span class="nx">name</span><span class="p">,</span>
      <span class="nx">description</span><span class="p">,</span>
    <span class="p">}));</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Here you can see we only check for undefined against the <code class="language-plaintext highlighter-rouge">description</code> key but we want to map to include <code class="language-plaintext highlighter-rouge">description</code> and <code class="language-plaintext highlighter-rouge">name</code>. That’s ok! We can add an optional second parameter to <code class="language-plaintext highlighter-rouge">pickAllDefined</code> for <code class="language-plaintext highlighter-rouge">outputKeys</code>. As a convenience we can have <code class="language-plaintext highlighter-rouge">definedKeys</code> as a default value:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">pickAllDefined</span><span class="p">(</span><span class="nx">definedKeys</span><span class="p">,</span> <span class="nx">outputKeys</span> <span class="o">=</span> <span class="nx">definedKeys</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nx">R</span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span>
    <span class="nx">R</span><span class="p">.</span><span class="nx">or</span><span class="p">(</span><span class="nx">R</span><span class="p">.</span><span class="nx">__</span><span class="p">,</span> <span class="p">[]),</span>
    <span class="nx">R</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">R</span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span>
      <span class="nx">R</span><span class="p">.</span><span class="nx">props</span><span class="p">(</span><span class="nx">definedKeys</span><span class="p">),</span>
      <span class="nx">R</span><span class="p">.</span><span class="nx">all</span><span class="p">(</span><span class="nx">isDefined</span><span class="p">),</span>
    <span class="p">)),</span>
    <span class="nx">R</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">R</span><span class="p">.</span><span class="nx">pickAll</span><span class="p">(</span><span class="nx">outputKeys</span><span class="p">)),</span>
  <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Then our three functions look like</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">adaptCareSymbols</span> <span class="o">=</span> <span class="nx">pickAllDefined</span><span class="p">([</span><span class="dl">'</span><span class="s1">id</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">image</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">name</span><span class="dl">'</span><span class="p">]);</span>
<span class="kd">const</span> <span class="nx">adaptCareInstructions</span> <span class="o">=</span> <span class="nx">pickAllDefined</span><span class="p">([</span><span class="dl">'</span><span class="s1">id</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">name</span><span class="dl">'</span><span class="p">]);</span>
<span class="kd">const</span> <span class="nx">adaptTechnicalInfos</span> <span class="o">=</span> <span class="nx">pickAllDefined</span><span class="p">([</span><span class="dl">'</span><span class="s1">description</span><span class="dl">'</span><span class="p">],</span> <span class="p">[</span><span class="dl">'</span><span class="s1">description</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">name</span><span class="dl">'</span><span class="p">]);</span>
</code></pre></div></div>

<p>Of course we didn’t have to use ramda or functional programming to write <code class="language-plaintext highlighter-rouge">pickAllDefined</code>, but by doing so we assess the function in terms of what it <em>does</em> and not what is does it <em>to</em>. The data target is removed entirely from the code.</p>

<h3 id="analytics">Analytics</h3>

<p>The <code class="language-plaintext highlighter-rouge">pipe</code> function is great if we want to transform and then pass values on to subsequent functions. But what if we want to apply a function without interrupting the flow of the remaining functions? Consider the following utility function:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">passThrough</span> <span class="o">=</span> <span class="nx">curry</span><span class="p">(</span>
  <span class="p">(</span><span class="nx">fn</span><span class="p">,</span> <span class="nx">value</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">fn</span><span class="p">(</span><span class="nx">value</span><span class="p">);</span>
    <span class="k">return</span> <span class="nx">value</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">);</span>
</code></pre></div></div>

<p>Here we want to apply a function, but we don’t care about the returned value. Maybe we want to log the current value:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">log</span> <span class="o">=</span> <span class="nx">passThrough</span><span class="p">(</span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">);</span>
</code></pre></div></div>

<p>Now we can add this to <code class="language-plaintext highlighter-rouge">pipe</code> wherever we want a data snapshot:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">R</span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span>
  <span class="nx">log</span><span class="p">,</span> <span class="c1">//=&gt; 7</span>
  <span class="nx">double</span><span class="p">,</span>
  <span class="nx">log</span><span class="p">,</span> <span class="c1">//=&gt; 14</span>
  <span class="nx">square</span><span class="p">,</span>
  <span class="nx">log</span><span class="p">,</span> <span class="c1">//=&gt; 196</span>
  <span class="nx">addOne</span><span class="p">,</span>
  <span class="nx">log</span><span class="p">,</span> <span class="c1">//=&gt; 197</span>
<span class="p">)(</span><span class="mi">7</span><span class="p">);</span>
</code></pre></div></div>

<p>Maybe we want to track data as we go:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">track</span> <span class="o">=</span> <span class="p">(</span><span class="nx">adapter</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">passThrough</span><span class="p">(</span>
  <span class="nx">R</span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">adapter</span><span class="p">,</span> <span class="nx">trackWithConsent</span><span class="p">)</span>
<span class="p">);</span>

<span class="kd">const</span> <span class="nx">dataWithTimestamp</span> <span class="o">=</span> <span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({</span>
  <span class="nx">data</span><span class="p">,</span>
  <span class="na">time</span><span class="p">:</span> <span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">(),</span>
<span class="p">});</span>

<span class="nx">R</span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span>
  <span class="nx">double</span><span class="p">,</span> <span class="c1">//=&gt; 14</span>
  <span class="nx">square</span><span class="p">,</span> <span class="c1">//=&gt; 196</span>
  <span class="nx">track</span><span class="p">(</span><span class="nx">dataWithTimestamp</span><span class="p">),</span> <span class="c1">//=&gt; trackWithConsent({ data: 196, time: 1739001045665 })</span>
  <span class="nx">addOne</span><span class="p">,</span> <span class="c1">//=&gt; 197</span>
<span class="p">)(</span><span class="mi">7</span><span class="p">);</span>
</code></pre></div></div>

<p>This is flexible and doesn’t alter the end result.</p>

<h2 id="final-thoughts">Final Thoughts</h2>

<ul>
  <li>Not everything needs the FP treatment</li>
  <li>Good FP is about maintainability above all else</li>
  <li>If FP conversion is too hard use smaller functions or rethink underlying data structure</li>
</ul>

<h2 id="additional-references">Additional References</h2>

<ul>
  <li><a href="https://medium.com/javascript-scene/the-hidden-treasures-of-object-composition-60cd89480381">The Hidden Treasures of Object Composition</a> by Eric Elliot</li>
  <li><a href="https://medium.com/javascript-scene/lenses-b85976cb0534">Lenses</a> by Eric Elliott</li>
  <li><a href="https://ramdajs.com/">Ramda documentation</a></li>
  <li><a href="https://github.com/lodash/lodash/wiki/FP-Guide">Lodash FP guide</a></li>
</ul>]]></content><author><name></name></author><category term="composition" /><category term="fp" /><summary type="html"><![CDATA[Functional Programming in JavaScript]]></summary></entry><entry><title type="html">Anatomy of a Word Search Game</title><link href="https://aarondilley.com/combinatorics/sql/2024/09/14/anatomy-of-a-word-search-game.html" rel="alternate" type="text/html" title="Anatomy of a Word Search Game" /><published>2024-09-14T19:03:02+00:00</published><updated>2024-09-14T19:03:02+00:00</updated><id>https://aarondilley.com/combinatorics/sql/2024/09/14/anatomy-of-a-word-search-game</id><content type="html" xml:base="https://aarondilley.com/combinatorics/sql/2024/09/14/anatomy-of-a-word-search-game.html"><![CDATA[<h1 id="anatomy-of-a-word-search-game">Anatomy of a Word Search Game</h1>

<p><img src="/assets/img/2024-09-14-anatomy-of-a-word-search-game/7B293196-81BC-4E32-93FD-E0BD82CE4606.png" alt="" class="blog-roll-image" /></p>

<p>The New York Times has a daily game called Spelling Bee. Here are the basics of the game:</p>
<ul>
  <li>The board features 7 letters in a honeycomb array with one highlighted letter in the middle.</li>
  <li>Goal is to find as many valid words as possible. A valid word is any word that is at least 4 letters in length and includes the center letter. Letters can be used more than once.</li>
  <li>All puzzles include at least one <strong>pangram</strong> — a word that uses all 7 letters on the board at least once.</li>
  <li>Scoring works as follows: 4 letter words are worth 1 point each. All other words are worth 1 point for each letter in the word. Pangrams are worth an additional 7 points each.</li>
</ul>

<h2 id="building-the-database">Building the database</h2>

<p>To replicate the game I first need a dictionary of words to choose from. The dictionary can be whatever I want. This <a href="https://raw.githubusercontent.com/jonbcard/scrabble-bot/master/src/dictionary.txt">scrabble dictionary</a> was a good enough starting point and had the added benefit of not having any duplicates in the list.</p>

<p>I have a MySQL server on my home machine. It’s easy enough to import a txt or csv file into my database, but before I did that I wanted to make some useful modifications to the word list for faster querying later.</p>

<h3 id="random-letters">Random letters?</h3>

<p>Each day the NYT features a new septet of letters. In building my replica of this game I could just pick an arbitrary set of 7 letters and put one of them in the middle and then check each word submission after the fact. This has a couple of glaring problems:</p>
<ol>
  <li>No guarantee of any valid words, and</li>
  <li>No guarantee of a pangram</li>
</ol>

<p>Point 2 seemed to be the key for the whole game setup. Having at least one pangram is a sufficient condition for a valid game board. So I knew I was going to need to be able to determine which words in my list were valid pangrams. How could I do this?</p>

<p>In short a given word is a pangram if the number of unique letters in that word is exactly 7. So I decided to write a utility function to help:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">uniqueChars</span> <span class="o">=</span> <span class="nx">str</span> <span class="o">=&gt;</span> <span class="p">[...(</span><span class="k">new</span> <span class="nb">Set</span><span class="p">([...</span><span class="nx">str</span><span class="p">]))].</span><span class="nx">join</span><span class="p">(</span><span class="dl">""</span><span class="p">);</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">[…str]</code> turns my string into an array of individual characters. <code class="language-plaintext highlighter-rouge">Set</code> turns this array into a set which has the added benefit of removing any duplicate characters. Wrapping this with <code class="language-plaintext highlighter-rouge">[…&lt;set&gt;]</code> turns our set back into an array which finally allows us to chain <code class="language-plaintext highlighter-rouge">.join("")</code> to yield a string of all the unique characters. With this I could then write another utility function:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">isPangram</span> <span class="o">=</span> <span class="nx">str</span> <span class="o">=&gt;</span> <span class="nx">uniqueChars</span><span class="p">(</span><span class="nx">str</span><span class="p">).</span><span class="nx">length</span> <span class="o">===</span> <span class="mi">7</span><span class="p">;</span>
</code></pre></div></div>

<p>I could extend the initial txt file to replace each line so <code class="language-plaintext highlighter-rouge">&lt;word&gt;</code> becomes
<code class="language-plaintext highlighter-rouge">&lt;word&gt;,&lt;is_pangram&gt;</code> which would shape my MySQL table accordingly.</p>

<p>Now I’d be able to tell if a given word was a candidate for for my pangram. So instead of</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">random_word</span> <span class="k">FROM</span> <span class="k">dictionary</span><span class="p">;</span>
</code></pre></div></div>

<p>until I get something that works I’d be able to query</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">random_word</span> <span class="k">FROM</span> <span class="k">dictionary</span>
<span class="k">WHERE</span> <span class="n">is_pangram</span> <span class="k">IS</span> <span class="k">TRUE</span><span class="p">;</span>
</code></pre></div></div>

<h3 id="getting-all-valid-words">Getting all valid words</h3>

<p>With the approach thus far I would be able to query for a single pangram. With this one word I could then choose one of the characters to be the center character and let the user try and find the rest of the words. After all, determining whether or not a set of letters is a word in the dictionary is a straightforward query:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="k">dictionary</span>
<span class="k">WHERE</span> <span class="n">word</span> <span class="o">=</span> <span class="o">&lt;</span><span class="n">submitted_guess</span><span class="o">&gt;</span><span class="p">;</span>
</code></pre></div></div>

<p>But this has a couple of drawbacks:</p>
<ol>
  <li>Every guess requires another roundtrip call to my MySQL server</li>
  <li>No obvious way to know if I’ve guessed all possible words from the set of 7 letters given to the user</li>
</ol>

<p>What if I could get all valid words in a single query? What would I need to know up front?</p>
<ol>
  <li>Which words were made up of a subset of my initial 7 letters</li>
  <li>Which of those included the required central letter</li>
</ol>

<p>The challenge from a query perspective is that I don’t want to try and determine character sets on the fly. MySQL isn’t set up to do this efficiently (if at all) adding a lot of compute time to such a query.</p>

<p>One way to go about this is calculating the unique characters and adding to the dictionary table as a standalone column. Then I will be able to filter all words whose unique characters match a specified character subset of the original 7. Recall our earlier function</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">uniqueChars</span> <span class="o">=</span> <span class="nx">str</span> <span class="o">=&gt;</span> <span class="p">[...(</span><span class="k">new</span> <span class="nb">Set</span><span class="p">([...</span><span class="nx">str</span><span class="p">]))].</span><span class="nx">join</span><span class="p">(</span><span class="dl">""</span><span class="p">);</span>
</code></pre></div></div>

<p>This is a good start, but falls a little short if I want to use as a key in my table. For example consider the words <code class="language-plaintext highlighter-rouge">read</code> and <code class="language-plaintext highlighter-rouge">dared</code>:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">uniqueChars</span><span class="p">(</span><span class="dl">"</span><span class="s2">read</span><span class="dl">"</span><span class="p">);</span>  <span class="c1">// "read"</span>
<span class="nx">uniqueChars</span><span class="p">(</span><span class="dl">"</span><span class="s2">dared</span><span class="dl">"</span><span class="p">);</span> <span class="c1">// "dare"</span>
</code></pre></div></div>

<p>The letter sets for each of these words is the same but MySQL doesn’t know this. I need a way to consistently represent the same letter sets even if they’re string order isn’t the same. One way to solve this is to sort the letter array alphabetically before rejoining. I can modify the function to be</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">uniqueChars</span> <span class="o">=</span> <span class="nx">str</span> <span class="o">=&gt;</span>
  <span class="p">[...(</span><span class="k">new</span> <span class="nb">Set</span><span class="p">([...</span><span class="nx">str</span><span class="p">]))].</span><span class="nx">sort</span><span class="p">().</span><span class="nx">join</span><span class="p">(</span><span class="dl">""</span><span class="p">);</span>
</code></pre></div></div>

<p>Then I have</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">uniqueChars</span><span class="p">(</span><span class="dl">"</span><span class="s2">read</span><span class="dl">"</span><span class="p">);</span>  <span class="c1">// "ader"</span>
<span class="nx">uniqueChars</span><span class="p">(</span><span class="dl">"</span><span class="s2">dared</span><span class="dl">"</span><span class="p">);</span> <span class="c1">// "ader"</span>
</code></pre></div></div>

<p>If I add this string as a separate column I can quickly and easily query all words that can be formed from the same set of letters. For the above example such a query would be</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">word</span> <span class="k">FROM</span> <span class="k">dictionary</span>
<span class="k">WHERE</span> <span class="n">char_set</span> <span class="o">=</span> <span class="nv">"ader"</span><span class="p">;</span>
</code></pre></div></div>

<p>But this only retrieves the words for one character set. I need <em>all</em> possible character subsets from the pangram. If only looking at the above character set. If <code class="language-plaintext highlighter-rouge">read</code> were my word, <code class="language-plaintext highlighter-rouge">ader</code> is the character set and here are all the (non-empty) character subsets:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a
d
e
r
ad
ae
ar
de
dr
er
ade
adr
aer
der
ader
</code></pre></div></div>

<p>If I wanted all words with any of these character sets my query would look like</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">word</span> <span class="k">FROM</span> <span class="k">dictionary</span>
<span class="k">WHERE</span> <span class="n">char_set</span> <span class="o">=</span> <span class="nv">"a"</span>
<span class="k">OR</span> <span class="n">char_set</span> <span class="o">=</span> <span class="nv">"d"</span>
<span class="k">OR</span> <span class="n">char_set</span> <span class="o">=</span> <span class="nv">"e"</span>
<span class="k">OR</span> <span class="n">char_set</span> <span class="o">=</span> <span class="nv">"r"</span>
<span class="k">OR</span> <span class="n">char_set</span> <span class="o">=</span> <span class="nv">"ad"</span>
<span class="k">OR</span> <span class="n">char_set</span> <span class="o">=</span> <span class="nv">"ae"</span>
<span class="k">OR</span> <span class="n">char_set</span> <span class="o">=</span> <span class="nv">"ar"</span>
<span class="k">OR</span> <span class="n">char_set</span> <span class="o">=</span> <span class="nv">"de"</span>
<span class="k">OR</span> <span class="n">char_set</span> <span class="o">=</span> <span class="nv">"dr"</span>
<span class="k">OR</span> <span class="n">char_set</span> <span class="o">=</span> <span class="nv">"er"</span>
<span class="k">OR</span> <span class="n">char_set</span> <span class="o">=</span> <span class="nv">"ade"</span>
<span class="k">OR</span> <span class="n">char_set</span> <span class="o">=</span> <span class="nv">"adr"</span>
<span class="k">OR</span> <span class="n">char_set</span> <span class="o">=</span> <span class="nv">"aer"</span>
<span class="k">OR</span> <span class="n">char_set</span> <span class="o">=</span> <span class="nv">"der"</span>
<span class="k">OR</span> <span class="n">char_set</span> <span class="o">=</span> <span class="nv">"ader"</span><span class="p">;</span>
</code></pre></div></div>

<p>Even though the query is verbose, its runtime performance scales linearly so it looks worse than it actually is.</p>

<p>What’s left to do then is to write a way to generate all relevant character sets from a pangram and specified center letter.</p>

<h4 id="find-all-subsets">Find all subsets</h4>

<p><strong>WARNING</strong> A little combinatorics aside incoming.</p>

<p>Let’s count the number of total subsets that can be made from a set of N unique elements. One way to count them is to realize that for a given element in the superset each subset either contains or does not contain that particular element. That is there are 2 choices (include or exclude) for each of the N elements. These choices compound as their inclusion or exclusion is independent from the inclusion or exclusion of another element in the superset. So there are then</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2 × 2 × ... × 2 = 2^N
</code></pre></div></div>

<p>possible subsets. For my purposes I can ignore the subset where all elements are excluded (the empty set).</p>

<h4 id="subsets-to-strings">Subsets to strings</h4>

<p>Now that the number of subsets is understood, how can I get the string equivalent for each. In keeping with the base 2 nature of the combinatorial argument above I can look to binary numbers. If I count from 1 to 2^N in binary for the <code class="language-plaintext highlighter-rouge">["a", "d", "e", "r"]</code> example (N = 4):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0001
0010
0011
0100
...
1110
1111
</code></pre></div></div>

<p>If I take any of these binary representations, overlay them on the superset and say all 0s are excluded and only 1s are included I get</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0001  0010  0011  0100       1110  1111
ader  ader  ader  ader       ader  ader
----  ----  ----  ----  ...  ----  ----
   r    e     er   d         ade   ader
</code></pre></div></div>

<p>Even though the order of the subsets isn’t alphabetical it is complete. Algorithmically this looks like</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">getAllSubsets</span> <span class="o">=</span> <span class="p">(</span><span class="nx">str</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">subsets</span> <span class="o">=</span> <span class="p">[];</span>

  <span class="k">for</span> <span class="p">(</span><span class="nx">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">pow</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="nx">str</span><span class="p">.</span><span class="nx">length</span><span class="p">);</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// e.g. 5 -&gt; "101"</span>
    <span class="kd">const</span> <span class="nx">binary</span> <span class="o">=</span> <span class="nx">i</span><span class="p">.</span><span class="nx">toString</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span>
    <span class="c1">// "0" + "101" -&gt; "0101"</span>
    <span class="kd">const</span> <span class="nx">binaryWithLeadingZeroes</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">0</span><span class="dl">"</span><span class="p">.</span><span class="nx">repeat</span><span class="p">(</span><span class="nx">str</span><span class="p">.</span><span class="nx">length</span> <span class="o">-</span> <span class="nx">binary</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="o">+</span> <span class="nx">binary</span><span class="p">;</span>
    <span class="c1">// "0101" -&gt; ["0", "1", "0", "1"]</span>
    <span class="kd">const</span> <span class="nx">binaryArr</span> <span class="o">=</span> <span class="nx">binaryWithLeadingZeroes</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="dl">""</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">subset</span> <span class="o">=</span> <span class="p">[];</span>

    <span class="nx">binaryArr</span><span class="p">.</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">bit</span><span class="p">,</span> <span class="nx">position</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span><span class="nx">bit</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">1</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">subset</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">str</span><span class="p">[</span><span class="nx">position</span><span class="p">]);</span>
      <span class="p">}</span>
    <span class="p">});</span>

    <span class="nx">subsets</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">subset</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="dl">""</span><span class="p">));</span>
  <span class="p">}</span>

  <span class="k">return</span> <span class="nx">subsets</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>

<p>Keep in mind this will give all non-empty subsets. This is about twice the number of wanted subsets after the random center character is determined. I can modify the above function to further filter based on a passed extra parameter</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">getAllSubsets</span> <span class="o">=</span> <span class="p">(</span><span class="nx">str</span><span class="p">,</span> <span class="nx">centerChar</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">centerIndex</span> <span class="o">=</span> <span class="nx">str</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="nx">centerChar</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">subsets</span> <span class="o">=</span> <span class="p">[];</span>

  <span class="k">for</span> <span class="p">(</span><span class="nx">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">pow</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="nx">str</span><span class="p">.</span><span class="nx">length</span><span class="p">);</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// e.g. 5 -&gt; "101"</span>
    <span class="kd">const</span> <span class="nx">binary</span> <span class="o">=</span> <span class="nx">i</span><span class="p">.</span><span class="nx">toString</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span>
    <span class="c1">// "0" + "101" -&gt; "0101"</span>
    <span class="kd">const</span> <span class="nx">binaryWithLeadingZeroes</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">0</span><span class="dl">"</span><span class="p">.</span><span class="nx">repeat</span><span class="p">(</span><span class="nx">str</span><span class="p">.</span><span class="nx">length</span> <span class="o">-</span> <span class="nx">binary</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="o">+</span> <span class="nx">binary</span><span class="p">;</span>
    <span class="c1">// "0101" -&gt; ["0", "1", "0", "1"]</span>
    <span class="kd">const</span> <span class="nx">binaryArr</span> <span class="o">=</span> <span class="nx">binaryWithLeadingZeroes</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="dl">""</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">subset</span> <span class="o">=</span> <span class="p">[];</span>

    <span class="c1">// no need to iterate through if required</span>
    <span class="c1">// character won't be in resulting subset</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">binaryArr</span><span class="p">[</span><span class="nx">centerIndex</span><span class="p">]</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">0</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">continue</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nx">binaryArr</span><span class="p">.</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">bit</span><span class="p">,</span> <span class="nx">position</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span><span class="nx">bit</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">1</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">subset</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">str</span><span class="p">[</span><span class="nx">position</span><span class="p">]);</span>
      <span class="p">}</span>
    <span class="p">});</span>

    <span class="nx">subsets</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">subset</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="dl">""</span><span class="p">));</span>
  <span class="p">}</span>

  <span class="k">return</span> <span class="nx">subsets</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>

<p>Applying the modified version to a contrived example for <code class="language-plaintext highlighter-rouge">"ader"</code> with a required center letter of say <code class="language-plaintext highlighter-rouge">"d"</code></p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">getAllSubsets</span><span class="p">(</span><span class="dl">"</span><span class="s2">ader</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">d</span><span class="dl">"</span><span class="p">);</span>
<span class="cm">/***
[
  "d",
  "dr",
  "de",
  "der",
  "ad",
  "adr",
  "ade",
  "ader"
]
***/</span>
</code></pre></div></div>

<h4 id="creating-sql-query">Creating SQL query</h4>

<p>Using the above I can create the query that will retrieve all matching words</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">createQuery</span> <span class="o">=</span> <span class="p">(</span><span class="nx">charSetStr</span><span class="p">,</span> <span class="nx">centerChar</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">subsets</span> <span class="o">=</span> <span class="nx">getAllSubsets</span><span class="p">(</span><span class="nx">charSetStr</span><span class="p">,</span> <span class="nx">centerChar</span><span class="p">);</span>

  <span class="k">return</span> <span class="s2">`
    SELECT word FROM dictionary
    WHERE </span><span class="p">${</span><span class="nx">subsets</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">str</span> <span class="o">=&gt;</span> <span class="s2">`char_set = "</span><span class="p">${</span><span class="nx">str</span><span class="p">}</span><span class="s2">"`</span><span class="p">).</span><span class="nx">join</span><span class="p">(</span><span class="dl">"</span><span class="s2"> OR </span><span class="dl">"</span><span class="p">)}</span><span class="s2">;`</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>

<h2 id="appendix">Appendix</h2>

<h3 id="extending-word-list-csv">Extending word list CSV</h3>

<p>With a little bit of node <code class="language-plaintext highlighter-rouge">fs</code> and an npm package called I can combine our utility functions to extend our dictionary txt file into a csv that gets fed to MySQL</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nx">fs</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">fs</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">readline</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">linebyline</span><span class="dl">"</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">rl</span> <span class="o">=</span> <span class="nx">readline</span><span class="p">(</span><span class="dl">"</span><span class="s2">dict.txt</span><span class="dl">"</span><span class="p">);</span>

<span class="kd">const</span> <span class="nx">uniqueChars</span> <span class="o">=</span> <span class="nx">str</span> <span class="o">=&gt;</span>
  <span class="p">[...(</span><span class="k">new</span> <span class="nb">Set</span><span class="p">([...</span><span class="nx">str</span><span class="p">]))].</span><span class="nx">sort</span><span class="p">().</span><span class="nx">join</span><span class="p">(</span><span class="dl">""</span><span class="p">);</span>

<span class="kd">const</span> <span class="nx">isPangram</span> <span class="o">=</span> <span class="nx">str</span> <span class="o">=&gt;</span> <span class="nx">uniqueChars</span><span class="p">(</span><span class="nx">str</span><span class="p">).</span><span class="nx">length</span> <span class="o">===</span> <span class="mi">7</span><span class="p">;</span>

<span class="nx">rl</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">line</span><span class="dl">"</span><span class="p">,</span> <span class="p">(</span><span class="nx">ln</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">modifiedLine</span> <span class="o">=</span> <span class="s2">`</span><span class="p">${</span><span class="nx">ln</span><span class="p">}</span><span class="s2">,</span><span class="p">${</span><span class="nx">uniqueChars</span><span class="p">(</span><span class="nx">ln</span><span class="p">)}</span><span class="s2">,</span><span class="p">${</span><span class="nx">isPangram</span><span class="p">(</span><span class="nx">ln</span><span class="p">)</span> <span class="p">?</span> <span class="mi">1</span> <span class="p">:</span> <span class="mi">0</span><span class="p">}</span><span class="s2">\n`</span><span class="p">;</span>
  <span class="nx">fs</span><span class="p">.</span><span class="nx">appendFileSync</span><span class="p">(</span><span class="dl">"</span><span class="s2">ext_dict.csv</span><span class="dl">"</span><span class="p">,</span> <span class="nx">modifiedLine</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div></div>

<h3 id="getting-a-random-pangram">Getting a random pangram</h3>

<p>It’s a bit outside the scope of this conversation but this is how I can query a random pangram once <code class="language-plaintext highlighter-rouge">is_pangram</code> and <code class="language-plaintext highlighter-rouge">char_set</code> columns have been added to the <code class="language-plaintext highlighter-rouge">dictionary</code> table</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">char_set</span>
<span class="k">FROM</span> <span class="k">dictionary</span> <span class="k">AS</span> <span class="n">r1</span>
<span class="k">JOIN</span> <span class="p">(</span>
  <span class="k">SELECT</span> <span class="n">CEIL</span><span class="p">(</span><span class="n">RAND</span><span class="p">()</span> <span class="o">*</span> <span class="p">(</span>
    <span class="k">SELECT</span> <span class="k">MAX</span><span class="p">(</span><span class="n">id</span><span class="p">)</span>
    <span class="k">FROM</span> <span class="k">dictionary</span>
    <span class="k">WHERE</span> <span class="n">is_pangram</span> <span class="k">IS</span> <span class="k">TRUE</span>
  <span class="p">))</span> <span class="k">AS</span> <span class="n">id</span>
<span class="p">)</span> <span class="k">AS</span> <span class="n">r2</span>
<span class="k">WHERE</span> <span class="n">r1</span><span class="p">.</span><span class="n">id</span> <span class="o">&gt;=</span> <span class="n">r2</span><span class="p">.</span><span class="n">id</span>
<span class="k">AND</span> <span class="n">r1</span><span class="p">.</span><span class="n">is_pangram</span> <span class="k">IS</span> <span class="k">TRUE</span>
<span class="k">ORDER</span> <span class="k">BY</span> <span class="n">r1</span><span class="p">.</span><span class="n">id</span> <span class="k">ASC</span>
<span class="k">LIMIT</span> <span class="mi">1</span><span class="p">;</span>
</code></pre></div></div>]]></content><author><name></name></author><category term="combinatorics" /><category term="sql" /><summary type="html"><![CDATA[Anatomy of a Word Search Game]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://aarondilley.com/assets/img/2024-09-14-anatomy-of-a-word-search-game/7B293196-81BC-4E32-93FD-E0BD82CE4606.png" /><media:content medium="image" url="https://aarondilley.com/assets/img/2024-09-14-anatomy-of-a-word-search-game/7B293196-81BC-4E32-93FD-E0BD82CE4606.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>